Neotoma
A flexible, cached parser combinator framework for Rust with built-in memoization and backtracking capabilities.
Features
- Parser Combinators: Compose complex parsers from simple building blocks
- Built-in Caching: Automatic memoization prevents redundant parsing work
- Backtracking Support: Efficient backtracking with automatic position management
- UTF-8 Aware: Dual-level design supporting both byte-level and character-level parsing
- Generic Input Sources: Works with any
Read + Seekimplementation - Thread Safe: Uses
parking_lotfor efficient synchronization primitives - Grammar Support: Self-parsing grammar system for complex syntax definitions
Quick Start
Add Neotoma to your Cargo.toml:
[]
= "0.1.0"
Basic Example
use *;
use ;
use Cursor;
// Parse "hello world" with optional whitespace
let parser = seq!;
let input = new;
let mut source = new;
match parse
Character Classes
use *;
// Parse one or more digits
let numbers = with_min;
// Parse alphabetic characters
let letters = alpha;
// Parse alphanumeric with bounds
let identifier = alphanumeric.with_bounds;
Composition with Macros
use ;
use *;
// Sequential composition
let sequence = seq!;
// Alternative composition
let choice = oneof!;
Repetition and Lists
use *;
// Zero or more digits
let numbers = new;
// Comma-separated list
let csv = new
.with_joint;
// Bounded repetition
let bounded = new
.with_bounds;
Grammar System
Neotoma includes a self-parsing grammar system that allows you to
define complex parsers using a declarative syntax. The GrammarParser
can parse grammar definitions and produce executable parsers.
Basic Usage
use GrammarParser;
use *;
use Cursor;
let grammar_text = r#"
@start expression
expression = (number "+" number)
number = digits
"#;
let grammar_parser = new;
let mut input = new;
let mut source = new;
let grammar = parse.unwrap;
// Use the grammar to parse expressions
let mut expr_input = new;
let mut expr_source = new;
let result = parse.unwrap;
Grammar Syntax
Terminals and Literals
"text"- matches literal string (use\"for escaped quotes)eof- matches end of file (ensures complete input consumption)
Character Classes
digits- matches one or more decimal digits (0-9)alpha- matches one or more alphabetic characters (ASCII)alphanumeric- matches one or more alphanumeric characters (ASCII)whitespace- matches one or more whitespace characters (ASCII)hexdigits- matches one or more hexadecimal digits (0-9, a-f, A-F)udigits,ualpha,ualphanumeric,uwhitespace- UTF-8 equivalents[abc]- matches any character in the set (custom UTF-8 character class)[^abc]- matches any character NOT in the set (negated UTF-8 character class)
Composition
(A B C)- matches A followed by B followed by C (sequence)(| A B C)- matches either A or B or C (alternatives)
Repetition
(* A)- matches zero or more instances of A(+ A)- matches one or more instances of A(? A)- matches zero or one instances of A(* A / B)- matches zero or more A's separated by B(+ A / B)- matches one or more A's separated by B
Rules and References
name = rule- defines a named parsing rulename- references a named rule (enables recursion)@start name- sets the starting rule (defaults to last rule if not specified)
Complex Grammar Example
let arithmetic_grammar = r#"
@start complete_expression
complete_expression = (expression eof)
expression = additive_expr
additive_expr = (| addition subtraction multiplicative_expr)
addition = (multiplicative_expr (? whitespace) "+" (? whitespace) additive_expr)
subtraction = (multiplicative_expr (? whitespace) "-" (? whitespace) additive_expr)
multiplicative_expr = (| multiplication division primary_expr)
multiplication = (primary_expr (? whitespace) "*" (? whitespace) multiplicative_expr)
division = (primary_expr (? whitespace) "/" (? whitespace) multiplicative_expr)
primary_expr = (| number variable parenthesized_expr)
number = digits
variable = alpha
parenthesized_expr = ("(" (? whitespace) expression (? whitespace) ")")
"#;
This grammar handles:
- Proper operator precedence (multiplication/division before addition/subtraction)
- Recursive expressions with parentheses
- Variables and numbers
- Optional whitespace handling
- Left-associative operations
Architecture
Neotoma uses a Template Method Pattern for its core Parser trait:
read(): Implement your parsing logic hereparse(): Public API that automatically handles caching and backtrackingid(): Override for parameterized parsers to avoid cache conflicts
Key Design Principles
- Composability: Orthogonal combinators work together seamlessly
- Performance: Memoization prevents redundant parsing
- Type Safety: Generic composition prevents common mistakes
- Memory Efficiency: Smart pointer integration and optimized data structures
Documentation
Examples
The tests/ directory contains comprehensive examples:
- Arithmetic Parser: Mathematical expression parsing
- Lisp Expressions: Recursive data structure parsing
- Grammar Definitions: Self-parsing grammar systems
Python Bindings
Python bindings are available in the neotoma-py/ directory. See the Python README for details.
License
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributing
Contributions are welcome! Please feel free to submit a patch.