SLY - Sly Lex Yacc

0.5 · abandoned · verified Fri Apr 10

SLY is a 100% Python implementation of the lex and yacc tools, loosely based on the traditional compiler construction tools lex and yacc, implementing the LALR(1) parsing algorithm. It provides a bare-bones, yet fully capable, library for writing parsers in Python. The current version is 0.5. As of December 21, 2025, the project has been officially retired by its author, and no further maintenance is expected.

Warnings

Install

Imports

Quickstart

This quickstart demonstrates a simple calculator using SLY. It defines a lexer to tokenize input (numbers, IDs, operators) and a parser to build an abstract syntax tree and evaluate expressions, including variable assignments.

from sly import Lexer, Parser

class CalcLexer(Lexer):
    tokens = { NUMBER, ID, PLUS, MINUS, TIMES, DIVIDE, ASSIGN, LPAREN, RPAREN }
    literals = { '=', '+', '-', '*', '/', '(', ')' }

    # String containing ignored characters
    ignore = ' \t'

    # Regular expression rules for tokens
    NUMBER = r'\d+'
    ID = r'[a-zA-Z_][a-zA-Z0-9_]*'

    # Special rules for tokens
    def NUMBER(self, t):
        t.value = int(t.value)
        return t

    def ID(self, t):
        t.value = str(t.value)
        return t

    def error(self, t):
        print(f"Illegal character '{t.value[0]}' at line {t.lineno}")
        self.index += 1

class CalcParser(Parser):
    tokens = CalcLexer.tokens

    precedence = (
        ('left', PLUS, MINUS),
        ('left', TIMES, DIVIDE)
    )

    def __init__(self):
        self.names = { }

    @_('ID ASSIGN expr')
    def statement(self, p):
        self.names[p.ID] = p.expr
        return p.expr

    @_('expr')
    def statement(self, p):
        return p.expr

    @_('expr PLUS expr')
    def expr(self, p):
        return p.expr0 + p.expr1

    @_('expr MINUS expr')
    def expr(self, p):
        return p.expr0 - p.expr1

    @_('expr TIMES expr')
    def expr(self, p):
        return p.expr0 * p.expr1

    @_('expr DIVIDE expr')
    def expr(self, p):
        return p.expr0 / p.expr1

    @_('LPAREN expr RPAREN')
    def expr(self, p):
        return p.expr

    @_('NUMBER')
    def expr(self, p):
        return p.NUMBER

    @_('ID')
    def expr(self, p):
        try:
            return self.names[p.ID]
        except LookupError:
            print(f"Undefined name '{p.ID}'")
            return 0

    def error(self, p):
        if p:
            print(f"Syntax error at token {p.type}, value '{p.value}'")
        else:
            print("Syntax error at EOF")

if __name__ == '__main__':
    lexer = CalcLexer()
    parser = CalcParser()
    while True:
        try:
            text = input('calc > ')
            if text.lower() == 'quit':
                break
            result = parser.parse(lexer.tokenize(text))
            if result is not None: # Only print if there was a calculable result
                print(result)
        except EOFError:
            break
        except Exception as e:
            print(f"Error: {e}")

view raw JSON →