tinyscript_derive/
lib.rs

1// Copyright © 2025 Stephan Kunz
2
3//! Derive macro [`ScriptEnum`] for `tinyscript`
4//!
5
6#[doc(hidden)]
7extern crate proc_macro;
8
9#[doc(hidden)]
10extern crate alloc;
11
12use proc_macro2::TokenStream;
13use quote::quote;
14use syn::{DeriveInput, Expr, Lit};
15
16/// Implementation of the derive macro [`ScriptEnum`]
17fn derive_scripting_enum(input: &DeriveInput) -> TokenStream {
18    // structure name
19    let ident = &input.ident;
20
21    // Check type of input and handle enums
22    let mut discriminant = -1_i8;
23    let variants: Vec<(String, i8)> = match &input.data {
24        syn::Data::Enum(data) => data
25            .variants
26            .iter()
27            .map(|v| {
28                if let Some((_eq, expr)) = &v.discriminant {
29                    match expr {
30                        Expr::Lit(expr_lit) => match &expr_lit.lit {
31                            Lit::Int(lit_int) => {
32                                discriminant =
33                                    lit_int.base10_parse::<i8>().expect("value must be i8");
34                            }
35                            _ => panic!("value must be i8"),
36                        },
37                        _ => panic!("value must be i8"),
38                    }
39                } else {
40                    discriminant += 1;
41                }
42                (v.ident.to_string(), discriminant)
43            })
44            .collect(),
45        syn::Data::Struct(_struct) => panic!("structs not supported by ScriptEnum"),
46        syn::Data::Union(_union) => panic!("unions not supported by ScriptEnum"),
47    };
48    let variant_keys: Vec<String> = variants.iter().map(|v| v.0.clone()).collect();
49    let variant_discriminants: Vec<i8> = variants.iter().map(|v| v.1).collect();
50
51    //
52    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
53
54    let derived: TokenStream = "#[automatically_derived]"
55        .parse()
56        .expect("derive(ScriptEnum) - derived");
57    let diagnostic: TokenStream = "#[diagnostic::do_not_recommend]"
58        .parse()
59        .expect("derive(ScriptEnum) - diagnostic");
60
61    quote! {
62        #derived
63        #diagnostic
64        impl #impl_generics tinyscript::ScriptEnum for #ident #type_generics #where_clause {
65            fn key_value_tuples() -> alloc::vec::Vec<(&'static str, i8)> {
66                vec![#((#variant_keys, #variant_discriminants)),*]
67            }
68        }
69    }
70}
71
72/// Derive macro [`ScriptEnum`].
73/// Enables a Rust enum to be used in a 'C' like mannner within the `tinyscript` language.
74///
75/// # Usage
76/// ```no_test
77/// #[derive(ScriptEnum)]
78/// enum MyEnum {
79///     // specific elements
80///     ...
81/// }
82///
83/// impl MyEnum {
84///     // specific implementations
85///     ...
86/// }
87/// ```
88///
89/// # Result
90/// Expands the above example to
91/// ```no_test
92/// enum MyEnum {
93///     // specific elements
94///     ...
95/// }
96///
97/// impl MyEnum {
98///     // specific implementations
99///     ...
100/// }
101///
102/// #[automatically_derived]
103/// #[diagnostic::do_not_recommend]
104/// impl tinyscript::enum::ScriptEnum for MyEnum {}
105/// ```
106///
107/// # Errors
108///
109/// # Panics
110/// - if used on structs or unions
111#[proc_macro_derive(ScriptEnum, attributes(dimas))]
112pub fn derive_script_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
113    // Construct a representation of Rust code as a syntax tree
114    let input: DeriveInput = syn::parse(input).expect("could not parse input");
115
116    derive_scripting_enum(&input).into()
117}