diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js
index c158d3ea..310a2ad8 100644
--- a/app/imports/parser/grammar.js
+++ b/app/imports/parser/grammar.js
@@ -79,7 +79,7 @@ let ParserRules = [
{"name": "exponentExpression", "symbols": ["callExpression", "_", (lexer.has("exponentOperator") ? {type: "exponentOperator"} : exponentOperator), "_", "exponentExpression"], "postprocess": d => operator(d, 'exponent')},
{"name": "exponentExpression", "symbols": ["callExpression"], "postprocess": id},
{"name": "callExpression", "symbols": ["name", "_", "arguments"], "postprocess":
- d => new CallNode ({type: "call", fn: d[0], arguments: d[2]})
+ d => new CallNode ({functionName: d[0].name, args: d[2]})
},
{"name": "callExpression", "symbols": ["indexExpression"], "postprocess": id},
{"name": "arguments$ebnf$1$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne
index 3f6c7fc6..4e199c11 100644
--- a/app/imports/parser/grammar.ne
+++ b/app/imports/parser/grammar.ne
@@ -103,7 +103,7 @@ exponentExpression ->
callExpression ->
name _ arguments {%
- d => new CallNode ({type: "call", fn: d[0], arguments: d[2]})
+ d => new CallNode ({functionName: d[0].name, args: d[2]})
%}
| indexExpression {% id %}
diff --git a/app/imports/parser/parseTree/ArrayNode.js b/app/imports/parser/parseTree/ArrayNode.js
index 43f592ad..b74fc455 100644
--- a/app/imports/parser/parseTree/ArrayNode.js
+++ b/app/imports/parser/parseTree/ArrayNode.js
@@ -2,12 +2,12 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ArrayNode extends ParseNode {
constructor({values}) {
- super();
+ super(...arguments);
this.values = values;
}
- compile(){
- let values = this.values.map(node => node.compile());
- return new ArrayNode({values});
+ compile(scope){
+ let values = this.values.map(node => node.compile(scope));
+ return new ArrayNode({values, previousNodes: [this]});
}
toString(){
return `[${this.values.map(node => node.toString()).join(', ')}]`;
diff --git a/app/imports/parser/parseTree/CallNode.js b/app/imports/parser/parseTree/CallNode.js
index 8ad1dfb5..52ab227c 100644
--- a/app/imports/parser/parseTree/CallNode.js
+++ b/app/imports/parser/parseTree/CallNode.js
@@ -1 +1,82 @@
-//TODO
+import ParseNode from '/imports/parser/parseTree/ParseNode.js';
+import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
+import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
+
+export default class CallNode extends ParseNode {
+ constructor({functionName, args}) {
+ super(...arguments);
+ this.functionName = functionName;
+ this.args = args;
+ }
+ compile(scope){
+ return this.resolve('compile', scope);
+ }
+ roll(scope){
+ return this.resolve('roll', scope);
+ }
+ reduce(scope){
+ return this.resolve('reduce', scope);
+ }
+ resolve(fn, scope){
+ let func = functions[this.functionName];
+ if (!func) return new ErrorNode({
+ node: this,
+ error: `${this.functionName} is not a function`,
+ previousNodes: [this],
+ });
+ let args = castArgsToType({fn, scope, args: this.args, type: func.argumentType});
+ if (args.failed){
+ if (fn === 'reduce'){
+ return new ErrorNode({
+ node: this,
+ error: 'Could not convert all arguments to the correct type',
+ previousNodes: [this],
+ });
+ } else {
+ return new CallNode({
+ functionName: this.functionName,
+ args: args,
+ previousNodes: [this],
+ });
+ }
+ } else {
+ let value = func.fn.apply(null, args);
+ console.log({args})
+ return new ConstantNode({
+ value,
+ type: 'number',
+ previousNodes: [this],
+ });
+ }
+
+ }
+ toString(){
+ return `${this.functionName}(${this.args.map(node => node.toString()).join(', ')})`;
+ }
+}
+
+const functions = {
+ 'min': {
+ comment: 'Returns the lowest of the given numbers',
+ argumentType: 'number',
+ resultType: 'number',
+ fn: Math.min,
+ },
+}
+
+function castArgsToType({fn, scope, args, type}){
+ let resolvedArgs = args.map(node => node[fn](scope))
+ let result = [];
+ if (type === 'number'){
+ resolvedArgs.forEach(node => {
+ if (node.isNumber){
+ result.push(node.value);
+ } else {
+ resolvedArgs.failed = true;
+ }
+ })
+ }
+ if (resolvedArgs.failed) return resolvedArgs;
+ console.log({result})
+ return result;
+}
diff --git a/app/imports/parser/parseTree/ConstantNode.js b/app/imports/parser/parseTree/ConstantNode.js
index 4d3198fd..268011eb 100644
--- a/app/imports/parser/parseTree/ConstantNode.js
+++ b/app/imports/parser/parseTree/ConstantNode.js
@@ -1,12 +1,12 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ConstantNode extends ParseNode {
- constructor({value, type, errors}){
- super();
+ constructor({value, type, rollTable}){
+ super(...arguments);
// string, number, boolean, numberArray, uncompiledNode
this.type = type;
this.value = value;
- if (errors) this.errors = errors;
+ if (rollTable) this.rollTable = rollTable;
}
compile(){
return this;
@@ -18,6 +18,6 @@ export default class ConstantNode extends ParseNode {
return this.type === 'number';
}
get isInteger(){
- return this.isNumberNode && Number.isInteger(this.value);
+ return this.type === 'number' && Number.isInteger(this.value);
}
}
diff --git a/app/imports/parser/parseTree/ErrorNode.js b/app/imports/parser/parseTree/ErrorNode.js
index bbef94c3..44fe2434 100644
--- a/app/imports/parser/parseTree/ErrorNode.js
+++ b/app/imports/parser/parseTree/ErrorNode.js
@@ -2,7 +2,7 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ErrorNode extends ParseNode {
constructor({node, error}) {
- super();
+ super(...arguments);
this.node = node;
this.error = error;
}
diff --git a/app/imports/parser/parseTree/IfNode.js b/app/imports/parser/parseTree/IfNode.js
index a801e4bd..66334d84 100644
--- a/app/imports/parser/parseTree/IfNode.js
+++ b/app/imports/parser/parseTree/IfNode.js
@@ -1,9 +1,8 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class IfNode extends ParseNode {
constructor({condition, consequent, alternative}){
- super();
+ super(...arguments);
this.condition = condition;
this.consequent = consequent;
this.alternative = alternative;
@@ -12,33 +11,26 @@ export default class IfNode extends ParseNode {
let {condition, consequent, alternative} = this;
return `${condition.toString()} ? ${consequent.toString()} : ${alternative.toString()}`
}
- compile(){
- let condition = this.condition.compile();
- let consequent = this.consequent.compile();
- let alternative = this.alternative.compile();
- if (
- condition.type !== 'string' &&
- condition.type !== 'number' &&
- condition.type !== 'boolean'
- ){
- // Handle unresolved condition
- return new ConstantNode({
- value: `${condition.value}) ${consequent.value} else ${alternative.value}`,
- type: 'uncompiledNode',
- errors: [
- ...condition.errors,
- ...consequent.errors,
- ...alternative.errors,
- ],
- });
+ compile(scope){
+ return this.resolve('compile', scope);
+ }
+ roll(scope){
+ return this.resolve('roll', scope);
+ }
+ reduce(scope){
+ return this.resolve('reduce', scope);
+ }
+ resolve(fn, scope){
+ let condition = this.condition[fn](scope);
+ let consequent = this.consequent[fn](scope);
+ let alternative = this.alternative[fn](scope);
+ this.resolve(condition, consequent, alternative);
+ if (condition.value){
+ consequent.inheritDetails([condition, this]);
+ return consequent;
} else {
- // So long as the condition reolves, return the correct alternative,
- // even if it's unresolved
- if (condition.value){
- return consequent;
- } else {
- return alternative;
- }
+ alternative.inheritDetails([condition, this]);
+ return alternative;
}
}
}
diff --git a/app/imports/parser/parseTree/IndexNode.js b/app/imports/parser/parseTree/IndexNode.js
index 158da8c6..8014d942 100644
--- a/app/imports/parser/parseTree/IndexNode.js
+++ b/app/imports/parser/parseTree/IndexNode.js
@@ -2,23 +2,35 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class IndexNode extends ParseNode {
constructor({array, index}) {
- super();
+ super(...arguments);
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];
+ resolve(fn, scope){
+ let index = this.index[fn](scope);
+ if (index.isInteger){
+ let selection = this.array.values[index.value - 1];
if (selection){
- return selection.compile();
+ let result = selection[fn](scope);
+ result.inheritDetails([index, this]);
+ return result;
}
}
return new IndexNode({
- array: this.array.compile(),
- index: this.index.compile(),
+ array: this.array[fn](scope),
+ index: this.index[fn](scope),
+ previousNodes: [this],
});
}
+ compile(scope){
+ return this.resolve('compile', scope);
+ }
+ roll(scope){
+ return this.resolve('roll', scope);
+ }
+ reduce(scope){
+ return this.resolve('reduce', scope);
+ }
toString(){
return `${this.array.toString()}[${this.index.toString()}]`;
}
diff --git a/app/imports/parser/parseTree/OperatorNode.js b/app/imports/parser/parseTree/OperatorNode.js
index fa75f315..5b6830d8 100644
--- a/app/imports/parser/parseTree/OperatorNode.js
+++ b/app/imports/parser/parseTree/OperatorNode.js
@@ -3,22 +3,32 @@ import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class OperatorNode extends ParseNode {
constructor({left, right, operator, fn}) {
- super();
+ super(...arguments);
this.left = left;
this.right = right;
this.fn = fn;
this.operator = operator;
}
- compile(){
- let leftNode = this.left.compile();
- let rightNode = this.right.compile();
+ compile(scope){
+ return this.resolve('compile', scope);
+ }
+ roll(scope){
+ return this.resolve('roll', scope);
+ }
+ reduce(scope){
+ return this.resolve('reduce', scope);
+ }
+ resolve(fn, scope){
+ let leftNode = this.left[fn](scope);
+ let rightNode = this.right[fn](scope);
let left, right;
if (leftNode.type !== 'number' || rightNode.type !== 'number'){
return new OperatorNode({
left: leftNode,
right: rightNode,
operator: this.operator,
- fn: this.fn
+ fn: this.fn,
+ previousNodes: [this],
});
} else {
left = leftNode.value;
@@ -44,7 +54,11 @@ export default class OperatorNode extends ParseNode {
case '>=': result = left >= right; break;
case '<=': result = left <= right; break;
}
- return new ConstantNode({value: result, type: typeof result});
+ return new ConstantNode({
+ value: result,
+ type: typeof result,
+ previousNodes: [this, leftNode, rightNode],
+ });
}
toString(){
let {left, right, operator} = this;
diff --git a/app/imports/parser/parseTree/ParenthesisNode.js b/app/imports/parser/parseTree/ParenthesisNode.js
index 22da9c4b..2df81c3e 100644
--- a/app/imports/parser/parseTree/ParenthesisNode.js
+++ b/app/imports/parser/parseTree/ParenthesisNode.js
@@ -2,15 +2,24 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ParenthesisNode extends ParseNode {
constructor({content}) {
- super();
+ super(...arguments);
this.content = content;
}
- compile(){
- let content = this.content.compile();
+ compile(scope){
+ return this.resolve('compile', scope);
+ }
+ roll(scope){
+ return this.resolve('roll', scope);
+ }
+ reduce(scope){
+ return this.resolve('reduce', scope);
+ }
+ resolve(fn, scope){
+ let content = this.content[fn](scope);
if (content.constructor.name === 'ConstantNode'){
return content;
} else {
- return new ParenthesisNode({content});
+ return new ParenthesisNode({content, previousNodes: [this]});
}
}
toString(){
diff --git a/app/imports/parser/parseTree/ParseNode.js b/app/imports/parser/parseTree/ParseNode.js
index 0d8686d3..03f054ac 100644
--- a/app/imports/parser/parseTree/ParseNode.js
+++ b/app/imports/parser/parseTree/ParseNode.js
@@ -1,4 +1,17 @@
export default class ParseNode {
+ constructor({previousNodes, detail}){
+ this.inheritDetails(previousNodes);
+ if (detail) this.pushDetails([detail]);
+ }
+ inheritDetails(nodes){
+ if (!nodes || !nodes.length) return;
+ nodes.forEach(node => this.pushDetails(node.details));
+ }
+ pushDetails(details){
+ if (!details || !details.length) return;
+ if (!this.details) this.details = [];
+ details.forEach(detail => this.details.push(detail));
+ }
compile(){
// Returns a ParseNode, a ConstantNode if possible
throw new Meteor.Error('Compile not implemented on ' + this.constructor.name);
@@ -7,11 +20,11 @@ export default class ParseNode {
throw new Meteor.Error('toString not implemented on ' + this.constructor.name);
}
// Compile, but turn rolls into arrays
- roll(){
- return this.compile();
+ roll(scope){
+ return this.compile(scope);
}
// Compile, turn rolls into arrays, and reduce those arrays into single values
- reduce(){
- return this.roll();
+ reduce(scope){
+ return this.roll(scope);
}
}
diff --git a/app/imports/parser/parseTree/RollArrayNode.js b/app/imports/parser/parseTree/RollArrayNode.js
index d8d60236..fe21abd3 100644
--- a/app/imports/parser/parseTree/RollArrayNode.js
+++ b/app/imports/parser/parseTree/RollArrayNode.js
@@ -1,8 +1,9 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
+import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class RollArrayNode extends ParseNode {
constructor({values}) {
- super();
+ super(...arguments);
this.values = values;
}
compile(){
@@ -12,6 +13,11 @@ export default class RollArrayNode extends ParseNode {
return `[${this.values.join(', ')}]`;
}
reduce(){
- //TODO sum and return values
+ let total = this.values.reduce((a, b) => a + b);
+ return new ConstantNode({
+ value: total,
+ type: 'number',
+ previousNodes: [this],
+ });
}
}
diff --git a/app/imports/parser/parseTree/RollNode.js b/app/imports/parser/parseTree/RollNode.js
index d2f99379..8f141832 100644
--- a/app/imports/parser/parseTree/RollNode.js
+++ b/app/imports/parser/parseTree/RollNode.js
@@ -1,17 +1,17 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
+import RollArrayNode from '/imports/parser/parseTree/RollArrayNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
export default class RollNode extends ParseNode {
constructor({left, right}) {
- super();
+ super(...arguments);
this.left = left;
this.right = right;
}
- compile(){
- let left = this.left.compile();
- let right = this.right.compile();
- return new RollNode({left, right});
+ compile(scope){
+ let left = this.left.compile(scope);
+ let right = this.right.compile(scope);
+ return new RollNode({left, right, previousNodes: [this]});
}
toString(){
if (
@@ -22,19 +22,21 @@ export default class RollNode extends ParseNode {
return `${this.left.toString()}d${this.right.toString()}`;
}
}
- roll(){
- let left = this.left.reduce();
- let right = this.right.reduce();
+ roll(scope){
+ let left = this.left.reduce(scope);
+ let right = this.right.reduce(scope);
if (!left.isInteger){
return new ErrorNode({
node: this,
- error: 'Number of dice is not an integer'
+ error: 'Number of dice is not an integer',
+ previousNodes: [this, left, right],
});
}
if (!right.isInteger){
return new ErrorNode({
node: this,
- error: 'Dice size is not an integer'
+ error: 'Dice size is not an integer',
+ previousNodes: [this, left, right],
});
}
let number = left.value;
@@ -44,14 +46,18 @@ export default class RollNode extends ParseNode {
});
let diceSize = right.value;
let randomSrc = DDP.randomStream('diceRoller');
- let rolls = [];
+ let values = [];
for (let i = 0; i < number; i++){
let roll = ~~(randomSrc.fraction() * diceSize) + 1
- rolls.push(roll);
+ values.push(roll);
}
- return new ArrayNode({values: rolls});
+ return new RollArrayNode({
+ values,
+ detail: {number, diceSize, values},
+ previousNodes: [this, left, right],
+ });
}
- reduce(){
- this.roll().reduce();
+ reduce(scope){
+ return this.roll(scope).reduce(scope);
}
}
diff --git a/app/imports/parser/parseTree/SymbolNode.js b/app/imports/parser/parseTree/SymbolNode.js
index cb855b72..35c2ffab 100644
--- a/app/imports/parser/parseTree/SymbolNode.js
+++ b/app/imports/parser/parseTree/SymbolNode.js
@@ -1,9 +1,10 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
+import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
export default class SymbolNode extends ParseNode {
constructor({name}){
- super();
+ super(...arguments);
this.name = name;
}
toString(){
@@ -13,12 +14,11 @@ export default class SymbolNode extends ParseNode {
let value = scope && scope[this.name];
let type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean'){
- return new ConstantNode({value, type});
+ return new ConstantNode({value, type, previousNodes: [this]});
} else if (type === 'undefined'){
- return new ConstantNode({
- value: this.name,
- type: 'uncompiledNode',
- errors: [`${this.name} could not be resolved`]
+ return new ErrorNode({
+ node: this,
+ error: `${this.name} could not be resolved`,
});
} else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
diff --git a/app/imports/ui/pages/Parser.vue b/app/imports/ui/pages/Parser.vue
index abcc4e4b..ba8d7683 100644
--- a/app/imports/ui/pages/Parser.vue
+++ b/app/imports/ui/pages/Parser.vue
@@ -18,6 +18,21 @@
readonly
label="compiled"
/>
+
+
+
@@ -32,6 +47,9 @@ export default {
output: null,
compiled: null,
string: null,
+ rolled: null,
+ reduced: null,
+ reducedJson: null,
}},
watch: {
input(val){
@@ -41,7 +59,11 @@ export default {
this.output = JSON.stringify(output, null, 2);
if (!output) return;
this.string = output;
- this.compiled = output.compile();
+ let scope = {cat: 1, dog: 2, mouse: 3};
+ this.compiled = output.compile(scope);
+ this.rolled = output.roll(scope);
+ this.reduced = output.reduce(scope);
+ this.reducedJson = JSON.stringify(this.reduced, null, 2)
}
},
}
diff --git a/app/package-lock.json b/app/package-lock.json
index 711f73f9..8f43e02d 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -2805,7 +2805,7 @@
},
"signal-exit": {
"version": "3.0.2",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "resolved": false,
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simpl-schema": {