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

Cheat Sheet

Quick reference for common Sipha patterns and APIs.

Syntax Kinds

use sipha::syntax::SyntaxKind;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum MySyntaxKind {
    // Terminals
    Number, Plus, Minus, Eof,
    // Non-terminals
    Expr, Term,
}

impl SyntaxKind for MySyntaxKind {
    fn is_terminal(self) -> bool {
        !matches!(self, Self::Expr | Self::Term)
    }
    
    fn is_trivia(self) -> bool {
        false // No trivia in this example
    }
}

Lexer Builder

use sipha::syntax::SyntaxKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum MySyntaxKind { Number, Plus, Minus, Eof, Whitespace, Expr, Term, }
impl SyntaxKind for MySyntaxKind {
    fn is_terminal(self) -> bool { !matches!(self, Self::Expr | Self::Term) }
    fn is_trivia(self) -> bool { matches!(self, Self::Whitespace) }
}
use sipha::lexer::{LexerBuilder, Pattern, CharSet};

let lexer = LexerBuilder::new()
    .token(MySyntaxKind::Number, Pattern::Repeat {
        pattern: Box::new(Pattern::CharClass(CharSet::digits())),
        min: 1,
        max: None,
    })
    .token(MySyntaxKind::Plus, Pattern::Literal("+".into()))
    .trivia(MySyntaxKind::Whitespace)
    .build(MySyntaxKind::Eof, MySyntaxKind::Number)?;

Grammar Builder

use sipha::syntax::SyntaxKind;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum MySyntaxKind { Number, Plus, Minus, Eof, Whitespace, Expr, Term, }
impl SyntaxKind for MySyntaxKind {
    fn is_terminal(self) -> bool { !matches!(self, Self::Expr | Self::Term) }
    fn is_trivia(self) -> bool { matches!(self, Self::Whitespace) }
}
use sipha::grammar::{GrammarBuilder, Expr, NonTerminal};
use sipha::lexer::Token as LexerToken;
use sipha::syntax::{TextRange, TextSize};
use std::convert::TryFrom;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum MyNonTerminal { Expr, }
impl NonTerminal for MyNonTerminal {
    fn name(&self) -> &str { match self { MyNonTerminal::Expr => "Expr", } }
}

// Helper to create tokens
fn token(kind: MySyntaxKind, text: &str, offset: u32) -> LexerToken<MySyntaxKind> {
    let len = TextSize::from(u32::try_from(text.len()).unwrap_or(0));
    LexerToken::new(kind, text, TextRange::at(TextSize::from(offset), len))
}

let grammar = GrammarBuilder::new()
    .entry_point(MyNonTerminal::Expr)
    .rule(MyNonTerminal::Expr, Expr::token(token(MySyntaxKind::Number, "1", 0)))
    .build()?;

Grammar Expressions

ExpressionSyntaxDescription
TokenExpr::token(token)Match a specific token
Non-terminalExpr::non_terminal(NT::Expr)Match a non-terminal
SequenceExpr::seq(vec![...])Match expressions in order
ChoiceExpr::choice(vec![...])Match one of several alternatives
OptionalExpr::optional(Box::new(...))Match zero or one occurrence
RepeatExpr::repeat(Box::new(...), min, max)Match repeated occurrences
EmptyExpr::EmptyMatch nothing (epsilon)

Parser Usage

use sipha::grammar::{GrammarBuilder, Grammar, Expr, NonTerminal};
use sipha::lexer::Token;
type MyToken = ();
type MyNonTerminal = ();
type MySyntaxKind = ();
let grammar: Grammar<MyToken, MyNonTerminal> = todo!();
let tokens: Vec<Token<MySyntaxKind>> = vec![];
use sipha::backend::ll::{LlParser, LlConfig};
use sipha::backend::ParserBackend;
use sipha::syntax::SyntaxNode;

let config = LlConfig::default();
let mut parser = LlParser::new(&grammar, config)?;
let result = parser.parse(&tokens, MyNonTerminal::Expr);

if !result.errors.is_empty() {
    // Handle errors
}
let root = SyntaxNode::new_root(result.root.clone());

Incremental Parsing

use sipha::backend::ll::LlParser;
use sipha::grammar::Grammar;
use sipha::lexer::Token;
use sipha::syntax::GreenNode;
type MyToken = ();
type MyNonTerminal = ();
type MySyntaxKind = ();
let parser: LlParser = todo!();
let grammar: Grammar<MyToken, MyNonTerminal> = todo!();
let tokens: Vec<Token<MySyntaxKind>> = vec![];
let old_tree: Option<&GreenNode<MySyntaxKind>> = None;
let entry_point: MyNonTerminal = todo!();
use sipha::incremental::{IncrementalParser, TextEdit};
use sipha::syntax::{TextRange, TextSize};

let mut incremental = IncrementalParser::new(parser);
let edits = vec![TextEdit::replace(
    TextRange::new(TextSize::from(0), TextSize::from(2)),
    "new".into(),
)];
let result = incremental.parse_incremental(
    &tokens,
    old_tree,
    &edits,
    entry_point,
    Some(&grammar),
);

Syntax Tree Navigation

use sipha::syntax::{SyntaxNode, GreenNode, SyntaxKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum MySyntaxKind { Expr, }
impl SyntaxKind for MySyntaxKind {
    fn is_terminal(self) -> bool { false }
    fn is_trivia(self) -> bool { false }
}
let green_node: GreenNode<MySyntaxKind> = todo!();
use sipha::syntax::SyntaxNode;

let root = SyntaxNode::new_root(green_node);

// Children
for child in root.children() {
    println!("{:?}", child.kind());
}

// Descendants
for node in root.descendants() {
    if node.kind() == MySyntaxKind::Expr {
        // Found expression
    }
}

// Parent
if let Some(parent) = root.parent() {
    println!("Parent: {:?}", parent.kind());
}

// Siblings
for sibling in root.next_sibling().into_iter() {
    println!("Sibling: {:?}", sibling.kind());
}

Error Handling

use sipha::backend::ParserBackend;
type MyToken = ();
type MyNonTerminal = ();
struct Parser;
impl ParserBackend<MyToken, MyNonTerminal> for Parser {
    type Config = ();
    type Error = ();
    type State = ();
    fn new(_: &(), _: ()) -> Result<Self, ()> { Ok(Parser) }
    fn parse(&mut self, _: &[MyToken], _: MyNonTerminal) -> sipha::backend::ParseResult<MyToken, MyNonTerminal> { todo!() }
}
let mut parser = Parser;
let tokens: Vec<MyToken> = vec![];
let result = parser.parse(&tokens, MyNonTerminal::Expr);
// Check for errors
if !result.errors.is_empty() {
    for error in &result.errors {
        eprintln!("Error: {} at {:?}", error.message, error.span);
    }
}

// Check for warnings
for warning in &result.warnings {
    eprintln!("Warning: {}", warning.message);
}

Pattern Matching

PatternSyntaxExample
LiteralPattern::Literal("+".into())Match exact string
Char ClassPattern::CharClass(CharSet::digits())Match character class
RepeatPattern::Repeat { pattern, min, max }Match repetition
RegexPattern::Regex(regex)Match regex pattern

Common Character Sets

use sipha::lexer::CharSet;

let _ = CharSet::digits();        // [0-9]
let _ = CharSet::whitespace();    // \s
let _ = CharSet::letters();       // [a-zA-Z]
let _ = CharSet::alphanumeric();  // [a-zA-Z0-9]
let _ = CharSet::hex_digits();    // [0-9a-fA-F]

Backend Selection

Use CaseBackendReason
Simple grammarLLEasy to use, good error messages
Left recursionLRNatural support
Ambiguous grammarGLRHandles ambiguity
Maximum performanceLREfficient table-based parsing

Feature Flags

[dependencies]
sipha = { version = "0.5.0", features = [
    "backend-ll",      # LL(k) parser (default)
    "backend-lr",      # LR parser
    "backend-glr",     # GLR parser (requires backend-lr)
    "diagnostics",     # Rich error diagnostics
    "unicode",         # Unicode support
    "visitor",         # Visitor patterns
    "query",           # XPath-like queries
    "tree-utils",      # Tree utilities
] }

Common Patterns

Optional Semicolon

use sipha::grammar::{GrammarBuilder, Expr, NonTerminal};
use sipha::lexer::Token;
type MyToken = ();
type MyNonTerminal = ();
type MySyntaxKind = ();
let semicolon_token: Token<MySyntaxKind> = todo!();
let mut builder = GrammarBuilder::new();
builder.rule(MyNonTerminal::Stmt, Expr::seq(vec![
    Expr::non_terminal(MyNonTerminal::Expr),
    Expr::optional(Box::new(Expr::token(semicolon_token))),
]))

Delimited List

use sipha::grammar::{GrammarBuilder, Expr, NonTerminal};
use sipha::lexer::Token;
type MyToken = ();
type MyNonTerminal = ();
type MySyntaxKind = ();
let open_token: Token<MySyntaxKind> = todo!();
let close_token: Token<MySyntaxKind> = todo!();
let mut builder = GrammarBuilder::new();
builder.rule(MyNonTerminal::List, Expr::seq(vec![
    Expr::token(open_token),
    Expr::repeat(
        Box::new(Expr::non_terminal(MyNonTerminal::Item)),
        0,
        None,
    ),
    Expr::token(close_token),
]))

Operator Precedence

use sipha::grammar::{GrammarBuilder, Expr, NonTerminal};
use sipha::lexer::Token;
type MyToken = ();
type MyNonTerminal = ();
type MySyntaxKind = ();
let multiply_token: Token<MySyntaxKind> = todo!();
let plus_token: Token<MySyntaxKind> = todo!();
let mut builder = GrammarBuilder::new();
// Higher precedence first
builder.rule(MyNonTerminal::Expr, Expr::choice(vec![
    Expr::seq(vec![  // * has higher precedence
        Expr::non_terminal(MyNonTerminal::Term),
        Expr::token(multiply_token),
        Expr::non_terminal(MyNonTerminal::Term),
    ]),
    Expr::seq(vec![  // + has lower precedence
        Expr::non_terminal(MyNonTerminal::Expr),
        Expr::token(plus_token),
        Expr::non_terminal(MyNonTerminal::Term),
    ]),
    Expr::non_terminal(MyNonTerminal::Term),
]))

See Also