Skip to main content

texform_knowledge_macros/
lib.rs

1use proc_macro::TokenStream;
2
3use proc_macro2::TokenStream as TokenStream2;
4use quote::quote;
5use syn::{LitStr, parse_macro_input};
6use texform_argspec::{ArgForm, ArgSpec, DelimiterToken, ValueKind, parse_arg_specs};
7
8#[proc_macro]
9pub fn argspec(input: TokenStream) -> TokenStream {
10    let literal = parse_macro_input!(input as LitStr);
11    let source = literal.value();
12    match parse_arg_specs(source.as_str(), "argspec literal") {
13        Ok(specs) => render_parsed_arg_spec(specs.as_slice(), source.as_str()).into(),
14        Err(error) => syn::Error::new(
15            literal.span(),
16            format!(
17                "invalid argspec literal at char {}: {}",
18                error.char_index, error.message
19            ),
20        )
21        .to_compile_error()
22        .into(),
23    }
24}
25
26fn render_parsed_arg_spec(specs: &[ArgSpec], source: &str) -> TokenStream2 {
27    let rendered_specs = specs.iter().map(render_arg_spec);
28    quote! {
29        ::texform_knowledge::specs::ParsedArgSpec {
30            args: &[#(#rendered_specs),*],
31            source: #source,
32        }
33    }
34}
35
36fn render_arg_spec(spec: &ArgSpec) -> TokenStream2 {
37    let required = spec.required;
38    let no_leading_space = spec.no_leading_space;
39    let nullable = spec.nullable;
40    let kind = render_value_kind(spec.kind);
41    let form = render_arg_form(&spec.form);
42
43    quote! {
44        ::texform_knowledge::specs::ArgSpec {
45            required: #required,
46            no_leading_space: #no_leading_space,
47            nullable: #nullable,
48            kind: #kind,
49            form: #form,
50        }
51    }
52}
53
54fn render_value_kind(kind: ValueKind) -> TokenStream2 {
55    match kind {
56        ValueKind::Content { mode } => {
57            let mode = render_content_mode(mode);
58            quote! {
59                ::texform_knowledge::specs::ValueKind::Content { mode: #mode }
60            }
61        }
62        ValueKind::Delimiter => quote!(::texform_knowledge::specs::ValueKind::Delimiter),
63        ValueKind::CSName => quote!(::texform_knowledge::specs::ValueKind::CSName),
64        ValueKind::Dimension => quote!(::texform_knowledge::specs::ValueKind::Dimension),
65        ValueKind::Integer => quote!(::texform_knowledge::specs::ValueKind::Integer),
66        ValueKind::KeyVal => quote!(::texform_knowledge::specs::ValueKind::KeyVal),
67        ValueKind::Column => quote!(::texform_knowledge::specs::ValueKind::Column),
68        ValueKind::Star => quote!(::texform_knowledge::specs::ValueKind::Star),
69    }
70}
71
72fn render_arg_form(form: &ArgForm) -> TokenStream2 {
73    match form {
74        ArgForm::Standard => quote!(::texform_knowledge::specs::ArgForm::Standard),
75        ArgForm::Star => quote!(::texform_knowledge::specs::ArgForm::Star),
76        ArgForm::Group => quote!(::texform_knowledge::specs::ArgForm::Group),
77        ArgForm::Delimited { open, close } => {
78            let open = render_delimiter_token(open);
79            let close = render_delimiter_token(close);
80            quote! {
81                ::texform_knowledge::specs::ArgForm::Delimited {
82                    open: #open,
83                    close: #close,
84                }
85            }
86        }
87        ArgForm::Paired { pairs } => {
88            let rendered_pairs = pairs.iter().map(|(open, close)| {
89                let open = render_delimiter_token(open);
90                let close = render_delimiter_token(close);
91                quote! { (#open, #close) }
92            });
93            quote! {
94                ::texform_knowledge::specs::ArgForm::Paired {
95                    pairs: ::std::borrow::Cow::Borrowed(&[#(#rendered_pairs),*]),
96                }
97            }
98        }
99    }
100}
101
102fn render_delimiter_token(token: &DelimiterToken) -> TokenStream2 {
103    match token {
104        DelimiterToken::Char(ch) => quote!(::texform_knowledge::specs::DelimiterToken::Char(#ch)),
105        DelimiterToken::ControlSeq(name) => {
106            let name = name.as_ref();
107            quote! {
108                ::texform_knowledge::specs::DelimiterToken::ControlSeq(
109                    ::std::borrow::Cow::Borrowed(#name)
110                )
111            }
112        }
113    }
114}
115
116fn render_content_mode(mode: texform_argspec::ContentMode) -> TokenStream2 {
117    match mode {
118        texform_argspec::ContentMode::Math => quote!(::texform_knowledge::specs::ContentMode::Math),
119        texform_argspec::ContentMode::Text => quote!(::texform_knowledge::specs::ContentMode::Text),
120    }
121}