Finished migrating parser to be object orientation free. All tests pass
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user