Skip to content

Commit

Permalink
feat(extras): Add stored attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
pdubroy committed Jul 31, 2023
1 parent 1a88964 commit 1050ad0
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 1 deletion.
16 changes: 15 additions & 1 deletion packages/ohm-js/extras/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {MatchResult, Grammar, Semantics} from 'ohm-js';
import {BaseActionDict, Grammar, MatchResult, Node, Semantics} from 'ohm-js';

interface LineAndColumnInfo {
offset: number;
Expand Down Expand Up @@ -44,3 +44,17 @@ interface Example {
* `//- "shouldn't match"`. The examples text is a JSON string.
*/
export function extractExamples(grammarsDef: string): [Example];

export type StoredAttributeSetter<T> = (node: Node, val: T) => T;

/**
* Add a stored attribute named `attrName` to `semantics`. A stored attribute
* is similar to a normal attribute, but instead of being lazily computed, the
* value for each node is initialized by an initialization operation.
*/
export function addStoredAttribute<T>(
semantics: Semantics,
attrName: string,
initSignature: string,
actionProducer: (setter: StoredAttributeSetter<T>) => BaseActionDict<void>
): Semantics;
1 change: 1 addition & 0 deletions packages/ohm-js/extras/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export {getLineAndColumnMessage, getLineAndColumn} from '../src/util.js';
export {VisitorFamily} from './VisitorFamily.js';
export {semanticsForToAST, toAST} from './semantics-toAST.js';
export {extractExamples} from './extractExamples.js';
export {addStoredAttribute} from './storedAttributes.js';
24 changes: 24 additions & 0 deletions packages/ohm-js/extras/storedAttributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Normal attributes are lazily computed. A stored attribute is initialized
by a separate initialization operation. After initialization, you can read
a stored attribute's value just like a normal (computed) attribute.
*/
export function addStoredAttribute(semantics, attrName, initSignature, fn) {
semantics.addAttribute(attrName, {
_default() {
throw new Error(`Attribute '${attrName}' not initialized`);
}

Check failure on line 10 in packages/ohm-js/extras/storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Missing trailing comma

Check failure on line 10 in packages/ohm-js/extras/storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Missing trailing comma

Check failure on line 10 in packages/ohm-js/extras/storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Missing trailing comma
});

// Create the attribute setter, which the semantic actions of the init
// operation will close over.
const key = semantics._getSemantics().attributeKeys[attrName];
const setAttr = (wrapper, val) => {
wrapper._node[key] = val;
return val;
};

// Create the init operation. It's the user's responsibility to call it.
semantics.addOperation(initSignature, fn(setAttr));
return semantics;
}
48 changes: 48 additions & 0 deletions packages/ohm-js/test/extras/test-storedAttributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import test from 'ava';
import fs from 'node:fs';
import {URL} from 'node:url';

import {addStoredAttribute} from '../../extras/storedAttributes.js';
import * as ohm from '../../index.mjs';

const grammarPath = new URL('../data/arithmetic.ohm', import.meta.url);
const g = ohm.grammar(fs.readFileSync(grammarPath));

test('stored attributes', t => {
const semantics = g.createSemantics();
const exp = semantics(g.match('3 + 4 - 1'));

// A meaningless stored attribute, where nodes have a "polarity" depending
// on the type of operation they are involved in. All nodes pass their
// polarity downwards, and the default action gives the child the polarity
// of its parent.
addStoredAttribute(semantics, 'polarity', 'initPolarity(pol)', setPolarity => ({
AddExp_plus(expA, _, expB) {
setPolarity(this, '+');
// Note that we explicitly skip initializing the operator.
expA.initPolarity(this.polarity);
expB.initPolarity(this.polarity);
},
AddExp_minus(expA, _, expB) {
setPolarity(this, '-');
// Note that we explicitly skip initializing the operator.
expA.initPolarity(this.polarity);
expB.initPolarity(this.polarity);
},
_default(...children) {
// By default, inherit polarity from the parent node.
setPolarity(this, this.args.pol);
children.forEach(c => c.initPolarity(this.args.pol));
}

Check failure on line 36 in packages/ohm-js/test/extras/test-storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Missing trailing comma

Check failure on line 36 in packages/ohm-js/test/extras/test-storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Missing trailing comma

Check failure on line 36 in packages/ohm-js/test/extras/test-storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Missing trailing comma
}));
exp.initPolarity('=');
t.is(exp.polarity, '='); // Exp
t.is(exp.child(0).polarity, '='); // AddExp
t.is(exp.child(0).child(0).polarity, '-'); // AddExp_minus
t.is(exp.child(0).child(0).child(0).polarity, '-'); // AddExp
t.is(exp.child(0).child(0).child(0).child(0).polarity, '+'); // AddExp_plus

t.throws(() => exp.child(0).child(0).child(1).polarity, {
message: 'Attribute \'polarity\' not initialized'

Check failure on line 46 in packages/ohm-js/test/extras/test-storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (16.x)

Missing trailing comma

Check failure on line 46 in packages/ohm-js/test/extras/test-storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (18.x)

Missing trailing comma

Check failure on line 46 in packages/ohm-js/test/extras/test-storedAttributes.js

View workflow job for this annotation

GitHub Actions / build (20.x)

Missing trailing comma
});
});

0 comments on commit 1050ad0

Please sign in to comment.