ptx_90_parser_construct/
lib.rs

1///! - c!, ok!, err! are used to construct structs with automatic span field.
2///! - cclosure! is used to create closures that map tuples to structs with automatic span field. okmap! is like cclosure! but wraps the result in Ok(...).
3///! - func! adds a span parameter to closures argument list. 
4use proc_macro::TokenStream;
5use quote::quote;
6use syn::{
7    parse::{Parse, ParseStream},
8    parse_macro_input,
9    punctuated::Punctuated,
10    Expr, Ident, Path, Token,
11};
12
13/// A field in the cmap macro - either `field = expr`, `field`, or `_`
14enum CmapField {
15    /// `field = expr` - assign expr to field
16    Assign { name: Ident, expr: Expr },
17    /// `field` - shorthand for `field: field`
18    Shorthand { name: Ident },
19    /// `_` - wildcard, ignored in pattern
20    Wildcard,
21}
22
23impl Parse for CmapField {
24    fn parse(input: ParseStream) -> syn::Result<Self> {
25        // Check for wildcard first
26        if input.peek(Token![_]) {
27            let _: Token![_] = input.parse()?;
28            return Ok(CmapField::Wildcard);
29        }
30
31        let name: Ident = input.parse()?;
32
33        // Check if there's an `=` token
34        if input.peek(Token![=]) {
35            let _eq: Token![=] = input.parse()?;
36            let expr: Expr = input.parse()?;
37            Ok(CmapField::Assign { name, expr })
38        } else {
39            Ok(CmapField::Shorthand { name })
40        }
41    }
42}
43
44/// Input structure for the cmap macro
45///
46/// Syntax: `Type { field1 = expr1, field2, ... }`
47struct CmapInput {
48    type_path: Path,
49    fields: Punctuated<CmapField, Token![,]>,
50}
51
52impl Parse for CmapInput {
53    fn parse(input: ParseStream) -> syn::Result<Self> {
54        // Parse Type path
55        let type_path: Path = input.parse()?;
56
57        // Parse { fields } - now required
58        let fields_content;
59        syn::braced!(fields_content in input);
60        let fields = fields_content.parse_terminated(CmapField::parse, Token![,])?;
61
62        Ok(CmapInput {
63            type_path,
64            fields,
65        })
66    }
67}
68
69/// Constructor mapping macro - eliminates field repetition
70///
71/// Syntax:
72/// ```rust
73/// cclosure!(Type { field1, field2=expr })
74/// ```
75///
76/// Example:
77/// ```rust
78/// cclosure!(Operand::SymbolOffset { symbol, _, offset=offset.unwrap() })
79/// // Generates: |(symbol, _, offset), span| c!(Operand::SymbolOffset { symbol, _, offset=offset.unwrap() })
80/// ```
81#[proc_macro]
82pub fn cclosure(input: TokenStream) -> TokenStream {
83    let CmapInput {
84        type_path,
85        fields,
86    } = parse_macro_input!(input as CmapInput);
87
88    // Extract pattern elements from field names (in order)
89    let mut pattern_elements = Vec::new();
90    for field in &fields {
91        match field {
92            CmapField::Assign { name, .. } => {
93                pattern_elements.push(quote! { #name });
94            }
95            CmapField::Shorthand { name } => {
96                pattern_elements.push(quote! { #name });
97            }
98            CmapField::Wildcard => {
99                pattern_elements.push(quote! { _ });
100            }
101        }
102    }
103
104    // Build the tuple pattern from the field names
105    let pattern = if pattern_elements.len() == 1 {
106        let elem = &pattern_elements[0];
107        quote! { #elem }
108    } else {
109        quote! { (#(#pattern_elements),*) }
110    };
111
112    // Generate field assignments (skip wildcards)
113    let field_assignments = fields.iter().filter_map(|field| match field {
114        CmapField::Assign { name, expr } => {
115            Some(quote! { #name: #expr })
116        }
117        CmapField::Shorthand { name } => {
118            Some(quote! { #name: #name })
119        }
120        CmapField::Wildcard => None,
121    });
122
123    // Generate the closure
124    let expanded = quote! {
125        move |#pattern, span| {
126            #type_path {
127                #(#field_assignments,)*
128                span
129            }
130        }
131    };
132
133    TokenStream::from(expanded)
134}
135
136/// Constructor macro - builds a struct with automatic span field
137///
138/// Syntax:
139/// ```rust
140/// c!(Type { field1 = expr1, field2, ... })
141/// ```
142///
143/// Example:
144/// ```rust
145/// c!(VariableDirective { name=something, foo })
146/// // Expands to: VariableDirective { name: something, foo: foo, span: span }
147/// ```
148#[proc_macro]
149pub fn c(input: TokenStream) -> TokenStream {
150    // Parse: [span_expr =>] Type { fields }
151    let input_parsed = parse_macro_input!(input with parse_c_input);
152
153    let (span_expr, type_path, fields) = input_parsed;
154
155    // Process fields similar to cmap
156    let field_assignments = fields.iter().filter_map(|field| match field {
157        CmapField::Assign { name, expr } => {
158            Some(quote! { #name: #expr })
159        }
160        CmapField::Shorthand { name } => {
161            Some(quote! { #name: #name })
162        }
163        CmapField::Wildcard => None,
164    });
165
166    // Generate the struct construction
167    let expanded = quote! {
168        #type_path {
169            #(#field_assignments,)*
170            span: #span_expr
171        }
172    };
173
174    TokenStream::from(expanded)
175}
176
177fn parse_c_input(input: ParseStream) -> syn::Result<(Expr, Path, Punctuated<CmapField, Token![,]>)> {
178    // Fork the input to try parsing with span expression first
179    let fork = input.fork();
180
181    // Try to parse: Expr => Path { fields }
182    let (span_expr, type_path) = if let Ok(_expr) = fork.parse::<Expr>() {
183        if fork.peek(Token![=>]) {
184            // Successfully parsed expr followed by =>, so advance the main input
185            let expr: Expr = input.parse()?;
186            let _arrow: Token![=>] = input.parse()?;
187            let type_path: Path = input.parse()?;
188            (expr, type_path)
189        } else {
190            // No =>, so the first thing must be the type path
191            let type_path: Path = input.parse()?;
192            (syn::parse_quote!(span), type_path)
193        }
194    } else {
195        // Couldn't parse as expr, try as path
196        let type_path: Path = input.parse()?;
197        (syn::parse_quote!(span), type_path)
198    };
199
200    // Parse { fields } if present
201    let fields = if input.peek(syn::token::Brace) {
202        let fields_content;
203        syn::braced!(fields_content in input);
204        fields_content.parse_terminated(CmapField::parse, Token![,])?
205    } else {
206        Punctuated::new()
207    };
208
209    Ok((span_expr, type_path, fields))
210}
211
212/// Ok wrapper macro - wraps result in Ok(...) with automatic span field
213///
214/// Syntax:
215/// ```rust
216/// ok!(Type { field1 = expr1, field2, ... })
217/// ```
218///
219/// Example:
220/// ```rust
221/// ok!(TexHandler2 { operands, name=foo.to_string() })
222/// // Expands to: Ok(TexHandler2 { operands: operands, name: foo.to_string(), span: span })
223/// ```
224#[proc_macro]
225pub fn ok(input: TokenStream) -> TokenStream {
226    // Reuse c! macro logic but wrap in Ok(value)
227    // Used inside try_map closures where try_with_span will add the outer tuple
228    let c_result = c(input);
229    let c_tokens: proc_macro2::TokenStream = c_result.into();
230
231    let expanded = quote! {
232        Ok(#c_tokens)
233    };
234
235    TokenStream::from(expanded)
236}
237
238/// Error constructor macro - builds a PtxParseError with automatic span field
239///
240/// Syntax:
241/// ```rust
242/// err!(ErrorKind)
243/// ```
244///
245/// Example:
246/// ```rust
247/// err!(ParseErrorKind::InvalidLiteral("message".into()))
248/// // Expands to: Err(PtxParseError { kind: ParseErrorKind::InvalidLiteral("message".into()), span: span })
249/// ```
250#[proc_macro]
251pub fn err(input: TokenStream) -> TokenStream {
252    // Parse: [span_expr =>] error_kind
253    let input_parsed = parse_macro_input!(input with parse_err_input);
254
255    let (span_expr, error_kind) = input_parsed;
256
257    // Generate the error construction
258    // Note: We use crate::parser::PtxParseError which will resolve in the calling code's context
259    let expanded = quote! {
260        Err(crate::parser::PtxParseError {
261            kind: #error_kind,
262            span: #span_expr
263        })
264    };
265
266    TokenStream::from(expanded)
267}
268
269fn parse_err_input(input: ParseStream) -> syn::Result<(Expr, Expr)> {
270    // Try to parse span expression followed by =>
271    let span_expr = if input.peek2(Token![=>]) {
272        let expr: Expr = input.parse()?;
273        let _arrow: Token![=>] = input.parse()?;
274        expr
275    } else {
276        // If no span => provided, use `span` identifier
277        syn::parse_quote!(span)
278    };
279
280    // Parse error kind expression
281    let error_kind: Expr = input.parse()?;
282
283    Ok((span_expr, error_kind))
284}
285
286/// Constructor mapping macro with Ok wrapper - like cclosure! but wraps result with Ok
287///
288/// Syntax:
289/// ```rust
290/// okmap!(Type { field1, field2=expr })
291/// ```
292///
293/// Example:
294/// ```rust
295/// okmap!(Operand::SymbolOffset { symbol, _, offset=offset.unwrap() })
296/// // Generates: |(symbol, _, offset), span| Ok(Operand::SymbolOffset { symbol, offset: offset.unwrap(), span })
297/// ```
298#[proc_macro]
299pub fn okmap(input: TokenStream) -> TokenStream {
300    let CmapInput {
301        type_path,
302        fields,
303    } = parse_macro_input!(input as CmapInput);
304
305    // Extract pattern elements from field names (in order)
306    let mut pattern_elements = Vec::new();
307    for field in &fields {
308        match field {
309            CmapField::Assign { name, .. } => {
310                pattern_elements.push(quote! { #name });
311            }
312            CmapField::Shorthand { name } => {
313                pattern_elements.push(quote! { #name });
314            }
315            CmapField::Wildcard => {
316                pattern_elements.push(quote! { _ });
317            }
318        }
319    }
320
321    // Build the tuple pattern from the field names
322    let pattern = if pattern_elements.len() == 1 {
323        let elem = &pattern_elements[0];
324        quote! { #elem }
325    } else {
326        quote! { (#(#pattern_elements),*) }
327    };
328
329    // Generate field assignments (skip wildcards)
330    let field_assignments = fields.iter().filter_map(|field| match field {
331        CmapField::Assign { name, expr } => {
332            Some(quote! { #name: #expr })
333        }
334        CmapField::Shorthand { name } => {
335            Some(quote! { #name: #name })
336        }
337        CmapField::Wildcard => None,
338    });
339
340    // Generate the closure wrapped in Ok
341    let expanded = quote! {
342        move |#pattern, span| {
343            Ok(#type_path {
344                #(#field_assignments,)*
345                span
346            })
347        }
348    };
349
350    TokenStream::from(expanded)
351}
352
353/// Function macro - adds span parameter to closures
354///
355/// Syntax:
356/// ```rust
357/// func!(|param1, param2| body)
358/// ```
359///
360/// Example:
361/// ```rust
362/// func!(|x, y| x + y)
363/// // Expands to: |x, y, span| x + y
364/// ```
365#[proc_macro]
366pub fn func(input: TokenStream) -> TokenStream {
367    use syn::{ExprClosure, Pat};
368
369    let closure = parse_macro_input!(input as ExprClosure);
370
371    // Extract the existing parameters
372    let mut params = closure.inputs.clone();
373
374    // Add span parameter
375    let span_param: Pat = syn::parse_quote!(span);
376    params.push(span_param);
377
378    // Get the body
379    let body = closure.body;
380
381    // Generate the expanded closure
382    let expanded = quote! {
383        |#params| #body
384    };
385
386    TokenStream::from(expanded)
387}