This module is used by the parser to build an Abstract Syntax Tree (AST).
The AST can be thought of as a subset of the full parse tree.
Each node of the AST holds the phrase that was matched at the corresponding, named parse tree node.
It is built as the parser successfully matches phrases to the rule names
(RNM operators) and UDTs as it parses an input string.
The user controls which RNM or UDT names to keep on the AST.
The user can also associate callback functions with some or all of the retained
AST nodes to be used to translate the node phrases. That is, associate semantic
actions to the matched phrases.
Translating the AST rather that attempting to apply semantic actions during
the parsing process, has the advantage that there is no backtracking and that the phrases
are known while traversing down tree as will as up.
Let ast be an ast.js object. To identify a node to be kept on the AST:
ast.callbacks["rulename"] = true; (all nodes default to false)
To associate a callback function with a node:
ast.callbacks["rulename"] = fn
rulename is any RNM or UDT name defined by the associated grammar
and fn is a user-written callback function.
(See apg-examples for examples of how to create an AST,
define the nodes and callback functions and attach it to a parser.)
module.exports = function exportsAst() {
const id = require('./identifiers');
const utils = require('./utilities');
const thisFileName = 'ast.js: ';
const that = this;
let rules = null;
let udts = null;
let chars = null;
let nodeCount = 0;
const nodesDefined = [];
const nodeCallbacks = [];
const stack = [];
const records = [];
this.callbacks = [];
this.astObject = 'astObject';
this.init = function init(rulesIn, udtsIn, charsIn) {
stack.length = 0;
records.length = 0;
nodesDefined.length = 0;
nodeCount = 0;
rules = rulesIn;
udts = udtsIn;
chars = charsIn;
let i;
const list = [];
for (i = 0; i < rules.length; i += 1) {
list.push(rules[i].lower);
}
for (i = 0; i < udts.length; i += 1) {
list.push(udts[i].lower);
}
nodeCount = rules.length + udts.length;
for (i = 0; i < nodeCount; i += 1) {
nodesDefined[i] = false;
nodeCallbacks[i] = null;
}
for (const index in that.callbacks) {
const lower = index.toLowerCase();
i = list.indexOf(lower);
if (i < 0) {
throw new Error(`${thisFileName}init: node '${index}' not a rule or udt name`);
}
if (typeof that.callbacks[index] === 'function') {
nodesDefined[i] = true;
nodeCallbacks[i] = that.callbacks[index];
}
if (that.callbacks[index] === true) {
nodesDefined[i] = true;
}
}
};
this.ruleDefined = function ruleDefined(index) {
return nodesDefined[index] !== false;
};
this.udtDefined = function udtDefined(index) {
return nodesDefined[rules.length + index] !== false;
};
this.down = function down(callbackIndex, name) {
const thisIndex = records.length;
stack.push(thisIndex);
records.push({
name,
thisIndex,
thatIndex: null,
state: id.SEM_PRE,
callbackIndex,
phraseIndex: null,
phraseLength: null,
stack: stack.length,
});
return thisIndex;
};
this.up = function up(callbackIndex, name, phraseIndex, phraseLength) {
const thisIndex = records.length;
const thatIndex = stack.pop();
records.push({
name,
thisIndex,
thatIndex,
state: id.SEM_POST,
callbackIndex,
phraseIndex,
phraseLength,
stack: stack.length,
});
records[thatIndex].thatIndex = thisIndex;
records[thatIndex].phraseIndex = phraseIndex;
records[thatIndex].phraseLength = phraseLength;
return thisIndex;
};