97 lines
2.4 KiB
JavaScript
97 lines
2.4 KiB
JavaScript
// All the classes that make up a parse tree
|
|
|
|
class ParseNode {
|
|
// Compiling a node must return a ConstantNode
|
|
compile(){
|
|
throw new Meteor.Error('Compile not implemented on ' + this);
|
|
}
|
|
compileToSingleValue(){
|
|
return this.compile();
|
|
}
|
|
}
|
|
|
|
function sum(total, num) {
|
|
return total + num;
|
|
}
|
|
|
|
class ConstantNode extends ParseNode {
|
|
constructor({value, type, errors}){
|
|
// string, number, boolean, numberArray, uncompiledNode
|
|
this.type = type;
|
|
this.value = value;
|
|
if (errors) this.errors = errors;
|
|
}
|
|
compile(){
|
|
return this;
|
|
}
|
|
compileToSingleValue(){
|
|
if (this.type !== 'numberArray') return this;
|
|
return this.value.reduce(sum, 0);
|
|
}
|
|
}
|
|
|
|
class SymbolNode extends ParseNode {
|
|
constructor({name}){
|
|
this.name = name;
|
|
}
|
|
compile(scope){
|
|
let value = scope[this.name];
|
|
let type = typeof value;
|
|
if (type === 'string' || type === 'number' || type === 'boolean'){
|
|
return new ConstantNode({value, type});
|
|
} else if (type === 'undefined'){
|
|
return new ConstantNode({
|
|
value: this.name,
|
|
type: 'uncompiledNode',
|
|
errors: [`${this.name} could not be resolved`]
|
|
});
|
|
} else {
|
|
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
class ifNode extends ParseNode {
|
|
constructor({condition, consequent, alternative}){
|
|
this.condition = condition;
|
|
this.consequent = consequent;
|
|
this.alternative = alternative;
|
|
}
|
|
compile(){
|
|
let condition = this.condition.compile();
|
|
let consequent = this.consequent.compile();
|
|
let alternative = this.alternative.compile();
|
|
if (
|
|
condition.type === 'uncompiledNode' ||
|
|
consequent.type === 'uncompiledNode' ||
|
|
alternative.type === 'uncompiledNode'
|
|
){
|
|
// Handle uncompiled child nodes
|
|
return new ConstantNode({
|
|
value: `if (${condition.value}) ${consequent.value} else ${alternative.value}`,
|
|
type: 'uncompiledNode',
|
|
errors: [
|
|
...condition.errors,
|
|
...consequent.errors,
|
|
...alternative.errors,
|
|
],
|
|
});
|
|
} else {
|
|
if (condition.value){
|
|
return consequent;
|
|
} else {
|
|
return alternative;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class OperatorNode extends ParseNode {
|
|
constructor({left, right, operator, fn}) {
|
|
this.left = left;
|
|
this.right = right;
|
|
this.fn = fn;
|
|
this.operator = operator;
|
|
}
|
|
}
|