diff --git a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js
index 69f747ee..0e91f833 100644
--- a/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js
+++ b/app/imports/api/engine/computation/buildComputation/linkCalculationDependencies.js
@@ -1,6 +1,5 @@
-import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
-import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
import findAncestorByType from '/imports/api/engine/computation/utility/findAncestorByType.js';
+import { traverse } from '/imports/parser/resolve.js';
export default function linkCalculationDependencies(dependencyGraph, prop, {propsById}){
prop._computationDetails.calculations.forEach(calcObj => {
@@ -9,9 +8,9 @@ export default function linkCalculationDependencies(dependencyGraph, prop, {prop
// ancestors: {} //this gets added if there are resolved ancestors
};
// Traverse the parsed calculation looking for variable names
- calcObj._parsedCalculation.traverse(node => {
+ traverse(calcObj._parsedCalculation, node => {
// Skip nodes that aren't symbols or accessors
- if (!(node instanceof SymbolNode || node instanceof AccessorNode)) return;
+ if (node.parseType !== 'symbol' && node.parseType !== 'accessor') return;
// Link ancestor references as direct property dependencies
if (node.name[0] === '#'){
let ancestorProp = getAncestorProp(
diff --git a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js
index 72e698f3..1d824e85 100644
--- a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js
+++ b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js
@@ -1,8 +1,8 @@
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
import { prettifyParseError, parse } from '/imports/parser/parser.js';
-import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
-import { get, unset } from 'lodash';
+import { get } from 'lodash';
+import errorNode from '/imports/parser/parseTree/error.js'
export default function parseCalculationFields(prop, schemas){
discoverInlineCalculationFields(prop, schemas);
@@ -66,6 +66,6 @@ function parseCalculation(calcObj){
calcObj.errors ?
calcObj.errors.push(error) :
calcObj.errors = [error];
- calcObj._parsedCalculation = new ErrorNode({error});
+ calcObj._parsedCalculation = errorNode.create({error});
}
}
diff --git a/app/imports/api/engine/computation/computeComputation/computeCalculations.js b/app/imports/api/engine/computation/computeComputation/computeCalculations.js
index 4034b5eb..ccbaf79f 100644
--- a/app/imports/api/engine/computation/computeComputation/computeCalculations.js
+++ b/app/imports/api/engine/computation/computeComputation/computeCalculations.js
@@ -1,7 +1,5 @@
-import { CompilationContext } from '/imports/parser/parser.js';
+import resolve, { toString } from '/imports/parser/resolve.js';
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
-import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
-import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
export default function computeCalculations(computation, node){
if (!node.data) return;
@@ -15,17 +13,16 @@ export default function computeCalculations(computation, node){
}
function evaluateCalculation(calculation, scope){
- const context = new CompilationContext();
const parseNode = calculation._parsedCalculation;
const fn = calculation._parseLevel;
const calculationScope = {...calculation._localScope, ...scope};
- const resultNode = parseNode[fn](calculationScope, context);
- if (resultNode instanceof ConstantNode){
+ const {result: resultNode, context} = resolve(fn, parseNode, calculationScope);
+ if (resultNode.parseType === 'constant'){
calculation.value = resultNode.value;
- } else if (resultNode instanceof ErrorNode){
+ } else if (resultNode.parseType === 'error'){
calculation.value = null;
} else {
- calculation.value = resultNode.toString();
+ calculation.value = toString(resultNode);
}
if (calculation.errors){
calculation.errors = [...calculation.errors, ...context.errors]
diff --git a/app/imports/api/properties/Constants.js b/app/imports/api/properties/Constants.js
index 4b1f382f..0995ccd9 100644
--- a/app/imports/api/properties/Constants.js
+++ b/app/imports/api/properties/Constants.js
@@ -3,12 +3,10 @@ import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
import {
parse,
- CompilationContext,
prettifyParseError,
} from '/imports/parser/parser.js';
-import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
-import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
+import resolve, { Context, traverse } from '/imports/parser/resolve.js';
/*
* Constants are primitive values that can be used elsewhere in computations
@@ -50,12 +48,9 @@ let ConstantSchema = new SimpleSchema({
// Any existing errors will result in an early failure
if (context && context.errors.length) return context.errors;
// Ban variables in constants if necessary
- result && result.traverse(node => {
- if (node instanceof SymbolNode || node instanceof AccessorNode){
- context.storeError({
- type: 'error',
- message: 'Variables can\'t be used to define a constant'
- });
+ result && traverse(result, node => {
+ if (node.parseType === 'symbol' || node.parseType === 'accessor'){
+ context.error('Variables can\'t be used to define a constant');
}
});
return context && context.errors || [];
@@ -67,7 +62,7 @@ let ConstantSchema = new SimpleSchema({
});
function parseString(string, fn = 'compile'){
- let context = new CompilationContext();
+ let context = new Context();
if (!string){
return {result: string, context};
}
@@ -78,10 +73,11 @@ function parseString(string, fn = 'compile'){
node = parse(string);
} catch (e) {
let message = prettifyParseError(e);
- context.storeError({type: 'error', message});
+ context.error(message);
return {context};
}
- let result = node[fn]({/*empty scope*/}, context);
+ if (!node) return {context};
+ let {result} = resolve(fn, node, {/*empty scope*/}, context);
return {result, context}
}
diff --git a/app/imports/parser/ResolveContext.js b/app/imports/parser/ResolveContext.js
deleted file mode 100644
index 3edd2fa8..00000000
--- a/app/imports/parser/ResolveContext.js
+++ /dev/null
@@ -1,20 +0,0 @@
-export default class Context {
- constructor({errors = [], rolls = []}){
- this.errors = errors;
- this.rolls = rolls;
- }
- error(e){
- if (!e) return;
- if (typeof e === 'string'){
- this.errors.push({
- type: 'error',
- message: e,
- });
- } else {
- this.errors.push(e);
- }
- }
- roll(r){
- this.rolls.push(r);
- }
-}
diff --git a/app/imports/parser/TextField.vue b/app/imports/parser/TextField.vue
new file mode 100644
index 00000000..9c9f0919
--- /dev/null
+++ b/app/imports/parser/TextField.vue
@@ -0,0 +1,38 @@
+
+ $emit('keyup', e)"
+ >
+
+
+
+
+
+
+
+
+
diff --git a/app/imports/parser/functions.js b/app/imports/parser/functions.js
index b1ec5a91..9213665e 100644
--- a/app/imports/parser/functions.js
+++ b/app/imports/parser/functions.js
@@ -1,5 +1,3 @@
-import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
-
export default {
'abs': {
comment: 'Returns the absolute value of a number',
@@ -101,7 +99,7 @@ export default {
{input: 'tableLookup([100, 300, 900, 1200], 900)', result: '3'},
{input: 'tableLookup([100, 300], 594)', result: '2'},
],
- arguments: [ArrayNode, 'number'],
+ arguments: ['array', 'number'],
resultType: 'number',
fn: function tableLookup(arrayNode, number){
for(let i in arrayNode.values){
diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js
index 8e29fb16..064fc2f2 100644
--- a/app/imports/parser/grammar.js
+++ b/app/imports/parser/grammar.js
@@ -1,19 +1,8 @@
-// Generated automatically by nearley, version 2.16.0
+// Generated automatically by nearley, version 2.20.1
// http://github.com/Hardmath123/nearley
function id(x) { return x[0]; }
- import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
- import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
- import CallNode from '/imports/parser/parseTree/CallNode.js';
- import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
- import IfNode from '/imports/parser/parseTree/IfNode.js';
- import IndexNode from '/imports/parser/parseTree/IndexNode.js';
- import OperatorNode from '/imports/parser/parseTree/OperatorNode.js';
- import ParenthesisNode from '/imports/parser/parseTree/ParenthesisNode.js';
- import RollNode from '/imports/parser/parseTree/RollNode.js';
- import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
- import UnaryOperatorNode from '/imports/parser/parseTree/UnaryOperatorNode.js';
- import NotOperatorNode from '/imports/parser/parseTree/NotOperatorNode.js';
+ import node from './parseTree/_index.js';
import moo from 'moo';
@@ -53,7 +42,7 @@ function id(x) { return x[0]; }
function nuller() { return null; }
function operator([left, _1, operator, _2, right], fn){
- return new OperatorNode({
+ return node.operator.create({
left,
right,
operator: operator.value,
@@ -65,7 +54,7 @@ let ParserRules = [
{"name": "spacedExpression", "symbols": ["_", "expression", "_"], "postprocess": d => d[1]},
{"name": "expression", "symbols": ["ifStatement"], "postprocess": id},
{"name": "ifStatement", "symbols": ["orExpression", "_", (lexer.has("ifOperator") ? {type: "ifOperator"} : ifOperator), "_", "orExpression", "_", (lexer.has("elseOperator") ? {type: "elseOperator"} : elseOperator), "_", "ifStatement"], "postprocess":
- d => new IfNode({condition: d[0], consequent: d[4], alternative: d[8]})
+ d => node.if.create({condition: d[0], consequent: d[4], alternative: d[8]})
},
{"name": "ifStatement", "symbols": ["orExpression"], "postprocess": id},
{"name": "orExpression", "symbols": ["orExpression", "_", (lexer.has("orOperator") ? {type: "orOperator"} : orOperator), "_", "andExpression"], "postprocess": d => operator(d, 'or')},
@@ -82,18 +71,18 @@ let ParserRules = [
{"name": "remainderExpression", "symbols": ["multiplicativeExpression"], "postprocess": id},
{"name": "multiplicativeExpression", "symbols": ["multiplicativeExpression", "_", (lexer.has("multiplicativeOperator") ? {type: "multiplicativeOperator"} : multiplicativeOperator), "_", "rollExpression"], "postprocess": d => operator(d, 'multiply')},
{"name": "multiplicativeExpression", "symbols": ["rollExpression"], "postprocess": id},
- {"name": "rollExpression", "symbols": ["rollExpression", "_", (lexer.has("diceOperator") ? {type: "diceOperator"} : diceOperator), "_", "exponentExpression"], "postprocess": d => new RollNode({left: d[0], right: d[4]})},
+ {"name": "rollExpression", "symbols": ["rollExpression", "_", (lexer.has("diceOperator") ? {type: "diceOperator"} : diceOperator), "_", "exponentExpression"], "postprocess": d => node.roll.create({left: d[0], right: d[4]})},
{"name": "rollExpression", "symbols": ["singleRollExpression"], "postprocess": id},
- {"name": "singleRollExpression", "symbols": [{"literal":"d"}, "_", "singleRollExpression"], "postprocess": d => new RollNode({left: new ConstantNode({value: 1, type: 'number'}), right: d[2]})},
+ {"name": "singleRollExpression", "symbols": [{"literal":"d"}, "_", "singleRollExpression"], "postprocess": d => node.roll.create({left: node.constant.create({value: 1}), right: d[2]})},
{"name": "singleRollExpression", "symbols": ["exponentExpression"], "postprocess": id},
{"name": "exponentExpression", "symbols": ["callExpression", "_", (lexer.has("exponentOperator") ? {type: "exponentOperator"} : exponentOperator), "_", "exponentExpression"], "postprocess": d => operator(d, 'exponent')},
{"name": "exponentExpression", "symbols": ["unaryExpression"], "postprocess": id},
- {"name": "unaryExpression", "symbols": [(lexer.has("additiveOperator") ? {type: "additiveOperator"} : additiveOperator), "_", "unaryExpression"], "postprocess": d => new UnaryOperatorNode({operator: d[0].value, right: d[2]})},
+ {"name": "unaryExpression", "symbols": [(lexer.has("additiveOperator") ? {type: "additiveOperator"} : additiveOperator), "_", "unaryExpression"], "postprocess": d => node.unaryOperator.create({operator: d[0].value, right: d[2]})},
{"name": "unaryExpression", "symbols": ["notExpression"], "postprocess": id},
- {"name": "notExpression", "symbols": [(lexer.has("notOperator") ? {type: "notOperator"} : notOperator), "_", "notExpression"], "postprocess": d => new NotOperatorNode({right: d[2]})},
+ {"name": "notExpression", "symbols": [(lexer.has("notOperator") ? {type: "notOperator"} : notOperator), "_", "notExpression"], "postprocess": d => node.notOperator.create({right: d[2]})},
{"name": "notExpression", "symbols": ["callExpression"], "postprocess": id},
{"name": "callExpression", "symbols": ["name", "_", "arguments"], "postprocess":
- d => new CallNode ({functionName: d[0].name, args: d[2]})
+ d => node.call.create({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]},
@@ -105,7 +94,7 @@ let ParserRules = [
{"name": "arguments", "symbols": [{"literal":"("}, "_", "arguments$ebnf$1", "arguments$ebnf$2", "_", {"literal":")"}], "postprocess":
d => [d[2], ...d[3]]
},
- {"name": "indexExpression", "symbols": ["arrayExpression", {"literal":"["}, "_", "expression", "_", {"literal":"]"}], "postprocess": d => new IndexNode ({array: d[0], index: d[3]})},
+ {"name": "indexExpression", "symbols": ["arrayExpression", {"literal":"["}, "_", "expression", "_", {"literal":"]"}], "postprocess": d => node.index.create({array: d[0], index: d[3]})},
{"name": "indexExpression", "symbols": ["arrayExpression"], "postprocess": id},
{"name": "arrayExpression$ebnf$1$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
{"name": "arrayExpression$ebnf$1", "symbols": ["arrayExpression$ebnf$1$subexpression$1"], "postprocess": id},
@@ -114,10 +103,10 @@ let ParserRules = [
{"name": "arrayExpression$ebnf$2$subexpression$1", "symbols": ["_", (lexer.has("separator") ? {type: "separator"} : separator), "_", "expression"], "postprocess": d => d[3]},
{"name": "arrayExpression$ebnf$2", "symbols": ["arrayExpression$ebnf$2", "arrayExpression$ebnf$2$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
{"name": "arrayExpression", "symbols": [{"literal":"["}, "_", "arrayExpression$ebnf$1", "arrayExpression$ebnf$2", "_", {"literal":"]"}], "postprocess":
- d => new ArrayNode({values: d[2] ? [d[2], ...d[3]] : []})
+ d => node.array.create({values: d[2] ? [d[2], ...d[3]] : []})
},
{"name": "arrayExpression", "symbols": ["parenthesizedExpression"], "postprocess": id},
- {"name": "parenthesizedExpression", "symbols": [{"literal":"("}, "_", "expression", "_", {"literal":")"}], "postprocess": d => new ParenthesisNode({content: d[2]})},
+ {"name": "parenthesizedExpression", "symbols": [{"literal":"("}, "_", "expression", "_", {"literal":")"}], "postprocess": d => node.parenthesis.create({content: d[2]})},
{"name": "parenthesizedExpression", "symbols": ["accessorExpression"], "postprocess": id},
{"name": "accessorExpression$subexpression$1", "symbols": [(lexer.has("name") ? {type: "name"} : name)], "postprocess": d => d[0].value},
{"name": "accessorExpression$ebnf$1$subexpression$1", "symbols": [{"literal":"."}, (lexer.has("name") ? {type: "name"} : name)], "postprocess": d => d[1].value},
@@ -125,18 +114,18 @@ let ParserRules = [
{"name": "accessorExpression$ebnf$1$subexpression$2", "symbols": [{"literal":"."}, (lexer.has("name") ? {type: "name"} : name)], "postprocess": d => d[1].value},
{"name": "accessorExpression$ebnf$1", "symbols": ["accessorExpression$ebnf$1", "accessorExpression$ebnf$1$subexpression$2"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}},
{"name": "accessorExpression", "symbols": ["accessorExpression$subexpression$1", "accessorExpression$ebnf$1"], "postprocess":
- d=> new AccessorNode({name: d[0], path: d[1]})
+ d=> node.accessor.create({name: d[0], path: d[1]})
},
{"name": "accessorExpression", "symbols": ["valueExpression"], "postprocess": id},
{"name": "valueExpression", "symbols": ["name"], "postprocess": id},
{"name": "valueExpression", "symbols": ["number"], "postprocess": id},
{"name": "valueExpression", "symbols": ["string"], "postprocess": id},
{"name": "valueExpression", "symbols": ["boolean"], "postprocess": id},
- {"name": "number", "symbols": [(lexer.has("number") ? {type: "number"} : number)], "postprocess": d => new ConstantNode({value: +d[0].value, type: 'number'})},
- {"name": "name", "symbols": [(lexer.has("name") ? {type: "name"} : name)], "postprocess": d => new SymbolNode({name: d[0].value})},
- {"name": "string", "symbols": [(lexer.has("string") ? {type: "string"} : string)], "postprocess": d => new ConstantNode({value: d[0].value, type: 'string'})},
- {"name": "boolean", "symbols": [{"literal":"true"}], "postprocess": d => new ConstantNode({value: true, type: 'boolean'})},
- {"name": "boolean", "symbols": [{"literal":"false"}], "postprocess": d => new ConstantNode({value: false, type: 'boolean'})},
+ {"name": "number", "symbols": [(lexer.has("number") ? {type: "number"} : number)], "postprocess": d => node.constant.create({value: +d[0].value})},
+ {"name": "name", "symbols": [(lexer.has("name") ? {type: "name"} : name)], "postprocess": d => node.symbol.create({name: d[0].value})},
+ {"name": "string", "symbols": [(lexer.has("string") ? {type: "string"} : string)], "postprocess": d => node.constant.create({value: d[0].value})},
+ {"name": "boolean", "symbols": [{"literal":"true"}], "postprocess": d => node.constant.create({value: true})},
+ {"name": "boolean", "symbols": [{"literal":"false"}], "postprocess": d => node.constant.create({value: false})},
{"name": "_", "symbols": []},
{"name": "_", "symbols": [(lexer.has("space") ? {type: "space"} : space)], "postprocess": nuller}
];
diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne
index cd278bab..82681659 100644
--- a/app/imports/parser/grammar.ne
+++ b/app/imports/parser/grammar.ne
@@ -1,17 +1,6 @@
@preprocessor esmodule
@{%
- import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
- import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
- import CallNode from '/imports/parser/parseTree/CallNode.js';
- import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
- import IfNode from '/imports/parser/parseTree/IfNode.js';
- import IndexNode from '/imports/parser/parseTree/IndexNode.js';
- import OperatorNode from '/imports/parser/parseTree/OperatorNode.js';
- import ParenthesisNode from '/imports/parser/parseTree/ParenthesisNode.js';
- import RollNode from '/imports/parser/parseTree/RollNode.js';
- import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
- import UnaryOperatorNode from '/imports/parser/parseTree/UnaryOperatorNode.js';
- import NotOperatorNode from '/imports/parser/parseTree/NotOperatorNode.js';
+ import node from './parseTree/_index.js';
import moo from 'moo';
@@ -51,7 +40,7 @@
function nuller() { return null; }
function operator([left, _1, operator, _2, right], fn){
- return new OperatorNode({
+ return node.operator.create({
left,
right,
operator: operator.value,
@@ -71,7 +60,7 @@ expression ->
ifStatement ->
orExpression _ %ifOperator _ orExpression _ %elseOperator _ ifStatement {%
- d => new IfNode({condition: d[0], consequent: d[4], alternative: d[8]})
+ d => node.if.create({condition: d[0], consequent: d[4], alternative: d[8]})
%}
| orExpression {% id %}
@@ -104,11 +93,11 @@ multiplicativeExpression ->
| rollExpression {% id %}
rollExpression ->
- rollExpression _ %diceOperator _ exponentExpression {% d => new RollNode({left: d[0], right: d[4]}) %}
+ rollExpression _ %diceOperator _ exponentExpression {% d => node.roll.create({left: d[0], right: d[4]}) %}
| singleRollExpression {% id %}
singleRollExpression ->
- "d" _ singleRollExpression {% d => new RollNode({left: new ConstantNode({value: 1, type: 'number'}), right: d[2]}) %}
+ "d" _ singleRollExpression {% d => node.roll.create({left: node.constant.create({value: 1}), right: d[2]}) %}
| exponentExpression {% id %}
exponentExpression ->
@@ -116,16 +105,16 @@ exponentExpression ->
| unaryExpression {% id %}
unaryExpression ->
- %additiveOperator _ unaryExpression {% d => new UnaryOperatorNode({operator: d[0].value, right: d[2]})%}
+ %additiveOperator _ unaryExpression {% d => node.unaryOperator.create({operator: d[0].value, right: d[2]})%}
| notExpression {% id %}
notExpression ->
- %notOperator _ notExpression {% d => new NotOperatorNode({right: d[2]})%}
+ %notOperator _ notExpression {% d => node.notOperator.create({right: d[2]})%}
| callExpression {% id %}
callExpression ->
name _ arguments {%
- d => new CallNode ({functionName: d[0].name, args: d[2]})
+ d => node.call.create({functionName: d[0].name, args: d[2]})
%}
| indexExpression {% id %}
@@ -135,22 +124,22 @@ arguments ->
%}
indexExpression ->
- arrayExpression "[" _ expression _ "]" {% d => new IndexNode ({array: d[0], index: d[3]}) %}
+ arrayExpression "[" _ expression _ "]" {% d => node.index.create({array: d[0], index: d[3]}) %}
| arrayExpression {% id %}
arrayExpression ->
"[" _ (expression {% d => d[0] %}):? ( _ %separator _ expression {% d => d[3] %} ):* _ "]" {%
- d => new ArrayNode({values: d[2] ? [d[2], ...d[3]] : []})
+ d => node.array.create({values: d[2] ? [d[2], ...d[3]] : []})
%}
| parenthesizedExpression {% id %}
parenthesizedExpression ->
- "(" _ expression _ ")" {% d => new ParenthesisNode({content: d[2]}) %}
+ "(" _ expression _ ")" {% d => node.parenthesis.create({content: d[2]}) %}
| accessorExpression {% id %}
accessorExpression ->
(%name {% d => d[0].value %}) ( "." %name {% d => d[1].value %} ):+ {%
- d=> new AccessorNode({name: d[0], path: d[1]})
+ d=> node.accessor.create({name: d[0], path: d[1]})
%}
| valueExpression {% id %}
@@ -162,17 +151,17 @@ valueExpression ->
# A number or a function of a number
number ->
- %number {% d => new ConstantNode({value: +d[0].value, type: 'number'}) %}
+ %number {% d => node.constant.create({value: +d[0].value}) %}
name ->
- %name {% d => new SymbolNode({name: d[0].value}) %}
+ %name {% d => node.symbol.create({name: d[0].value}) %}
string ->
- %string {% d => new ConstantNode({value: d[0].value, type: 'string'}) %}
+ %string {% d => node.constant.create({value: d[0].value}) %}
boolean ->
- "true" {% d => new ConstantNode({value: true, type: 'boolean'}) %}
-| "false" {% d => new ConstantNode({value: false, type: 'boolean'}) %}
+ "true" {% d => node.constant.create({value: true}) %}
+| "false" {% d => node.constant.create({value: false}) %}
_ ->
null
diff --git a/app/imports/parser/parseTree/IndexNode.js b/app/imports/parser/parseTree/IndexNode.js
deleted file mode 100644
index fdd6c6c6..00000000
--- a/app/imports/parser/parseTree/IndexNode.js
+++ /dev/null
@@ -1,64 +0,0 @@
-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 IndexNode extends ParseNode {
- constructor({array, index}) {
- super(...arguments);
- this.array = array;
- this.index = index;
- }
- resolve(fn, scope, context){
- let index = this.index[fn](scope, context);
- let array = this.array[fn](scope, context);
-
- if (index.isInteger && array instanceof ArrayNode){
- if (index.value < 1 || index.value > array.values.length){
- if (context){
- context.storeError({
- type: 'warning',
- message: `Index of ${index.value} is out of range for an array` +
- ` of length ${array.values.length}`,
- });
- }
- }
- let selection = array.values[index.value - 1];
- if (selection){
- let result = selection[fn](scope, context);
- return result;
- }
- } else if (fn === 'reduce'){
- if (!(array instanceof ArrayNode)){
- return new ErrorNode({
- node: this,
- error: 'Can not get the index of a non-array node: ' +
- this.array.toString() + ' = ' + array.toString(),
- context,
- });
- } else if (!index.isInteger){
- return new ErrorNode({
- node: this,
- error: array.toString() + ' is not an integer index of the array',
- context,
- });
- }
- }
- return new IndexNode({
- index,
- array,
- previousNodes: [this],
- });
- }
- toString(){
- return `${this.array.toString()}[${this.index.toString()}]`;
- }
- traverse(fn){
- fn(this);
- this.array.traverse(fn);
- this.index.traverse(fn);
- }
- replaceChildren(fn){
- this.array = this.array.replaceNodes(fn);
- this.index = this.index.replaceNodes(fn);
- }
-}
diff --git a/app/imports/parser/parseTree/NotOperatorNode.js b/app/imports/parser/parseTree/NotOperatorNode.js
deleted file mode 100644
index 47aca511..00000000
--- a/app/imports/parser/parseTree/NotOperatorNode.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
-
-export default class NotOperatorNode extends ParseNode {
- constructor({right}) {
- super(...arguments);
- this.right = right;
- }
- resolve(fn, scope, context){
- let rightNode = this.right[fn](scope, context);
- if (!(rightNode instanceof ConstantNode)){
- return new NotOperatorNode({
- right: rightNode,
- });
- }
- let right = rightNode.value;
- let result = !right;
- return new ConstantNode({
- value: result,
- type: typeof result,
- });
- }
- toString(){
- let {right} = this;
- return `!${right.toString()}`;
- }
- traverse(fn){
- fn(this);
- this.right.traverse(fn);
- }
- replaceChildren(fn){
- this.right = this.right.replaceNodes(fn);
- }
-}
diff --git a/app/imports/parser/parseTree/OperatorNode.js b/app/imports/parser/parseTree/OperatorNode.js
deleted file mode 100644
index 56633993..00000000
--- a/app/imports/parser/parseTree/OperatorNode.js
+++ /dev/null
@@ -1,71 +0,0 @@
-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}) {
- super(...arguments);
- this.left = left;
- this.right = right;
- this.fn = fn;
- this.operator = operator;
- }
- resolve(fn, scope, context){
- let leftNode = this.left[fn](scope, context);
- let rightNode = this.right[fn](scope, context);
- let left, right;
- if (!(leftNode instanceof ConstantNode) || !(rightNode instanceof ConstantNode)){
- 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 '%': result = left % right; break;
- case '&':
- case '&&': result = left && right; break;
- 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;
- 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;
- // special case of adding a negative number
- if (operator === '+' && right.isNumber && right.value < 0){
- return `${left.toString()} - ${-right.value}`
- }
- return `${left.toString()} ${operator} ${right.toString()}`;
- }
- traverse(fn){
- fn(this);
- this.left.traverse(fn);
- this.right.traverse(fn);
- }
- replaceChildren(fn){
- this.left = this.left.replaceNodes(fn);
- this.right = this.right.replaceNodes(fn);
- }
-}
diff --git a/app/imports/parser/parseTree/ParenthesisNode.js b/app/imports/parser/parseTree/ParenthesisNode.js
deleted file mode 100644
index db6b5e14..00000000
--- a/app/imports/parser/parseTree/ParenthesisNode.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-
-export default class ParenthesisNode extends ParseNode {
- constructor({content}) {
- super(...arguments);
- this.content = content;
- }
- resolve(fn, scope, context){
- let content = this.content[fn](scope, context);
- if (
- fn === 'reduce' ||
- content.constructor.name === 'ConstantNode' ||
- content.constructor.name === 'ErrorNode'
- ){
- return content;
- } else {
- return new ParenthesisNode({content});
- }
- }
- toString(){
- return `(${this.content.toString()})`;
- }
- traverse(fn){
- fn(this);
- this.content.traverse(fn);
- }
- replaceChildren(fn){
- this.content = this.content.replaceNodes(fn);
- }
-}
diff --git a/app/imports/parser/parseTree/ParseNode.js b/app/imports/parser/parseTree/ParseNode.js
deleted file mode 100644
index 740c7b14..00000000
--- a/app/imports/parser/parseTree/ParseNode.js
+++ /dev/null
@@ -1,43 +0,0 @@
-export default class ParseNode {
- toString(){
- throw new Meteor.Error('toString not implemented on ' + this.constructor.name);
- }
- compile(scope, context){
- // Returns a ParseNode, a ConstantNode if possible
- if(this.resolve) {
- return this.resolve('compile', scope, context);
- } else {
- throw new Meteor.Error('Compile not implemented on ' + this.constructor.name);
- }
- }
- // Compile, but turn rolls into arrays
- roll(scope, context){
- if (this.resolve){
- return this.resolve('roll', scope, context);
- } else {
- return this.compile(scope, context);
- }
- }
- // Compile, turn rolls into arrays, and reduce those arrays into single values
- reduce(scope, context){
- if (this.resolve){
- return this.resolve('reduce', scope, context);
- } else {
- return this.roll(scope, context);
- }
- }
- // If traverse isn't implemented, just apply it to the current node
- traverse(fn){
- fn(this);
- }
- // replace nodes, only replace nodes if fn returns a value
- replaceNodes(fn){
- let newNode = fn(this);
- if (newNode) {
- return newNode;
- } else {
- if (this.replaceChildren) this.replaceChildren(fn)
- return this;
- }
- }
-}
diff --git a/app/imports/parser/parseTree/RollArrayNode.js b/app/imports/parser/parseTree/RollArrayNode.js
deleted file mode 100644
index 978c20d6..00000000
--- a/app/imports/parser/parseTree/RollArrayNode.js
+++ /dev/null
@@ -1,22 +0,0 @@
-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(...arguments);
- this.values = values;
- }
- compile(){
- return this;
- }
- toString(){
- return `[${this.values.join(', ')}]`;
- }
- reduce(){
- let total = this.values.reduce((a, b) => a + b);
- return new ConstantNode({
- value: total,
- type: 'number',
- });
- }
-}
diff --git a/app/imports/parser/parseTree/RollNode.js b/app/imports/parser/parseTree/RollNode.js
deleted file mode 100644
index 5e1d178f..00000000
--- a/app/imports/parser/parseTree/RollNode.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-import RollArrayNode from '/imports/parser/parseTree/RollArrayNode.js';
-import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
-import roll from '/imports/parser/roll.js';
-import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
-
-export default class RollNode extends ParseNode {
- constructor({left, right}) {
- super(...arguments);
- this.left = left;
- this.right = right;
- }
- compile(scope, context){
- let left = this.left.compile(scope, context);
- let right = this.right.compile(scope, context);
- return new RollNode({left, right, previousNodes: [this]});
- }
- toString(){
- if (
- this.left.isNumberNode && this.left.value === 1
- ){
- return `d${this.right.toString()}`;
- } else {
- return `${this.left.toString()}d${this.right.toString()}`;
- }
- }
- roll(scope, context){
- let left = this.left.reduce(scope, context);
- let right = this.right.reduce(scope, context);
- 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 (context.doubleRolls){
- number *= 2;
- }
- if (number > STORAGE_LIMITS.diceRollValuesCount) return new ErrorNode({
- node: this,
- error: `Can't roll more than ${STORAGE_LIMITS.diceRollValuesCount} dice at once`,
- context,
- });
- let diceSize = right.value;
- let values = roll(number, diceSize);
- if (context){
- context.storeRoll({number, diceSize, values});
- }
- return new RollArrayNode({values});
- }
- reduce(scope, context){
- return this.roll(scope, context).reduce(scope, context);
- }
- traverse(fn){
- fn(this);
- this.left.traverse(fn);
- this.right.traverse(fn);
- }
- replaceChildren(fn){
- this.left = this.left.replaceNodes(fn);
- this.right = this.right.replaceNodes(fn);
- }
-}
diff --git a/app/imports/parser/parseTree/SymbolNode.js b/app/imports/parser/parseTree/SymbolNode.js
deleted file mode 100644
index 814ea261..00000000
--- a/app/imports/parser/parseTree/SymbolNode.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
-
-export default class SymbolNode extends ParseNode {
- constructor({name}){
- super(...arguments);
- this.name = name;
- }
- toString(){
- return `${this.name}`
- }
- compile(scope, context, calledFromReduce = false){
- let value = scope && scope[this.name];
- let type = typeof value;
- // For objects, default to their .value
- if (type === 'object'){
- value = value.value;
- type = typeof value;
- }
- // For parse nodes, compile and return
- if (value instanceof ParseNode){
- if (calledFromReduce){
- return value.reduce(scope, context);
- } else {
- return value.compile(scope, context);
- }
- }
- if (type === 'string' || type === 'number' || type === 'boolean'){
- return new ConstantNode({value, type});
- } else if (type === 'undefined'){
- return new SymbolNode({
- name: this.name,
- });
- } else {
- throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
- }
- }
- reduce(scope, context){
- let result = this.compile(scope, context, true);
- if (result instanceof SymbolNode){
- if (context) context.storeError({
- type: 'info',
- message: `${result.toString()} not found, set to 0`
- });
- return new ConstantNode({
- type: 'number',
- value: 0,
- });
- } else {
- return result;
- }
- }
-}
diff --git a/app/imports/parser/parseTree/UnaryOperatorNode.js b/app/imports/parser/parseTree/UnaryOperatorNode.js
deleted file mode 100644
index c8109e6a..00000000
--- a/app/imports/parser/parseTree/UnaryOperatorNode.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import ParseNode from '/imports/parser/parseTree/ParseNode.js';
-import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
-
-export default class UnaryOperatorNode extends ParseNode {
- constructor({operator, right}) {
- super(...arguments);
- this.operator = operator;
- this.right = right;
- }
- resolve(fn, scope, context){
- let rightNode = this.right[fn](scope, context);
- if (rightNode.type !== 'number'){
- return new UnaryOperatorNode({
- operator: this.operator,
- right: rightNode,
- });
- }
- let right = rightNode.value;
- let result;
- switch(this.operator){
- case '-': result = -right; break;
- case '+': result = +right; break;
- }
- return new ConstantNode({
- value: result,
- type: typeof result,
- });
- }
- toString(){
- let {right, operator} = this;
- return `${operator}${right.toString()}`;
- }
- traverse(fn){
- fn(this);
- this.right.traverse(fn);
- }
- replaceChildren(fn){
- this.right = this.right.replaceNodes(fn);
- }
-}
diff --git a/app/imports/parser/parseTree/_index.js b/app/imports/parser/parseTree/_index.js
index 571f7e7a..9900c381 100644
--- a/app/imports/parser/parseTree/_index.js
+++ b/app/imports/parser/parseTree/_index.js
@@ -1,5 +1,31 @@
import accessor from './accessor.js';
+import array from './array.js';
+import call from './call.js';
+import constant from './constant.js';
+import error from './error.js';
+import ifNode from './if.js';
+import index from './index.js';
+import not from './not.js';
+import operator from './operator.js';
+import parenthesis from './parenthesis.js';
+import roll from './roll.js';
+import rollArray from './rollArray.js';
+import symbol from './symbol.js';
+import unaryOperator from './unaryOperator.js';
export default Object.freeze({
accessor,
+ array,
+ call,
+ constant,
+ error,
+ if: ifNode,
+ index,
+ not,
+ operator,
+ parenthesis,
+ roll,
+ rollArray,
+ symbol,
+ unaryOperator,
});
diff --git a/app/imports/parser/parseTree/accessor.js b/app/imports/parser/parseTree/accessor.js
index 5ebbf246..8c5b3773 100644
--- a/app/imports/parser/parseTree/accessor.js
+++ b/app/imports/parser/parseTree/accessor.js
@@ -3,7 +3,7 @@ import constant from './constant.js';
const accessor = {
create({name, path}) {
return {
- type: 'accessor',
+ parseType: 'accessor',
path,
name,
};
@@ -45,12 +45,11 @@ const accessor = {
},
reduce(node, scope, context){
let { result } = accessor.compile(node, scope, context);
- if (result.type === 'accessor'){
+ if (result.parseType === 'accessor'){
context.error(`${accessor.toString(result)} not found, set to 0`);
return {
result: constant.create({
value: 0,
- valueType: 'number',
}),
context
};
diff --git a/app/imports/parser/parseTree/array.js b/app/imports/parser/parseTree/array.js
index 407ab92a..7ee711a6 100644
--- a/app/imports/parser/parseTree/array.js
+++ b/app/imports/parser/parseTree/array.js
@@ -1,10 +1,10 @@
-import constant from './constant.js';
import resolve, { toString, traverse } from '../resolve.js';
+import constant from './constant.js';
const array = {
create({values}) {
return {
- type: 'array',
+ parseType: 'array',
values,
};
},
@@ -24,7 +24,7 @@ const array = {
});
return array.create({values});
},
- resolve(fn, node, scope){
+ resolve(fn, node, scope, context){
let values = node.values.map(node => {
let { result } = resolve(fn, node, scope, context);
return result;
diff --git a/app/imports/parser/parseTree/call.js b/app/imports/parser/parseTree/call.js
index 6893dbf4..52b16967 100644
--- a/app/imports/parser/parseTree/call.js
+++ b/app/imports/parser/parseTree/call.js
@@ -1,12 +1,12 @@
import error from './error.js';
import constant from './constant.js';
import functions from '/imports/parser/functions.js';
-import resolve, { toString, traverse, mergeResolvedNodes } from '../resolve.js';
+import resolve, { toString, traverse } from '../resolve.js';
const call = {
create({functionName, args}) {
return {
- type: 'call',
+ parseType: 'call',
functionName,
args,
}
@@ -62,7 +62,7 @@ const call = {
// Map contant nodes to constants before attempting to run the function
let mappedArgs = resolvedArgs.map(arg => {
- if (arg.type === 'constant'){
+ if (arg.parseType === 'constant'){
return arg.value;
} else {
return arg;
@@ -125,16 +125,10 @@ const call = {
} else {
type = argumentsExpected[index];
}
- if (typeof type === 'string'){
- // Type being a string means a constant node with matching type
- if (node.valueType !== type) failed = true;
- } else {
- // Otherwise check that the node is an instance of the given type
- if (!(node instanceof type)) failed = true;
- }
+ if (node.parseType !== type && node.valueType !== type) failed = true;
if (failed && fn === 'reduce'){
let typeName = typeof type === 'string' ? type : type.constructor.name;
- let nodeName = node.type;
+ let nodeName = node.parseType;
context.error(`Incorrect arguments to ${node.functionName} function` +
`expected ${typeName} got ${nodeName}`);
}
diff --git a/app/imports/parser/parseTree/constant.js b/app/imports/parser/parseTree/constant.js
index 4220bc96..e382b897 100644
--- a/app/imports/parser/parseTree/constant.js
+++ b/app/imports/parser/parseTree/constant.js
@@ -1,14 +1,13 @@
const constant = {
- create({value, valueType}){
- if (!valueType) throw `Expected valueType to be set, got ${valueType}`
+ create({value}){
return {
- type: 'constant',
- valueType,
+ parseType: 'constant',
+ valueType: typeof value,
value,
}
},
- compile(node){
- return node;
+ compile(node, scope, context){
+ return {result: node, context};
},
toString(node){
return `${node.value}`;
diff --git a/app/imports/parser/parseTree/error.js b/app/imports/parser/parseTree/error.js
index 8533061b..f79ac4af 100644
--- a/app/imports/parser/parseTree/error.js
+++ b/app/imports/parser/parseTree/error.js
@@ -1,13 +1,13 @@
const error = {
create({node, error}) {
return {
- type: 'error',
+ parseType: 'error',
node,
error,
}
},
- compile(node){
- return node;
+ compile(node, scope, context){
+ return {result: node, context};
},
toString(node){
return node.error.toString();
diff --git a/app/imports/parser/parseTree/if.js b/app/imports/parser/parseTree/if.js
index 369ff924..fc56bf66 100644
--- a/app/imports/parser/parseTree/if.js
+++ b/app/imports/parser/parseTree/if.js
@@ -1,10 +1,9 @@
-import resolve, {traverse, toString, mergeResolvedNodes} from '../resolve';
-import collate from '/imports/api/engine/computation/utility/collate.js';
+import resolve, { traverse, toString } from '../resolve';
const ifNode = {
create({condition, consequent, alternative}){
return {
- type: 'if',
+ parseType: 'if',
condition,
consequent,
alternative,
@@ -14,28 +13,14 @@ const ifNode = {
let {condition, consequent, alternative} = node;
return `${toString(condition)} ? ${toString(consequent)} : ${toString(alternative)}`
},
- resolve(fn, node, scope){
- let rest, condition, consequent, alternative;
- let resolved = {};
+ resolve(fn, node, scope, context){
+ let {result: condition} = resolve(fn, node.condition, scope, context);
- ({result: condition, ...rest} = resolve(fn, node.condition, scope));
- mergeResolvedNodes(resolved, rest);
-
- if (condition.type === 'constant'){
+ if (condition.parseType === 'constant'){
if (condition.value){
- ({result: consequent, ...rest} = resolve(fn, node.consequent, scope));
- mergeResolvedNodes(resolved, rest);
- return {
- result: consequent,
- ...resolved
- };
+ return resolve(fn, node.consequent, scope, context);
} else {
- ({result: alternative, ...rest} = resolve(fn, node.alternative, scope));
- mergeResolvedNodes(resolved, rest);
- return {
- result: alternative,
- ...resolved
- };
+ return resolve(fn, node.alternative, scope, context);
}
} else {
return {
@@ -44,7 +29,7 @@ const ifNode = {
consequent: node.consequent,
alternative: node.alternative,
}),
- ...resolved
+ context,
};
}
},
diff --git a/app/imports/parser/parseTree/index.js b/app/imports/parser/parseTree/index.js
index dcb19e2d..17b00a21 100644
--- a/app/imports/parser/parseTree/index.js
+++ b/app/imports/parser/parseTree/index.js
@@ -1,76 +1,73 @@
-import resolve, {traverse, toString, mergeResolvedNodes} from '../resolve';
+import resolve, { traverse, toString } from '../resolve';
+import error from './error';
const index = {
create({array, index}) {
return {
- type: 'index',
+ parseType: 'index',
array,
index,
}
},
- resolve(fn, node, scope){
- let index, array, rest;
- let resolved = {};
- ({result: index, ...rest} = resolve(fn, node.index, scope));
- mergeResolvedNodes(resolved, rest);
- ({result: array, ...rest} = resolve(fn, node.array, scope));
- mergeResolvedNodes(resolved, rest);
+ resolve(fn, node, scope, context){
+ let {result: index} = resolve(fn, node.index, scope, context);
+ let {result: array} = resolve(fn, node.array, scope, context);
if (
index.valueType === 'number' &&
Number.isInteger(index.value) &&
- array.type === 'array'
+ array.parseType === 'array'
){
if (index.value < 1 || index.value > array.values.length){
- mergeResolvedNodes(resolved, {
- errors: [{
- type: 'warning',
- message: `Index of ${index.value} is out of range for an array` +
- ` of length ${array.values.length}`,
- }]
+ context.error({
+ type: 'warning',
+ message: `Index of ${index.value} is out of range for an array` +
+ ` of length ${array.values.length}`,
});
}
let selection = array.values[index.value - 1];
if (selection){
- let result;
- ({result, ...rest} = resolve(fn, selection, scope));
- mergeResolvedNodes(resolved, rest)
- return result;
+ return resolve(fn, selection, scope, context);
}
} else if (fn === 'reduce'){
- if (!(array instanceof ArrayNode)){
- return new ErrorNode({
- node: node,
- error: 'Can not get the index of a non-array node: ' +
- node.array.toString() + ' = ' + array.toString(),
+ if (array.parseType !== 'array'){
+ const message = `Can not get the index of a non-array node: ${node.array.toString()} = ${array.toString()}`
+ context.error(message);
+ return {
+ result: error.create({
+ node,
+ error: message,
+ }),
context,
- });
+ };
} else if (!index.isInteger){
- return new ErrorNode({
- node: node,
- error: array.toString() + ' is not an integer index of the array',
+ const message = `${array.toString()} is not an integer index of the array`
+ context.error(message);
+ return {
+ result: error.create({
+ node,
+ error: message,
+ }),
context,
- });
+ };
}
}
- return new IndexNode({
- index,
- array,
- previousNodes: [node],
- });
+ return {
+ result: index.create({
+ index,
+ array,
+ }),
+ context,
+ };
},
- toString(){
- return `${node.array.toString()}[${node.index.toString()}]`;
+ toString(node){
+ return `${toString(node.array)}[${toString(node.index)}]`;
},
- traverse(fn){
+ traverse(node, fn){
fn(node);
- node.array.traverse(fn);
- node.index.traverse(fn);
+ traverse(node.array, fn);
+ traverse(node.index, fn);
},
- replaceChildren(fn){
- node.array = node.array.replaceNodes(fn);
- node.index = node.index.replaceNodes(fn);
- }
}
export default index;
diff --git a/app/imports/parser/parseTree/not.js b/app/imports/parser/parseTree/not.js
new file mode 100644
index 00000000..9488a96c
--- /dev/null
+++ b/app/imports/parser/parseTree/not.js
@@ -0,0 +1,37 @@
+import resolve, { toString, traverse } from '../resolve.js';
+import constant from './constant.js';
+
+const not = {
+ create({right}) {
+ return {
+ parseType: 'not',
+ right,
+ }
+ },
+ resolve(fn, node, scope, context){
+ const {result: right} = resolve(fn, node.right, scope, context);
+ if (right.parseType !== 'constant'){
+ return {
+ result: not.create({
+ right: right,
+ }),
+ context,
+ };
+ }
+ return {
+ result: constant.create({
+ value: !right.value,
+ }),
+ context,
+ };
+ },
+ toString(node){
+ return `!${toString(node.right)}`;
+ },
+ traverse(node, fn){
+ fn(node);
+ traverse(node.right, fn);
+ }
+}
+
+export default not;
diff --git a/app/imports/parser/parseTree/operator.js b/app/imports/parser/parseTree/operator.js
new file mode 100644
index 00000000..7cc4a099
--- /dev/null
+++ b/app/imports/parser/parseTree/operator.js
@@ -0,0 +1,76 @@
+import resolve, { toString, traverse } from '../resolve.js';
+import constant from './constant.js';
+
+const operator = {
+ create({left, right, operator, fn}) {
+ return {
+ parseType: 'operator',
+ left,
+ right,
+ operator,
+ fn
+ };
+ },
+ resolve(fn, node, scope, context){
+ const {result: leftNode} = resolve(fn, node.left, scope, context);
+ const {result: rightNode} = resolve(fn, node.right, scope, context);
+ let left, right;
+ if (leftNode.parseType !== 'constant' || rightNode.parseType !== 'constant'){
+ return {
+ result: operator.create({
+ left: leftNode,
+ right: rightNode,
+ operator: node.operator,
+ fn: node.fn,
+ }),
+ context,
+ };
+ } else {
+ left = leftNode.value;
+ right = rightNode.value;
+ }
+ let result;
+ switch(node.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 '%': result = left % right; break;
+ case '&':
+ case '&&': result = left && right; break;
+ 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;
+ case '>': result = left > right; break;
+ case '<': result = left < right; break;
+ case '>=': result = left >= right; break;
+ case '<=': result = left <= right; break;
+ }
+ return {
+ result: constant.create({
+ value: result,
+ }),
+ context,
+ };
+ },
+ toString(node){
+ let {left, right, operator} = node;
+ // special case of adding a negative number
+ if (operator === '+' && right.valueType === 'number' && right.value < 0){
+ return `${toString(left)} - ${-right.value}`
+ }
+ return `${toString(left)} ${operator} ${toString(right)}`;
+ },
+ traverse(node, fn){
+ fn(node);
+ traverse(node.left, fn);
+ traverse(node.right, fn);
+ },
+}
+
+export default operator;
diff --git a/app/imports/parser/parseTree/parenthesis.js b/app/imports/parser/parseTree/parenthesis.js
new file mode 100644
index 00000000..cd0cfa1a
--- /dev/null
+++ b/app/imports/parser/parseTree/parenthesis.js
@@ -0,0 +1,34 @@
+import resolve, { toString, traverse } from '../resolve.js';
+
+const parenthesis = {
+ create({content}) {
+ return {
+ parseType: 'parenthesis',
+ content,
+ };
+ },
+ resolve(fn, node, scope, context){
+ const {result: content} = resolve(fn, node.content, scope, context);
+ if (
+ fn === 'reduce' ||
+ content.parseType === 'constant' ||
+ content.parseType === 'error'
+ ){
+ return {result: content, context};
+ } else {
+ return {
+ result: parenthesis.create({content}),
+ context
+ };
+ }
+ },
+ toString(node){
+ return `(${toString(node.content)})`;
+ },
+ traverse(node, fn){
+ fn(node);
+ traverse(node.content, fn);
+ }
+}
+
+export default parenthesis;
diff --git a/app/imports/parser/parseTree/roll.js b/app/imports/parser/parseTree/roll.js
new file mode 100644
index 00000000..cc87cbcc
--- /dev/null
+++ b/app/imports/parser/parseTree/roll.js
@@ -0,0 +1,82 @@
+import resolve, { toString, traverse } from '../resolve.js';
+import error from './error.js';
+import rollArray from './rollArray.js';
+import roll from '/imports/parser/roll.js';
+import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
+
+const rollNode = {
+ create({left, right}) {
+ return {
+ parseType: 'roll',
+ left,
+ right,
+ };
+ },
+ compile(node, scope, context){
+ const {result: left} = resolve('compile', node.left, scope, context);
+ const {result: right} = resolve('compile', node.right, scope, context);
+ return {
+ result: rollNode.create({left, right}),
+ context,
+ };
+ },
+ toString(node){
+ if (
+ node.left.parseType === 'number' && node.left.value === 1
+ ){
+ return `d${toString(node.right)}`;
+ } else {
+ return `${toString(node.left)}d${toString(node.right)}`;
+ }
+ },
+ roll(node, scope, context){
+ const {result: left} = resolve('reduce', node.left, scope, context);
+ const {result: right} = resolve('reduce', node.right, scope, context);
+ if (left.parseType !== 'number' && !Number.isInteger(left.value)){
+ return errorResult('Number of dice is not an integer', node, context);
+ }
+ if (!right.isInteger){
+ return errorResult('Dice size is not an integer', node, context);
+ }
+ let number = left.value;
+ if (context.doubleRolls){
+ number *= 2;
+ }
+ if (number > STORAGE_LIMITS.diceRollValuesCount){
+ const message = `Can't roll more than ${STORAGE_LIMITS.diceRollValuesCount} dice at once`;
+ return errorResult(message, node, context);
+ }
+ let diceSize = right.value;
+ let values = roll(number, diceSize);
+ if (context){
+ context.storeRoll({number, diceSize, values});
+ }
+ return {
+ result: rollArray.create({
+ values,
+ diceSize,
+ diceNum: left.value,
+ }),
+ context
+ };
+ },
+ reduce(node, scope, context){
+ const {result} = rollNode.roll(node, scope, context);
+ return resolve('reduce', result, scope, context);
+ },
+ traverse(node, fn){
+ fn(node);
+ traverse(node.left, fn);
+ traverse(node.right, fn);
+ },
+}
+
+function errorResult(message, node, context){
+ context.error(message);
+ return {
+ result: error.create({ node, error: message }),
+ context,
+ };
+}
+
+export default rollNode;
diff --git a/app/imports/parser/parseTree/rollArray.js b/app/imports/parser/parseTree/rollArray.js
new file mode 100644
index 00000000..bb778947
--- /dev/null
+++ b/app/imports/parser/parseTree/rollArray.js
@@ -0,0 +1,35 @@
+import constant from './constant.js';
+
+const rollArray = {
+ constructor({values, diceSize, diceNum}) {
+ return {
+ parseType: 'rollArray',
+ values,
+ diceSize,
+ diceNum,
+ };
+ },
+ compile(node, scope, context){
+ return {
+ result: node,
+ context
+ };
+ },
+ toString(node){
+ return `[${node.values.join(', ')}]`;
+ },
+ reduce(node, scope, context){
+ const total = node.values.reduce((a, b) => a + b, 0);
+ return {
+ result: constant.create({
+ value: total,
+ }),
+ context,
+ };
+ },
+ traverse(node, fn){
+ return fn(node);
+ }
+}
+
+export default rollArray;
diff --git a/app/imports/parser/parseTree/symbol.js b/app/imports/parser/parseTree/symbol.js
new file mode 100644
index 00000000..208baa66
--- /dev/null
+++ b/app/imports/parser/parseTree/symbol.js
@@ -0,0 +1,61 @@
+import resolve from '../resolve.js';
+import constant from './constant.js';
+
+const symbol = {
+ create({name}){
+ return {
+ parseType: 'symbol',
+ name,
+ };
+ },
+ toString(node){
+ return `${node.name}`
+ },
+ compile(node, scope, context, calledFromReduce = false){
+ let value = scope && scope[node.name];
+ let type = typeof value;
+ // For objects, default to their .value
+ if (type === 'object'){
+ value = value.value;
+ type = typeof value;
+ }
+ // For parse nodes, compile and return
+ if (value?.parseType){
+ if (calledFromReduce){
+ return resolve('reduce', value, scope, context);
+ } else {
+ return resolve('compile', value, scope, context);
+ }
+ }
+ if (type === 'string' || type === 'number' || type === 'boolean'){
+ return {
+ result: constant.create({value}),
+ context,
+ };
+ } else if (type === 'undefined'){
+ return {
+ result: symbol.create({name: node.name}),
+ context,
+ };
+ } else {
+ throw new Meteor.Error(`Unexpected case: ${node.name} resolved to ${value}`);
+ }
+ },
+ reduce(node, scope, context){
+ let {result} = symbol.compile(node, scope, context, true);
+ if (result.parseType === 'symbol'){
+ context.error({
+ type: 'info',
+ message: `${result.toString()} not found, set to 0`
+ });
+ return {
+ result: constant.create({value: 0}),
+ context,
+ };
+ } else {
+ return {result, context};
+ }
+ }
+}
+
+export default symbol;
diff --git a/app/imports/parser/parseTree/unaryOperator.js b/app/imports/parser/parseTree/unaryOperator.js
new file mode 100644
index 00000000..1b348f90
--- /dev/null
+++ b/app/imports/parser/parseTree/unaryOperator.js
@@ -0,0 +1,46 @@
+import resolve, { toString, traverse } from '../resolve.js';
+import constant from './constant.js';
+
+const unaryOperator = {
+ create({operator, right}) {
+ return {
+ parseType: 'unaryOperator',
+ operator,
+ right,
+ };
+ },
+ resolve(fn, node, scope, context){
+ const {result: rightNode} = resolve(fn, node.right, scope, context);
+ if (rightNode.parseType !== 'number'){
+ return {
+ result: unaryOperator.create({
+ operator: node.operator,
+ right: rightNode,
+ }),
+ context,
+ };
+ }
+ let right = rightNode.value;
+ let result;
+ switch(node.operator){
+ case '-': result = -right; break;
+ case '+': result = +right; break;
+ }
+ return {
+ result: constant.create({
+ value: result,
+ parseType: typeof result,
+ }),
+ context,
+ };
+ },
+ toString(node){
+ return `${node.operator}${toString(node.right)}`;
+ },
+ traverse(node, fn){
+ fn(node);
+ traverse(node.right, fn);
+ },
+};
+
+export default unaryOperator;
diff --git a/app/imports/parser/parser.js b/app/imports/parser/parser.js
index 13f24579..8532055b 100644
--- a/app/imports/parser/parser.js
+++ b/app/imports/parser/parser.js
@@ -7,20 +7,6 @@ export default function parser(){
return new nearley.Parser(nearleyGrammar);
}
-export class CompilationContext {
- constructor({doubleRolls} = {}){
- this.errors = [];
- this.rolls = [];
- this.doubleRolls = doubleRolls;
- }
- storeError(e){
- this.errors.push(e);
- }
- storeRoll(r){
- this.rolls.push(r);
- }
-}
-
export function parse(string){
let parser = new nearley.Parser(nearleyGrammar);
parser.feed(string);
diff --git a/app/imports/parser/resolve.js b/app/imports/parser/resolve.js
index 86d2fad6..add29f25 100644
--- a/app/imports/parser/resolve.js
+++ b/app/imports/parser/resolve.js
@@ -1,13 +1,11 @@
-import nodeTypeIndex from './parseTree/index.js';
-import collate from '/imports/api/engine/computation/utility/collate.js';
-import Context from './ResolveContext.js';
+import nodeTypeIndex from './parseTree/_index.js';
// Takes a parse ndoe and computes it to a set detail level
// returns {result, context}
export default function resolve(fn, node, scope, context = new Context()){
- let type = nodeTypeIndex[node.type];
+ let type = nodeTypeIndex[node.parseType];
if (!type){
- throw new Meteor.Error(`Parse node type: ${node.type} not implemented`);
+ throw new Meteor.Error(`Parse node type: ${node.parseType} not implemented`);
}
if (type.resolve){
return type.resolve(fn, node, scope, context);
@@ -18,28 +16,49 @@ export default function resolve(fn, node, scope, context = new Context()){
} else if (type.compile){
return type.compile(node, scope, context)
} else {
- throw new Meteor.Error('Compile not implemented on ' + node.type);
+ throw new Meteor.Error('Compile not implemented on ' + node.parseType);
}
}
export function toString(node){
- let type = nodeTypeIndex[node.type];
+ let type = nodeTypeIndex[node.parseType];
if (!type.toString){
- throw new Meteor.Error('toString not implemented on ' + node.type);
+ throw new Meteor.Error('toString not implemented on ' + node.parseType);
}
return type.toString(node);
}
export function traverse(node, fn){
- let type = nodeTypeIndex[node.type];
+ if (!node) return;
+ let type = nodeTypeIndex[node.parseType];
+ if (!type){
+ console.error(node);
+ throw new Meteor.Error('Not valid parse node');
+ }
if (type.traverse){
return type.traverse(node, fn);
}
return fn(node);
}
-export function mergeResolvedNodes(main, other){
- main.errors = collate(main.errors, other.errors);
- main.rolls = collate(main.rolls, other.rolls);
- return main;
+export class Context {
+ constructor({errors = [], rolls = [], doubleRolls} = {}){
+ this.errors = errors;
+ this.rolls = rolls;
+ this.doubleRolls = doubleRolls;
+ }
+ error(e){
+ if (!e) return;
+ if (typeof e === 'string'){
+ this.errors.push({
+ type: 'error',
+ message: e,
+ });
+ } else {
+ this.errors.push(e);
+ }
+ }
+ roll(r){
+ this.rolls.push(r);
+ }
}