0
# Semantic Actions
1
2
System for defining and executing semantic actions on parse results, completely separated from grammar definitions. This enables modular processing of parse trees with operations and attributes.
3
4
## Imports
5
6
```javascript
7
import { grammar } from "ohm-js";
8
// Semantics objects are created from grammar instances
9
```
10
11
For TypeScript:
12
13
```typescript
14
import {
15
grammar,
16
Grammar,
17
Semantics,
18
ActionDict,
19
BaseActionDict,
20
Action,
21
Node,
22
IterationNode,
23
NonterminalNode,
24
TerminalNode,
25
MatchResult
26
} from "ohm-js";
27
```
28
29
## Capabilities
30
31
### Semantics Interface
32
33
Core interface for creating and managing semantic actions on parse results.
34
35
```typescript { .api }
36
/**
37
* A Semantics is a family of operations and/or attributes for a given
38
* grammar. Each operation/attribute has a unique name within the
39
* Semantics. A grammar may have any number of Semantics instances
40
* associated with it.
41
*/
42
interface Semantics {
43
/**
44
* Returns a dictionary containing operations and attributes defined by
45
* this Semantics on the result of a matched grammar. Operations are
46
* no-arg functions and attributes are properties.
47
* @param match - MatchResult from successful grammar match
48
* @returns Dictionary with operations as functions and attributes as properties
49
*/
50
(match: MatchResult): Dict;
51
52
/**
53
* Add a new operation named name to this Semantics, using the
54
* semantic actions contained in actionDict. It is an error if there
55
* is already an operation or attribute called name in this semantics.
56
* @param name - Name for the operation
57
* @param actionDict - Dictionary of semantic actions by rule name
58
* @returns This Semantics for chaining
59
*/
60
addOperation<T>(name: string, actionDict: ActionDict<T>): Semantics;
61
62
/**
63
* Add a new attribute named name to this Semantics, using the
64
* semantic actions contained in actionDict. It is an error if there
65
* is already an operation or attribute called name in this semantics.
66
* @param name - Name for the attribute
67
* @param actionDict - Dictionary of semantic actions by rule name
68
* @returns This Semantics for chaining
69
*/
70
addAttribute<T>(name: string, actionDict: ActionDict<T>): Semantics;
71
72
/**
73
* Extend the operation named name with the semantic actions contained
74
* in actionDict. name must be the name of an operation in the super
75
* semantics.
76
* @param name - Name of operation to extend
77
* @param actionDict - Additional semantic actions
78
* @returns This Semantics for chaining
79
*/
80
extendOperation<T>(name: string, actionDict: ActionDict<T>): Semantics;
81
82
/**
83
* Extend the attribute named name with the semantic actions contained
84
* in actionDict. name must be the name of an attribute in the super
85
* semantics.
86
* @param name - Name of attribute to extend
87
* @param actionDict - Additional semantic actions
88
* @returns This Semantics for chaining
89
*/
90
extendAttribute<T>(name: string, actionDict: ActionDict<T>): Semantics;
91
}
92
```
93
94
**Usage Examples:**
95
96
```javascript
97
import { grammar } from "ohm-js";
98
99
const g = grammar(`
100
Arithmetic {
101
Exp = AddExp
102
AddExp = MulExp ("+" MulExp | "-" MulExp)*
103
MulExp = PriExp ("*" PriExp | "/" PriExp)*
104
PriExp = "(" Exp ")" | number
105
number = digit+
106
}
107
`);
108
109
// Create semantics with evaluation operation
110
const semantics = g.createSemantics().addOperation('eval', {
111
Exp(e) {
112
return e.eval();
113
},
114
AddExp(first, rest) {
115
return rest.children.reduce((acc, child) => {
116
const [op, operand] = child.children;
117
return op.sourceString === '+' ? acc + operand.eval() : acc - operand.eval();
118
}, first.eval());
119
},
120
MulExp(first, rest) {
121
return rest.children.reduce((acc, child) => {
122
const [op, operand] = child.children;
123
return op.sourceString === '*' ? acc * operand.eval() : acc / operand.eval();
124
}, first.eval());
125
},
126
PriExp(expr) {
127
return expr.eval();
128
},
129
number(digits) {
130
return parseInt(this.sourceString);
131
}
132
});
133
134
// Use the semantics
135
const match = g.match("2 + 3 * 4");
136
const result = semantics(match).eval(); // Returns 14
137
```
138
139
### Action Dictionary
140
141
Dictionary interface for mapping rule names to semantic actions.
142
143
```typescript { .api }
144
/**
145
* An ActionDict is a dictionary of Actions indexed by rule names.
146
*/
147
interface ActionDict<T> extends BaseActionDict<T> {
148
[index: string]: Action<T> | undefined;
149
}
150
151
/**
152
* Base ActionDict with built-in rule actions
153
*/
154
interface BaseActionDict<T> {
155
/** Default action for iteration nodes */
156
_iter?: (this: IterationNode, ...children: Node[]) => T;
157
/** Default action for nonterminal nodes */
158
_nonterminal?: (this: NonterminalNode, ...children: Node[]) => T;
159
/** Default action for terminal nodes */
160
_terminal?: (this: TerminalNode) => T;
161
162
// Built-in rules
163
alnum?: (this: NonterminalNode, arg0: NonterminalNode) => T;
164
letter?: (this: NonterminalNode, arg0: NonterminalNode) => T;
165
digit?: (this: NonterminalNode, arg0: TerminalNode) => T;
166
hexDigit?: (this: NonterminalNode, arg0: NonterminalNode | TerminalNode) => T;
167
ListOf?: (this: NonterminalNode, arg0: NonterminalNode) => T;
168
NonemptyListOf?: (this: NonterminalNode, arg0: Node, arg1: IterationNode, arg2: IterationNode) => T;
169
EmptyListOf?: (this: NonterminalNode) => T;
170
listOf?: (this: NonterminalNode, arg0: NonterminalNode) => T;
171
nonemptyListOf?: (this: NonterminalNode, arg0: Node, arg1: IterationNode, arg2: IterationNode) => T;
172
emptyListOf?: (this: NonterminalNode) => T;
173
applySyntactic?: (this: NonterminalNode, arg0: Node) => T;
174
}
175
176
/**
177
* An Action is a function from ParseNodes, called with the children nodes
178
* of the node it is being executed on.
179
* The current node is passed as a dynamic this, requiring an ES5
180
* anonymous function with this typed as any.
181
*/
182
type Action<T> = (this: Node, ...args: Node[]) => T;
183
```
184
185
**Usage Examples:**
186
187
```javascript
188
// Actions can access the current node via 'this'
189
const semantics = grammar.createSemantics().addOperation('stringify', {
190
_terminal() {
191
return this.sourceString; // Access source text
192
},
193
_nonterminal(...children) {
194
return children.map(child => child.stringify()).join('');
195
},
196
number(digits) {
197
return `NUM(${this.sourceString})`;
198
}
199
});
200
```
201
202
### Parse Tree Nodes
203
204
Node interfaces representing different types of nodes in the parse tree.
205
206
```typescript { .api }
207
/**
208
* A node in the parse tree, passed to Action functions
209
*/
210
interface Node {
211
/** Returns the child at index idx */
212
child(idx: number): Node;
213
/** true if the node is a terminal node, otherwise false */
214
isTerminal(): boolean;
215
/** true if the node is an iteration node (*, +, ?) */
216
isIteration(): boolean;
217
/** True if Node is ? option */
218
isOptional(): boolean;
219
/** Convert certain NonterminalNodes into IterationNodes */
220
asIteration(): IterationNode;
221
222
/** Array containing the node's children */
223
children: Node[];
224
/** The name of grammar rule that created the node */
225
ctorName: string;
226
/** Captures the portion of the input consumed by the node */
227
source: Interval;
228
/** Returns the contents of the input stream consumed by this node */
229
sourceString: string;
230
/** The number of child nodes that the node has */
231
numChildren: number;
232
233
/**
234
* In addition to the properties defined above, within a given
235
* semantics, every node also has a method/property corresponding to
236
* each operation/attribute in the semantics.
237
*/
238
[index: string]: any;
239
}
240
241
interface IterationNode extends Node {}
242
interface NonterminalNode extends Node {}
243
interface TerminalNode extends Node {}
244
```
245
246
**Usage Examples:**
247
248
```javascript
249
const semantics = grammar.createSemantics().addOperation('analyze', {
250
number(digits) {
251
console.log('Rule name:', this.ctorName); // 'number'
252
console.log('Source text:', this.sourceString); // e.g., '42'
253
console.log('Child count:', this.numChildren); // number of digits
254
console.log('Is terminal:', this.isTerminal()); // false
255
256
// Access children
257
const firstDigit = this.child(0);
258
console.log('First digit:', firstDigit.sourceString);
259
260
return parseInt(this.sourceString);
261
},
262
digit(_) {
263
console.log('Is terminal:', this.isTerminal()); // true
264
return this.sourceString;
265
}
266
});
267
```
268
269
### Operation vs Attribute
270
271
**Operations** are functions that must be called, while **attributes** are properties that are computed lazily:
272
273
```javascript
274
// Operation (function)
275
const semantics = grammar.createSemantics()
276
.addOperation('eval', {
277
number(digits) { return parseInt(this.sourceString); }
278
})
279
.addAttribute('value', {
280
number(digits) { return parseInt(this.sourceString); }
281
});
282
283
const match = grammar.match("42");
284
const result = semantics(match);
285
286
// Operations are functions
287
const evaluated = result.eval(); // Call as function
288
289
// Attributes are properties
290
const value = result.value; // Access as property
291
```
292
293
### Extending Semantics
294
295
Semantics can inherit from other semantics and extend operations:
296
297
```javascript
298
// Base semantics
299
const baseSemantics = grammar.createSemantics().addOperation('eval', {
300
number(digits) { return parseInt(this.sourceString); }
301
});
302
303
// Extended semantics
304
const extendedSemantics = grammar.extendSemantics(baseSemantics)
305
.extendOperation('eval', {
306
// Add handling for new rules or override existing ones
307
hexNumber(digits) { return parseInt(this.sourceString, 16); }
308
});
309
```
310
311
## Error Handling
312
313
Semantic actions can handle errors and provide meaningful feedback:
314
315
```javascript
316
const semantics = grammar.createSemantics().addOperation('eval', {
317
division(left, _op, right) {
318
const rightVal = right.eval();
319
if (rightVal === 0) {
320
throw new Error(`Division by zero at ${right.source.getLineAndColumnMessage()}`);
321
}
322
return left.eval() / rightVal;
323
}
324
});
325
```
326
327
## Type Definitions
328
329
```typescript { .api }
330
interface Dict {
331
[index: string]: any;
332
}
333
```