Finished migrating parser to be object orientation free. All tests pass
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
38
app/imports/parser/TextField.vue
Normal file
38
app/imports/parser/TextField.vue
Normal 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>
|
||||
@@ -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){
|
||||
|
||||
@@ -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}
|
||||
];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
37
app/imports/parser/parseTree/not.js
Normal file
37
app/imports/parser/parseTree/not.js
Normal 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;
|
||||
76
app/imports/parser/parseTree/operator.js
Normal file
76
app/imports/parser/parseTree/operator.js
Normal 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;
|
||||
34
app/imports/parser/parseTree/parenthesis.js
Normal file
34
app/imports/parser/parseTree/parenthesis.js
Normal 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;
|
||||
82
app/imports/parser/parseTree/roll.js
Normal file
82
app/imports/parser/parseTree/roll.js
Normal 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;
|
||||
35
app/imports/parser/parseTree/rollArray.js
Normal file
35
app/imports/parser/parseTree/rollArray.js
Normal 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;
|
||||
61
app/imports/parser/parseTree/symbol.js
Normal file
61
app/imports/parser/parseTree/symbol.js
Normal 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;
|
||||
46
app/imports/parser/parseTree/unaryOperator.js
Normal file
46
app/imports/parser/parseTree/unaryOperator.js
Normal 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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user