Skip to main content

marser_macros/
lib.rs

1//! Procedural macros for [`marser`](https://docs.rs/marser).
2//!
3//! Prefer `use marser::capture;` (re-exported from the main crate). This crate is the proc-macro
4//! implementation; [`capture`] builds a `marser::parser::capture::Capture` parser from a grammar expression.
5
6use std::cell::{Cell, RefCell};
7
8use proc_macro::TokenStream;
9use proc_macro_crate::{FoundCrate, crate_name};
10use proc_macro2::Span;
11use quote::{quote, quote_spanned};
12use syn::parse::{Parse, ParseStream, Result as ParseResult};
13use syn::visit::{self, Visit};
14use syn::visit_mut::{self, VisitMut};
15use syn::{Expr, ExprClosure, Ident, Index, Pat, Path, Token, Type, parse_quote};
16
17// ---------------------------------------------------------------------------
18// Input structs
19// ---------------------------------------------------------------------------
20
21struct CaptureInput {
22    grammar: Expr,
23    _arrow: Token![=>],
24    result_expr: Expr,
25}
26
27impl Parse for CaptureInput {
28    fn parse(input: ParseStream) -> ParseResult<Self> {
29        let grammar = input.parse()?;
30        let _arrow = input.parse::<Token![=>]>()?;
31        let result_expr = input.parse()?;
32        if !input.is_empty() {
33            return Err(input.error("unexpected tokens after `capture!( … => … )`"));
34        }
35        Ok(CaptureInput {
36            grammar,
37            _arrow,
38            result_expr,
39        })
40    }
41}
42
43#[derive(Clone, PartialEq)]
44enum BindKind {
45    Single,
46    Multiple,
47    Optional,
48}
49
50/// Shared helper: peek at an optional `*` / `?` sigil, then parse the ident.
51fn parse_kind_and_ident(input: ParseStream) -> ParseResult<(BindKind, Ident)> {
52    if input.peek(Token![*]) {
53        input.parse::<Token![*]>()?;
54        Ok((BindKind::Multiple, input.parse()?))
55    } else if input.peek(Token![?]) {
56        input.parse::<Token![?]>()?;
57        Ok((BindKind::Optional, input.parse()?))
58    } else {
59        Ok((BindKind::Single, input.parse()?))
60    }
61}
62
63#[derive(Clone)]
64struct TypedBindTarget {
65    kind: BindKind,
66    ident: Ident,
67    ty: Option<Type>,
68}
69
70fn parse_typed_target(input: ParseStream) -> ParseResult<TypedBindTarget> {
71    let (kind, ident) = parse_kind_and_ident(input)?;
72    let ty = if input.peek(Token![as]) {
73        input.parse::<Token![as]>()?;
74        Some(input.parse::<Type>()?)
75    } else {
76        None
77    };
78    Ok(TypedBindTarget { kind, ident, ty })
79}
80
81/// `bind!(parser, [*|?]ident [as Type] [, [*|?]span_ident [as Type]])`
82struct BindInfo {
83    parser: Expr,
84    ident: Ident,
85    kind: BindKind,
86    value_ty: Option<Type>,
87    span_ident: Option<Ident>,
88    span_kind: Option<BindKind>,
89    span_ty: Option<Type>,
90}
91
92impl Parse for BindInfo {
93    fn parse(input: ParseStream) -> ParseResult<Self> {
94        let parser: Expr = input.parse()?;
95        let _: Token![,] = input.parse()?;
96        let value_target = parse_typed_target(input)?;
97
98        let (span_ident, span_kind, span_ty) = if input.peek(Token![,]) {
99            let _: Token![,] = input.parse()?;
100            let span_target = parse_typed_target(input)?;
101            (
102                Some(span_target.ident),
103                Some(span_target.kind),
104                span_target.ty,
105            )
106        } else {
107            (None, None, None)
108        };
109
110        if !input.is_empty() {
111            return Err(input.error(
112                "unexpected tokens in `bind!` (expected `bind!(parser, target [, span_target])`)",
113            ));
114        }
115
116        Ok(BindInfo {
117            parser,
118            ident: value_target.ident,
119            kind: value_target.kind,
120            value_ty: value_target.ty,
121            span_ident,
122            span_kind,
123            span_ty,
124        })
125    }
126}
127
128/// `bind_span!(parser, [*|?]span_ident [as Type])` – binds only the span, no value capture.
129struct BindSpanInfo {
130    parser: Expr,
131    span_ident: Ident,
132    kind: BindKind,
133    ty: Option<Type>,
134}
135
136impl Parse for BindSpanInfo {
137    fn parse(input: ParseStream) -> ParseResult<Self> {
138        let parser: Expr = input.parse()?;
139        let _: Token![,] = input.parse()?;
140        let target = parse_typed_target(input)?;
141        if !input.is_empty() {
142            return Err(input.error("unexpected tokens in `bind_span!`"));
143        }
144        Ok(BindSpanInfo {
145            parser,
146            span_ident: target.ident,
147            kind: target.kind,
148            ty: target.ty,
149        })
150    }
151}
152
153/// `bind_slice!(parser, [*|?]slice_ident [as Type])` – binds only the consumed slice.
154struct BindSliceInfo {
155    parser: Expr,
156    slice_ident: Ident,
157    kind: BindKind,
158    ty: Option<Type>,
159}
160
161impl Parse for BindSliceInfo {
162    fn parse(input: ParseStream) -> ParseResult<Self> {
163        let parser: Expr = input.parse()?;
164        let _: Token![,] = input.parse()?;
165        let target = parse_typed_target(input)?;
166        if !input.is_empty() {
167            return Err(input.error("unexpected tokens in `bind_slice!`"));
168        }
169        Ok(BindSliceInfo {
170            parser,
171            slice_ident: target.ident,
172            kind: target.kind,
173            ty: target.ty,
174        })
175    }
176}
177
178#[derive(Clone)]
179struct TypedBinding {
180    ident: Ident,
181    ty: Option<Type>,
182}
183
184/// Registry of `bind!` / `bind_span!` / `bind_slice!` idents (layout matches [`BindCollector`] output).
185#[derive(Default, Clone)]
186struct BindRegistry {
187    single_values: Vec<TypedBinding>,
188    single_spans: Vec<TypedBinding>,
189    multiple_values: Vec<TypedBinding>,
190    multiple_spans: Vec<TypedBinding>,
191    optional_values: Vec<TypedBinding>,
192    optional_spans: Vec<TypedBinding>,
193}
194
195impl BindRegistry {
196    fn types_compatible(a: &Option<Type>, b: &Option<Type>) -> bool {
197        match (a, b) {
198            (None, _) | (_, None) => true,
199            (Some(t1), Some(t2)) => quote!(#t1).to_string() == quote!(#t2).to_string(),
200        }
201    }
202
203    fn value_kind_if_present(&self, id: &Ident) -> Option<BindKind> {
204        if self.single_values.iter().any(|b| b.ident == *id) {
205            Some(BindKind::Single)
206        } else if self.multiple_values.iter().any(|b| b.ident == *id) {
207            Some(BindKind::Multiple)
208        } else if self.optional_values.iter().any(|b| b.ident == *id) {
209            Some(BindKind::Optional)
210        } else {
211            None
212        }
213    }
214
215    fn span_kind_if_present(&self, id: &Ident) -> Option<BindKind> {
216        if self.single_spans.iter().any(|b| b.ident == *id) {
217            Some(BindKind::Single)
218        } else if self.multiple_spans.iter().any(|b| b.ident == *id) {
219            Some(BindKind::Multiple)
220        } else if self.optional_spans.iter().any(|b| b.ident == *id) {
221            Some(BindKind::Optional)
222        } else {
223            None
224        }
225    }
226
227    fn ident_in_any_span_list(&self, id: &Ident) -> bool {
228        self.span_kind_if_present(id).is_some()
229    }
230
231    fn ident_in_any_value_list(&self, id: &Ident) -> bool {
232        self.value_kind_if_present(id).is_some()
233    }
234
235    fn values_mut(&mut self, kind: &BindKind) -> &mut Vec<TypedBinding> {
236        match kind {
237            BindKind::Single => &mut self.single_values,
238            BindKind::Multiple => &mut self.multiple_values,
239            BindKind::Optional => &mut self.optional_values,
240        }
241    }
242
243    fn spans_mut(&mut self, kind: &BindKind) -> &mut Vec<TypedBinding> {
244        match kind {
245            BindKind::Single => &mut self.single_spans,
246            BindKind::Multiple => &mut self.multiple_spans,
247            BindKind::Optional => &mut self.optional_spans,
248        }
249    }
250
251    fn merge_into(
252        list: &mut Vec<TypedBinding>,
253        ident: Ident,
254        ty: Option<Type>,
255    ) -> std::result::Result<(), syn::Error> {
256        if let Some(existing) = list.iter_mut().find(|e| e.ident == ident) {
257            if !Self::types_compatible(&existing.ty, &ty) {
258                return Err(syn::Error::new_spanned(
259                    &ident,
260                    format!(
261                        "conflicting explicit `as` types for repeated binding `{}` in `capture!`",
262                        ident
263                    ),
264                ));
265            }
266            if existing.ty.is_none()
267                && let Some(t) = ty
268            {
269                existing.ty = Some(t);
270            }
271            Ok(())
272        } else {
273            list.push(TypedBinding { ident, ty });
274            Ok(())
275        }
276    }
277
278    /// Register a value capture. Repeated uses of the same `ident` with the same sigil bucket are merged
279    /// when `as` types are compatible (see module docs on `capture!`).
280    fn register_value(
281        &mut self,
282        ident: Ident,
283        ty: Option<Type>,
284        kind: &BindKind,
285    ) -> std::result::Result<(), syn::Error> {
286        if self.ident_in_any_span_list(&ident) {
287            return Err(syn::Error::new_spanned(
288                &ident,
289                format!(
290                    "binding `{}` is already used as a span binding; value and span captures cannot share an identifier in `capture!`",
291                    ident
292                ),
293            ));
294        }
295        if let Some(existing) = self.value_kind_if_present(&ident)
296            && existing != *kind
297        {
298            return Err(syn::Error::new_spanned(
299                &ident,
300                format!(
301                    "binding `{}` is used with incompatible sigils (for example `x` vs `*x` vs `?x`) in the same `capture!`",
302                    ident
303                ),
304            ));
305        }
306        let list = self.values_mut(kind);
307        Self::merge_into(list, ident, ty)
308    }
309
310    fn register_span(
311        &mut self,
312        ident: Ident,
313        ty: Option<Type>,
314        kind: &BindKind,
315    ) -> std::result::Result<(), syn::Error> {
316        if self.ident_in_any_value_list(&ident) {
317            return Err(syn::Error::new_spanned(
318                &ident,
319                format!(
320                    "binding `{}` is already used as a value binding; value and span captures cannot share an identifier in `capture!`",
321                    ident
322                ),
323            ));
324        }
325        if let Some(existing) = self.span_kind_if_present(&ident)
326            && existing != *kind
327        {
328            return Err(syn::Error::new_spanned(
329                &ident,
330                format!(
331                    "span binding `{}` is used with incompatible sigils in the same `capture!`",
332                    ident
333                ),
334            ));
335        }
336        let list = self.spans_mut(kind);
337        Self::merge_into(list, ident, ty)
338    }
339}
340
341/// Walk the raw grammar `Expr` before `bind!` expansion and collect binding targets.
342struct BindCollector {
343    reg: BindRegistry,
344    errors: Option<syn::Error>,
345}
346
347impl BindCollector {
348    fn bump_err(&mut self, e: syn::Error) {
349        self.errors = Some(match self.errors.take() {
350            None => e,
351            Some(mut prev) => {
352                prev.combine(e);
353                prev
354            }
355        });
356    }
357
358    fn collect(expr: &Expr) -> std::result::Result<BindRegistry, syn::Error> {
359        let mut c = Self {
360            reg: BindRegistry::default(),
361            errors: None,
362        };
363        c.visit_expr(expr);
364        if let Some(e) = c.errors {
365            Err(e)
366        } else {
367            Ok(c.reg)
368        }
369    }
370}
371
372impl<'ast> Visit<'ast> for BindCollector {
373    fn visit_expr(&mut self, expr: &'ast Expr) {
374        if let Expr::Macro(m) = expr {
375            if m.mac.path.is_ident("bind") {
376                let info = match m.mac.parse_body::<BindInfo>() {
377                    Ok(i) => i,
378                    Err(e) => {
379                        self.bump_err(e);
380                        visit::visit_expr(self, expr);
381                        return;
382                    }
383                };
384                if let Some(ref span_ident) = info.span_ident
385                    && *span_ident == info.ident
386                {
387                    self.bump_err(syn::Error::new_spanned(
388                        span_ident,
389                        "`bind!` value and span targets must use distinct identifiers",
390                    ));
391                    self.visit_expr(&info.parser);
392                    return;
393                }
394                if let Err(e) =
395                    self.reg
396                        .register_value(info.ident.clone(), info.value_ty.clone(), &info.kind)
397                {
398                    self.bump_err(e);
399                }
400                if let Some(span_ident) = &info.span_ident {
401                    let span_kind = info.span_kind.as_ref().unwrap();
402                    if let Err(e) =
403                        self.reg
404                            .register_span(span_ident.clone(), info.span_ty.clone(), span_kind)
405                    {
406                        self.bump_err(e);
407                    }
408                }
409                self.visit_expr(&info.parser);
410                return;
411            }
412            if m.mac.path.is_ident("bind_span") {
413                let info = match m.mac.parse_body::<BindSpanInfo>() {
414                    Ok(i) => i,
415                    Err(e) => {
416                        self.bump_err(e);
417                        visit::visit_expr(self, expr);
418                        return;
419                    }
420                };
421                if let Err(e) =
422                    self.reg
423                        .register_span(info.span_ident.clone(), info.ty.clone(), &info.kind)
424                {
425                    self.bump_err(e);
426                }
427                self.visit_expr(&info.parser);
428                return;
429            }
430            if m.mac.path.is_ident("bind_slice") {
431                let info = match m.mac.parse_body::<BindSliceInfo>() {
432                    Ok(i) => i,
433                    Err(e) => {
434                        self.bump_err(e);
435                        visit::visit_expr(self, expr);
436                        return;
437                    }
438                };
439                if let Err(e) =
440                    self.reg
441                        .register_value(info.slice_ident.clone(), info.ty.clone(), &info.kind)
442                {
443                    self.bump_err(e);
444                }
445                self.visit_expr(&info.parser);
446                return;
447            }
448        }
449        visit::visit_expr(self, expr);
450    }
451}
452
453/// Build the `(S, M, O)` tuple for [`Capture::<MRes, _, _>`] (explicit `as T` preserved; untyped values use `_`).
454fn build_capture_mres_tuple(registry: &BindRegistry) -> proc_macro2::TokenStream {
455    let build_bucket = |values: &[TypedBinding], spans: &[TypedBinding], is_vec: bool| {
456        let wrap = |inner: proc_macro2::TokenStream| {
457            if is_vec {
458                quote! { ::std::vec::Vec<#inner> }
459            } else {
460                quote! { ::std::option::Option<#inner> }
461            }
462        };
463        let mut pieces = Vec::new();
464        for b in values.iter() {
465            let inner = if let Some(ty) = &b.ty {
466                quote! { #ty }
467            } else {
468                quote! { _ }
469            };
470            pieces.push(wrap(inner));
471        }
472        for b in spans.iter() {
473            let inner = if let Some(ty) = &b.ty {
474                quote! { #ty }
475            } else {
476                quote! { (_, _) }
477            };
478            pieces.push(wrap(inner));
479        }
480        if pieces.is_empty() {
481            quote! { () }
482        } else {
483            quote! { ( #(#pieces,)* ) }
484        }
485    };
486
487    let s_ty = build_bucket(&registry.single_values, &registry.single_spans, false);
488    let m_ty = build_bucket(&registry.multiple_values, &registry.multiple_spans, true);
489    let o_ty = build_bucket(&registry.optional_values, &registry.optional_spans, false);
490    quote! { (#s_ty, #m_ty, #o_ty) }
491}
492
493// ---------------------------------------------------------------------------
494// `use_binds!` expansion
495// ---------------------------------------------------------------------------
496//
497// User-facing syntax is `use_binds!(|ctx| { … })` for `err_if_*` factories. Hand-written
498// grammars can use `marser::error::SnapshotFactory(|snap, ctx| { … })` instead; that path goes
499// through `SnapCallable` in the main crate.
500//
501// We do **not** expand each `use_binds!` to an inline `SnapshotFactory` closure because:
502//
503// 1. **Type inference** — `Capture::<MRes, …>` needs a concrete `MRes` triple. An untyped closure
504//    in the grammar blocks inference (`E0282`). Putting `__BindTn` on `Capture::new` itself would
505//    leak those names into the user's function scope (`E0412`).
506//
507// 2. **`'src` / `erase_types`** — Wrapping a site in `SnapshotFactory(…)` makes `err_if_*` see
508//    `SnapshotFactory<F>` with `F: for<'a> SnapCallable<'a, MRes>`. That bound can still force the
509//    `MRes: 'static` well-formedness trap (see `inline_error.rs`), breaking parsers with
510//    `bind_slice!` and `.erase_types()` on borrowed input.
511//
512// The approach below matches what worked before: one ZST per capture, `BuildInlineError` with an
513// explicit `build_inline_error<'snap>(…, snapshot) where MRes: 'snap`, and `__BindTn` only on the
514// **impl** (not on `Capture`). Sites are `__UseBindsSite::<N>`; multiple sites share one struct and
515// a `match SITE` body. Same snapshot locals as a closure expansion would use (`snapshot_bind_lets`).
516
517/// `(S, M, O)` for [`BuildInlineError`] on `__UseBindsSite`, plus `__BindTn` impl type parameters.
518///
519/// Separate from [`build_capture_mres_tuple`]: `Capture` keeps `_` for inference; the factory impl
520/// declares `__BindT0`, … so rustc can unify capture slots with `bind!` output types.
521fn build_factory_mres_tuple(registry: &BindRegistry) -> (proc_macro2::TokenStream, Vec<Ident>) {
522    let mut gen_names: Vec<Ident> = Vec::new();
523    let mut next_ty = |span: Span| -> proc_macro2::TokenStream {
524        let n = gen_names.len();
525        let id = Ident::new(&format!("__BindT{n}"), span);
526        gen_names.push(id.clone());
527        quote! { #id }
528    };
529
530    let mut build_bucket = |values: &[TypedBinding], spans: &[TypedBinding], is_vec: bool| {
531        let wrap = |inner: proc_macro2::TokenStream| {
532            if is_vec {
533                quote! { ::std::vec::Vec<#inner> }
534            } else {
535                quote! { ::std::option::Option<#inner> }
536            }
537        };
538        let mut pieces = Vec::new();
539        for b in values.iter() {
540            pieces.push(wrap(next_ty(b.ident.span())));
541        }
542        for b in spans.iter() {
543            let inner = if let Some(ty) = &b.ty {
544                quote! { #ty }
545            } else {
546                quote! { (usize, usize) }
547            };
548            pieces.push(wrap(inner));
549        }
550        if pieces.is_empty() {
551            quote! { () }
552        } else {
553            quote! { ( #(#pieces,)* ) }
554        }
555    };
556
557    let s_ty = build_bucket(&registry.single_values, &registry.single_spans, false);
558    let m_ty = build_bucket(&registry.multiple_values, &registry.multiple_spans, true);
559    let o_ty = build_bucket(&registry.optional_values, &registry.optional_spans, false);
560    (quote! { (#s_ty, #m_ty, #o_ty) }, gen_names)
561}
562
563fn snapshot_bind_lets(
564    registry: &BindRegistry,
565) -> (
566    Vec<proc_macro2::TokenStream>,
567    Vec<proc_macro2::TokenStream>,
568    Vec<proc_macro2::TokenStream>,
569) {
570    let mut single_lets = Vec::new();
571    for (i, b) in registry
572        .single_values
573        .iter()
574        .chain(&registry.single_spans)
575        .enumerate()
576    {
577        let idx = Index::from(i);
578        let id = &b.ident;
579        single_lets.push(quote! {
580            #[allow(unused_variables)]
581            let #id = __single.#idx;
582        });
583    }
584    let mut multiple_lets = Vec::new();
585    for (i, b) in registry
586        .multiple_values
587        .iter()
588        .chain(&registry.multiple_spans)
589        .enumerate()
590    {
591        let idx = Index::from(i);
592        let id = &b.ident;
593        multiple_lets.push(quote! {
594            #[allow(unused_variables)]
595            let #id = &__multiple.#idx;
596        });
597    }
598    let mut optional_lets = Vec::new();
599    for (i, b) in registry
600        .optional_values
601        .iter()
602        .chain(&registry.optional_spans)
603        .enumerate()
604    {
605        let idx = Index::from(i);
606        let id = &b.ident;
607        optional_lets.push(quote! {
608            #[allow(unused_variables)]
609            let #id = __optional.#idx;
610        });
611    }
612    (single_lets, multiple_lets, optional_lets)
613}
614
615/// One `use_binds!` site collected before codegen (becomes a `match` arm in [`emit_use_binds_sites`]).
616struct UseBindSite {
617    site: usize,
618    ctx_ident: Ident,
619    inner: proc_macro2::TokenStream,
620}
621
622/// Emit `__UseBindsSite` and a single `BuildInlineError` impl for all sites in this `capture!`.
623fn emit_use_binds_sites(
624    sites: &[UseBindSite],
625    registry: &BindRegistry,
626    marser: &Path,
627    mres: &proc_macro2::TokenStream,
628    mres_generics: &[Ident],
629) -> proc_macro2::TokenStream {
630    if sites.is_empty() {
631        return quote! {};
632    }
633
634    let (single_lets, multiple_lets, optional_lets) = snapshot_bind_lets(registry);
635
636    let const_site_params = quote! { <const __SITE: usize> };
637    let build_inline_params = if mres_generics.is_empty() {
638        quote! { <const __SITE: usize> }
639    } else {
640        quote! { <const __SITE: usize, #(#mres_generics),*> }
641    };
642
643    let arms: Vec<_> = sites
644        .iter()
645        .map(|s| {
646            let lit = syn::LitInt::new(&format!("{}", s.site), Span::call_site());
647            let ctx = &s.ctx_ident;
648            let inner = &s.inner;
649            quote! {
650                #lit => {
651                    let #ctx = __ctx;
652                    #inner
653                }
654            }
655        })
656        .collect();
657
658    quote! {
659        struct __UseBindsSite<const __SITE: usize>;
660
661        impl #const_site_params ::core::clone::Clone for __UseBindsSite<__SITE> {
662            fn clone(&self) -> Self {
663                *self
664            }
665        }
666
667        impl #const_site_params ::core::marker::Copy for __UseBindsSite<__SITE> {}
668
669        impl #build_inline_params #marser::error::BuildInlineError<#mres> for __UseBindsSite<__SITE> {
670            fn build_inline_error<'__snap>(
671                &self,
672                __ctx: #marser::error::MatchDiagCtx,
673                __snap: <#mres as #marser::parser::capture::MatchResult>::Snapshot<'__snap>,
674            ) -> #marser::error::InlineError
675            where
676                #mres: '__snap,
677            {
678                let __single = &__snap.0;
679                let __multiple = &__snap.1;
680                let __optional = &__snap.2;
681                #(#single_lets)*
682                #(#multiple_lets)*
683                #(#optional_lets)*
684                match __SITE {
685                    #(#arms)*
686                    _ => ::core::unreachable!("use_binds site out of range"),
687                }
688            }
689        }
690    }
691}
692
693fn parse_use_binds_closure(
694    closure: ExprClosure,
695) -> std::result::Result<(Ident, proc_macro2::TokenStream), syn::Error> {
696    let ctx_ident = match closure.inputs.iter().next() {
697        Some(Pat::Type(pt)) => {
698            if let Pat::Ident(pi) = pt.pat.as_ref() {
699                pi.ident.clone()
700            } else {
701                Ident::new("ctx", Span::call_site())
702            }
703        }
704        Some(Pat::Ident(pi)) => pi.ident.clone(),
705        _ => Ident::new("ctx", Span::call_site()),
706    };
707
708    let inner = match closure.body.as_ref() {
709        Expr::Block(b) => {
710            let stmts = b.block.stmts.iter();
711            quote! { #(#stmts)* }
712        }
713        expr => quote! { #expr },
714    };
715    Ok((ctx_ident, inner))
716}
717
718struct UseBindsRewriter {
719    sites: RefCell<Vec<UseBindSite>>,
720    next_site: Cell<usize>,
721    errors: RefCell<Option<syn::Error>>,
722}
723
724impl UseBindsRewriter {
725    fn bump_err(&self, e: syn::Error) {
726        let mut slot = self.errors.borrow_mut();
727        *slot = Some(match slot.take() {
728            None => e,
729            Some(mut prev) => {
730                prev.combine(e);
731                prev
732            }
733        });
734    }
735}
736
737impl VisitMut for UseBindsRewriter {
738    fn visit_expr_mut(&mut self, expr: &mut Expr) {
739        if let Expr::Macro(m) = expr
740            && m.mac.path.is_ident("use_binds")
741        {
742            let closure = match m.mac.parse_body::<ExprClosure>() {
743                Ok(c) => c,
744                Err(e) => {
745                    self.bump_err(e);
746                    return;
747                }
748            };
749            let (ctx_ident, inner) = match parse_use_binds_closure(closure) {
750                Ok(x) => x,
751                Err(e) => {
752                    self.bump_err(e);
753                    return;
754                }
755            };
756            let site = self.next_site.get();
757            self.next_site.set(site + 1);
758            self.sites.borrow_mut().push(UseBindSite {
759                site,
760                ctx_ident,
761                inner,
762            });
763            let lit = syn::LitInt::new(&format!("{}", site), Span::call_site());
764            // ZST factory type, not `SnapshotFactory(closure)` — see module comment above.
765            match syn::parse2::<Expr>(quote! { __UseBindsSite::<#lit> }) {
766                Ok(expanded) => *expr = expanded,
767                Err(e) => self.bump_err(e),
768            }
769            return;
770        }
771        visit_mut::visit_expr_mut(self, expr);
772    }
773}
774
775/// Reject `use_binds!` in the `capture!` result expression (only meaningful in the grammar).
776struct UseBindsInResultChecker {
777    error: Option<syn::Error>,
778}
779
780impl<'ast> Visit<'ast> for UseBindsInResultChecker {
781    fn visit_expr(&mut self, expr: &'ast Expr) {
782        if let Expr::Macro(m) = expr
783            && m.mac.path.is_ident("use_binds")
784        {
785            self.error = Some(syn::Error::new_spanned(
786                m.mac.path.get_ident().unwrap(),
787                "`use_binds!` is only allowed in the grammar of `capture!`, not in the `=>` result expression",
788            ));
789            return;
790        }
791        visit::visit_expr(self, expr);
792    }
793}
794
795// ---------------------------------------------------------------------------
796// Bind macro expansion
797// ---------------------------------------------------------------------------
798
799/// Expands `bind!` / `bind_span!` / `bind_slice!` inside `capture!` after [`BindCollector`] validation.
800struct BindMacroExpander {
801    marser_path: Path,
802    errors: Option<syn::Error>,
803}
804
805impl BindMacroExpander {
806    fn new(marser_path: Path) -> Self {
807        Self {
808            marser_path,
809            errors: None,
810        }
811    }
812
813    fn bump_err(&mut self, e: syn::Error) {
814        self.errors = Some(match self.errors.take() {
815            None => e,
816            Some(mut prev) => {
817                prev.combine(e);
818                prev
819            }
820        });
821    }
822
823    fn take_errors(self) -> Option<syn::Error> {
824        self.errors
825    }
826}
827
828impl VisitMut for BindMacroExpander {
829    fn visit_expr_mut(&mut self, i: &mut Expr) {
830        if let Expr::Macro(m) = i {
831            if m.mac.path.is_ident("bind") {
832                let info = match m.mac.parse_body::<BindInfo>() {
833                    Ok(info) => info,
834                    Err(e) => {
835                        self.bump_err(e);
836                        visit_mut::visit_expr_mut(self, i);
837                        return;
838                    }
839                };
840                let id = &info.ident;
841                let parser = &info.parser;
842                let bind_span = id.span();
843                let rewrite = if let Some(span_id) = &info.span_ident {
844                    let marser = self.marser_path.clone();
845                    quote_spanned! {bind_span=>
846                        #marser::parser::capture::bind_span(
847                            #marser::parser::capture::bind_result(#parser, #id),
848                            #span_id
849                        )
850                    }
851                } else {
852                    let marser = self.marser_path.clone();
853                    quote_spanned! {bind_span=>
854                        #marser::parser::capture::bind_result(#parser, #id)
855                    }
856                };
857                match syn::parse2(rewrite) {
858                    Ok(expr) => *i = expr,
859                    Err(e) => self.bump_err(e),
860                }
861                return;
862            }
863
864            if m.mac.path.is_ident("bind_span") {
865                let info = match m.mac.parse_body::<BindSpanInfo>() {
866                    Ok(i) => i,
867                    Err(e) => {
868                        self.bump_err(e);
869                        visit_mut::visit_expr_mut(self, i);
870                        return;
871                    }
872                };
873                let span_id = &info.span_ident;
874                let parser = &info.parser;
875                let marser = self.marser_path.clone();
876                *i = parse_quote! { #marser::parser::capture::bind_span(#parser, #span_id) };
877                return;
878            }
879
880            if m.mac.path.is_ident("bind_slice") {
881                let info = match m.mac.parse_body::<BindSliceInfo>() {
882                    Ok(i) => i,
883                    Err(e) => {
884                        self.bump_err(e);
885                        visit_mut::visit_expr_mut(self, i);
886                        return;
887                    }
888                };
889                let slice_id = &info.slice_ident;
890                let parser = &info.parser;
891                let marser = self.marser_path.clone();
892                *i = parse_quote! { #marser::parser::capture::bind_slice(#parser, #slice_id) };
893                return;
894            }
895        }
896        visit_mut::visit_expr_mut(self, i);
897    }
898}
899
900// ---------------------------------------------------------------------------
901// capture! proc-macro
902// ---------------------------------------------------------------------------
903
904/// Build a parser from grammar + result expressions (via [`Capture`](https://docs.rs/marser/latest/marser/parser/capture/struct.Capture.html), returned as `impl Parser` to limit type size).
905///
906/// # Syntax
907///
908/// ```rust,ignore
909/// capture!( <grammar> => <result> )
910/// ```
911///
912/// - **`<grammar>`** — any expression after `bind!` / `bind_span!` expansion (typically a tuple
913///   of matchers, often using [`crate::matcher::Matcher`](https://docs.rs/marser/latest/marser/matcher/trait.Matcher.html)
914///   combinators like `many` / `one_of`).
915/// - **`<result>`** — Rust expression that receives the captured bindings and produces the parser output.
916///
917/// Inside `<grammar>`, the macro recognizes:
918///
919/// - **`bind!(parser, ident)`** — single capture into `ident` (`Option<_>` in the bucket).
920/// - **`bind!(parser, *ident)`** — repeated capture into `ident` (`Vec<_>`).
921/// - **`bind!(parser, ?ident)`** — optional capture (`Option<_>`).
922/// - **`bind!(parser, ident as T)`** / **`bind!(parser, *ident as T)`** / **`bind!(parser, ?ident as T)`** —
923///   typed captures. With `*` / `?`, the sigil still wraps `T` (Option A semantics).
924/// - **`bind!(parser, ident [as T], *span_ident [as U])`** (and `?` / `*` on the value) — value plus span capture.
925/// - **`bind_span!(parser, ident)`** / **`bind_span!(parser, *ident)`** / **`bind_span!(parser, ?ident)`** / **`bind_span!(parser, ident as T)`** —
926///   capture only a span (expands to `marser::parser::capture::bind_span`).
927/// - **`bind_slice!(parser, ident)`** / **`bind_slice!(parser, *ident)`** / **`bind_slice!(parser, ?ident)`** / **`bind_slice!(parser, ident as T)`** —
928///   capture only the consumed input slice (expands to `marser::parser::capture::bind_slice`).
929///
930/// - **`use_binds!(|ctx| { … })`** — expands to `__UseBindsSite::<N>` (not an inline
931///   [`marser::error::SnapshotFactory`] closure). One shared `__UseBindsSite<const SITE>` per
932///   `capture!` implements [`marser::error::BuildInlineError`] with `match SITE` dispatch. See the
933///   `use_binds! expansion` comment in this crate for why (inference, `'src`, `erase_types`).
934///
935/// Repeated **compatible** binds to the same identifier (same sigil bucket and compatible `as`
936/// types) are merged into one capture slot. Conflicting sigils, incompatible explicit types, or
937/// reusing the same name for both value and span captures are **compile errors** with spans on the
938/// offending `bind!` / `bind_span!` / `bind_slice!` sites.
939///
940/// These helper macros are only expanded meaningfully inside `capture!`; using them elsewhere
941/// yields normal unresolved-macro errors unless you import the `marser` crate and use `capture!`
942/// from it (or depend on `marser_macros` directly for experimentation).
943///
944/// Each binding becomes a parameter to both the grammar closure and the result closure; the grammar
945/// side often ignores those names because wiring goes through `bind_result` / `bind_span`.
946///
947/// The expansion prefixes APIs with the dependency name from Cargo (via `proc_macro_crate::crate_name("marser")`).
948/// If you rename the `marser` dependency in your `Cargo.toml`, generated paths use that name.
949#[proc_macro]
950pub fn capture(input: TokenStream) -> TokenStream {
951    let mut input: CaptureInput = syn::parse_macro_input!(input as CaptureInput);
952    let marser_path = marser_crate_path();
953    let registry = match BindCollector::collect(&input.grammar) {
954        Ok(r) => r,
955        Err(e) => return e.to_compile_error().into(),
956    };
957
958    let mut expander = BindMacroExpander::new(marser_path.clone());
959    expander.visit_expr_mut(&mut input.grammar);
960    if let Some(e) = expander.take_errors() {
961        return e.to_compile_error().into();
962    }
963
964    let pat_tuple = |values: &[TypedBinding], spans: &[TypedBinding]| {
965        let all: Vec<_> = values
966            .iter()
967            .chain(spans.iter())
968            .map(|x| &x.ident)
969            .collect();
970        if all.is_empty() {
971            quote! { () }
972        } else {
973            quote! { ( #(#all,)* ) }
974        }
975    };
976
977    let s_pat = pat_tuple(&registry.single_values, &registry.single_spans);
978    let m_pat = pat_tuple(&registry.multiple_values, &registry.multiple_spans);
979    let o_pat = pat_tuple(&registry.optional_values, &registry.optional_spans);
980
981    let mres_capture = build_capture_mres_tuple(&registry);
982    // Only used when the grammar contains `use_binds!`; see `use_binds!` section above.
983    let (mres_factory, mres_generics) = build_factory_mres_tuple(&registry);
984
985    let mut use_binds_rw = UseBindsRewriter {
986        sites: RefCell::new(Vec::new()),
987        next_site: Cell::new(0),
988        errors: RefCell::new(None),
989    };
990    use_binds_rw.visit_expr_mut(&mut input.grammar);
991    if let Some(e) = use_binds_rw.errors.into_inner() {
992        return e.to_compile_error().into();
993    }
994    let sites = use_binds_rw.sites.into_inner();
995
996    let mut result_checker = UseBindsInResultChecker { error: None };
997    result_checker.visit_expr(&input.result_expr);
998    if let Some(e) = result_checker.error {
999        return e.to_compile_error().into();
1000    }
1001
1002    let use_binds_block = emit_use_binds_sites(
1003        &sites,
1004        &registry,
1005        &marser_path,
1006        &mres_factory,
1007        &mres_generics,
1008    );
1009
1010    let grammar = &input.grammar;
1011    let result_expr = &input.result_expr;
1012
1013    TokenStream::from(quote! {
1014        {
1015            #use_binds_block
1016            #[allow(unused_variables)]
1017            #marser_path::parser::as_parser(
1018                #marser_path::parser::capture::Capture::<#mres_capture, _, _>::new(
1019                    |#s_pat, #m_pat, #o_pat| { #grammar     },
1020                    |#s_pat, #m_pat, #o_pat| { #result_expr },
1021                ),
1022            )
1023        }
1024    })
1025}
1026
1027fn marser_crate_path() -> Path {
1028    match crate_name("marser") {
1029        Ok(FoundCrate::Itself) => parse_quote!(::marser),
1030        Ok(FoundCrate::Name(name)) => {
1031            let ident = Ident::new(&name, Span::call_site());
1032            parse_quote!(::#ident)
1033        }
1034        Err(_) => parse_quote!(::marser),
1035    }
1036}