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 = lit_int
33									.base10_parse::<i8>()
34									.expect("value must be i8");
35							}
36							_ => panic!("value must be i8"),
37						},
38						_ => panic!("value must be i8"),
39					}
40				} else {
41					discriminant += 1;
42				}
43				(v.ident.to_string(), discriminant)
44			})
45			.collect(),
46		syn::Data::Struct(_struct) => panic!("structs not supported by ScriptEnum"),
47		syn::Data::Union(_union) => panic!("unions not supported by ScriptEnum"),
48	};
49	let variant_keys: Vec<String> = variants.iter().map(|v| v.0.clone()).collect();
50	let variant_discriminants: Vec<i8> = variants.iter().map(|v| v.1).collect();
51
52	//
53	let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
54
55	let derived: TokenStream = "#[automatically_derived]"
56		.parse()
57		.expect("derive(ScriptEnum) - derived");
58	let diagnostic: TokenStream = "#[diagnostic::do_not_recommend]"
59		.parse()
60		.expect("derive(ScriptEnum) - diagnostic");
61
62	quote! {
63		#derived
64		#diagnostic
65		impl #impl_generics tinyscript::ScriptEnum for #ident #type_generics #where_clause {
66			fn key_value_tuples<'a>() -> alloc::vec::Vec<(&'a str, i8)> {
67				vec![#((#variant_keys, #variant_discriminants)),*]
68			}
69		}
70	}
71}
72
73/// Derive macro [`ScriptEnum`].
74/// Enables a Rust enum to be used in a 'C' like mannner within the `tinyscript` language.
75///
76/// # Usage
77/// ```no_test
78/// #[derive(ScriptEnum)]
79/// enum MyEnum {
80///     // specific elements
81///     ...
82/// }
83///
84/// impl MyEnum {
85///     // specific implementations
86///     ...
87/// }
88/// ```
89///
90/// # Result
91/// Expands the above example to
92/// ```no_test
93/// enum MyEnum {
94///     // specific elements
95///     ...
96/// }
97///
98/// impl MyEnum {
99///     // specific implementations
100///     ...
101/// }
102///
103/// #[automatically_derived]
104/// #[diagnostic::do_not_recommend]
105/// impl tinyscript::enum::ScriptEnum for MyEnum {}
106/// ```
107///
108/// # Errors
109///
110/// # Panics
111/// - if used on structs or unions
112#[proc_macro_derive(ScriptEnum, attributes(tscript))]
113pub fn derive_script_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
114	// Construct a representation of Rust code as a syntax tree
115	let input: DeriveInput = syn::parse(input).expect("could not parse input");
116
117	derive_scripting_enum(&input).into()
118}