Skip to main content

steel_derive/
lib.rs

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    // Parse the input function
20    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                                // Accessors
198                                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                // TODO:
211                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                            // Accessors
230                            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            // Iterate over the variants, and generate a function
291            // to check each of the variants.
292            '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                                // TODO: Filter out those we want to ignore for accessors, but
321                                // otherwise don't do it here?
322                                .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                                    // Accessors
350                                    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                                // Accessors
408                                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    //Context functions have 'ctx' as the first argument, so we get the second one for them
763    let sig_inputs = if is_context_function {
764        input.sig.inputs.get(1)
765    } else {
766        input.sig.inputs.first()
767    };
768
769    //This is to account for the parameter sometimes being "args", other times "values"
770    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    // This function extracts the Arity type and integer values
782    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                // Handle Range(start, end)
788                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                // Handle AtLeast(n) or single-value cases
797                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    //Context functions return values wrapped in Option, so we must use builtin_stop for them
806    let stop_type = if is_context_function {
807        quote! {crate::builtin_stop!}
808    } else {
809        quote! {stop!}
810    };
811
812    //Determines which line of code to inject into the beginning of the function as an Arity check
813    let injected_code = match name {
814        "AtLeast" => {
815            // If AtLeast = 0 , then do not perform arity check, since any amount is valid
816            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    // Inject the new statements at the beginning of the function
847    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        // Not sure why, but it says this is unused even when generating functions
946        // marked as pub
947        #[allow(dead_code)]
948        #modified_input
949
950        #definition_struct
951    };
952
953    // Uncomment this to see the generated code
954    // eprintln!("{}", output.to_string());
955
956    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        // Not sure why, but it says this is unused even when generating functions
995        // marked as pub
996        #[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        // Not sure why, but it says this is unused even when generating functions
1042        // marked as pub
1043        #[allow(dead_code)]
1044        #modified_input
1045
1046        #definition_struct
1047    };
1048
1049    // Uncomment this to see the generated code
1050    // eprintln!("{}", output.to_string());
1051
1052    output.into()
1053}
1054// See REmacs : https://github.com/remacs/remacs/blob/16b6fb9319a6d48fbc7b27d27c3234990f6718c5/rust_src/remacs-macros/lib.rs#L17-L161
1055// TODO: Pass the new name in to this function
1056#[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 (_, value) = parse_key_value_pair(&args);
1064
1065    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    // If this is constant evaluatable
1072    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 ident = input.sig.ident.clone();
1083    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    // let mut argument_signatures: Vec<&'static str> = Vec::new();
1121    // let mut return_type = "void";
1122
1123    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            /*
1147            TODO: Attempt to bake the type information into the native
1148            function definition. This can give a lot more help to the optimizer
1149            and also the LSP if we have the types for every built in definition
1150            when we make it.
1151
1152            // Attempt to calculate the function signature
1153            match pat_ty.ty.clone().into_token_stream().to_string().as_str() {
1154                "char" => argument_signatures.push("char"),
1155                "bool" => argument_signatures.push("bool"),
1156                "f64" => argument_signatures.push("f64"),
1157                "isize" => argument_signatures.push("isize"),
1158                "SteelString" => argument_signatures.push("string"),
1159                "&SteelString" => argument_signatures.push("string"),
1160                _ => argument_signatures.push("any"),
1161            }
1162            */
1163
1164            type_vec.push(pat_ty.ty);
1165        }
1166    }
1167
1168    let mut arity_number = type_vec.len();
1169
1170    // TODO: Awful hack, but this just keeps track of which
1171    // variables are presented as mutable, which we can then use to chn
1172    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_names_with_colon = std::iter::repeat(function_name_with_colon.clone());
1196    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 we have a rest arg, we need to modify passing in values to pass in a slice to the remaining
1252    // values in the
1253    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            // Not sure why, but it says this is unused even when generating functions
1271            // marked as pub
1272            #[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                        // TODO: Distinguish reference types here if possible - make a special implementation
1294                        // for builtin pointer types here to distinguish them
1295                        <#arg_type>::#conversion_functions(&args[#arg_index])
1296                            .map_err(err_thunk)
1297                        ?,
1298                    )*
1299                );
1300
1301                #ret_val
1302            }
1303        };
1304
1305        // Uncomment this to see the generated code
1306        // eprintln!("{}", output.to_string());
1307
1308        return output.into();
1309    }
1310
1311    // TODO: Promotion to mutable means we can both avoid allocations and also
1312    // take advantage of linear types to reduce ref count thrash
1313
1314    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                // Not sure why, but it says this is unused even when generating functions
1328                // marked as pub
1329                #[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                    // if args.len() != #arity_number {
1339                    //     crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1340                    // }
1341
1342
1343                    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                                // TODO: Distinguish reference types here if possible - make a special implementation
1354                                // for builtin pointer types here to distinguish them
1355                                <#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                        // if let [arg1, arg2, ..] = &mut vec[..] {
1367            //  println!("{}:{} says '{}'", ip, port, msg);
1368            // check_two(arg1, arg2);
1369        // }
1370
1371                    // let res = #function_name(
1372                    //     #(
1373                    //         // TODO: Distinguish reference types here if possible - make a special implementation
1374                    //         // for builtin pointer types here to distinguish them
1375                    //         <#arg_type>::#conversion_functions(&mut std::mem::replace(&mut args[#arg_index], SteelVal::Void))
1376                    //             .map_err(err_thunk)?,
1377                    //     )*
1378                    // );
1379
1380                    // #ret_val
1381                }
1382            };
1383
1384        return output.into();
1385    }
1386
1387    let output = quote! {
1388        // Not sure why, but it says this is unused even when generating functions
1389        // marked as pub
1390        #[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                    // TODO: Distinguish reference types here if possible - make a special implementation
1413                    // for builtin pointer types here to distinguish them
1414                    <#arg_type>::#conversion_functions(&args[#arg_index])
1415                        .map_err(err_thunk)?,
1416                )*
1417            );
1418
1419            #ret_val
1420        }
1421    };
1422
1423    // Uncomment this to see the generated code
1424    // eprintln!("{}", output.to_string());
1425
1426    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    // If this is constant evaluatable
1443    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 ident = input.sig.ident.clone();
1454    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    // let mut argument_signatures: Vec<&'static str> = Vec::new();
1492    // let mut return_type = "void";
1493
1494    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            /*
1518            TODO: Attempt to bake the type information into the native
1519            function definition. This can give a lot more help to the optimizer
1520            and also the LSP if we have the types for every built in definition
1521            when we make it.
1522
1523            // Attempt to calculate the function signature
1524            match pat_ty.ty.clone().into_token_stream().to_string().as_str() {
1525                "char" => argument_signatures.push("char"),
1526                "bool" => argument_signatures.push("bool"),
1527                "f64" => argument_signatures.push("f64"),
1528                "isize" => argument_signatures.push("isize"),
1529                "SteelString" => argument_signatures.push("string"),
1530                "&SteelString" => argument_signatures.push("string"),
1531                _ => argument_signatures.push("any"),
1532            }
1533            */
1534
1535            type_vec.push(pat_ty.ty);
1536        }
1537    }
1538
1539    let mut arity_number = type_vec.len();
1540
1541    // TODO: Awful hack, but this just keeps track of which
1542    // variables are presented as mutable, which we can then use to chn
1543    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_names_with_colon = std::iter::repeat(function_name_with_colon.clone());
1567    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        // We don't want to include the rest argument in the count
1584        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 we have a rest arg, we need to modify passing in values to pass in a slice to the remaining
1624    // values in the
1625    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            // Not sure why, but it says this is unused even when generating functions
1643            // marked as pub
1644            #[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                        // TODO: Distinguish reference types here if possible - make a special implementation
1666                        // for builtin pointer types here to distinguish them
1667                        <#arg_type>::#conversion_functions(&args[#arg_index])
1668                            .map_err(err_thunk)
1669                        ?,
1670                    )*
1671                );
1672
1673                #ret_val
1674            }
1675        };
1676
1677        // Uncomment this to see the generated code
1678        // eprintln!("{}", output.to_string());
1679
1680        return output.into();
1681    }
1682
1683    // TODO: Promotion to mutable means we can both avoid allocations and also
1684    // take advantage of linear types to reduce ref count thrash
1685
1686    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                // Not sure why, but it says this is unused even when generating functions
1700                // marked as pub
1701                #[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                    // if args.len() != #arity_number {
1711                    //     crate::stop!(ArityMismatch => format!("{} expected {} arguments, got {}", #value, #arity_number.to_string(), args.len()))
1712                    // }
1713
1714
1715                    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                                // TODO: Distinguish reference types here if possible - make a special implementation
1726                                // for builtin pointer types here to distinguish them
1727                                <#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                        // if let [arg1, arg2, ..] = &mut vec[..] {
1739            //  println!("{}:{} says '{}'", ip, port, msg);
1740            // check_two(arg1, arg2);
1741        // }
1742
1743                    // let res = #function_name(
1744                    //     #(
1745                    //         // TODO: Distinguish reference types here if possible - make a special implementation
1746                    //         // for builtin pointer types here to distinguish them
1747                    //         <#arg_type>::#conversion_functions(&mut std::mem::replace(&mut args[#arg_index], SteelVal::Void))
1748                    //             .map_err(err_thunk)?,
1749                    //     )*
1750                    // );
1751
1752                    // #ret_val
1753                }
1754            };
1755
1756        return output.into();
1757    }
1758
1759    let output = quote! {
1760        // Not sure why, but it says this is unused even when generating functions
1761        // marked as pub
1762        #[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                    // TODO: Distinguish reference types here if possible - make a special implementation
1785                    // for builtin pointer types here to distinguish them
1786                    <#arg_type>::#conversion_functions(&args[#arg_index])
1787                        .map_err(err_thunk)?,
1788                )*
1789            );
1790
1791            #ret_val
1792        }
1793    };
1794
1795    // Uncomment this to see the generated code
1796    // eprintln!("{}", output.to_string());
1797
1798    output.into()
1799}