Finished migrating parser to be object orientation free. All tests pass

This commit is contained in:
Stefan Zermatten
2021-10-03 13:54:17 +02:00
parent d30184434c
commit b78517b61f
35 changed files with 589 additions and 654 deletions

View File

@@ -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(

View File

@@ -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});
}
}

View File

@@ -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]

View File

@@ -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}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,38 @@
<template lang="html">
<v-text-field
ref="input"
v-bind="$attrs"
class="dc-text-field"
:loading="loading"
:error-messages="errors"
:value="safeValue"
:disabled="isDisabled"
:outlined="!regular"
@input="input"
@focus="focused = true"
@blur="focused = false"
@keyup="e => $emit('keyup', e)"
>
<template #append>
<slot name="value" />
</template>
</v-text-field>
</template>
<script lang="js">
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
export default {
mixins: [SmartInput],
props: {
regular: Boolean,
},
};
</script>
<style lang="css">
.dc-text-field .v-input__append-inner{
font-size: 12px;
margin-top: 36px;
}
</style>

View File

@@ -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){

View File

@@ -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}
];

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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',
});
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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,
});

View File

@@ -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
};

View File

@@ -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;

View File

@@ -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}`);
}

View File

@@ -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}`;

View File

@@ -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();

View File

@@ -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,
};
}
},

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}
}