Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project Setup

Create the Project

cargo new toml-parser --lib
cd toml-parser

Dependencies

[package]
name = "toml-parser"
version = "0.1.0"
edition = "2024"

[dependencies]
synkit = "0.1"
thiserror = "2"
logos = "0.15"

Error Type

Define an error type that implements Default (required by logos):

#[derive(Error, Debug, Clone, Default, PartialEq)]
pub enum TomlError {
    #[default]
    #[error("unknown lexing error")]
    Unknown,

    #[error("expected {expect}, found {found}")]
    Expected { expect: &'static str, found: String },

    #[error("expected {expect}, found EOF")]
    Empty { expect: &'static str },

    #[error("unclosed string")]
    UnclosedString,

    #[error("{source}")]
    Spanned {
        #[source]
        source: Box<TomlError>,
        span: Span,
    },
}

Key requirements:

  • #[default] variant for unknown tokens
  • Expected variant with expect and found fields
  • Empty variant for EOF errors
  • Spanned variant wrapping errors with location

parser_kit! Invocation

The macro generates all parsing infrastructure:

synkit::parser_kit! {
    error: TomlError,

    skip_tokens: [Space, Tab],

    tokens: {
        // Whitespace
        #[token(" ", priority = 0)]
        Space,

        #[token("\t", priority = 0)]
        Tab,

        #[regex(r"\r?\n")]
        #[fmt("newline")]
        #[no_to_tokens]
        Newline,

        // Comments
        #[regex(r"#[^\n]*", allow_greedy = true)]
        #[fmt("comment")]
        Comment,

        // Punctuation
        #[token("=")]
        Eq,

        #[token(".")]
        Dot,

        #[token(",")]
        Comma,

        #[token("[")]
        LBracket,

        #[token("]")]
        RBracket,

        #[token("{")]
        LBrace,

        #[token("}")]
        RBrace,

        // Keywords/literals
        #[token("true")]
        True,

        #[token("false")]
        False,

        // Bare keys: alphanumeric, underscores, dashes
        #[regex(r"[A-Za-z0-9_-]+", |lex| lex.slice().to_string(), priority = 1)]
        #[fmt("bare key")]
        #[derive(PartialOrd, Ord, Hash, Eq)]
        BareKey(String),

        // Basic strings (double-quoted) - needs custom ToTokens for quote handling
        #[regex(r#""([^"\\]|\\.)*""#, |lex| {
            let s = lex.slice();
            // Remove surrounding quotes
            s[1..s.len()-1].to_string()
        })]
        #[fmt("string")]
        #[no_to_tokens]
        BasicString(String),

        // Integers
        #[regex(r"-?[0-9]+", |lex| lex.slice().parse::<i64>().ok())]
        #[fmt("integer")]
        Integer(i64),
    },

    delimiters: {
        Bracket => (LBracket, RBracket),
        Brace => (LBrace, RBrace),
    },

    span_derives: [Debug, Clone, PartialEq, Eq, Hash, Copy],
    token_derives: [Clone, PartialEq, Debug],
}

This generates:

  • span module with Span, Spanned<T>
  • tokens module with Token enum and *Token structs
  • stream module with TokenStream
  • traits module with Parse, Peek, ToTokens
  • delimiters module with Bracket, Brace

Error Helpers

Add convenience methods for error creation:

impl TomlError {
    pub fn expected<D: Diagnostic>(found: &Token) -> Self {
        Self::Expected {
            expect: D::fmt(),
            found: format!("{}", found),
        }
    }

    pub fn empty<D: Diagnostic>() -> Self {
        Self::Empty { expect: D::fmt() }
    }
}

impl synkit::SpannedError for TomlError {
    type Span = Span;

    fn with_span(self, span: Span) -> Self {
        Self::Spanned {
            source: Box::new(self),
            span,
        }
    }

    fn span(&self) -> Option<&Span> {
        match self {
            Self::Spanned { span, .. } => Some(span),
            _ => None,
        }
    }
}

Module Structure

// lib.rs
mod ast;
mod parse;
mod print;
mod visitor;

pub use ast::*;
pub use parse::*;
pub use visitor::*;

Verify Setup

cargo check

The macro should expand without errors. If you see errors about missing traits, ensure your error type has the required variants.