TatSu takes a grammar in a variation of EBNF as input, and outputs a memoizing PEG/Packrat parser in Python.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Build custom semantic actions to transform parse results, construct object models, and implement domain-specific processing during parsing. Semantic actions enable converting raw parse trees into meaningful data structures and executing computations during the parsing process.
Transform parse results by defining methods that correspond to grammar rule names.
class ASTSemantics:
"""
Basic AST building semantics for parse tree construction.
Provides default implementations for common AST operations:
- group(): Handle grouped expressions
- element(): Process individual elements
- sequence(): Build sequences of elements
- choice(): Select from choice alternatives
"""
def group(self, ast):
"""Handle grouped expressions, typically removing grouping parentheses."""
def element(self, ast):
"""Process individual grammar elements."""
def sequence(self, ast):
"""Build sequences of parsed elements."""
def choice(self, ast):
"""Select result from choice alternatives."""Usage example:
import tatsu
grammar = '''
expr = term ("+" term)*;
term = factor ("*" factor)*;
factor = "(" expr ")" | number;
number = /\d+/;
'''
class BasicSemantics:
def number(self, ast):
return int(ast)
def factor(self, ast):
# Handle parenthesized expressions
if len(ast) == 3: # "(" expr ")"
return ast[1] # Return the inner expression
return ast # Return the number directly
def term(self, ast):
result = ast[0]
for op, operand in ast[1]:
result *= operand
return result
def expr(self, ast):
result = ast[0]
for op, operand in ast[1]:
result += operand
return result
model = tatsu.compile(grammar)
result = model.parse("2 + 3 * 4", semantics=BasicSemantics())
print(result) # 14Automatically construct object model instances from parse results with type registration and inheritance support.
class ModelBuilderSemantics:
"""
Object model building semantics with type registration.
Features:
- Automatic object creation from grammar rules
- Type registration for custom classes
- Base type inheritance for all model objects
- Constructor parameter mapping
"""
def __init__(self, context=None, base_type=None, types=None):
"""
Initialize model builder semantics.
Parameters:
- context: parsing context object
- base_type (type, optional): Base class for generated nodes (default: Node)
- types (dict, optional): Rule name to type mappings
"""
def _register_constructor(self, rule_name, constructor):
"""Register a constructor function for a specific rule."""
def _find_existing_constructor(self, rule_name):
"""Find existing constructor for rule name."""
def _get_constructor(self, rule_name):
"""Get or create constructor for rule name."""Usage example:
import tatsu
from tatsu.semantics import ModelBuilderSemantics
from tatsu.objectmodel import Node
grammar = '''
program::Program = statement*;
statement::Statement = assignment | expression;
assignment::Assignment = identifier "=" expression;
expression::Expression = identifier | number;
identifier::Identifier = /[a-zA-Z][a-zA-Z0-9]*/;
number::Number = /\d+/;
'''
class MyNode(Node):
def __repr__(self):
return f"{self.__class__.__name__}({dict(self)})"
# Custom type mappings
class Program(MyNode): pass
class Statement(MyNode): pass
class Assignment(MyNode): pass
class Expression(MyNode): pass
class Identifier(MyNode): pass
class Number(MyNode): pass
type_map = {
'Program': Program,
'Statement': Statement,
'Assignment': Assignment,
'Expression': Expression,
'Identifier': Identifier,
'Number': Number
}
semantics = ModelBuilderSemantics(base_type=MyNode, types=type_map)
model = tatsu.compile(grammar)
result = model.parse("x = 42", semantics=semantics)
print(result) # Program containing Assignment with Identifier and NumberImplement domain-specific logic and transformations during parsing.
# Custom semantic action patterns
class CalculatorSemantics:
"""Example: Calculator with immediate evaluation."""
def number(self, ast):
"""Convert number tokens to integers."""
return int(ast)
def factor(self, ast):
"""Handle factors: numbers or parenthesized expressions."""
if isinstance(ast, list) and len(ast) == 3:
return ast[1] # Return content of parentheses
return ast
def term(self, ast):
"""Handle multiplication and division."""
result = ast[0]
for operator, operand in ast[1]:
if operator == '*':
result *= operand
elif operator == '/':
result /= operand
return result
def expr(self, ast):
"""Handle addition and subtraction."""
result = ast[0]
for operator, operand in ast[1]:
if operator == '+':
result += operand
elif operator == '-':
result -= operand
return resultHandle errors and validation within semantic actions.
from tatsu.exceptions import FailedSemantics
class ValidatingSemantics:
"""Semantic actions with validation and error handling."""
def number(self, ast):
try:
value = int(ast)
if value < 0:
raise FailedSemantics(f"Negative numbers not allowed: {value}")
return value
except ValueError as e:
raise FailedSemantics(f"Invalid number format: {ast}") from e
def division(self, ast):
left, operator, right = ast
if operator == '/' and right == 0:
raise FailedSemantics("Division by zero is not allowed")
return left / right
def variable_ref(self, ast):
var_name = str(ast)
if var_name not in self.variables:
raise FailedSemantics(f"Undefined variable: {var_name}")
return self.variables[var_name]
def __init__(self):
self.variables = {}Access parsing context and maintain state across semantic actions.
class ContextAwareSemantics:
"""Semantic actions with context and state management."""
def __init__(self):
self.symbol_table = {}
self.scope_stack = [{}]
self.current_scope = self.scope_stack[-1]
def enter_scope(self, ast):
"""Enter a new lexical scope."""
new_scope = {}
self.scope_stack.append(new_scope)
self.current_scope = new_scope
return ast
def exit_scope(self, ast):
"""Exit current lexical scope."""
if len(self.scope_stack) > 1:
self.scope_stack.pop()
self.current_scope = self.scope_stack[-1]
return ast
def variable_declaration(self, ast):
"""Handle variable declarations."""
var_name, _, value = ast
if var_name in self.current_scope:
raise FailedSemantics(f"Variable '{var_name}' already declared in current scope")
self.current_scope[var_name] = value
return ast
def variable_reference(self, ast):
"""Handle variable references with scope resolution."""
var_name = str(ast)
# Search scope stack from innermost to outermost
for scope in reversed(self.scope_stack):
if var_name in scope:
return scope[var_name]
raise FailedSemantics(f"Undefined variable: {var_name}")class AdvancedSemantics:
"""Advanced semantic action patterns."""
def __init__(self):
self.type_checker = TypeChecker()
self.optimizer = ASTOptimizer()
def typed_expression(self, ast):
"""Type checking during parsing."""
expr_type = self.type_checker.infer_type(ast)
return TypedExpression(ast, expr_type)
def optimized_expression(self, ast):
"""AST optimization during parsing."""
return self.optimizer.optimize(ast)
def macro_expansion(self, ast):
"""Macro expansion during parsing."""
if self.is_macro_call(ast):
return self.expand_macro(ast)
return ast
def code_generation(self, ast):
"""Direct code generation from AST."""
return self.code_generator.generate(ast)# Combine different semantic approaches
class HybridSemantics(ModelBuilderSemantics):
"""Combine model building with custom transformations."""
def __init__(self):
super().__init__(base_type=MyNode)
self.evaluator = ExpressionEvaluator()
def constant_expression(self, ast):
"""Evaluate constants at parse time."""
if self.is_constant(ast):
return ConstantNode(value=self.evaluator.evaluate(ast))
return super().constant_expression(ast)class CompositeSemantics:
"""Compose multiple semantic action objects."""
def __init__(self, *semantics_objects):
self.semantics = semantics_objects
def __getattr__(self, name):
"""Delegate to first semantics object that has the method."""
for sem in self.semantics:
if hasattr(sem, name):
return getattr(sem, name)
raise AttributeError(f"No semantic action found for: {name}")
# Usage
combined = CompositeSemantics(
CalculatorSemantics(),
ValidatingSemantics(),
ModelBuilderSemantics()
)class OptimizedSemantics:
"""Performance-optimized semantic actions."""
def __init__(self):
# Pre-compile regex patterns
self.number_pattern = re.compile(r'\d+')
# Cache frequently used objects
self.node_cache = {}
def number(self, ast):
"""Optimized number parsing with caching."""
if ast in self.node_cache:
return self.node_cache[ast]
result = int(ast)
if len(self.node_cache) < 1000: # Limit cache size
self.node_cache[ast] = result
return result
def list_expression(self, ast):
"""Efficient list building."""
if not ast[1]: # Empty list case
return []
# Use list comprehension for better performance
return [ast[0]] + [item for _, item in ast[1]]Install with Tessl CLI
npx tessl i tessl/pypi-tatsu