1use glob::glob;
21use proc_macro::TokenStream;
22use proc_macro2;
23use quote::{quote, quote_spanned};
24use syn;
25use tokay_04 as tokay; fn tokay_run(src: &str, input: &str) -> Result<Option<tokay::value::Value>, String> {
29 std::env::set_var("TOKAY_DEBUG", "0");
31 std::env::set_var("TOKAY_PARSER_DEBUG", "0");
32
33 let mut compiler = tokay::compiler::Compiler::new();
34 let program = compiler.compile(tokay::reader::Reader::new(Box::new(std::io::Cursor::new(
35 src.to_owned(),
36 ))));
37
38 match program {
39 Ok(program) => program
40 .run_from_string(input.to_owned())
41 .map_err(|err| err.to_string()),
42 Err(errors) => Err(errors
43 .into_iter()
44 .map(|err| err.to_string())
45 .collect::<Vec<String>>()
46 .join("\n")),
47 }
48}
49
50struct BuiltinDef {
52 name: syn::Ident,
53 arguments: Vec<(String, String)>,
54 body: syn::Expr,
55}
56
57impl syn::parse::Parse for BuiltinDef {
58 fn parse(stream: syn::parse::ParseStream) -> syn::Result<Self> {
59 let signature = stream.parse::<syn::LitStr>()?;
60 let _ = stream.parse::<syn::Token![,]>()?;
61 let body = stream.parse::<syn::Expr>()?;
62
63 let res = match tokay_run(include_str!("signature.tok"), &signature.value()) {
65 Err(msg) => return Err(syn::parse::Error::new(signature.span(), msg)),
66 Ok(ast) => ast.unwrap().to_list(),
67 };
68
69 let mut arguments = Vec::new();
70 let name = res[0].borrow().to_string();
71
72 if res.len() > 1 {
73 let args = res[1].borrow().to_list();
74
75 for item in args.iter() {
78 let arg = &*item.borrow();
79 if let Some(arg) = arg.get_list() {
80 arguments.push((arg[0].borrow().to_string(), arg[1].borrow().to_string()));
82 } else {
83 arguments.push((args[0].borrow().to_string(), args[1].borrow().to_string()));
85 break; }
87 }
88 }
89
90 Ok(BuiltinDef {
91 name: syn::Ident::new(&name, proc_macro2::Span::call_site()),
92 arguments,
93 body,
94 })
95 }
96}
97
98fn gen_assign_arguments(arguments: Vec<(String, String)>) -> Vec<proc_macro2::TokenStream> {
99 let mut ret = Vec::new();
100
101 let mut count: usize = 0;
102 let mut args = false;
103 let mut nargs = false;
104
105 for (arg, default) in arguments {
106 if arg == "*args" {
107 if args {
109 ret.push(quote! {
110 compile_error!("Multiple usage of *args");
111 });
112 }
113
114 args = true;
115 continue;
116 } else if arg == "**nargs" {
117 if nargs {
119 ret.push(quote! {
120 compile_error!("Multiple usage of *nargs");
121 });
122 }
123
124 nargs = true;
125 continue;
126 }
127
128 count += 1;
129
130 let arg = syn::Ident::new(
131 &arg,
132 proc_macro2::Span::call_site(), );
134
135 ret.push({
136 let required = default.is_empty();
137 let default = match &default[..] {
138 "void" | "" => quote!(tokay::value!(void)),
139 "null" => quote!(tokay::value!(null)),
140 "true" => quote!(tokay::value!(true)),
141 "false" => quote!(tokay::value!(false)),
142 int if int.parse::<i64>().is_ok() => {
143 let int = int.parse::<i64>().unwrap();
144 quote!(tokay::value!(#int))
145 }
146 _ => unreachable!(),
147 };
148
149 quote! {
150 let mut #arg =
151 if !args.is_empty() {
152 args.remove(0)
153 }
154 else {
155 let mut value = None;
156
157 if let Some(nargs) = &mut nargs {
158 value = nargs.remove_str(stringify!(#arg));
159 }
160
161 if value.is_none() {
162 if #required {
163 return Err(format!("{} expected argument '{}'", __function, stringify!(#arg)).into()).into();
164 }
165 else {
166 #default
167 }
168 }
169 else {
170 value.unwrap()
171 }
172 }
173 ;
174
175 }
177 });
178 }
179
180 if !args {
181 ret.push(quote! {
182 if args.len() > 0 {
183 return Err(
184 match #count {
185 0 => format!("{} doesn't accept any arguments ({} given)", __function, args.len()),
186 1 => format!("{} takes exactly one argument ({} given)", __function, #count + args.len()),
187 _ => format!("{} expected at most {} arguments ({} given)", __function, #count, #count + args.len()),
188 }.into()
189 ).into()
190 }
191 });
192 }
193
194 if !nargs {
195 ret.push(quote! {
196 if let Some(mut nargs) = nargs {
197 if let Some((name, _)) = nargs.pop() {
198 return Err(
199 match nargs.len() {
200 0 => format!("{} doesn't accept named argument '{}'", __function, name.to_string()),
201 n => format!("{} doesn't accept named arguments ({} given)", __function, n + 1),
202 }.into()
203 ).into()
204 }
205 }
206 });
207 }
208
209 ret
210}
211
212#[proc_macro]
213pub fn tokay_method(input: TokenStream) -> TokenStream {
214 let def = syn::parse_macro_input!(input as BuiltinDef);
215
216 let name = def.name;
217 let internal = syn::Ident::new(
218 &format!("{}_internal", name.to_string()),
219 proc_macro2::Span::call_site(),
220 );
221 let callable = syn::Ident::new(
222 &format!("tokay_method_{}", name.to_string()),
223 proc_macro2::Span::call_site(),
224 );
225
226 if !name.to_string().chars().next().unwrap().is_lowercase() {
228 return quote_spanned! {
229 name.span() => compile_error!(
230 "Method identifier must start with a lower-case letter"
231 );
232 }
233 .into();
234 }
235
236 let arguments = gen_assign_arguments(def.arguments);
238 let body = def.body;
239
240 let gen = quote! {
245 fn #internal(
246 context: Option<&mut tokay::Context>,
247 mut args: Vec<tokay::RefValue>,
248 mut nargs: Option<tokay::Dict>
249 ) -> Result<tokay::RefValue, tokay::Error> {
250 let __function = concat!(stringify!(#name), "()");
252
253 #(#arguments)*
255
256 #body
258 }
259
260 pub fn #name(
261 args: Vec<tokay::RefValue>,
262 nargs: Option<tokay::Dict>
263 ) -> Result<tokay::RefValue, tokay::Error> {
264 Self::#internal(None, args, nargs)
265 }
266
267 pub fn #callable(
268 context: Option<&mut tokay::Context>,
269 args: Vec<tokay::RefValue>,
270 nargs: Option<tokay::Dict>
271 ) -> Result<tokay::Accept, tokay::Reject> {
272 let ret = Self::#internal(context, args, nargs)?;
273 Ok(tokay::Accept::Push(tokay::Capture::Value(ret, None, 10)))
274 }
275 };
276
277 TokenStream::from(gen)
280}
281
282#[proc_macro]
283pub fn tokay_function(input: TokenStream) -> TokenStream {
284 let def = syn::parse_macro_input!(input as BuiltinDef);
285
286 let name = def.name;
287 let callable = syn::Ident::new(
288 &format!("tokay_function_{}", name.to_string()),
289 proc_macro2::Span::call_site(),
290 );
291
292 if !name.to_string().chars().next().unwrap().is_lowercase() {
294 return quote_spanned! {
295 name.span() => compile_error!(
296 "Function identifier must start with a lower-case letter"
297 );
298 }
299 .into();
300 }
301
302 let arguments = gen_assign_arguments(def.arguments);
304 let body = def.body;
305
306 let gen = quote! {
308 pub fn #callable(
309 context: Option<&mut tokay::vm::Context>,
310 mut args: Vec<tokay::RefValue>,
311 mut nargs: Option<tokay::Dict>
312 ) -> Result<tokay::vm::Accept, tokay::vm::Reject> {
313 let __function = concat!(stringify!(#name), "()");
315
316 #(#arguments)*
318
319 #body
321 }
322 };
323
324 TokenStream::from(gen)
325}
326
327#[proc_macro]
328pub fn tokay_token(input: TokenStream) -> TokenStream {
329 let def = syn::parse_macro_input!(input as BuiltinDef);
330
331 let name = def.name;
332
333 if !{
335 let ch = name.to_string().chars().next().unwrap();
336 ch.is_uppercase() || ch == '_'
337 } {
338 return quote_spanned! {
339 name.span() => compile_error!(
340 "Token identifier must start with an upper-case letter or underscore"
341 );
342 }
343 .into();
344 }
345
346 let function = syn::Ident::new(
347 &name.to_string().to_lowercase(),
348 proc_macro2::Span::call_site(),
349 );
350 let callable = syn::Ident::new(
351 &format!("tokay_token_{}", name.to_string().to_lowercase()),
352 proc_macro2::Span::call_site(),
353 );
354
355 let arguments = gen_assign_arguments(def.arguments);
357 let body = def.body;
358
359 let gen = quote! {
361 pub fn #function(
362 context: &mut tokay::vm::Context,
363 mut args: Vec<tokay::RefValue>,
364 mut nargs: Option<tokay::Dict>
365 ) -> Result<tokay::Accept, tokay::Reject> {
366 let __function = concat!(stringify!(#name), "()");
368
369 #(#arguments)*
371
372 #body
374 }
375
376 pub fn #callable(
377 context: Option<&mut tokay::Context>,
378 args: Vec<tokay::RefValue>,
379 nargs: Option<tokay::Dict>
380 ) -> Result<tokay::Accept, tokay::Reject> {
381 #function(context.unwrap(), args, nargs)
382 }
383 };
384
385 TokenStream::from(gen)
386}
387
388#[proc_macro]
389pub fn tokay_tests(input: TokenStream) -> TokenStream {
390 let pattern = syn::parse_macro_input!(input as syn::LitStr);
391 let pattern = pattern.value();
392
393 let mut tests = Vec::new();
394
395 for test in glob(&pattern).expect(&format!("Failed to read {:?}", pattern)) {
396 let test = test.unwrap();
397 let name = format!("test_{}", test.file_stem().unwrap().to_str().unwrap());
398 let name = syn::Ident::new(&name, proc_macro2::Span::call_site());
399 let path = test.to_str().unwrap();
400
401 tests.push(TokenStream::from(quote!(
402 #[test]
403 fn #name() {
404 crate::utils::testcase(#path);
405 }
406 )));
407 }
408
409 return TokenStream::from_iter(tests.into_iter());
412}