1extern crate proc_macro;
2
3use proc_macro::{Span, TokenStream};
4use quote::quote;
5use std::{
6 process::Command,
7 collections::{hash_map::Entry, HashMap},
8 env,
9 fs::{read_dir, read_to_string},
10 str::FromStr,
11};
12use syn::{parse_macro_input, Error};
13
14#[proc_macro]
16pub fn addfn(_: TokenStream) -> TokenStream {
17 let dir = match env::var_os("CARGO_MANIFEST_DIR") {
18 Some(d) => match d.to_str() {
19 Some(s) => s.to_owned(),
20 None => return error("CARGO_MANIFEST_DIR contains non-printable characters"),
21 },
22 None => return error("Can't fetch the environment variable CARGO_MANIFEST_DIR"),
23 };
24 let list = load_files(&dir).unwrap_or_default();
25
26 let mut vec = Vec::new();
27 vec.push("let mut app: std::collections::BTreeMap<i64, std::collections::BTreeMap<i64, std::collections::BTreeMap<i64, tiny_web::sys::action::Act>>> = std::collections::BTreeMap::new();"
28 .to_string(),
29 );
30 for (key, v) in list {
31 vec.push(format!(
32 "let mut {}: std::collections::BTreeMap<i64, std::collections::BTreeMap<i64, tiny_web::sys::action::Act>> = std::collections::BTreeMap::new();",
33 key
34 ));
35 for file in v {
36 let func = get_func(&dir, &key, &file);
37 vec.push(format!(
38 "let mut {}_{}: std::collections::BTreeMap<i64, tiny_web::sys::action::Act> = std::collections::BTreeMap::new();",
39 key, file
40 ));
41 for f in func {
42 vec.push(format!(
43 "{0}_{1}.insert({2}_i64, |action| Box::pin(app::{0}::{1}::{3}(action)));",
44 key,
45 file,
46 fnv1a_64_impl(&f),
47 f
48 ));
49 }
50 vec.push(format!(
51 "{0}.insert({1}_i64, {0}_{2});",
52 key,
53 fnv1a_64_impl(&file),
54 file
55 ));
56 }
57 vec.push(format!("app.insert({}_i64, {});", fnv1a_64_impl(&key), key));
58 }
59 vec.push("\nreturn app;".to_owned());
60
61 TokenStream::from_str(&vec.join("\n")).unwrap()
62}
63
64fn get_func(dir: &str, key: &str, file: &str) -> Vec<String> {
68 let mut vec = Vec::new();
69 let file = format!("{}/src/app/{}/{}.rs", dir, key, file);
70 if let Ok(str) = read_to_string(file) {
71 let mut str = str
72 .replace('(', " ( ")
73 .replace(')', " ) ")
74 .replace(':', " : ")
75 .replace("->", " -> ")
76 .replace('{', " { ");
77 loop {
78 if str.contains(" ") {
79 str = str.replace(" ", " ");
80 continue;
81 }
82 break;
83 }
84 for line in str.lines() {
85 if let Some(i) = line.find("pub async fn ") {
86 if let Some(j) = line[i + 13..].find(" ( ") {
87 if line[i + 13 + j..].contains(" : &mut Action ) -> Answer {") {
88 let name = &line[i + 13..i + 13 + j];
89 if name.chars().all(|c| c.is_ascii_lowercase() || c == '_') {
90 vec.push(line[i + 13..i + 13 + j].to_owned());
91 }
92 }
93 }
94 }
95 }
96 }
97 vec.shrink_to_fit();
98 vec
99}
100
101#[proc_macro]
103pub fn addmod(_: TokenStream) -> TokenStream {
104 let dir = match env::var_os("CARGO_MANIFEST_DIR") {
106 Some(d) => match d.to_str() {
107 Some(s) => s.to_owned(),
108 None => return error("CARGO_MANIFEST_DIR contains non-printable characters"),
109 },
110 None => return error("Can't fetch the environment variable CARGO_MANIFEST_DIR"),
111 };
112 let list = match load_files(&dir) {
114 Ok(l) => l,
115 Err(e) => return error(&e),
116 };
117 let mut vec = Vec::new();
119 for (key, v) in list {
120 vec.push(format!("pub mod {} {{", check_name(key)));
121 for f in v {
122 vec.push(format!(" pub mod {};", check_name(f)));
123 }
124 vec.push("}".to_owned());
125 }
126 TokenStream::from_str(&vec.join("\n")).unwrap()
127}
128
129fn check_name(text: String) -> String {
131 if text.contains('-') {
132 return text.replace('-', "_");
133 }
134 text
135}
136
137fn load_files(dir: &str) -> Result<HashMap<String, Vec<String>>, String> {
139 let src = format!("{}/src/app", dir);
140 let mut list: HashMap<String, Vec<String>> = HashMap::new();
141 match read_dir(&src) {
143 Ok(dir) => {
144 for entry in dir.flatten() {
145 let path = entry.path();
146 if !path.is_dir() {
147 continue;
148 }
149 if let Some(name) = path.file_name() {
150 let dir_name = match name.to_str() {
151 Some(n) => n,
152 None => continue,
153 };
154 let dir = match read_dir(format!("{}/{}", &src, dir_name)) {
156 Ok(d) => d,
157 Err(_) => continue,
158 };
159 for entry in dir.flatten() {
160 let path = entry.path();
161 if !path.is_file() {
162 continue;
163 }
164 let file_name = match path.file_name() {
165 Some(name) => match name.to_str() {
166 Some(file_name) => file_name,
167 None => continue,
168 },
169 None => continue,
170 };
171 if file_name.len() > 3 && file_name.ends_with(".rs") {
173 let file_name = file_name[..file_name.len() - 3].to_owned();
174 match list.entry(dir_name.to_owned()) {
175 Entry::Occupied(mut o) => {
176 let vec = o.get_mut();
177 vec.push(file_name);
178 vec.shrink_to_fit();
179 }
180 Entry::Vacant(v) => {
181 let vec = vec![file_name];
182 v.insert(vec);
183 }
184 }
185 }
186 }
187 }
188 }
189 }
190 Err(e) => return Err(format!("{}. File name: {}", e, src)),
191 };
192 list.shrink_to_fit();
193 Ok(list)
194}
195
196fn error(text: &str) -> TokenStream {
198 TokenStream::from(Error::new(Span::call_site().into(), text).to_compile_error())
199}
200
201#[inline]
211fn fnv1a_64_impl(text: &str) -> i64 {
212 let mut hash: u64 = 0xcbf29ce484222325;
213 let prime: u64 = 0x100000001b3;
214
215 for c in text.bytes() {
216 hash ^= u64::from(c);
217 hash = hash.wrapping_mul(prime);
218 }
219 unsafe { *(&hash as *const u64 as *const i64) }
220}
221
222#[proc_macro]
225pub fn fnv1a_64(params: TokenStream) -> TokenStream {
226 let text = parse_macro_input!(params as syn::LitStr).value();
227 let result = fnv1a_64_impl(&text);
228 let result_token_stream = quote! { #result };
229 result_token_stream.into()
230}
231
232#[proc_macro]
234pub fn version(_: TokenStream) -> TokenStream {
235 let output = match Command::new("rustc").arg("--version").output() {
236 Ok(output) => output,
237 Err(e) => return error(&format!("Can't execute command `rustc --version`: {}", e)),
238 };
239 let result = String::from_utf8_lossy(&output.stdout);
240 if !result.starts_with("rustc ") {
241 return error(&format!("`rustc --version` returns an incorrect version string: {}", result))
242 }
243
244 let result_token_stream = quote! { #result };
245 result_token_stream.into()
246}