Parsanol
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)),
]))