1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#![doc(html_playground_url = "https://play.rust-lang.org/")]
extern crate frunk_core;
extern crate proc_macro;
#[macro_use]
extern crate quote;
extern crate syn;
use proc_macro::TokenStream;
use quote::ToTokens;
use quote::__rt::Span;
use syn::{DeriveInput, Expr, Ident, Member};
const ALPHA_CHARS: &'static [char] = &[
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];
const UNDERSCORE_CHARS: &'static [char] = &['_', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
pub fn to_ast(input: TokenStream) -> DeriveInput {
syn::parse(input).unwrap()
}
pub fn call_site_ident(s: &str) -> Ident {
Ident::new(s, Span::call_site())
}
pub fn build_hcons_constr(accessors: &Vec<Ident>) -> impl ToTokens {
match accessors.len() {
0 => quote! { ::frunk_core::hlist::HNil },
1 => {
let h = accessors[0].clone();
quote! { ::frunk_core::hlist::HCons{ head: #h, tail: ::frunk_core::hlist::HNil } }
}
_ => {
let h = accessors[0].clone();
let tail = accessors[1..].to_vec();
let hlist_tail = build_hcons_constr(&tail);
quote! { ::frunk_core::hlist::HCons{ head: #h, tail: #hlist_tail }}
}
}
}
pub fn build_type_level_name_for(ident: &Ident) -> impl ToTokens {
let as_string = ident.to_string();
let name = as_string.as_str();
let name_as_idents: Vec<Ident> = name.chars().flat_map(|c| encode_as_ident(&c)).collect();
let name_as_tokens: Vec<_> = name_as_idents
.iter()
.map(|ident| {
quote! { ::frunk_core::labelled::chars::#ident }
})
.collect();
quote! { (#(#name_as_tokens),*) }
}
fn encode_as_ident(c: &char) -> Vec<Ident> {
if ALPHA_CHARS.contains(c) {
vec![call_site_ident(&c.to_string())]
} else if UNDERSCORE_CHARS.contains(c) {
vec![call_site_ident(&format!("_{}", c))]
} else {
let as_unicode = c.escape_unicode();
let delimited_hex = as_unicode.filter(|c| c.is_alphanumeric());
let mut hex_idents: Vec<Ident> = delimited_hex.flat_map(|c| encode_as_ident(&c)).collect();
let mut book_ended: Vec<Ident> = vec![call_site_ident("_uc")];
book_ended.append(&mut hex_idents);
book_ended.push(call_site_ident("uc_"));
book_ended
}
}
pub fn build_path_type(path_expr: Expr) -> impl ToTokens {
let idents = find_idents_in_expr(path_expr);
idents.iter().map(|i| build_type_level_name_for(i)).fold(
quote!(::frunk_core::hlist::HNil),
|acc, t| {
quote! {
::frunk_core::path::Path<
::frunk_core::hlist::HCons<
#t,
#acc
>
>
}
},
)
}
pub fn find_idents_in_expr(path_expr: Expr) -> Vec<Ident> {
fn go(current: Expr, mut v: Vec<Ident>) -> Vec<Ident> {
match current {
Expr::Field(e) => {
let m = e.member;
match m {
Member::Named(i) => {
v.push(i);
}
_ => panic!("Only named access is supported"),
}
go(*e.base, v)
}
Expr::Path(p) => {
if p.path.segments.len() != 1 {
panic!("Invalid name; this has collons in it")
} else {
let i = p.path.segments[0].ident.clone();
v.push(i);
v
}
}
_ => panic!("Invalid input"),
}
}
go(path_expr, Vec::new())
}