0
# Utilities and Extras
1
2
Additional utilities for error reporting, AST conversion, example extraction, and advanced tree traversal patterns available through the extras module.
3
4
## Imports
5
6
```javascript
7
import { toAST, getLineAndColumnMessage, getLineAndColumn, VisitorFamily, extractExamples, semanticsForToAST } from "ohm-js/extras";
8
import { grammar } from "ohm-js";
9
```
10
11
For TypeScript:
12
13
```typescript
14
import {
15
toAST,
16
getLineAndColumnMessage,
17
getLineAndColumn,
18
VisitorFamily,
19
extractExamples,
20
semanticsForToAST,
21
LineAndColumnInfo,
22
Example,
23
VisitorConfig
24
} from "ohm-js/extras";
25
import { grammar, Grammar, MatchResult, Semantics } from "ohm-js";
26
```
27
28
## Capabilities
29
30
### Error Reporting Utilities
31
32
Functions for creating detailed error messages with line and column information.
33
34
```typescript { .api }
35
/**
36
* Returns contextual information (line and column number, etc.) about a
37
* specific character in a string.
38
* @param str - Input string
39
* @param offset - Character position
40
* @returns Line and column information
41
*/
42
function getLineAndColumn(str: string, offset: number): LineAndColumnInfo;
43
44
/**
45
* Returns a nicely-formatted message (appropriate for syntax errors, etc.)
46
* pointing to a specific character within a string. Optionally, one or more
47
* ranges within the string can also be highlighted.
48
* @param str - Input string
49
* @param offset - Error position
50
* @param ranges - Optional ranges to highlight
51
* @returns Formatted error message
52
*/
53
function getLineAndColumnMessage(
54
str: string,
55
offset: number,
56
...ranges: number[][]
57
): string;
58
59
interface LineAndColumnInfo {
60
offset: number;
61
lineNumber: number;
62
colNum: number;
63
line: string;
64
prevLine: string;
65
nextLine: string;
66
toString(...ranges: number[][]): string;
67
}
68
```
69
70
**Usage Examples:**
71
72
```javascript
73
import { getLineAndColumn, getLineAndColumnMessage } from "ohm-js/extras";
74
75
const source = `line 1
76
line 2 with error here
77
line 3`;
78
79
// Get position information
80
const info = getLineAndColumn(source, 25); // Position of 'error'
81
console.log(`Line ${info.lineNumber}, Column ${info.colNum}`);
82
83
// Create formatted error message
84
const errorMsg = getLineAndColumnMessage(source, 25);
85
console.log(errorMsg);
86
// Output shows the error position with context and a pointer
87
```
88
89
### AST Conversion
90
91
Convert Ohm's Concrete Syntax Tree (CST) to Abstract Syntax Tree (AST) format.
92
93
```typescript { .api }
94
/**
95
* Returns a plain JavaScript object that includes an abstract syntax tree (AST)
96
* for the given match result containing a concrete syntax tree (CST) and grammar.
97
* The optional `mapping` parameter can be used to customize how the nodes of the CST
98
* are mapped to the AST.
99
* @param res - Successful MatchResult
100
* @param mapping - Optional mapping configuration
101
* @returns AST object
102
* @throws Error if MatchResult failed
103
*/
104
function toAST(res: MatchResult, mapping?: {}): {};
105
106
/**
107
* Returns a semantics containing the toAST(mapping) operation for the given grammar g.
108
* @param g - Grammar instance
109
* @returns Semantics with toAST operation
110
* @throws Error if parameter is not a Grammar
111
*/
112
function semanticsForToAST(g: Grammar): Semantics;
113
```
114
115
**Usage Examples:**
116
117
```javascript
118
import { toAST, semanticsForToAST } from "ohm-js/extras";
119
import { grammar } from "ohm-js";
120
121
const g = grammar(`
122
Arithmetic {
123
Exp = AddExp
124
AddExp = number ("+" number)*
125
number = digit+
126
}
127
`);
128
129
const match = g.match("2 + 3 + 4");
130
if (match.succeeded()) {
131
// Simple AST conversion
132
const ast = toAST(match);
133
console.log(JSON.stringify(ast, null, 2));
134
135
// Custom mapping
136
const customAst = toAST(match, {
137
number: 0, // Use first child (the digits)
138
AddExp: {
139
left: 0, // First number
140
right: 1 // Addition operations
141
}
142
});
143
144
// Alternative: create reusable semantics
145
const astSemantics = semanticsForToAST(g);
146
const result = astSemantics(match).toAST({});
147
}
148
```
149
150
### Visitor Pattern for Tree Traversal
151
152
Advanced pattern for implementing operations over tree structures with automatic traversal.
153
154
```typescript { .api }
155
/**
156
* A VisitorFamily contains a set of recursive operations that are defined over some kind of
157
* tree structure. The `config` parameter specifies how to walk the tree.
158
*/
159
class VisitorFamily {
160
/**
161
* @param config - Configuration object specifying tree traversal
162
* @param config.getTag - Function to get node type from a node
163
* @param config.shapes - Object mapping node types to traversal patterns
164
*/
165
constructor(config: VisitorConfig);
166
167
/**
168
* Wrap a tree node for use with this visitor family
169
* @param thing - Tree node to wrap
170
* @returns Wrapped node with visitor operations
171
*/
172
wrap(thing: any): any;
173
174
/**
175
* Add a new operation to this visitor family
176
* @param signature - Operation signature like "operationName(param1, param2)"
177
* @param actions - Dictionary of actions by node type
178
* @returns This VisitorFamily for chaining
179
*/
180
addOperation(signature: string, actions: {}): VisitorFamily;
181
182
/** Adapter class for wrapped nodes */
183
Adapter: any;
184
/** Dictionary of defined operations */
185
operations: {};
186
}
187
188
interface VisitorConfig {
189
getTag: (node: any) => string;
190
shapes: {[nodeType: string]: string | string[] | ((node: any, fn: any) => any[])};
191
}
192
```
193
194
**Usage Examples:**
195
196
```javascript
197
import { VisitorFamily } from "ohm-js/extras";
198
199
// Define how to traverse your tree structure
200
const visitors = new VisitorFamily({
201
getTag(node) {
202
return node.type; // How to get node type
203
},
204
shapes: {
205
// Define traversal patterns for each node type
206
'BinaryOp': ['left', 'right'], // Visit left and right properties
207
'UnaryOp': 'operand', // Visit single operand property
208
'Literal': [], // Leaf node - no children
209
'List': 'items[]' // Visit each item in items array
210
}
211
});
212
213
// Add operations
214
visitors.addOperation('evaluate(env)', {
215
BinaryOp(left, right) {
216
const leftVal = left.evaluate(this.args.env);
217
const rightVal = right.evaluate(this.args.env);
218
return this._adaptee.op === '+' ? leftVal + rightVal : leftVal - rightVal;
219
},
220
Literal() {
221
return this._adaptee.value;
222
}
223
});
224
225
// Use the visitor
226
const tree = { type: 'BinaryOp', op: '+', left: {type: 'Literal', value: 2}, right: {type: 'Literal', value: 3} };
227
const wrappedTree = visitors.wrap(tree);
228
const result = wrappedTree.evaluate({}); // Returns 5
229
```
230
231
### Example Extraction
232
233
Extract examples from grammar comments for testing and documentation.
234
235
```typescript { .api }
236
/**
237
* Given a string containing one or more grammar definitions, returns an array
238
* of examples extracted from the comments.
239
* Positive examples look like `//+ "one", "two"` and negative examples like
240
* `//- "shouldn't match"`. The examples text is a JSON string.
241
* @param grammarsDef - String containing grammar definitions with example comments
242
* @returns Array of extracted examples
243
*/
244
function extractExamples(grammarsDef: string): Example[];
245
246
interface Example {
247
grammar: string;
248
rule: string;
249
example: string;
250
shouldMatch: boolean;
251
}
252
```
253
254
**Usage Examples:**
255
256
```javascript
257
import { extractExamples } from "ohm-js/extras";
258
259
const grammarSource = `
260
Calculator {
261
number = digit+ //+ "123", "0", "999"
262
//- "abc", "", "12.34"
263
264
expr = number ("+" number)* //+ "1+2+3", "42"
265
//- "1+", "+2"
266
}
267
`;
268
269
const examples = extractExamples(grammarSource);
270
examples.forEach(ex => {
271
console.log(`${ex.grammar}.${ex.rule}: "${ex.example}" should ${ex.shouldMatch ? 'match' : 'not match'}`);
272
});
273
274
// Use examples for testing
275
examples.forEach(ex => {
276
const grammar = /* get grammar */;
277
const match = grammar.match(ex.example, ex.rule);
278
const success = match.succeeded();
279
280
if (success !== ex.shouldMatch) {
281
console.error(`Test failed for ${ex.rule}: "${ex.example}"`);
282
}
283
});
284
```
285
286
## Integration with Core Ohm
287
288
### Using Utilities with Grammar Results
289
290
```javascript
291
import { grammar } from "ohm-js";
292
import { getLineAndColumnMessage, toAST } from "ohm-js/extras";
293
294
const g = grammar(`/* your grammar */`);
295
const match = g.match(input);
296
297
if (match.failed()) {
298
// Create detailed error message
299
const errorPos = match.getInterval().startIdx;
300
const errorMsg = getLineAndColumnMessage(input, errorPos);
301
throw new Error(`Parse failed:\n${errorMsg}`);
302
} else {
303
// Convert to AST for easier processing
304
const ast = toAST(match);
305
processAST(ast);
306
}
307
```
308
309
### Combining with Semantic Actions
310
311
```javascript
312
// Use utilities within semantic actions
313
const semantics = grammar.createSemantics().addOperation('validate', {
314
rule(child) {
315
try {
316
return child.validate();
317
} catch (error) {
318
const pos = this.source.startIdx;
319
const msg = getLineAndColumnMessage(this.source.sourceString, pos);
320
throw new Error(`Validation error in ${this.ctorName}:\n${msg}\n${error.message}`);
321
}
322
}
323
});
324
```
325
326
### Advanced AST Customization
327
328
```javascript
329
// Complex AST mapping
330
const ast = toAST(match, {
331
// Direct child selection
332
ParenExpr: 1, // Select the middle child (skip parentheses)
333
334
// Custom properties
335
BinaryOp: {
336
operator: 1, // Middle child is the operator
337
left: 0, // First child
338
right: 2 // Third child
339
},
340
341
// Computed values
342
NumberLiteral: function(children) {
343
return parseFloat(this.sourceString);
344
},
345
346
// Conditional mapping
347
OptionalPart: function(children) {
348
return this.numChildren > 0 ? children[0] : null;
349
}
350
});
351
```
352
353
## Error Handling
354
355
The utilities provide comprehensive error handling:
356
357
```javascript
358
try {
359
const ast = toAST(failedMatch);
360
} catch (error) {
361
console.error("toAST() expects a successful MatchResult");
362
}
363
364
try {
365
const semantics = semanticsForToAST("not a grammar");
366
} catch (error) {
367
console.error("semanticsForToAST() expects a Grammar");
368
}
369
```