0
# Semantic Actions
1
2
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.
3
4
## Capabilities
5
6
### Basic Semantic Actions
7
8
Transform parse results by defining methods that correspond to grammar rule names.
9
10
```python { .api }
11
class ASTSemantics:
12
"""
13
Basic AST building semantics for parse tree construction.
14
15
Provides default implementations for common AST operations:
16
- group(): Handle grouped expressions
17
- element(): Process individual elements
18
- sequence(): Build sequences of elements
19
- choice(): Select from choice alternatives
20
"""
21
22
def group(self, ast):
23
"""Handle grouped expressions, typically removing grouping parentheses."""
24
25
def element(self, ast):
26
"""Process individual grammar elements."""
27
28
def sequence(self, ast):
29
"""Build sequences of parsed elements."""
30
31
def choice(self, ast):
32
"""Select result from choice alternatives."""
33
```
34
35
Usage example:
36
37
```python
38
import tatsu
39
40
grammar = '''
41
expr = term ("+" term)*;
42
term = factor ("*" factor)*;
43
factor = "(" expr ")" | number;
44
number = /\d+/;
45
'''
46
47
class BasicSemantics:
48
def number(self, ast):
49
return int(ast)
50
51
def factor(self, ast):
52
# Handle parenthesized expressions
53
if len(ast) == 3: # "(" expr ")"
54
return ast[1] # Return the inner expression
55
return ast # Return the number directly
56
57
def term(self, ast):
58
result = ast[0]
59
for op, operand in ast[1]:
60
result *= operand
61
return result
62
63
def expr(self, ast):
64
result = ast[0]
65
for op, operand in ast[1]:
66
result += operand
67
return result
68
69
model = tatsu.compile(grammar)
70
result = model.parse("2 + 3 * 4", semantics=BasicSemantics())
71
print(result) # 14
72
```
73
74
### Model Builder Semantics
75
76
Automatically construct object model instances from parse results with type registration and inheritance support.
77
78
```python { .api }
79
class ModelBuilderSemantics:
80
"""
81
Object model building semantics with type registration.
82
83
Features:
84
- Automatic object creation from grammar rules
85
- Type registration for custom classes
86
- Base type inheritance for all model objects
87
- Constructor parameter mapping
88
"""
89
90
def __init__(self, context=None, base_type=None, types=None):
91
"""
92
Initialize model builder semantics.
93
94
Parameters:
95
- context: parsing context object
96
- base_type (type, optional): Base class for generated nodes (default: Node)
97
- types (dict, optional): Rule name to type mappings
98
"""
99
100
def _register_constructor(self, rule_name, constructor):
101
"""Register a constructor function for a specific rule."""
102
103
def _find_existing_constructor(self, rule_name):
104
"""Find existing constructor for rule name."""
105
106
def _get_constructor(self, rule_name):
107
"""Get or create constructor for rule name."""
108
```
109
110
Usage example:
111
112
```python
113
import tatsu
114
from tatsu.semantics import ModelBuilderSemantics
115
from tatsu.objectmodel import Node
116
117
grammar = '''
118
program::Program = statement*;
119
statement::Statement = assignment | expression;
120
assignment::Assignment = identifier "=" expression;
121
expression::Expression = identifier | number;
122
identifier::Identifier = /[a-zA-Z][a-zA-Z0-9]*/;
123
number::Number = /\d+/;
124
'''
125
126
class MyNode(Node):
127
def __repr__(self):
128
return f"{self.__class__.__name__}({dict(self)})"
129
130
# Custom type mappings
131
class Program(MyNode): pass
132
class Statement(MyNode): pass
133
class Assignment(MyNode): pass
134
class Expression(MyNode): pass
135
class Identifier(MyNode): pass
136
class Number(MyNode): pass
137
138
type_map = {
139
'Program': Program,
140
'Statement': Statement,
141
'Assignment': Assignment,
142
'Expression': Expression,
143
'Identifier': Identifier,
144
'Number': Number
145
}
146
147
semantics = ModelBuilderSemantics(base_type=MyNode, types=type_map)
148
model = tatsu.compile(grammar)
149
result = model.parse("x = 42", semantics=semantics)
150
print(result) # Program containing Assignment with Identifier and Number
151
```
152
153
### Custom Semantic Actions
154
155
Implement domain-specific logic and transformations during parsing.
156
157
```python { .api }
158
# Custom semantic action patterns
159
160
class CalculatorSemantics:
161
"""Example: Calculator with immediate evaluation."""
162
163
def number(self, ast):
164
"""Convert number tokens to integers."""
165
return int(ast)
166
167
def factor(self, ast):
168
"""Handle factors: numbers or parenthesized expressions."""
169
if isinstance(ast, list) and len(ast) == 3:
170
return ast[1] # Return content of parentheses
171
return ast
172
173
def term(self, ast):
174
"""Handle multiplication and division."""
175
result = ast[0]
176
for operator, operand in ast[1]:
177
if operator == '*':
178
result *= operand
179
elif operator == '/':
180
result /= operand
181
return result
182
183
def expr(self, ast):
184
"""Handle addition and subtraction."""
185
result = ast[0]
186
for operator, operand in ast[1]:
187
if operator == '+':
188
result += operand
189
elif operator == '-':
190
result -= operand
191
return result
192
```
193
194
### Semantic Action Error Handling
195
196
Handle errors and validation within semantic actions.
197
198
```python { .api }
199
from tatsu.exceptions import FailedSemantics
200
201
class ValidatingSemantics:
202
"""Semantic actions with validation and error handling."""
203
204
def number(self, ast):
205
try:
206
value = int(ast)
207
if value < 0:
208
raise FailedSemantics(f"Negative numbers not allowed: {value}")
209
return value
210
except ValueError as e:
211
raise FailedSemantics(f"Invalid number format: {ast}") from e
212
213
def division(self, ast):
214
left, operator, right = ast
215
if operator == '/' and right == 0:
216
raise FailedSemantics("Division by zero is not allowed")
217
return left / right
218
219
def variable_ref(self, ast):
220
var_name = str(ast)
221
if var_name not in self.variables:
222
raise FailedSemantics(f"Undefined variable: {var_name}")
223
return self.variables[var_name]
224
225
def __init__(self):
226
self.variables = {}
227
```
228
229
### Context-Aware Semantic Actions
230
231
Access parsing context and maintain state across semantic actions.
232
233
```python { .api }
234
class ContextAwareSemantics:
235
"""Semantic actions with context and state management."""
236
237
def __init__(self):
238
self.symbol_table = {}
239
self.scope_stack = [{}]
240
self.current_scope = self.scope_stack[-1]
241
242
def enter_scope(self, ast):
243
"""Enter a new lexical scope."""
244
new_scope = {}
245
self.scope_stack.append(new_scope)
246
self.current_scope = new_scope
247
return ast
248
249
def exit_scope(self, ast):
250
"""Exit current lexical scope."""
251
if len(self.scope_stack) > 1:
252
self.scope_stack.pop()
253
self.current_scope = self.scope_stack[-1]
254
return ast
255
256
def variable_declaration(self, ast):
257
"""Handle variable declarations."""
258
var_name, _, value = ast
259
if var_name in self.current_scope:
260
raise FailedSemantics(f"Variable '{var_name}' already declared in current scope")
261
self.current_scope[var_name] = value
262
return ast
263
264
def variable_reference(self, ast):
265
"""Handle variable references with scope resolution."""
266
var_name = str(ast)
267
268
# Search scope stack from innermost to outermost
269
for scope in reversed(self.scope_stack):
270
if var_name in scope:
271
return scope[var_name]
272
273
raise FailedSemantics(f"Undefined variable: {var_name}")
274
```
275
276
### Advanced Semantic Patterns
277
278
```python { .api }
279
class AdvancedSemantics:
280
"""Advanced semantic action patterns."""
281
282
def __init__(self):
283
self.type_checker = TypeChecker()
284
self.optimizer = ASTOptimizer()
285
286
def typed_expression(self, ast):
287
"""Type checking during parsing."""
288
expr_type = self.type_checker.infer_type(ast)
289
return TypedExpression(ast, expr_type)
290
291
def optimized_expression(self, ast):
292
"""AST optimization during parsing."""
293
return self.optimizer.optimize(ast)
294
295
def macro_expansion(self, ast):
296
"""Macro expansion during parsing."""
297
if self.is_macro_call(ast):
298
return self.expand_macro(ast)
299
return ast
300
301
def code_generation(self, ast):
302
"""Direct code generation from AST."""
303
return self.code_generator.generate(ast)
304
```
305
306
## Integration Patterns
307
308
### Using Multiple Semantic Approaches
309
310
```python
311
# Combine different semantic approaches
312
class HybridSemantics(ModelBuilderSemantics):
313
"""Combine model building with custom transformations."""
314
315
def __init__(self):
316
super().__init__(base_type=MyNode)
317
self.evaluator = ExpressionEvaluator()
318
319
def constant_expression(self, ast):
320
"""Evaluate constants at parse time."""
321
if self.is_constant(ast):
322
return ConstantNode(value=self.evaluator.evaluate(ast))
323
return super().constant_expression(ast)
324
```
325
326
### Semantic Action Composition
327
328
```python
329
class CompositeSemantics:
330
"""Compose multiple semantic action objects."""
331
332
def __init__(self, *semantics_objects):
333
self.semantics = semantics_objects
334
335
def __getattr__(self, name):
336
"""Delegate to first semantics object that has the method."""
337
for sem in self.semantics:
338
if hasattr(sem, name):
339
return getattr(sem, name)
340
raise AttributeError(f"No semantic action found for: {name}")
341
342
# Usage
343
combined = CompositeSemantics(
344
CalculatorSemantics(),
345
ValidatingSemantics(),
346
ModelBuilderSemantics()
347
)
348
```
349
350
### Performance Considerations
351
352
```python
353
class OptimizedSemantics:
354
"""Performance-optimized semantic actions."""
355
356
def __init__(self):
357
# Pre-compile regex patterns
358
self.number_pattern = re.compile(r'\d+')
359
# Cache frequently used objects
360
self.node_cache = {}
361
362
def number(self, ast):
363
"""Optimized number parsing with caching."""
364
if ast in self.node_cache:
365
return self.node_cache[ast]
366
367
result = int(ast)
368
if len(self.node_cache) < 1000: # Limit cache size
369
self.node_cache[ast] = result
370
return result
371
372
def list_expression(self, ast):
373
"""Efficient list building."""
374
if not ast[1]: # Empty list case
375
return []
376
377
# Use list comprehension for better performance
378
return [ast[0]] + [item for _, item in ast[1]]
379
```