Lots of work on the parser including testing interface

This commit is contained in:
Stefan Zermatten
2020-09-09 17:09:50 +02:00
parent 445171ce80
commit 81645df2a6
15 changed files with 341 additions and 39 deletions

View File

@@ -0,0 +1,15 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ArrayNode extends ParseNode {
constructor({values}) {
super();
this.values = values;
}
compile(){
let values = this.values.map(node => node.compile());
return new ArrayNode({values});
}
toString(){
return `[${this.values.map(node => node.toString()).join(', ')}]`;
}
}

View File

@@ -11,11 +11,13 @@ export default class ConstantNode extends ParseNode {
compile(){
return this;
}
reduce(){
if (this.type === 'numberArray'){
return this.value.reduce((total, num) => total + num, 0);
} else {
return this;
}
toString(){
return `${this.value}`;
}
get isNumber(){
return this.type === 'number';
}
get isInteger(){
return this.isNumberNode && Number.isInteger(this.value);
}
}

View File

@@ -0,0 +1,15 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ErrorNode extends ParseNode {
constructor({node, error}) {
super();
this.node = node;
this.error = error;
}
compile(){
return this;
}
toString(){
return '###';
}
}

View File

@@ -8,6 +8,10 @@ export default class IfNode extends ParseNode {
this.consequent = consequent;
this.alternative = alternative;
}
toString(){
let {condition, consequent, alternative} = this;
return `${condition.toString()} ? ${consequent.toString()} : ${alternative.toString()}`
}
compile(){
let condition = this.condition.compile();
let consequent = this.consequent.compile();
@@ -19,7 +23,7 @@ export default class IfNode extends ParseNode {
){
// Handle unresolved condition
return new ConstantNode({
value: `if (${condition.value}) ${consequent.value} else ${alternative.value}`,
value: `${condition.value}) ${consequent.value} else ${alternative.value}`,
type: 'uncompiledNode',
errors: [
...condition.errors,

View File

@@ -0,0 +1,25 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class IndexNode extends ParseNode {
constructor({array, index}) {
super();
this.array = array;
this.index = index;
}
compile(){
let index = this.index.compile();
if (index.constructor.name === 'ConstantNode' && index.type === 'number'){
let selection = this.array.values[index.value];
if (selection){
return selection.compile();
}
}
return new IndexNode({
array: this.array.compile(),
index: this.index.compile(),
});
}
toString(){
return `${this.array.toString()}[${this.index.toString()}]`;
}
}

View File

@@ -1,4 +1,5 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class OperatorNode extends ParseNode {
constructor({left, right, operator, fn}) {
@@ -8,4 +9,45 @@ export default class OperatorNode extends ParseNode {
this.fn = fn;
this.operator = operator;
}
compile(){
let leftNode = this.left.compile();
let rightNode = this.right.compile();
let left, right;
if (leftNode.type !== 'number' || rightNode.type !== 'number'){
return new OperatorNode({
left: leftNode,
right: rightNode,
operator: this.operator,
fn: this.fn
});
} else {
left = leftNode.value;
right = rightNode.value;
}
let result;
switch(this.operator){
case '+': result = left + right; break;
case '-': result = left - right; break;
case '*': result = left * right; break;
case '/': result = left / right; break;
case '^': result = Math.pow(left, right); break;
case '&':
case '&&': result = left && right; break;
case '|':
case '||': result = left || right; break;
case '=':
case '==':
case '===': result = left == right; break;
case '!=':
case '>': result = left > right; break;
case '<': result = left < right; break;
case '>=': result = left >= right; break;
case '<=': result = left <= right; break;
}
return new ConstantNode({value: result, type: typeof result});
}
toString(){
let {left, right, operator} = this;
return `${left.toString()} ${operator} ${right.toString()}`;
}
}

View File

@@ -0,0 +1,19 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ParenthesisNode extends ParseNode {
constructor({content}) {
super();
this.content = content;
}
compile(){
let content = this.content.compile();
if (content.constructor.name === 'ConstantNode'){
return content;
} else {
return new ParenthesisNode({content});
}
}
toString(){
return `(${this.content.toString()})`;
}
}

View File

@@ -1,7 +1,10 @@
export default class ParseNode {
// Compiling a node must return a ConstantNode
compile(){
throw new Meteor.Error('Compile not implemented on ' + this);
// Returns a ParseNode, a ConstantNode if possible
throw new Meteor.Error('Compile not implemented on ' + this.constructor.name);
}
toString(){
throw new Meteor.Error('toString not implemented on ' + this.constructor.name);
}
// Compile, but turn rolls into arrays
roll(){
@@ -9,6 +12,6 @@ export default class ParseNode {
}
// Compile, turn rolls into arrays, and reduce those arrays into single values
reduce(){
return this.compileAndRoll()
return this.roll();
}
}

View File

@@ -0,0 +1,17 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class RollArrayNode extends ParseNode {
constructor({values}) {
super();
this.values = values;
}
compile(){
return this;
}
toString(){
return `[${this.values.join(', ')}]`;
}
reduce(){
//TODO sum and return values
}
}

View File

@@ -0,0 +1,57 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
export default class RollNode extends ParseNode {
constructor({left, right}) {
super();
this.left = left;
this.right = right;
}
compile(){
let left = this.left.compile();
let right = this.right.compile();
return new RollNode({left, right});
}
toString(){
if (
this.left.isNumberNode && this.left.value === 1
){
return `d${this.right.toString()}`;
} else {
return `${this.left.toString()}d${this.right.toString()}`;
}
}
roll(){
let left = this.left.reduce();
let right = this.right.reduce();
if (!left.isInteger){
return new ErrorNode({
node: this,
error: 'Number of dice is not an integer'
});
}
if (!right.isInteger){
return new ErrorNode({
node: this,
error: 'Dice size is not an integer'
});
}
let number = left.value;
if (number > 100) return new ErrorNode({
node: this,
error: 'Can\'t roll more than 100 dice at once'
});
let diceSize = right.value;
let randomSrc = DDP.randomStream('diceRoller');
let rolls = [];
for (let i = 0; i < number; i++){
let roll = ~~(randomSrc.fraction() * diceSize) + 1
rolls.push(roll);
}
return new ArrayNode({values: rolls});
}
reduce(){
this.roll().reduce();
}
}

View File

@@ -6,6 +6,9 @@ export default class SymbolNode extends ParseNode {
super();
this.name = name;
}
toString(){
return `${this.name}`
}
compile(scope){
let value = scope && scope[this.name];
let type = typeof value;