1extern crate proc_macro;
2extern crate proc_macro2;
3#[macro_use]
4extern crate syn;
5extern crate quote;
6use std::collections::HashMap;
7
8use proc_macro::TokenStream;
9use proc_macro2::Group;
10use quote::{quote, ToTokens};
11use syn::{
12 punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, DeriveInput, Expr,
13 ExprGroup, ExprLit, FnArg, Ident, ItemFn, Lit, LitStr, Meta, Pat, ReturnType, Signature, Type,
14 TypeReference,
15};
16
17#[proc_macro_attribute]
18pub fn cross_platform_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
19 let input = parse_macro_input!(item as ItemFn);
21
22 let expanded = quote! {
23 #[cfg(target_os = "windows")]
24 #[allow(improper_ctypes_definitions)]
25 pub extern "sysv64-unwind" #input
26
27 #[cfg(not(target_os = "windows"))]
28 #[allow(improper_ctypes_definitions)]
29 pub extern "C-unwind" #input
30 };
31
32 TokenStream::from(expanded)
33}
34
35#[proc_macro]
36pub fn steel_quote(input: TokenStream) -> TokenStream {
37 let token_iter = proc_macro2::TokenStream::from(input).into_iter();
38
39 let mut identifiers: Vec<Ident> = Vec::new();
40 let mut list_identifiers: Vec<Ident> = Vec::new();
41 let mut tokens: Vec<proc_macro2::TokenTree> = Vec::new();
42
43 walk(
44 token_iter,
45 &mut list_identifiers,
46 &mut identifiers,
47 &mut tokens,
48 );
49
50 let original = proc_macro2::TokenStream::from_iter(tokens).to_string();
51
52 let identifier_str = identifiers.iter().map(|x| x.to_string());
53 let list_identifiers_str = list_identifiers.iter().map(|x| x.to_string());
54
55 quote! {
56 ::steel::parser::replace_idents::expand_template_pair(
57 steel::::parser::parser::Parser::parse(#original).unwrap(),
58 vec![
59 #(
60 (#identifier_str.into(), (::steel::parser::expander::BindingKind::Single, #identifiers)),
61 )*
62
63 #(
64 (#list_identifiers_str.into(), (::steel::parser::expander::BindingKind::Many, #list_identifiers)),
65 )*
66 ]
67 )
68
69 }
70 .into()
71}
72
73#[proc_macro]
74pub fn internal_steel_quote(input: TokenStream) -> TokenStream {
75 let token_iter = proc_macro2::TokenStream::from(input).into_iter();
76
77 let mut identifiers: Vec<Ident> = Vec::new();
78 let mut list_identifiers: Vec<Ident> = Vec::new();
79 let mut tokens: Vec<proc_macro2::TokenTree> = Vec::new();
80
81 walk(
82 token_iter,
83 &mut list_identifiers,
84 &mut identifiers,
85 &mut tokens,
86 );
87
88 let original = proc_macro2::TokenStream::from_iter(tokens).to_string();
89
90 let identifier_str = identifiers.iter().map(|x| x.to_string());
91 let list_identifiers_str = list_identifiers.iter().map(|x| x.to_string());
92
93 quote! {
94 crate::parser::replace_idents::expand_template_pair(
95 crate::parser::parser::Parser::parse(#original).unwrap(),
96 vec![
97 #(
98 (#identifier_str.into(), (crate::parser::expander::BindingKind::Single, #identifiers)),
99 )*
100
101 #(
102 (#list_identifiers_str.into(), (crate::parser::expander::BindingKind::Many, #list_identifiers)),
103 )*
104 ]
105 )
106
107 }
108 .into()
109}
110
111fn walk(
112 mut token_iter: proc_macro2::token_stream::IntoIter,
113 list_identifiers: &mut Vec<Ident>,
114 identifiers: &mut Vec<Ident>,
115 tokens: &mut Vec<proc_macro2::TokenTree>,
116) {
117 while let Some(next) = token_iter.next() {
118 match &next {
119 proc_macro2::TokenTree::Group(g) => {
120 let mut child = Vec::new();
121
122 walk(
123 g.stream().into_iter(),
124 list_identifiers,
125 identifiers,
126 &mut child,
127 );
128
129 tokens.push(proc_macro2::TokenTree::Group(Group::new(
130 g.delimiter(),
131 proc_macro2::TokenStream::from_iter(child.into_iter()),
132 )));
133 }
134 proc_macro2::TokenTree::Punct(p) if p.as_char() == '@' => {
135 if let Some(proc_macro2::TokenTree::Ident(next_ident)) = token_iter.next() {
136 list_identifiers.push(next_ident.clone());
137 tokens.push(proc_macro2::TokenTree::Ident(next_ident));
138 } else {
139 panic!();
140 };
141 }
142 proc_macro2::TokenTree::Punct(p) if p.as_char() == '#' => {
143 if let Some(proc_macro2::TokenTree::Ident(next_ident)) = token_iter.next() {
144 identifiers.push(next_ident.clone());
145 tokens.push(proc_macro2::TokenTree::Ident(next_ident));
146 } else {
147 panic!();
148 };
149 }
150 _ => {
151 tokens.push(next);
152 }
153 }
154 }
155}
156
157fn derive_steel_impl(input: DeriveInput, prefix: proc_macro2::TokenStream) -> TokenStream {
158 let name = &input.ident;
159 let mut names = Vec::new();
160 let mut values = Vec::new();
161
162 let should_impl_equals = should_derive_param(&input, "equality");
163 let should_impl_getters = should_derive_param(&input, "getters");
164 let should_impl_constructor =
165 should_derive_param(&input, "constructor") || should_derive_param(&input, "constructors");
166 let should_impl_hash = should_derive_param(&input, "hash");
167
168 match &input.data {
169 Data::Struct(s) => {
170 match &s.fields {
171 syn::Fields::Named(_) => {
172 let field_names_args = s.fields.iter().filter_map(|x| x.ident.clone());
173 let field_names_body = s.fields.iter().filter_map(|x| x.ident.clone());
174
175 if should_impl_constructor {
176 names.push(format!("{}", name));
177 values.push(quote! {
178 |#(
179 #field_names_args,
180 )*| #name {
181 #(
182 #field_names_body,
183 )*
184 }
185 });
186 }
187
188 if should_impl_getters {
189 for field in s.fields.iter() {
190 if !filter_out_ignored(field) {
191 continue;
192 }
193
194 if let Some(field_name) = &field.ident {
195 let accessor_func = format!("{}-{}", name, field_name);
196
197 names.push(accessor_func.clone());
199
200 values.push(quote! {
201 |value: &#name| {
202 use #prefix::rvals::IntoSteelVal;
203 value.#field_name.clone().into_steelval()
204 }
205 });
206 }
207 }
208 }
209 }
210 syn::Fields::Unnamed(_) => {
212 if should_impl_constructor {
213 names.push(format!("{}", name,));
214 values.push(quote! {
215 #name
216 });
217 }
218
219 if should_impl_getters {
220 for (index, field) in s.fields.iter().enumerate() {
221 if !filter_out_ignored(field) {
222 continue;
223 }
224
225 let accessor_func = format!("{}-{}", name, index);
226
227 let index = syn::Index::from(index);
228
229 names.push(accessor_func.clone());
231
232 values.push(quote! {
233 |value: &#name| {
234 use #prefix::rvals::IntoSteelVal;
235 value.#index.clone().into_steelval()
236 }
237 });
238 }
239 }
240 }
241 syn::Fields::Unit => {
242 if should_impl_constructor {
243 names.push(format!("{}", name));
244 values.push(quote! {
245 || #name
246 });
247 }
248 }
249 }
250
251 let equality_impl = if should_impl_equals {
252 quote! {
253 fn equality_hint(&self, other: &dyn #prefix::rvals::CustomType) -> bool {
254 if let Some(other) = #prefix::rvals::as_underlying_type::<#name>(other) {
255 self == other
256 } else {
257 false
258 }
259 }
260 }
261 } else {
262 quote! {}
263 };
264
265 let generated = quote! {
266 impl #prefix::rvals::Custom for #name {
267 #equality_impl
268 }
269
270 impl #name {
271 #[doc = "Registers the struct functions with this module"]
272 pub fn register_type(module: &mut #prefix::steel_vm::builtin::BuiltInModule) ->
273 &mut #prefix::steel_vm::builtin::BuiltInModule {
274 use #prefix::steel_vm::register_fn::RegisterFn;
275 #(
276 module.register_fn(#names, #values);
277 )*
278
279 module
280 }
281 }
282 };
283
284 generated.into()
285 }
286 Data::Enum(e) => {
287 let mut names = Vec::new();
288 let mut values = Vec::new();
289
290 'variant: for variant in &e.variants {
293 let identifier = &variant.ident;
294
295 for attr in &variant.attrs {
296 if !filter_out_ignored_attr(attr) {
297 continue 'variant;
298 }
299 }
300
301 match variant.fields {
302 syn::Fields::Named(_) => {
303 names.push(format!("{}-{}?", name, identifier));
304 values.push(quote! {
305 |value: #prefix::rvals::SteelVal| {
306 use #prefix::gc::ShareableMut;
307 if let #prefix::rvals::SteelVal::Custom(c) = value {
308 if let Some(inner) = #prefix::rvals::as_underlying_type::<#name>(c.read().as_ref())
309 {
310 return matches!(inner, #name::#identifier{..});
311 }
312 }
313 false
314 }});
315
316 if should_impl_constructor {
317 let field_names_args = variant
318 .fields
319 .iter()
320 .filter_map(|x| x.ident.clone());
323
324 let field_names_body =
325 variant.fields.iter().filter_map(|x| x.ident.clone());
326
327 names.push(format!("{}-{}", name, identifier));
328 values.push(quote! {
329 |#(
330 #field_names_args,
331 )*| #name::#identifier {
332 #(
333 #field_names_body,
334 )*
335 }
336 });
337 }
338
339 if should_impl_getters {
340 for field in variant.fields.iter() {
341 if !filter_out_ignored(field) {
342 continue;
343 }
344
345 if let Some(field_name) = &field.ident {
346 let accessor_func =
347 format!("{}-{}-{}", name, identifier, field_name);
348
349 names.push(accessor_func.clone());
351
352 values.push(quote! {
353 |value: &#name| {
354 use #prefix::rvals::IntoSteelVal;
355 use #prefix::stop;
356 use #prefix::rerrs::SteelErr;
357
358 if let #name::#identifier { #field_name, .. } = &value {
359 #field_name.clone().into_steelval()
360 } else {
361 #prefix::stop!(TypeMismatch =>
362 format!("{} expected {}-{}, found {:?}",
363 #accessor_func,
364 stringify!(#name),
365 stringify!(#identifier),
366 value));
367 }
368 }
369 });
370 }
371 }
372 }
373 }
374 syn::Fields::Unnamed(_) => {
375 names.push(format!("{}-{}?", name, identifier));
376 values.push(quote! {
377 |value: #prefix::rvals::SteelVal| {
378 use #prefix::gc::ShareableMut;
379 if let #prefix::rvals::SteelVal::Custom(c) = value {
380 if let Some(inner) = #prefix::rvals::as_underlying_type::<#name>(c.read().as_ref())
381 {
382 return matches!(inner, #name::#identifier(..));
383 }
384 }
385 false
386 }});
387
388 if should_impl_constructor {
389 names.push(format!("{}-{}", name, identifier));
390 values.push(quote! {
391 #name::#identifier
392 });
393 }
394
395 if should_impl_getters {
396 for (field_name, _) in variant
397 .fields
398 .iter()
399 .filter(|x| filter_out_ignored(x))
400 .enumerate()
401 {
402 let accessor_func =
403 format!("{}-{}-{}", name, identifier, field_name);
404
405 let blank = vec![quote!(_); field_name];
406
407 names.push(accessor_func.clone());
409
410 values.push(quote! {
411 |value: &#name| {
412 use #prefix::rvals::IntoSteelVal;
413 use #prefix::stop;
414 use #prefix::rerrs::SteelErr;
415
416 if let #name::#identifier(#(#blank,)* value, ..) = value {
417 value.clone().into_steelval()
418 } else {
419 #prefix::stop!(TypeMismatch =>
420 format!("{} expected {}-{}, found {:?}",
421 #accessor_func,
422 stringify!(#name),
423 stringify!(#identifier), value));
424 }
425 }
426 });
427 }
428 }
429 }
430 syn::Fields::Unit => {
431 names.push(format!("{}-{}?", name, identifier));
432 values.push(quote! {
433 |value: #prefix::rvals::SteelVal| {
434 use #prefix::gc::ShareableMut;
435 if let #prefix::rvals::SteelVal::Custom(c) = value {
436 if let Some(inner) = #prefix::rvals::as_underlying_type::<#name>(c.read().as_ref())
437 {
438 return matches!(inner, #name::#identifier);
439 }
440 }
441 false
442 }});
443
444 if should_impl_getters {
445 names.push(format!("{}-{}", name, identifier));
446 values.push(quote! {
447 || #name::#identifier
448 });
449 }
450 }
451 }
452 }
453
454 let equality_impl = if should_impl_equals {
455 quote! {
456 fn equality_hint(&self, other: &dyn #prefix::rvals::CustomType) -> bool {
457 if let Some(other) = #prefix::rvals::as_underlying_type::<#name>(other) {
458 self == other
459 } else {
460 false
461 }
462 }
463 }
464 } else {
465 quote! {}
466 };
467
468 let hash_impl = if should_impl_hash {
469 quote! {
470 fn try_as_dyn_hash(&self) -> Option<&dyn #prefix::rvals::DynHash> {
471 Some(self)
472 }
473 }
474 } else {
475 quote! {}
476 };
477
478 let generated = quote! {
479 impl #prefix::rvals::Custom for #name {
480 #equality_impl
481
482 #hash_impl
483 }
484
485 impl #name {
486 #[doc = "Registers the enum variant functions with this module"]
487 pub fn register_enum_variants(module: &mut #prefix::steel_vm::builtin::BuiltInModule) ->
488 &mut #prefix::steel_vm::builtin::BuiltInModule {
489 use #prefix::steel_vm::register_fn::RegisterFn;
490 #(
491 module.register_fn(#names, #values);
492 )*
493
494 module
495 }
496 }
497 };
498
499 generated.into()
500 }
501 _ => {
502 let output = quote! {};
503 output.into()
504 }
505 }
506}
507
508fn should_derive_param(derive: &DeriveInput, kind: &str) -> bool {
509 for attr in &derive.attrs {
510 match &attr.meta {
511 Meta::Path(_) => {}
512 Meta::List(p) => {
513 if p.path.is_ident("steel") {
514 let args = ::syn::parse::Parser::parse2(
515 Punctuated::<Ident, Token![,]>::parse_terminated,
516 p.tokens.clone(),
517 )
518 .unwrap();
519
520 for arg in args {
521 if arg == kind {
522 return true;
523 }
524 }
525 }
526 }
527 Meta::NameValue(_) => {}
528 }
529 }
530
531 false
532}
533
534fn filter_out_ignored_attr(attr: &Attribute) -> bool {
535 match &attr.meta {
536 Meta::Path(_) => {}
537 Meta::List(p) => {
538 if p.path.is_ident("steel") {
539 let args = ::syn::parse::Parser::parse2(
540 Punctuated::<Ident, Token![,]>::parse_terminated,
541 p.tokens.clone(),
542 )
543 .unwrap();
544
545 for arg in args {
546 if arg == "ignore" {
547 return false;
548 }
549 }
550 }
551 }
552 Meta::NameValue(_) => {}
553 }
554
555 true
556}
557
558fn filter_out_ignored(field: &syn::Field) -> bool {
559 for attr in &field.attrs {
560 match &attr.meta {
561 Meta::Path(_) => {}
562 Meta::List(p) => {
563 if p.path.is_ident("steel") {
564 let args = ::syn::parse::Parser::parse2(
565 Punctuated::<Ident, Token![,]>::parse_terminated,
566 p.tokens.clone(),
567 )
568 .unwrap();
569
570 for arg in args {
571 if arg == "ignore" {
572 return false;
573 }
574 }
575 }
576 }
577 Meta::NameValue(_) => {}
578 }
579 }
580
581 true
582}
583
584#[proc_macro_derive(Steel, attributes(steel))]
585pub fn derive_steel(input: TokenStream) -> TokenStream {
586 let input = parse_macro_input!(input as DeriveInput);
587 let prefix = quote! { ::steel };
588
589 derive_steel_impl(input, prefix)
590}
591
592#[proc_macro_derive(_Steel, attributes(steel))]
593pub fn derive_steel_internal(input: TokenStream) -> TokenStream {
594 let input = parse_macro_input!(input as DeriveInput);
595 let prefix = quote! { crate };
596 derive_steel_impl(input, prefix)
597}
598
599fn parse_key_value_pairs(args: &Punctuated<Meta, Token![,]>) -> HashMap<String, String> {
600 let mut map = HashMap::new();
601
602 for nested_meta in args.iter() {
603 if let Meta::NameValue(n) = nested_meta {
604 let key = n.path.get_ident().unwrap().to_string();
605
606 let mut value = &n.value;
607
608 loop {
609 match value {
610 Expr::Lit(ExprLit {
611 lit: Lit::Str(s), ..
612 }) => {
613 map.insert(key, s.value());
614 break;
615 }
616 Expr::Lit(ExprLit {
617 lit: Lit::Bool(b), ..
618 }) => {
619 map.insert(key, b.value().to_string());
620 break;
621 }
622 Expr::Group(ExprGroup { expr, .. }) => {
623 value = &**expr;
624 }
625 _ => break,
626 }
627 }
628 }
629 }
630
631 map
632}
633
634fn parse_doc_comment(input: ItemFn) -> Option<proc_macro2::TokenStream> {
635 let span = input.span();
636
637 let maybe_str_literals = input
638 .attrs
639 .into_iter()
640 .filter_map(|attr| match attr.meta {
641 Meta::NameValue(name_value) if name_value.path.is_ident("doc") => {
642 Some(name_value.value)
643 }
644 _ => None,
645 })
646 .map(|expr| match expr {
647 Expr::Lit(ExprLit {
648 lit: Lit::Str(s), ..
649 }) => Ok(s),
650 e => Err(e),
651 })
652 .collect::<Vec<_>>();
653
654 if maybe_str_literals.is_empty() {
655 return None;
656 }
657
658 if let Some(literals) = maybe_str_literals
659 .iter()
660 .map(|item| item.as_ref().ok())
661 .collect::<Option<Vec<_>>>()
662 {
663 let trimmed: Vec<_> = literals
664 .iter()
665 .flat_map(|lit| {
666 lit.value()
667 .split('\n')
668 .map(|s| s.to_string())
669 .collect::<Vec<_>>()
670 })
671 .map(|line| {
672 line.strip_prefix(" ")
673 .map(ToOwned::to_owned)
674 .unwrap_or(line)
675 })
676 .collect();
677
678 let doc = trimmed.join("\n");
679
680 return Some(quote! { #doc });
681 }
682
683 let mut args = vec![];
684
685 for (i, item) in maybe_str_literals.into_iter().enumerate() {
686 if i > 0 {
687 args.push(Expr::Lit(ExprLit {
688 attrs: vec![],
689 lit: Lit::Str(LitStr::new("\n", span)),
690 }));
691 }
692
693 let expr = match item {
694 Ok(lit) => Expr::Lit(ExprLit {
695 attrs: vec![],
696 lit: Lit::Str(lit),
697 }),
698 Err(expr) => expr,
699 };
700
701 args.push(expr);
702 }
703
704 Some(quote! {
705 concat![#(#args),*]
706 })
707}
708
709#[proc_macro_attribute]
710pub fn define_module(
711 args: proc_macro::TokenStream,
712 input: proc_macro::TokenStream,
713) -> proc_macro::TokenStream {
714 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
715 let input = parse_macro_input!(input as ItemFn);
716 let keyword_map = parse_key_value_pairs(&args);
717
718 let value = keyword_map
719 .get("name")
720 .expect("native definition requires a name!");
721
722 let sign: Signature = input.sig.clone();
723
724 let maybe_doc_comments = parse_doc_comment(input.clone());
725
726 let function_name = sign.ident;
727
728 if let Some(doc_comments) = maybe_doc_comments {
729 quote! {
730 pub fn #function_name() -> BuiltInModule {
731 #input
732
733 let mut module = #function_name();
734
735 module.register_doc(#value, crate::steel_vm::builtin::MarkdownDoc(#doc_comments.into()));
736
737 module
738 }
739 }
740 .into()
741 } else {
742 quote! {
743 #input
744 }
745 .into()
746 }
747}
748
749fn arity_code_injection(
750 input: &ItemFn,
751 args: &Punctuated<Meta, Comma>,
752 is_context_function: bool,
753) -> ItemFn {
754 let keyword_map = parse_key_value_pairs(args);
755
756 let arity_number = keyword_map
757 .get("arity")
758 .expect("native definition requires an arity");
759
760 let func_name = input.sig.ident.to_string();
761
762 let sig_inputs = if is_context_function {
764 input.sig.inputs.get(1)
765 } else {
766 input.sig.inputs.first()
767 };
768
769 let parameter_name = if let FnArg::Typed(pat_type) = sig_inputs.unwrap() {
771 let pat_type = pat_type.clone();
772 if let Pat::Ident(ident) = *pat_type.pat {
773 ident.ident
774 } else {
775 panic!("Could not extract parameter name")
776 }
777 } else {
778 panic!("Could not extract parameter name")
779 };
780
781 let (name, numb, end_numb) = arity_number
783 .strip_suffix(')')
784 .and_then(|stripped| stripped.split_once('('))
785 .map(|(name, rest)| {
786 if let Some((start, end)) = rest.split_once(',') {
787 let start = start
789 .parse::<usize>()
790 .expect("Arity start value must be an integer");
791 let end = end
792 .parse::<usize>()
793 .expect("Arity end value must be an integer");
794 (name, start, end)
795 } else {
796 let num = rest
798 .parse::<usize>()
799 .expect("Arity value must be an integer");
800 (name, num, 0)
801 }
802 })
803 .expect("Arity header is wrongly formatted");
804
805 let stop_type = if is_context_function {
807 quote! {crate::builtin_stop!}
808 } else {
809 quote! {stop!}
810 };
811
812 let injected_code = match name {
814 "AtLeast" => {
815 if numb == 0 {
817 quote! {{}}
818 } else {
819 quote! {
820 if #parameter_name.len() < #numb {
821 #stop_type(ArityMismatch => "{} expects at least {} arguments, found: {}", #func_name, #numb, #parameter_name.len());
822 }
823 }
824 }
825 }
826
827 "AtMost" => quote! {
828 if #parameter_name.len() > #numb {
829 #stop_type(ArityMismatch => "{} expects at most {} arguments, found: {}",#func_name, #numb ,#parameter_name.len());
830 }
831 },
832
833 "Exact" => quote! {
834 if #parameter_name.len() != #numb {
835 #stop_type(ArityMismatch => "{} expects exactly {} arguments, found: {}",#func_name, #numb ,#parameter_name.len());
836 }
837 },
838 "Range" => quote! {
839 if (#parameter_name.len() < #numb) || (#parameter_name.len() > #end_numb) {
840 #stop_type(ArityMismatch => "{} expects {} to {} arguments, found: {}",#func_name, #numb , #end_numb ,#parameter_name.len());
841 }
842 },
843
844 _ => panic!("Unsupported Arity Type"),
845 };
846 let mut modified_input = input.clone();
848 modified_input
849 .block
850 .stmts
851 .insert(0, syn::parse_quote!(#injected_code));
852 modified_input
853}
854
855struct NativeMacroComponents {
856 pub doc_field: proc_macro2::TokenStream,
857 pub doc_name: proc_macro2::Ident,
858 pub steel_function_name: String,
859 pub rust_function_name: proc_macro2::Ident,
860 pub arity_number: syn::Expr,
861 pub is_const: bool,
862}
863
864fn native_macro_setup(input: &ItemFn, args: &Punctuated<Meta, Comma>) -> NativeMacroComponents {
865 let keyword_map = parse_key_value_pairs(args);
866
867 let steel_function_name = keyword_map
868 .get("name")
869 .expect("native definition requires a name!")
870 .to_string();
871
872 let arity_number = keyword_map
873 .get("arity")
874 .expect("native definition requires an arity");
875
876 let is_const = keyword_map
877 .get("constant")
878 .map(|x| x == "true")
879 .unwrap_or_default();
880
881 let arity_number: syn::Expr =
882 syn::parse_str(arity_number).expect("Unable to parse arity definition");
883
884 let sign: Signature = input.clone().sig;
885
886 let maybe_doc_comments = parse_doc_comment(input.clone());
887 let rust_function_name = sign.ident.clone();
888
889 let doc_name = Ident::new(
890 &(rust_function_name.to_string().to_uppercase() + "_DEFINITION"),
891 sign.ident.span(),
892 );
893 let doc_field = if let Some(doc) = maybe_doc_comments {
894 quote! { Some(crate::steel_vm::builtin::MarkdownDoc::from_str(#doc)) }
895 } else {
896 quote! { None }
897 };
898
899 NativeMacroComponents {
900 doc_field,
901 doc_name,
902 steel_function_name,
903 rust_function_name,
904 arity_number,
905 is_const,
906 }
907}
908
909#[proc_macro_attribute]
910pub fn native(
911 args: proc_macro::TokenStream,
912 input: proc_macro::TokenStream,
913) -> proc_macro::TokenStream {
914 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
915 let input = parse_macro_input!(input as ItemFn);
916
917 let modified_input = if cfg!(feature = "disable-arity-checking") {
918 input.clone()
919 } else {
920 arity_code_injection(&input, &args, false)
921 };
922
923 let NativeMacroComponents {
924 doc_field,
925 doc_name,
926 steel_function_name,
927 rust_function_name,
928 arity_number,
929 is_const,
930 } = native_macro_setup(&input, &args);
931
932 let definition_struct = quote! {
933 pub const #doc_name: crate::steel_vm::builtin::NativeFunctionDefinition = crate::steel_vm::builtin::NativeFunctionDefinition {
934 name: #steel_function_name,
935 aliases: &[],
936 func: crate::steel_vm::builtin::BuiltInFunctionType::Reference(#rust_function_name),
937 arity: crate::steel_vm::builtin::Arity::#arity_number,
938 doc: #doc_field,
939 is_const: #is_const,
940 signature: None,
941 };
942 };
943
944 let output = quote! {
945 #[allow(dead_code)]
948 #modified_input
949
950 #definition_struct
951 };
952
953 output.into()
957}
958
959#[proc_macro_attribute]
960pub fn context(
961 args: proc_macro::TokenStream,
962 input: proc_macro::TokenStream,
963) -> proc_macro::TokenStream {
964 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
965 let input = parse_macro_input!(input as ItemFn);
966 let modified_input = if cfg!(feature = "disable-arity-checking") {
967 input.clone()
968 } else {
969 arity_code_injection(&input, &args, true)
970 };
971
972 let NativeMacroComponents {
973 doc_field,
974 doc_name,
975 steel_function_name,
976 rust_function_name,
977 arity_number,
978 is_const,
979 } = native_macro_setup(&input, &args);
980
981 let definition_struct = quote! {
982 pub const #doc_name: crate::steel_vm::builtin::NativeFunctionDefinition = crate::steel_vm::builtin::NativeFunctionDefinition {
983 name: #steel_function_name,
984 aliases: &[],
985 func: crate::steel_vm::builtin::BuiltInFunctionType::Context(#rust_function_name),
986 arity: crate::steel_vm::builtin::Arity::#arity_number,
987 doc: #doc_field,
988 is_const: #is_const,
989 signature: None,
990 };
991 };
992
993 let output = quote! {
994 #[allow(dead_code)]
997 #modified_input
998
999 #definition_struct
1000 };
1001
1002 output.into()
1003}
1004
1005#[proc_macro_attribute]
1006pub fn native_mut(
1007 args: proc_macro::TokenStream,
1008 input: proc_macro::TokenStream,
1009) -> proc_macro::TokenStream {
1010 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
1011 let input = parse_macro_input!(input as ItemFn);
1012
1013 let modified_input = if cfg!(feature = "disable-arity-checking") {
1014 input.clone()
1015 } else {
1016 arity_code_injection(&input, &args, false)
1017 };
1018
1019 let NativeMacroComponents {
1020 doc_field,
1021 doc_name,
1022 steel_function_name,
1023 rust_function_name,
1024 arity_number,
1025 is_const,
1026 } = native_macro_setup(&input, &args);
1027
1028 let definition_struct = quote! {
1029 pub const #doc_name: crate::steel_vm::builtin::NativeFunctionDefinition = crate::steel_vm::builtin::NativeFunctionDefinition {
1030 name: #steel_function_name,
1031 aliases: &[],
1032 func: crate::steel_vm::builtin::BuiltInFunctionType::Mutable(#rust_function_name),
1033 arity: crate::steel_vm::builtin::Arity::#arity_number,
1034 doc: #doc_field,
1035 is_const: #is_const,
1036 signature: None,
1037 };
1038 };
1039
1040 let output = quote! {
1041 #[allow(dead_code)]
1044 #modified_input
1045
1046 #definition_struct
1047 };
1048
1049 output.into()
1053}
1054#[proc_macro_attribute]
1057pub fn function(
1058 args: proc_macro::TokenStream,
1059 input: proc_macro::TokenStream,
1060) -> proc_macro::TokenStream {
1061 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
1062
1063 let keyword_map = parse_key_value_pairs(&args);
1066
1067 let value = keyword_map
1068 .get("name")
1069 .expect("native definition requires a name!");
1070
1071 let is_const = keyword_map
1073 .get("constant")
1074 .map(|x| x == "true")
1075 .unwrap_or_default();
1076
1077 let function_name_with_colon = value.clone() + ": ";
1078
1079 let input = parse_macro_input!(input as ItemFn);
1080
1081 let modified_input = input.clone();
1082 let sign: Signature = input.clone().sig;
1084
1085 let maybe_doc_comments = parse_doc_comment(input);
1086
1087 let return_type: ReturnType = sign.output;
1088
1089 let ret_val = match return_type {
1090 ReturnType::Default => quote! {
1091 Ok(SteelVal::Void)
1092 },
1093 ReturnType::Type(_, r) => {
1094 if let Type::Path(val) = *r {
1095 let last = val.path.segments.into_iter().last();
1096 if let Some(last) = last {
1097 match last.ident.into_token_stream().to_string().as_str() {
1098 "Result" => quote! { res },
1099 _ => quote! {
1100 res.into_steelval().map_err(err_thunk)
1101 },
1102 }
1103 } else {
1104 quote! {
1105 Ok(SteelVal::Void)
1106 }
1107 }
1108 } else {
1109 quote! {
1110 res.into_steelval().map_err(err_thunk)
1111 }
1112 }
1113 }
1114 };
1115
1116 let mut type_vec: Vec<Box<Type>> = Vec::new();
1117
1118 let mut rest_arg_generic_inner_type = false;
1119
1120 for (i, arg) in sign.inputs.iter().enumerate() {
1124 if let FnArg::Typed(pat_ty) = arg.clone() {
1125 if let Type::Path(p) = pat_ty.ty.as_ref() {
1126 let primary_type = p.path.segments.iter().last();
1127 if let Some(ty) = primary_type {
1128 match ty.ident.to_token_stream().to_string().as_str() {
1129 "RestArgs" | "RestArgsIter" => {
1130 if rest_arg_generic_inner_type {
1131 panic!("There cannot be multiple `RestArg`s for a given function.")
1132 }
1133
1134 if i != sign.inputs.len() - 1 {
1135 panic!(
1136 "The rest argument must be the last argument in the function."
1137 )
1138 }
1139 rest_arg_generic_inner_type = true;
1140 }
1141 _ => {}
1142 }
1143 }
1144 }
1145
1146 type_vec.push(pat_ty.ty);
1165 }
1166 }
1167
1168 let mut arity_number = type_vec.len();
1169
1170 let promote_to_mutable = type_vec.iter().any(|x| {
1173 matches!(
1174 **x,
1175 Type::Reference(TypeReference {
1176 mutability: Some(_),
1177 ..
1178 })
1179 )
1180 });
1181
1182 let conversion_functions = type_vec.clone().into_iter().map(|x| {
1183 if let Type::Reference(_) = *x {
1184 quote! { primitive_as_ref }
1185 } else if x.to_token_stream().to_string().starts_with("Either") {
1186 quote! { primitive_as_ref }
1187 } else {
1188 quote! { from_steelval }
1189 }
1190 });
1191
1192 let arg_enumerate = type_vec.iter().enumerate();
1193 let arg_type = arg_enumerate.clone().map(|(_, x)| x);
1194 let arg_index = arg_enumerate.clone().map(|(i, _)| i);
1195 let function_name = sign.ident.clone();
1197 let _arity_name = Ident::new(
1198 &(function_name.to_string().to_uppercase() + "_ARITY"),
1199 sign.ident.span(),
1200 );
1201 let copied_function_name = Ident::new(
1202 &("steel_".to_string() + &function_name.to_string()),
1203 sign.ident.span(),
1204 );
1205
1206 let doc_name = Ident::new(
1207 &(function_name.to_string().to_uppercase() + "_DEFINITION"),
1208 sign.ident.span(),
1209 );
1210
1211 let arity_exactness = if rest_arg_generic_inner_type {
1212 arity_number -= 1;
1213 quote! { AtLeast }
1214 } else {
1215 quote! { Exact }
1216 };
1217
1218 let function_type = if promote_to_mutable {
1219 quote! {
1220 crate::steel_vm::builtin::BuiltInFunctionType::Mutable(#copied_function_name)
1221 }
1222 } else {
1223 quote! {
1224 crate::steel_vm::builtin::BuiltInFunctionType::Reference(#copied_function_name)
1225 }
1226 };
1227
1228 let aliases = match keyword_map.get("alias") {
1229 Some(alias) => quote! { &[ #alias ] },
1230 None => quote! { &[] },
1231 };
1232
1233 let doc_field = if let Some(doc) = maybe_doc_comments {
1234 quote! { Some(crate::steel_vm::builtin::MarkdownDoc::from_str(#doc)) }
1235 } else {
1236 quote! { None }
1237 };
1238
1239 let definition_struct = quote! {
1240 pub const #doc_name: crate::steel_vm::builtin::NativeFunctionDefinition = crate::steel_vm::builtin::NativeFunctionDefinition {
1241 name: #value,
1242 aliases: #aliases,
1243 func: #function_type,
1244 arity: crate::steel_vm::builtin::Arity::#arity_exactness(#arity_number),
1245 doc: #doc_field,
1246 is_const: #is_const,
1247 signature: None,
1248 };
1249 };
1250
1251 if rest_arg_generic_inner_type {
1254 let mut conversion_functions = conversion_functions.collect::<Vec<_>>();
1255 let mut arg_index = arg_enumerate
1256 .map(|(i, _)| quote! { #i })
1257 .collect::<Vec<_>>();
1258
1259 if let Some(last) = arg_index.last_mut() {
1260 *last = quote! { #last.. };
1261 }
1262
1263 if let Some(last) = conversion_functions.last_mut() {
1264 *last = quote! { from_slice };
1265 }
1266
1267 let function_name = sign.ident;
1268
1269 let output = quote! {
1270 #[allow(dead_code)]
1273 #modified_input
1274
1275 #definition_struct
1276
1277 pub fn #copied_function_name(args: &[SteelVal]) -> std::result::Result<SteelVal, crate::rerrs::SteelErr> {
1278
1279 use crate::rvals::{IntoSteelVal, FromSteelVal, PrimitiveAsRef};
1280
1281 if args.len() < #arity_number {
1282 crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1283 }
1284
1285 fn err_thunk(mut err: crate::rerrs::SteelErr) -> crate::rerrs::SteelErr {
1286 err.prepend_message(#function_name_with_colon);
1287 err.set_kind(crate::rerrs::ErrorKind::TypeMismatch);
1288 err
1289 };
1290
1291 let res = #function_name(
1292 #(
1293 <#arg_type>::#conversion_functions(&args[#arg_index])
1296 .map_err(err_thunk)
1297 ?,
1298 )*
1299 );
1300
1301 #ret_val
1302 }
1303 };
1304
1305 return output.into();
1309 }
1310
1311 if promote_to_mutable {
1315 let temporary_fields: Vec<_> = type_vec
1316 .iter()
1317 .enumerate()
1318 .map(|(i, _)| {
1319 Ident::new(
1320 &("temporary_".to_string() + &i.to_string()),
1321 sign.ident.span(),
1322 )
1323 })
1324 .collect();
1325
1326 let output = quote! {
1327 #[allow(dead_code)]
1330 #modified_input
1331
1332 #definition_struct
1333
1334 pub fn #copied_function_name(args: &mut [SteelVal]) -> std::result::Result<SteelVal, crate::rerrs::SteelErr> {
1335
1336 use crate::rvals::{IntoSteelVal, FromSteelVal, PrimitiveAsRef, PrimitiveAsRefMut};
1337
1338 fn err_thunk(mut err: crate::rerrs::SteelErr) -> crate::rerrs::SteelErr {
1344 err.prepend_message(#function_name_with_colon);
1345 err.set_kind(crate::rerrs::ErrorKind::TypeMismatch);
1346 err
1347 };
1348
1349 if let [ #(#temporary_fields,)* ] = args {
1350
1351 let res = #function_name(
1352 #(
1353 <#arg_type>::#conversion_functions(#temporary_fields)
1356 .map_err(err_thunk)?,
1357 )*
1358 );
1359
1360 #ret_val
1361 } else {
1362 crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1363
1364 }
1365
1366 }
1382 };
1383
1384 return output.into();
1385 }
1386
1387 let output = quote! {
1388 #[allow(dead_code)]
1391 #modified_input
1392
1393 #definition_struct
1394
1395 pub fn #copied_function_name(args: &[SteelVal]) -> std::result::Result<SteelVal, crate::rerrs::SteelErr> {
1396
1397 use crate::rvals::{IntoSteelVal, FromSteelVal, PrimitiveAsRef};
1398
1399 if args.len() != #arity_number {
1400 crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1401 }
1402
1403
1404 fn err_thunk(mut err: crate::rerrs::SteelErr) -> crate::rerrs::SteelErr {
1405 err.prepend_message(#function_name_with_colon);
1406 err.set_kind(crate::rerrs::ErrorKind::TypeMismatch);
1407 err
1408 };
1409
1410 let res = #function_name(
1411 #(
1412 <#arg_type>::#conversion_functions(&args[#arg_index])
1415 .map_err(err_thunk)?,
1416 )*
1417 );
1418
1419 #ret_val
1420 }
1421 };
1422
1423 output.into()
1427}
1428
1429#[proc_macro_attribute]
1430pub fn custom_function(
1431 args: proc_macro::TokenStream,
1432 input: proc_macro::TokenStream,
1433) -> proc_macro::TokenStream {
1434 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
1435
1436 let keyword_map = parse_key_value_pairs(&args);
1437
1438 let value = keyword_map
1439 .get("name")
1440 .expect("native definition requires a name!");
1441
1442 let is_const = keyword_map
1444 .get("constant")
1445 .map(|x| x == "true")
1446 .unwrap_or_default();
1447
1448 let function_name_with_colon = value.clone() + ": ";
1449
1450 let input = parse_macro_input!(input as ItemFn);
1451
1452 let modified_input = input.clone();
1453 let sign: Signature = input.clone().sig;
1455
1456 let maybe_doc_comments = parse_doc_comment(input);
1457
1458 let return_type: ReturnType = sign.output;
1459
1460 let ret_val = match return_type {
1461 ReturnType::Default => quote! {
1462 Ok(SteelVal::Void)
1463 },
1464 ReturnType::Type(_, r) => {
1465 if let Type::Path(val) = *r {
1466 let last = val.path.segments.into_iter().last();
1467 if let Some(last) = last {
1468 match last.ident.into_token_stream().to_string().as_str() {
1469 "Result" => quote! { res },
1470 _ => quote! {
1471 res.into_steelval().map_err(err_thunk)
1472 },
1473 }
1474 } else {
1475 quote! {
1476 Ok(SteelVal::Void)
1477 }
1478 }
1479 } else {
1480 quote! {
1481 res.into_steelval().map_err(err_thunk)
1482 }
1483 }
1484 }
1485 };
1486
1487 let mut type_vec: Vec<Box<Type>> = Vec::new();
1488
1489 let mut rest_arg_generic_inner_type = false;
1490
1491 for (i, arg) in sign.inputs.iter().enumerate() {
1495 if let FnArg::Typed(pat_ty) = arg.clone() {
1496 if let Type::Path(p) = pat_ty.ty.as_ref() {
1497 let primary_type = p.path.segments.iter().last();
1498 if let Some(ty) = primary_type {
1499 match ty.ident.to_token_stream().to_string().as_str() {
1500 "RestArgs" | "RestArgsIter" => {
1501 if rest_arg_generic_inner_type {
1502 panic!("There cannot be multiple `RestArg`s for a given function.")
1503 }
1504
1505 if i != sign.inputs.len() - 1 {
1506 panic!(
1507 "The rest argument must be the last argument in the function."
1508 )
1509 }
1510 rest_arg_generic_inner_type = true;
1511 }
1512 _ => {}
1513 }
1514 }
1515 }
1516
1517 type_vec.push(pat_ty.ty);
1536 }
1537 }
1538
1539 let mut arity_number = type_vec.len();
1540
1541 let promote_to_mutable = type_vec.iter().any(|x| {
1544 matches!(
1545 **x,
1546 Type::Reference(TypeReference {
1547 mutability: Some(_),
1548 ..
1549 })
1550 )
1551 });
1552
1553 let conversion_functions = type_vec.clone().into_iter().map(|x| {
1554 if let Type::Reference(_) = *x {
1555 quote! { primitive_as_ref }
1556 } else if x.to_token_stream().to_string().starts_with("Either") {
1557 quote! { primitive_as_ref }
1558 } else {
1559 quote! { from_steelval }
1560 }
1561 });
1562
1563 let arg_enumerate = type_vec.iter().enumerate();
1564 let arg_type = arg_enumerate.clone().map(|(_, x)| x);
1565 let arg_index = arg_enumerate.clone().map(|(i, _)| i);
1566 let function_name = sign.ident.clone();
1568 let _arity_name = Ident::new(
1569 &(function_name.to_string().to_uppercase() + "_ARITY"),
1570 sign.ident.span(),
1571 );
1572 let copied_function_name = Ident::new(
1573 &("steel_".to_string() + &function_name.to_string()),
1574 sign.ident.span(),
1575 );
1576
1577 let doc_name = Ident::new(
1578 &(function_name.to_string().to_uppercase() + "_DEFINITION"),
1579 sign.ident.span(),
1580 );
1581
1582 let arity_exactness = if rest_arg_generic_inner_type {
1583 arity_number -= 1;
1585 quote! { AtLeast }
1586 } else {
1587 quote! { Exact }
1588 };
1589
1590 let function_type = if promote_to_mutable {
1591 quote! {
1592 crate::steel_vm::builtin::BuiltInFunctionType::Mutable(#copied_function_name)
1593 }
1594 } else {
1595 quote! {
1596 crate::steel_vm::builtin::BuiltInFunctionType::Reference(#copied_function_name)
1597 }
1598 };
1599
1600 let aliases = match keyword_map.get("alias") {
1601 Some(alias) => quote! { &[ #alias ] },
1602 None => quote! { &[] },
1603 };
1604
1605 let doc_field = if let Some(doc) = maybe_doc_comments {
1606 quote! { Some(crate::steel_vm::builtin::MarkdownDoc::from_str(#doc)) }
1607 } else {
1608 quote! { None }
1609 };
1610
1611 let definition_struct = quote! {
1612 pub const #doc_name: crate::steel_vm::builtin::NativeFunctionDefinition = crate::steel_vm::builtin::NativeFunctionDefinition {
1613 name: #value,
1614 aliases: #aliases,
1615 func: #function_type,
1616 arity: crate::steel_vm::builtin::Arity::#arity_exactness(#arity_number),
1617 doc: #doc_field,
1618 is_const: #is_const,
1619 signature: None,
1620 };
1621 };
1622
1623 if rest_arg_generic_inner_type {
1626 let mut conversion_functions = conversion_functions.collect::<Vec<_>>();
1627 let mut arg_index = arg_enumerate
1628 .map(|(i, _)| quote! { #i })
1629 .collect::<Vec<_>>();
1630
1631 if let Some(last) = arg_index.last_mut() {
1632 *last = quote! { #last.. };
1633 }
1634
1635 if let Some(last) = conversion_functions.last_mut() {
1636 *last = quote! { from_slice };
1637 }
1638
1639 let function_name = sign.ident;
1640
1641 let output = quote! {
1642 #[allow(dead_code)]
1645 #modified_input
1646
1647 #definition_struct
1648
1649 pub fn #copied_function_name(args: &[SteelVal]) -> std::result::Result<SteelVal, crate::rerrs::SteelErr> {
1650
1651 use crate::rvals::{IntoSteelVal, FromSteelVal, PrimitiveAsRef};
1652
1653 if args.len() < #arity_number {
1654 crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1655 }
1656
1657 fn err_thunk(mut err: crate::rerrs::SteelErr) -> crate::rerrs::SteelErr {
1658 err.prepend_message(#function_name_with_colon);
1659 err.set_kind(crate::rerrs::ErrorKind::TypeMismatch);
1660 err
1661 };
1662
1663 let res = #function_name(
1664 #(
1665 <#arg_type>::#conversion_functions(&args[#arg_index])
1668 .map_err(err_thunk)
1669 ?,
1670 )*
1671 );
1672
1673 #ret_val
1674 }
1675 };
1676
1677 return output.into();
1681 }
1682
1683 if promote_to_mutable {
1687 let temporary_fields: Vec<_> = type_vec
1688 .iter()
1689 .enumerate()
1690 .map(|(i, _)| {
1691 Ident::new(
1692 &("temporary_".to_string() + &i.to_string()),
1693 sign.ident.span(),
1694 )
1695 })
1696 .collect();
1697
1698 let output = quote! {
1699 #[allow(dead_code)]
1702 #modified_input
1703
1704 #definition_struct
1705
1706 pub fn #copied_function_name(args: &mut [SteelVal]) -> std::result::Result<SteelVal, crate::rerrs::SteelErr> {
1707
1708 use crate::rvals::{IntoSteelVal, FromSteelVal, PrimitiveAsRef, PrimitiveAsRefMut};
1709
1710 fn err_thunk(mut err: crate::rerrs::SteelErr) -> crate::rerrs::SteelErr {
1716 err.prepend_message(#function_name_with_colon);
1717 err.set_kind(crate::rerrs::ErrorKind::TypeMismatch);
1718 err
1719 };
1720
1721 if let [ #(#temporary_fields,)* ] = args {
1722
1723 let res = #function_name(
1724 #(
1725 <#arg_type>::#conversion_functions(#temporary_fields)
1728 .map_err(err_thunk)?,
1729 )*
1730 );
1731
1732 #ret_val
1733 } else {
1734 crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1735
1736 }
1737
1738 }
1754 };
1755
1756 return output.into();
1757 }
1758
1759 let output = quote! {
1760 #[allow(dead_code)]
1763 #modified_input
1764
1765 #definition_struct
1766
1767 pub fn #copied_function_name(args: &[SteelVal]) -> std::result::Result<SteelVal, crate::rerrs::SteelErr> {
1768
1769 use crate::rvals::{IntoSteelVal, FromSteelVal, PrimitiveAsRef};
1770
1771 if args.len() != #arity_number {
1772 crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1773 }
1774
1775
1776 fn err_thunk(mut err: crate::rerrs::SteelErr) -> crate::rerrs::SteelErr {
1777 err.prepend_message(#function_name_with_colon);
1778 err.set_kind(crate::rerrs::ErrorKind::TypeMismatch);
1779 err
1780 };
1781
1782 let res = #function_name(
1783 #(
1784 <#arg_type>::#conversion_functions(&args[#arg_index])
1787 .map_err(err_thunk)?,
1788 )*
1789 );
1790
1791 #ret_val
1792 }
1793 };
1794
1795 output.into()
1799}