ProvenPin Install

parsimonious PEG grammar with a NodeVisitor tree walker: working versions (tested 2026-10-18)

Define a recursive PEG arithmetic grammar, turn the parse tree into Python values with a NodeVisitor, and handle ParseError, IncompleteParseError and wrapped VisitationError.

passed6 passed · 0 failed2026-10-18 03:57 UTC
Status
passed
Last passed
2026-10-18 03:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
parsimonious@0.11.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 39095370682

The code

Exactly the files that passed. Your agent can pull them with get_recipe("parsimonious-peg-grammar") (install), or see them on GitHub.

requirements.txt
parsimonious==0.11.0
pytest==9.1.1
grammar.py
import operator

from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor

GRAMMAR = Grammar(
    r"""
    expr   = term (add_op term)*
    term   = factor (mul_op factor)*
    factor = number / paren
    paren  = "(" expr ")"
    add_op = "+" / "-"
    mul_op = "*" / "/"
    number = ~r"\d+"
    """
)

OPS = {"+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv}


class Calc(NodeVisitor):
    grammar = GRAMMAR

    def generic_visit(self, node, children):
        # Literals and wrapper nodes just pass their children through.
        return children or node

    def visit_expr(self, node, children):
        value, rest = children
        for op, term in rest:
            value = op(value, term)
        return value

    def visit_term(self, node, children):
        value, rest = children
        for op, factor in rest:
            value = op(value, factor)
        return value

    def visit_factor(self, node, children):
        return children[0]

    def visit_paren(self, node, children):
        return children[1]

    def visit_add_op(self, node, children):
        return OPS[node.text]

    def visit_mul_op(self, node, children):
        return OPS[node.text]

    def visit_number(self, node, children):
        return int(node.text)


def evaluate(text):
    return Calc().parse(text)
tests/test_grammar.py
import pytest
from parsimonious.exceptions import (IncompleteParseError, ParseError,
                                     VisitationError)

from grammar import evaluate


def test_evaluates_precedence_and_parens():
    assert evaluate("2+3*4") == 14
    assert evaluate("(2+3)*4") == 20
    assert evaluate("7/2") == 3.5


def test_subtraction_is_left_associative():
    assert evaluate("10-3-2") == 5


def test_trailing_input_raises_incomplete_parse_error():
    assert issubclass(IncompleteParseError, ParseError)
    with pytest.raises(IncompleteParseError):
        evaluate("1+2)")


def test_leading_garbage_raises_plain_parse_error():
    with pytest.raises(ParseError) as exc:
        evaluate("+1")
    assert not isinstance(exc.value, IncompleteParseError)


def test_whitespace_is_not_implicitly_skipped():
    with pytest.raises(IncompleteParseError):
        evaluate("1 + 2")


def test_visitor_exceptions_are_wrapped():
    with pytest.raises(VisitationError):
        evaluate("1/0")
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.