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 UDT
s 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;
};