tinyscript_derive/
lib.rs

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