Parsimonious Documentation

repository·master·Indexed 23 days ago

https://github.com/erikrose/parsimonious

A fast, pure-Python PEG parser (version 0.11.0) designed to transform simplified EBNF-like grammars into Abstract Syntax Trees (ASTs). It provides tools for defining grammars via the Grammar and TokenGrammar classes, matching text using Regex and Literal expressions, and traversing parse trees with NodeVisitor. The library includes a comprehensive set of exceptions for handling parsing, visitation, and grammar definition errors.

Tokens
5.6K
Snippets
8
Records
38
Agent score
83%

What's inside parsimonious

  1. Optimize grammars by avoiding repeated expressions

    master

    To improve performance and reduce memory usage, avoid defining the same expression multiple times in your grammar. Instead, define the expression as its own rule and reference that rule wherever it is needed.

    Why this matters:

    • Caching: Parsimoniuos uses expression object identity for fast cache lookups. Referencing a single rule ensures better cache hits.
    • RAM Efficiency: Repeating expressions can lead to increased RAM usage, as the parser may cache an integer for every character in the text for every unique expression object.

    Balancing Regex vs. Rules:

    • Large Regexes: Jamming more logic into a single regex executes faster because it avoids running Python code between segments.
    • Broken-up Rules: Breaking expressions into smaller, reusable rules provides better cache performance if those pieces are used in multiple places.
  2. How NodeVisitor works to process parsed trees

    master

    Once you have parsed a string into a tree of nodes, you typically use a NodeVisitor subclass to transform or extract data from that tree.

    To implement a visitor:

    1. Subclass parsimonious.nodes.NodeVisitor.
    2. Implement visit_<rule_name>(self, node, visited_children) methods for each rule in your grammar. These methods receive the current node and the results returned by the visitor for its children.
    3. Implement generic_visit(self, node, visited_children) to define default behavior (usually returning visited_children or node).
    4. Call .visit(tree) on your visitor instance.
    from parsimonious.grammar import Grammar
    from parsimonious.nodes import NodeVisitor
    
    grammar = Grammar(r"""
    expr        = (entry / emptyline)*
    entry       = section pair*
    section     = lpar word rpar ws
    pair        = key equal value ws?
    key         = word+
    value       = (word / quoted)+
    word        = ~r"[-\\w]+"
    quoted      = ~'"[^\"]+"'
    equal       = ws? "=" ws?
    lpar        = "["
    rpar        = "]"
    ws          = ~r"\\s*"
    emptyline   = ws+
    """
    )
    
    class IniVisitor(NodeVisitor):
        def visit_expr(self, node, visited_children):
            output = {}
            for child in visited_children:
                output.update(child[0])
            return output
    
        def visit_entry(self, node, visited_children):
            key, values = visited_children
            return {key: dict(values)}
    
        def visit_section(self, node, visited_children):
            _, section, *_ = visited_children
            return section.text
    
        def visit_pair(self, node, visited_children):
            key, _, value, *_ = node.children
            return key.text, value.text
    
        def generic_visit(self, node, visited_children):
            return visited_children or node
    
    # Usage
    data = "[section]\nsomekey = somevalue\n"
    tree = grammar.parse(data)
    iv = IniVisitor()
    print(iv.visit(tree))
  3. Optimize quantifiers in grammars

    master

    When using quantifiers like ? (optional) and * (zero or more), place them at the highest possible level in your grammar hierarchy.

    Reasoning: If quantifiers are placed at lower levels, the parser might succeed on empty matches, creating unnecessary nodes in your parse tree that do not represent actual content.

  4. Create a grammar and parse text

    master

    To use Parsimonious, define a grammar using the Grammar class with a string representing your PEG rules. The first rule defined is the default start symbol. You can then call .parse(text) on the grammar instance to generate an Abstract Syntax Tree (AST) of Node objects.

    from parsimonious.grammar import Grammar
    
    grammar = Grammar(
        """
        bold_text  = bold_open text bold_close
        text       = ~"[A-Z 0-9]*"i
        bold_open  = "(("
        bold_close = "))"
        """
    )
    
    # Returns a tree of Node objects
    print(grammar.parse('((bold stuff))'))
  5. Transform parse trees using NodeVisitor

    master

    To turn a parse tree into a useful representation (like an AST or a string), subclass NodeVisitor.

    How it works:

    1. Subclass NodeVisitor.
    2. Implement methods named visit_<rule_name> for each grammar rule you want to handle. The <rule_name> corresponds to the name of the expression in the grammar.
    3. Each visit_<rule_name> method receives two arguments:
      • node: The current Node being visited.
      • visited_children: A list containing the results of calling visit() on each of the node's children.
    4. Call visitor.visit(root_node) to start the depth-first traversal.

    Note: NodeVisitor does not transform the tree in place. It returns a new representation. If a method is not implemented for a specific rule, generic_visit is called, which raises a NotImplementedError by default.

  6. Use TokenGrammar for pre-lexed tokens

    master

    If you want to perform lexing as a separate pass (e.g., to handle indentation-based languages), use the TokenGrammar class. Instead of operating on raw text strings, TokenGrammar operates on sequences of pre-lexed tokens.

    Note that Regex expressions are not supported in TokenGrammar because it operates on tokens rather than individual characters.

  7. Compose grammars with Sequence and OneOf

    master

    Parsimonious provides compound expressions to build complex rules from simpler ones.

    • Sequence(*members): A concatenation operator. All members must match in order, one after another.
    • OneOf(*members): An alternation operator. The first member that matches wins. It tests members in the order they are provided.
  8. Define and use a PEG grammar with the Grammar class

    master

    The Grammar class is the primary interface for defining a language using a PEG (Parsing Expression Grammar) syntax. You can define rules using a multi-line string where each line represents a production rule.

    Key Features

    • Default Rule: When calling .parse() or .match() directly on a Grammar instance, it uses the first rule defined in the string as the entry point.
    • Rule Access: Rules can be accessed like a dictionary to start parsing from a specific rule: grammar['rule_name'].parse(text).
    • Custom Rules: You can pass additional rules as keyword arguments to the constructor. These take precedence over string-based rules in case of naming conflicts.
    • Optimizations: The Grammar class automatically performs optimizations, such as factoring repeated subexpressions to improve cache hits.
    from parsimonious.grammar import Grammar
    
    g = Grammar('''
                    polite_greeting = greeting ", my good " title
                    greeting        = "Hi" / "Hello"
                    title           = "madam" / "sir"
                    ''')
    
    # Parse using the default rule (polite_greeting)
    g.parse('Hello, my good sir')
    
    # Parse using a specific rule
    g['title'].parse('sir')
  9. Debug visitation errors with parse tree traces

    master

    If an error occurs during the tree visitation process (e.g., a VisitationException because a method is missing), Parsimoniuos provides a detailed error report. The report includes:

    1. The standard Python traceback.
    2. The specific VisitationException message.
    3. A visual representation of the parse tree at the point of failure, with a pointer (<-- *** We were here. ***) indicating exactly which node caused the error.
  10. Process parse trees using NodeVisitor

    master

    The NodeVisitor class provides an inversion-of-control framework for walking a parse tree and transforming it into a new construct (such as a string, a new tree, or another object).

    Implementation Pattern: When implementing visitor methods, you can take advantage of the fact that nodes are iterable. This allows you to use tuple unpacking in your method signatures to capture child nodes directly.

    For example, if a production is defined as or_term = "/" "_" term, your visitor method can unpack the children like this:

    def visit_or_term(self, or_term, (slash, _, term)):
        ...
  11. Grammar Syntax Reference

    master

    Parsimonious uses a PEG (Parsing Expression Grammar) syntax. Below are the available operators and notation:

    OperatorDescription
    "literal"Quoted literal string
    b"literal"Bytes literal (use for binary files)
    a b cSequence: matches a, then b, then c
    a / b / cAlternatives: matches the first successful option (priority-based)
    thing?Optional: matches thing zero or one time (greedy)
    &thingPositive lookahead: ensures thing matches without consuming text
    !thingNegative lookahead: ensures thing does NOT match without consuming text
    things*Zero or more repetitions (greedy)
    things+One or more repetitions (greedy)
    ~r"regex"Regular expression (uses regex library). Flags can follow: ~r"..."asilmx
    ~br"regex"Bytes regular expression
    (things)Grouping
    thing{n}Exactly n repetitions
    thing{n,m}Between n and m repetitions (inclusive)
    thing{,m}At most m repetitions
    thing{n,}At least n repetitions