CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-tatsu

TatSu takes a grammar in a variation of EBNF as input, and outputs a memoizing PEG/Packrat parser in Python.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

semantic-actions.mddocs/

Semantic Actions

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.

Capabilities

Basic Semantic Actions

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)  # 14

Model Builder Semantics

Automatically 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 Number

Custom Semantic Actions

Implement 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 result

Semantic Action Error Handling

Handle 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 = {}

Context-Aware Semantic Actions

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}")

Advanced Semantic Patterns

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)

Integration Patterns

Using Multiple Semantic Approaches

# 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)

Semantic Action Composition

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()
)

Performance Considerations

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

docs

ast-models.md

code-generation.md

configuration.md

core-parsing.md

exceptions.md

index.md

semantic-actions.md

tree-walking.md

tile.json