Parser generator · Python 3.13+

Write the grammar.
Get the parser.

A small Python parser generator with no dependencies. Give it a grammar, and it builds LR0, SLR or LR1 parsing tables, and parsers that compute values on every reduction, with your callbacks or the grammar's primitives.

syntactes is a parser generator written in Python. The name comes from the Greek συντάκτης (/sin'daktis/), meaning editor or composer.

  • Grammars as text or as objects. Write one lhs -> symbols rule per line, or build the Tokens and Rules yourself. See Grammars.
  • Three kinds of tables. LR0, SLR and LR1 generators, with a readable printout and a list of conflicts. See Parsing tables.
  • Callbacks on every reduction. Each rule's callback gets one token per right-hand side symbol, and its return value becomes the value of the left-hand side. See Parsing.
  • Primitives in the grammar. Tag a rule with a primitive, like add(1,3) % expr -> expr PLUS expr or int % term -> NUMBER, and the parser computes its value without a callback. See Primitives.
  • No dependencies. It uses only the standard library.

Here's a calculator in a few lines:

from syntactes import Grammar, Token
from syntactes.parser import LR1Parser

grammar = Grammar.from_text("""
expr -> expr PLUS term
expr -> term
term -> term TIMES NUMBER
term -> NUMBER
""")
start, add, pass_term, multiply, number = grammar.rules

parser = LR1Parser.from_grammar(grammar)


@parser.execute_on(start)
def result(expr):
    return expr.value


@parser.execute_on(add)
def on_add(expr, plus, term):
    return expr.value + term.value


@parser.execute_on(pass_term)
def on_term(term):
    return term.value


@parser.execute_on(multiply)
def on_multiply(term, times, number):
    return term.value * number.value


@parser.execute_on(number)
def on_number(number):
    return number.value


def lex(text):
    for word in text.split():
        if word == "+":
            yield Token("PLUS", True)
        elif word == "*":
            yield Token("TIMES", True)
        else:
            yield Token("NUMBER", True, int(word))
    yield Token.eof()


print(parser.parse(lex("2 * 3 + 4")))  # 10

Need a scanner to produce the tokens? The sibling project lectes scans text with named regexes, and its tokens map onto syntactes Tokens.