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

semantic-actions.mddocs/

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

```