texform_knowledge_macros/
lib.rs1use 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}