SLY - Sly Lex Yacc
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
- breaking The SLY project was officially retired by its author on December 21, 2025. No further maintenance or development is expected. Users are advised to consider other parsing libraries or fork the project for continued use.
- gotcha SLY is a modernization of the PLY project, but code written for PLY is generally not compatible with SLY. Direct migration of PLY code to SLY without modifications will likely fail.
- gotcha SLY requires Python 3.6 or newer. It is not compatible with older Python versions.
- gotcha Lexer classes *must* define a `tokens` set specifying all possible token type names. Token names should generally be in all-caps. Incorrectly defined tokens or regex patterns are a common source of parsing issues.
- gotcha When defining parser rules, the `_()` decorator is crucial. Accessing parts of a rule (e.g., `expr PLUS expr`) requires careful indexing (e.g., `p.expr0`, `p.expr1`) if a symbol appears multiple times in a rule's right-hand side, or by name (e.g., `p.NUMBER`) otherwise.
Install
-
pip install sly
Imports
- Lexer
from sly import Lexer
- Parser
from sly import Parser
Quickstart
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}")