Back to Documentation
Feature Comparison & Decision Guide
Choose the right parsing approach for your use case. This guide compares Parsanol's features and helps you decide when to use each one.
Quick Reference
| I need to... | Feature | Example |
|---|---|---|
| Extract specific fields from input | Captures | capture("email", ...) |
| Parse nested structures cleanly | Scopes | scope(inner_parser) |
| Change grammar based on context | Dynamic | dynamic_with_id(id) |
| Process files larger than RAM | Streaming | StreamingParser::new(...) |
| Re-parse after small edits | Incremental | IncrementalParser::new(...) |
| Match heredocs / self-referencing | Captures + Dynamic | capture + dynamic callback |
Feature Deep Dive
Captures
Named Value Extraction
Best For
- • Extracting email parts (local@domain)
- • Parsing log entries (IP, timestamp, status)
- • Getting specific CSV/JSON fields
- • URL parsing (protocol, host, path)
Avoid When
- • You need the full AST anyway
- • Captures would duplicate AST info
- • Parsing deeply nested structures (use scopes)
Performance Overhead
~5%
Scopes
Isolated Capture Contexts
Best For
- • INI/TOML config parsing (per-section isolation)
- • Recursive descent parsing
- • Nested language constructs
- • Preventing capture name collisions
Avoid When
- • You need inner captures to persist
- • No nested structures in your grammar
- • Excessive nesting (performance impact)
Performance Overhead
~2% per level
Dynamic
Runtime Grammar Selection
Best For
- • Type-driven parsing (int vs str vs bool)
- • Language keyword detection
- • Heredoc delimiter matching
- • Configuration-driven grammars
- • Plugin/extensible parsers
Avoid When
- • Pattern is static (use regular atoms)
- • Callback does I/O (performance killer)
- • Using Bytecode/Streaming with many dynamic atoms
Performance Overhead (Packrat)
~5-20%
Streaming
Memory-Bounded Parsing
Best For
- • Files larger than available RAM
- • Network streams / real-time feeds
- • Log file analysis (10GB+)
- • Database dump processing
Avoid When
- • Input fits in memory (simpler to use regular parser)
- • Heavy backtracking needed (large window required)
- • Many dynamic atoms (fallback overhead)
Memory Usage
chunk_size × window_size
Backend Selection Guide
| Scenario | Recommended Backend | Reason |
|---|---|---|
| General purpose parsing | Packrat | Best feature support, O(n) complexity |
| Heavy dynamic atom usage | Packrat | Native support, no fallback overhead |
| Large file processing | Streaming | Bounded memory, chunk-based |
| Network / real-time feeds | Streaming | Process as data arrives |
| Grammar serialization | Bytecode | Compiled grammar, fast startup |
| Editor integration | Incremental | Cache invalidation, re-parse only changes |
Feature Compatibility Matrix
| Feature | Packrat | Bytecode | Streaming | Incremental |
|---|---|---|---|---|
| Capture Atoms | ✓ Full | ✓ Full | ✓ Full | ✓ Full |
| Scope Atoms | ✓ Full | ✓ Full | ✓ Full | ✓ Full |
| Dynamic Atoms | ✓ Full | ~ Fallback | ~ Fallback | ~ Fallback |
| Streaming Parse | - | - | ✓ Native | - |
| Incremental Re-parse | - | - | - | ✓ Native |
| Transform DSL | ✓ Full | ✓ Full | ✓ Full | ✓ Full |
| Rich Error Messages | ✓ Full | ✓ Full | ✓ Full | ✓ Full |
Note: "Fallback" means the feature works but uses Packrat internally within the Bytecode/Streaming backend. This may impact performance if you have many dynamic atoms. For heavy dynamic usage, prefer the Packrat backend.
Common Patterns
Pattern: Email + URL Extraction
Extract structured data without building AST
Captures
Packrat
// Just captures, no AST construction needed
let grammar = GrammarBuilder::new()
.rule("email", capture("email", dynamic(re(r"[^@\s]+@[^@\s]+"))))
.build();
Pattern: Config File with Sections
Parse INI-style config with isolated section captures
Captures
Scopes
// Section name persists, key/values isolated per section
.rule("section", seq(vec![
capture("section", ...), // Persists
scope(seq(vec![ // Isolated
capture("key", ...),
capture("value", ...),
])),
]))
Pattern: Large Log Analysis
Process 10GB+ logs with bounded memory
Captures
Streaming
// 1MB chunks, captures persist across streaming parse
let config = ChunkConfig { chunk_size: 1024 * 1024, window_size: 2 };
let result = parser.parse_from_reader(&mut file, &mut arena)?;
if let Some(captures) = &result.capture_state {
// All IPs from 10GB file, ~2MB memory used
}
Pattern: Heredoc / Here-Document
Match delimiter at both ends of document
Captures
Dynamic
// Capture delimiter, use dynamic to match at end
.rule("heredoc", seq(vec![
dynamic(str("<<")),
capture("delimiter", dynamic(re(r"[A-Z]+"))), // EOF
// ... content ...
// Dynamic callback uses captured delimiter
dynamic(dynamic_with_id(match_delimiter)),
]))