Skip to content

Commit 7a1f1bc

Browse files
authored
Merge pull request #1 from woshilapin/initial-upload
[feature] Add crates 'relations' and 'relations_procmacro'
2 parents a903e40 + 6881a3c commit 7a1f1bc

17 files changed

+858
-8
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Build
2+
on: [push, pull_request]
3+
4+
jobs:
5+
rustfmt:
6+
name: Formatting check
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@master
10+
- name: Install Rust stable
11+
uses: actions-rs/toolchain@v1
12+
with:
13+
toolchain: stable
14+
profile: minimal
15+
components: rustfmt
16+
- name: Run cargo fmt
17+
uses: actions-rs/cargo@v1
18+
with:
19+
command: fmt
20+
args: --all -- --check
21+
22+
clippy:
23+
name: Analyzing code with Clippy
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@master
27+
- name: Install Rust stable
28+
uses: actions-rs/toolchain@v1
29+
with:
30+
toolchain: stable
31+
profile: minimal
32+
components: clippy
33+
- name: Run cargo clippy
34+
uses: actions-rs/cargo@v1
35+
with:
36+
command: clippy
37+
args: --workspace -- -D warnings
38+
39+
tests:
40+
name: Tests
41+
runs-on: ${{ matrix.os }}
42+
strategy:
43+
matrix:
44+
build: [stable, beta]
45+
include:
46+
- build: stable
47+
os: ubuntu-latest
48+
rust: stable
49+
- build: beta
50+
os: ubuntu-latest
51+
rust: beta
52+
steps:
53+
- uses: actions/checkout@master
54+
- name: Install Rust ${{ matrix.rust }}
55+
uses: actions-rs/toolchain@v1
56+
with:
57+
toolchain: ${{ matrix.rust }}
58+
profile: minimal
59+
override: true
60+
- name: Run tests
61+
uses: actions-rs/cargo@v1
62+
with:
63+
command: test
64+
args: --verbose --workspace

.gitignore

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
# Generated by Cargo
2-
# will have compiled files and executables
31
/target/
4-
5-
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6-
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
72
Cargo.lock
8-
9-
# These are backup files generated by rustfmt
103
**/*.rs.bk

Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "relations"
3+
description = "Manage relations between objects"
4+
version = "1.0.0"
5+
authors = ["Kisio Digital <team.coretools@kisio.org>", "Guillaume Pinot <texitoi@texitoi.eu>"]
6+
edition = "2018"
7+
license = "MIT"
8+
homepage = "https://github.com/CanalTP/relations"
9+
repository = "https://github.com/CanalTP/relations"
10+
documentation = "https://docs.rs/relations"
11+
readme = "README.md"
12+
13+
keywords = ["relation", "collection"]
14+
15+
[workspace]
16+
members = [
17+
"relations_procmacro",
18+
]
19+
20+
[dependencies]
21+
derivative = "1"
22+
relations_procmacro = { version = "1", path = "./relations_procmacro/", optional = true }
23+
thiserror = "1"
24+
typed_index_collection = "1"
25+
26+
[features]
27+
default = ["relations_procmacro"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# relations
2-
Modeling the relations between objects
2+
Modeling the relations between objects.

relations_procmacro/Cargo.toml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "relations_procmacro"
3+
description = "Procmacro to help create relations between objects"
4+
version = "1.0.0"
5+
authors = ["Kisio Digital <team.coretools@kisio.org>", "Guillaume Pinot <texitoi@texitoi.eu>"]
6+
edition = "2018"
7+
license = "MIT"
8+
repository = "https://github.com/CanalTP/relations"
9+
keywords = ["macro", "floyd_marshall"]
10+
autotests = false
11+
12+
[lib]
13+
proc-macro = true
14+
15+
[dependencies]
16+
syn = "0.11.11"
17+
quote = "0.3.15"
18+
19+
[dev-dependencies]
20+
pretty_assertions = "0.6"
21+
trybuild = "1"
22+
typed_index_collection = "1"
23+
relations = { version = "1", path = "../" }
24+
25+
[[test]]
26+
name = "tests"
27+
path = "tests/tests.rs"

relations_procmacro/src/lib.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//! Custom derive for GetCorresponding. See `relations` for the documentation.
2+
3+
#![recursion_limit = "128"]
4+
5+
extern crate proc_macro;
6+
use quote::*;
7+
use syn;
8+
9+
use proc_macro::TokenStream;
10+
use std::collections::{HashMap, HashSet};
11+
12+
/// Generation of the `GetCorresponding` trait implementation.
13+
#[proc_macro_derive(GetCorresponding, attributes(get_corresponding))]
14+
pub fn get_corresponding(input: TokenStream) -> TokenStream {
15+
let s = input.to_string();
16+
let ast = syn::parse_derive_input(&s).unwrap();
17+
let gen = impl_get_corresponding(&ast);
18+
gen.parse().unwrap()
19+
}
20+
21+
fn impl_get_corresponding(ast: &syn::DeriveInput) -> quote::Tokens {
22+
if let syn::Body::Struct(syn::VariantData::Struct(ref fields)) = ast.body {
23+
let name = &ast.ident;
24+
let edges: Vec<_> = fields.iter().filter_map(to_edge).collect();
25+
let next = floyd_warshall(&edges);
26+
let edge_to_impl = make_edge_to_get_corresponding(name, &edges);
27+
let edges_impls = next.iter().map(|(&(from, to), &node)| {
28+
if from == to {
29+
quote! {
30+
impl GetCorresponding<#to> for IdxSet<#from> {
31+
fn get_corresponding(&self, _: &#name) -> IdxSet<#to> {
32+
self.clone()
33+
}
34+
}
35+
}
36+
} else if to == node {
37+
edge_to_impl[&(from, to)].clone()
38+
} else {
39+
quote! {
40+
impl GetCorresponding<#to> for IdxSet<#from> {
41+
fn get_corresponding(&self, pt_objects: &#name) -> IdxSet<#to> {
42+
let tmp: IdxSet<#node> = self.get_corresponding(pt_objects);
43+
tmp.get_corresponding(pt_objects)
44+
}
45+
}
46+
}
47+
}
48+
});
49+
quote! {
50+
/// A trait that returns a set of objects corresponding to
51+
/// a given type.
52+
pub trait GetCorresponding<T: Sized> {
53+
/// For the given self, returns the set of
54+
/// corresponding `T` indices.
55+
fn get_corresponding(&self, model: &#name) -> IdxSet<T>;
56+
}
57+
impl #name {
58+
/// Returns the set of `U` indices corresponding to the `from` set.
59+
pub fn get_corresponding<T, U>(&self, from: &IdxSet<T>) -> IdxSet<U>
60+
where
61+
IdxSet<T>: GetCorresponding<U>
62+
{
63+
from.get_corresponding(self)
64+
}
65+
/// Returns the set of `U` indices corresponding to the `from` index.
66+
pub fn get_corresponding_from_idx<T, U>(&self, from: Idx<T>) -> IdxSet<U>
67+
where
68+
IdxSet<T>: GetCorresponding<U>
69+
{
70+
self.get_corresponding(&Some(from).into_iter().collect())
71+
}
72+
}
73+
#(#edges_impls)*
74+
}
75+
} else {
76+
quote!()
77+
}
78+
}
79+
80+
fn to_edge(field: &syn::Field) -> Option<Edge> {
81+
use syn::MetaItem::*;
82+
use syn::NestedMetaItem::MetaItem;
83+
use syn::PathParameters::AngleBracketed;
84+
85+
let ident = field.ident.as_ref()?.as_ref();
86+
let mut split = ident.split("_to_");
87+
let _from_collection = split.next()?;
88+
let _to_collection = split.next()?;
89+
if split.next().is_some() {
90+
return None;
91+
}
92+
let segment = if let syn::Ty::Path(_, ref path) = field.ty {
93+
path.segments.last()
94+
} else {
95+
None
96+
}?;
97+
let (from_ty, to_ty) = if let AngleBracketed(ref data) = segment.parameters {
98+
match (data.types.get(0), data.types.get(1), data.types.get(2)) {
99+
(Some(from_ty), Some(to_ty), None) => Some((from_ty, to_ty)),
100+
_ => None,
101+
}
102+
} else {
103+
None
104+
}?;
105+
let weight = field
106+
.attrs
107+
.iter()
108+
.flat_map(|attr| match attr.value {
109+
List(ref i, ref v) if i == "get_corresponding" => v.as_slice(),
110+
_ => &[],
111+
})
112+
.map(|mi| match *mi {
113+
MetaItem(NameValue(ref i, syn::Lit::Str(ref l, _))) => {
114+
assert_eq!(i, "weight", "{} is not a valid attribute", i);
115+
l.parse::<f64>()
116+
.expect("`weight` attribute must be convertible to f64")
117+
}
118+
_ => panic!("Only `key = \"value\"` attributes supported."),
119+
})
120+
.last()
121+
.unwrap_or(1.);
122+
123+
Edge {
124+
ident: ident.into(),
125+
from: from_ty.clone(),
126+
to: to_ty.clone(),
127+
weight,
128+
}
129+
.into()
130+
}
131+
132+
fn make_edge_to_get_corresponding<'a>(
133+
name: &syn::Ident,
134+
edges: &'a [Edge],
135+
) -> HashMap<(&'a syn::Ty, &'a syn::Ty), quote::Tokens> {
136+
let mut res = HashMap::default();
137+
for e in edges {
138+
let ident: quote::Ident = e.ident.as_str().into();
139+
let from = &e.from;
140+
let to = &e.to;
141+
res.insert(
142+
(from, to),
143+
quote! {
144+
impl GetCorresponding<#to> for IdxSet<#from> {
145+
fn get_corresponding(&self, pt_objects: &#name) -> IdxSet<#to> {
146+
pt_objects.#ident.get_corresponding_forward(self)
147+
}
148+
}
149+
},
150+
);
151+
res.insert(
152+
(to, from),
153+
quote! {
154+
impl GetCorresponding<#from> for IdxSet<#to> {
155+
fn get_corresponding(&self, pt_objects: &#name) -> IdxSet<#from> {
156+
pt_objects.#ident.get_corresponding_backward(self)
157+
}
158+
}
159+
},
160+
);
161+
}
162+
res
163+
}
164+
165+
fn floyd_warshall<'a>(edges: &'a [Edge]) -> HashMap<(&'a Node, &'a Node), &'a Node> {
166+
use std::f64::INFINITY;
167+
let mut v = HashSet::<&Node>::default();
168+
let mut dist = HashMap::<(&Node, &Node), f64>::default();
169+
let mut next = HashMap::default();
170+
for e in edges {
171+
let from = &e.from;
172+
let to = &e.to;
173+
v.insert(from);
174+
v.insert(to);
175+
dist.insert((from, to), e.weight);
176+
dist.insert((to, from), e.weight);
177+
next.insert((from, to), to);
178+
next.insert((to, from), from);
179+
}
180+
for &k in &v {
181+
for &i in &v {
182+
let dist_ik = match dist.get(&(i, k)) {
183+
Some(d) => *d,
184+
None => continue,
185+
};
186+
for &j in &v {
187+
let dist_kj = match dist.get(&(k, j)) {
188+
Some(d) => *d,
189+
None => continue,
190+
};
191+
let dist_ij = dist.entry((i, j)).or_insert(INFINITY);
192+
if *dist_ij > dist_ik + dist_kj {
193+
*dist_ij = dist_ik + dist_kj;
194+
let next_ik = next[&(i, k)];
195+
next.insert((i, j), next_ik);
196+
}
197+
}
198+
}
199+
}
200+
next
201+
}
202+
203+
struct Edge {
204+
ident: String,
205+
from: Node,
206+
to: Node,
207+
weight: f64,
208+
}
209+
210+
type Node = syn::Ty;

0 commit comments

Comments
 (0)