liveBook · Manning

Parsing

As explained in Write Powerful Rust Macros

Parsing is the process of analyzing an input sequence according to a grammar and constructing its syntactic structure.

Parsing turns macro input into a structure the macro can inspect, validate, and later transform. In Rust procedural macros, the input arrives as a TokenStream: a sequence of TokenTrees rather than an already meaningful application model. A minimal macro can iterate through tokens, extract an identifier, generate Rust as a string, and parse that string back into a TokenStream, but this becomes tedious as the accepted input grows. section 3.6

For richer inputs, parse into custom structs first and generate output in a separate phase. This separation keeps syntax handling distinct from the work performed with the result; an infrastructure DSL, for example, can represent bucket and lambda declarations before any cloud resources are created. It also makes the parser a potential boundary between a high-level DSL and different backends, though provider details can still leak into the design. section 9.3

Build a practical parser incrementally:

  • Model the accepted input with structs such as a top-level input containing optional Bucket and Lambda values. section 9.3.2
  • Implement syn’s Parse trait at each level, letting the top-level parser delegate resource-specific syntax to the relevant struct. section 9.3.2
  • Use peek to choose a parser from the next token, and stop only when the ParseStream is empty. section 9.3.2
  • Define DSL-only words with custom_keyword!, then report unexpected tokens or missing values with span-aware custom errors. section 9.3.2
  • Validate relationships after parsing, such as requiring a lambda when a bucket declares an event. section 9.3.2

A small infrastructure DSL

An input such as `bucket unique => lambda my_name mem 1024 time 15` can be parsed into an IacInput containing a Bucket named `unique` with events enabled and a Lambda with optional memory and timeout settings. Parsing a malformed declaration instead produces focused errors, such as `lambda needs a name` or `expected integer literal`, at the relevant token. section 9.3.2

Listing 9.5 IacInput definition and Parse implementation
#[derive(Debug)]
struct IacInput {
    bucket: Option<Bucket>,
    lambda: Option<Lambda>,
}

impl Parse for IacInput {
    fn parse(input: ParseStream) -> Result<Self, syn::Error> {
        let mut bucket: Option<Bucket> = None;
        let mut lambda = None;

        loop {
            if input.peek(kw::bucket) {             #1
                bucket = Some(input.parse()?);       #2
            } else if input.peek(kw::lambda) {      
                lambda = Some(input.parse()?);      
            } else if !input.is_empty() {
                return Err(syn::Error::new( #3
                    input.lookahead1().error().span(),
                    "only 'bucket' and 'lambda' resources are supported")
                );       
            } else {
                break;          #4
            }
        }

        if bucket.as_ref().map(|v| v.has_event).unwrap_or(false)
        && lambda.is_none() {
            return Err(syn::Error::new(
                input.span(),
                "a lambda is required for an event ('=>')"
            ))
        }

        Ok(
            IacInput {
                bucket,
                lambda,
            }
        )
    }
}

Follow the top-level Parse implementation: it dispatches on `bucket` and `lambda`, rejects leftovers, ends at empty input, and then validates the completed structure. section 9.3.2

Choose the parsing tool to fit the grammar and build constraints. Direct TokenTree handling can be sufficient when only a name is needed and can avoid heavier dependencies. `syn` provides a widely used, powerful parsing model for more complete macros, while lightweight parsers such as `venial` can expose declarations through a simpler enum for matching. section 3.6 section 5.7

How Write Powerful Rust Macros covers it

How deep it goes
Works through hierarchical Parse implementations, lookahead, custom keywords, diagnostics, and validation while deferring cloud-resource creation.
Examples
Uses Hello-derived macro input, a bucket-and-lambda infrastructure DSL, parsed structs, and compiler error outputs.
What you can do after
Build it from scratch by implementing Parse for nested DSL structs with token checks, errors, and post-parse validation.
How it connects
Connects TokenStream, TokenTree, procedural macros, function-like macros, syn, custom keywords, and code generation.

Also explained in

Compare how 13 books explain parsing →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Write Powerful Rust Macros. The section links point to the parts of the book it draws on.