or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

grammar-management.mdindex.mdparsing-and-matching.mdparsing-expressions.mdsemantic-actions.mdutilities-and-extras.md

utilities-and-extras.mddocs/

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

```