import {InputStream} from './InputStream.js';import {IterationNode} from './nodes.js';import {MatchResult} from './MatchResult.js';import * as common from './common.js';import * as errors from './errors.js';import * as util from './util.js';
const globalActionStack = [];
const hasOwnProperty = (x, prop) => Object.prototype.hasOwnProperty.call(x, prop);
class Wrapper { constructor(node, sourceInterval, baseInterval) { this._node = node; this.source = sourceInterval;
this._baseInterval = baseInterval;
if (node.isNonterminal()) { common.assert(sourceInterval === baseInterval); } this._childWrappers = []; }
_forgetMemoizedResultFor(attributeName) { delete this._node[this._semantics.attributeKeys[attributeName]]; this.children.forEach(child => { child._forgetMemoizedResultFor(attributeName); }); }
child(idx) { if (!(0 <= idx && idx < this._node.numChildren())) { return undefined; } let childWrapper = this._childWrappers[idx]; if (!childWrapper) { const childNode = this._node.childAt(idx); const offset = this._node.childOffsets[idx];
const source = this._baseInterval.subInterval(offset, childNode.matchLength); const base = childNode.isNonterminal() ? source : this._baseInterval; childWrapper = this._childWrappers[idx] = this._semantics.wrap(childNode, source, base); } return childWrapper; }
_children() { for (let idx = 0; idx < this._node.numChildren(); idx++) { this.child(idx); } return this._childWrappers; }
isIteration() { return this._node.isIteration(); }
isTerminal() { return this._node.isTerminal(); }
isNonterminal() { return this._node.isNonterminal(); }
isSyntactic() { return this.isNonterminal() && this._node.isSyntactic(); }
isLexical() { return this.isNonterminal() && this._node.isLexical(); }
isOptional() { return this._node.isOptional(); }
iteration(optChildWrappers) { const childWrappers = optChildWrappers || [];
const childNodes = childWrappers.map(c => c._node); const iter = new IterationNode(childNodes, [], -1, false);
const wrapper = this._semantics.wrap(iter, null, null); wrapper._childWrappers = childWrappers; return wrapper; }
get children() { return this._children(); }
get ctorName() { return this._node.ctorName; }
get numChildren() { return this._node.numChildren(); }
get sourceString() { return this.source.contents; }}
export class Semantics { constructor(grammar, superSemantics) { const self = this; this.grammar = grammar; this.checkedActionDicts = false;
this.Wrapper = class extends (superSemantics ? superSemantics.Wrapper : Wrapper) { constructor(node, sourceInterval, baseInterval) { super(node, sourceInterval, baseInterval); self.checkActionDictsIfHaventAlready(); this._semantics = self; }
toString() { return '[semantics wrapper for ' + self.grammar.name + ']'; } };
this.super = superSemantics; if (superSemantics) { if (!(grammar.equals(this.super.grammar) || grammar._inheritsFrom(this.super.grammar))) { throw new Error( "Cannot extend a semantics for grammar '" + this.super.grammar.name + "' for use with grammar '" + grammar.name + "' (not a sub-grammar)", ); } this.operations = Object.create(this.super.operations); this.attributes = Object.create(this.super.attributes); this.attributeKeys = Object.create(null);
for (const attributeName in this.attributes) { Object.defineProperty(this.attributeKeys, attributeName, { value: util.uniqueId(attributeName), }); } } else { this.operations = Object.create(null); this.attributes = Object.create(null); this.attributeKeys = Object.create(null); } }
toString() { return '[semantics for ' + this.grammar.name + ']'; }
checkActionDictsIfHaventAlready() { if (!this.checkedActionDicts) { this.checkActionDicts(); this.checkedActionDicts = true; } }
checkActionDicts() { let name; for (name in this.operations) { this.operations[name].checkActionDict(this.grammar); } for (name in this.attributes) { this.attributes[name].checkActionDict(this.grammar); } }
toRecipe(semanticsOnly) { function hasSuperSemantics(s) { return s.super !== Semantics.BuiltInSemantics._getSemantics(); }
let str = '(function(g) {\n'; if (hasSuperSemantics(this)) { str += ' var semantics = ' + this.super.toRecipe(true) + '(g';
const superSemanticsGrammar = this.super.grammar; let relatedGrammar = this.grammar; while (relatedGrammar !== superSemanticsGrammar) { str += '.superGrammar'; relatedGrammar = relatedGrammar.superGrammar; }
str += ');\n'; str += ' return g.extendSemantics(semantics)'; } else { str += ' return g.createSemantics()'; } ['Operation', 'Attribute'].forEach(type => { const semanticOperations = this[type.toLowerCase() + 's']; Object.keys(semanticOperations).forEach(name => { const {actionDict, formals, builtInDefault} = semanticOperations[name];
let signature = name; if (formals.length > 0) { signature += '(' + formals.join(', ') + ')'; }
let method; if (hasSuperSemantics(this) && this.super[type.toLowerCase() + 's'][name]) { method = 'extend' + type; } else { method = 'add' + type; } str += '\n .' + method + '(' + JSON.stringify(signature) + ', {';
const srcArray = []; Object.keys(actionDict).forEach(actionName => { if (actionDict[actionName] !== builtInDefault) { let source = actionDict[actionName].toString().trim();
source = source.replace(/^.*\(/, 'function(');
srcArray.push('\n ' + JSON.stringify(actionName) + ': ' + source); } }); str += srcArray.join(',') + '\n })'; }); }); str += ';\n })';
if (!semanticsOnly) { str = '(function() {\n' + ' var grammar = this.fromRecipe(' + this.grammar.toRecipe() + ');\n' + ' var semantics = ' + str + '(grammar);\n' + ' return semantics;\n' + '});\n'; }
return str; }
addOperationOrAttribute(type, signature, actionDict) { const typePlural = type + 's';
const parsedNameAndFormalArgs = parseSignature(signature, type); const {name} = parsedNameAndFormalArgs; const {formals} = parsedNameAndFormalArgs;
this.assertNewName(name, type);
const builtInDefault = newDefaultAction(type, name, doIt); const realActionDict = {_default: builtInDefault}; Object.keys(actionDict).forEach(name => { realActionDict[name] = actionDict[name]; });
const entry = type === 'operation' ? new Operation(name, formals, realActionDict, builtInDefault) : new Attribute(name, realActionDict, builtInDefault);
entry.checkActionDict(this.grammar);
this[typePlural][name] = entry;
function doIt(...args) { const thisThing = this._semantics[typePlural][name];
if (arguments.length !== thisThing.formals.length) { throw new Error( 'Invalid number of arguments passed to ' + name + ' ' + type + ' (expected ' + thisThing.formals.length + ', got ' + arguments.length + ')', ); }
const argsObj = Object.create(null); for (const [idx, val] of Object.entries(args)) { const formal = thisThing.formals[idx]; argsObj[formal] = val; }
const oldArgs = this.args; this.args = argsObj; const ans = thisThing.execute(this._semantics, this); this.args = oldArgs; return ans; }
if (type === 'operation') { this.Wrapper.prototype[name] = doIt; this.Wrapper.prototype[name].toString = function() { return '[' + name + ' operation]'; }; } else { Object.defineProperty(this.Wrapper.prototype, name, { get: doIt, configurable: true, }); Object.defineProperty(this.attributeKeys, name, { value: util.uniqueId(name), }); } }
extendOperationOrAttribute(type, name, actionDict) { const typePlural = type + 's';
parseSignature(name, 'attribute');
if (!(this.super && name in this.super[typePlural])) { throw new Error( 'Cannot extend ' + type + " '" + name + "': did not inherit an " + type + ' with that name', ); } if (hasOwnProperty(this[typePlural], name)) { throw new Error('Cannot extend ' + type + " '" + name + "' again"); }
const inheritedFormals = this[typePlural][name].formals; const inheritedActionDict = this[typePlural][name].actionDict; const newActionDict = Object.create(inheritedActionDict); Object.keys(actionDict).forEach(name => { newActionDict[name] = actionDict[name]; });
this[typePlural][name] = type === 'operation' ? new Operation(name, inheritedFormals, newActionDict) : new Attribute(name, newActionDict);
this[typePlural][name].checkActionDict(this.grammar); }
assertNewName(name, type) { if (hasOwnProperty(Wrapper.prototype, name)) { throw new Error('Cannot add ' + type + " '" + name + "': that's a reserved name"); } if (name in this.operations) { throw new Error( 'Cannot add ' + type + " '" + name + "': an operation with that name already exists", ); } if (name in this.attributes) { throw new Error( 'Cannot add ' + type + " '" + name + "': an attribute with that name already exists", ); } }
wrap(node, source, optBaseInterval) { const baseInterval = optBaseInterval || source; return node instanceof this.Wrapper ? node : new this.Wrapper(node, source, baseInterval); }}
function parseSignature(signature, type) { if (!Semantics.prototypeGrammar) { common.assert(signature.indexOf('(') === -1); return { name: signature, formals: [], }; }
const r = Semantics.prototypeGrammar.match( signature, type === 'operation' ? 'OperationSignature' : 'AttributeSignature', ); if (r.failed()) { throw new Error(r.message); }
return Semantics.prototypeGrammarSemantics(r).parse();}
function newDefaultAction(type, name, doIt) { return function(...children) { const thisThing = this._semantics.operations[name] || this._semantics.attributes[name]; const args = thisThing.formals.map(formal => this.args[formal]);
if (!this.isIteration() && children.length === 1) { return doIt.apply(children[0], args); } else { throw errors.missingSemanticAction(this.ctorName, name, type, globalActionStack); } };}
Semantics.createSemantics = function(grammar, optSuperSemantics) { const s = new Semantics( grammar, optSuperSemantics !== undefined ? optSuperSemantics : Semantics.BuiltInSemantics._getSemantics(), );
const proxy = function ASemantics(matchResult) { if (!(matchResult instanceof MatchResult)) { throw new TypeError( 'Semantics expected a MatchResult, but got ' + common.unexpectedObjToString(matchResult), ); } if (matchResult.failed()) { throw new TypeError('cannot apply Semantics to ' + matchResult.toString()); }
const cst = matchResult._cst; if (cst.grammar !== grammar) { throw new Error( "Cannot use a MatchResult from grammar '" + cst.grammar.name + "' with a semantics for '" + grammar.name + "'", ); } const inputStream = new InputStream(matchResult.input); return s.wrap(cst, inputStream.interval(matchResult._cstOffset, matchResult.input.length)); };
proxy.addOperation = function(signature, actionDict) { s.addOperationOrAttribute('operation', signature, actionDict); return proxy; }; proxy.extendOperation = function(name, actionDict) { s.extendOperationOrAttribute('operation', name, actionDict); return proxy; }; proxy.addAttribute = function(name, actionDict) { s.addOperationOrAttribute('attribute', name, actionDict); return proxy; }; proxy.extendAttribute = function(name, actionDict) { s.extendOperationOrAttribute('attribute', name, actionDict); return proxy; }; proxy._getActionDict = function(operationOrAttributeName) { const action = s.operations[operationOrAttributeName] || s.attributes[operationOrAttributeName]; if (!action) { throw new Error( '"' + operationOrAttributeName + '" is not a valid operation or attribute ' + 'name in this semantics for "' + grammar.name + '"', ); } return action.actionDict; }; proxy._remove = function(operationOrAttributeName) { let semantic; if (operationOrAttributeName in s.operations) { semantic = s.operations[operationOrAttributeName]; delete s.operations[operationOrAttributeName]; } else if (operationOrAttributeName in s.attributes) { semantic = s.attributes[operationOrAttributeName]; delete s.attributes[operationOrAttributeName]; } delete s.Wrapper.prototype[operationOrAttributeName]; return semantic; }; proxy.getOperationNames = function() { return Object.keys(s.operations); }; proxy.getAttributeNames = function() { return Object.keys(s.attributes); }; proxy.getGrammar = function() { return s.grammar; }; proxy.toRecipe = function(semanticsOnly) { return s.toRecipe(semanticsOnly); };
proxy.toString = s.toString.bind(s);
proxy._getSemantics = function() { return s; };
return proxy;};
class Operation { constructor(name, formals, actionDict, builtInDefault) { this.name = name; this.formals = formals; this.actionDict = actionDict; this.builtInDefault = builtInDefault; }
checkActionDict(grammar) { grammar._checkTopDownActionDict(this.typeName, this.name, this.actionDict); }
execute(semantics, nodeWrapper) { try { const {ctorName} = nodeWrapper._node; let actionFn = this.actionDict[ctorName]; if (actionFn) { globalActionStack.push([this, ctorName]); return actionFn.apply(nodeWrapper, nodeWrapper._children()); }
if (nodeWrapper.isNonterminal()) { actionFn = this.actionDict._nonterminal; if (actionFn) { globalActionStack.push([this, '_nonterminal', ctorName]); return actionFn.apply(nodeWrapper, nodeWrapper._children()); } }
globalActionStack.push([this, 'default action', ctorName]); return this.actionDict._default.apply(nodeWrapper, nodeWrapper._children()); } finally { globalActionStack.pop(); } }}
Operation.prototype.typeName = 'operation';
class Attribute extends Operation { constructor(name, actionDict, builtInDefault) { super(name, [], actionDict, builtInDefault); }
execute(semantics, nodeWrapper) { const node = nodeWrapper._node; const key = semantics.attributeKeys[this.name]; if (!hasOwnProperty(node, key)) { node[key] = Operation.prototype.execute.call(this, semantics, nodeWrapper); } return node[key]; }}
Attribute.prototype.typeName = 'attribute';