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

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

View File

@@ -1,64 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ArrayNode from '/imports/parser/parseTree/ArrayNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
export default class IndexNode extends ParseNode {
constructor({array, index}) {
super(...arguments);
this.array = array;
this.index = index;
}
resolve(fn, scope, context){
let index = this.index[fn](scope, context);
let array = this.array[fn](scope, context);
if (index.isInteger && array instanceof ArrayNode){
if (index.value < 1 || index.value > array.values.length){
if (context){
context.storeError({
type: 'warning',
message: `Index of ${index.value} is out of range for an array` +
` of length ${array.values.length}`,
});
}
}
let selection = array.values[index.value - 1];
if (selection){
let result = selection[fn](scope, context);
return result;
}
} else if (fn === 'reduce'){
if (!(array instanceof ArrayNode)){
return new ErrorNode({
node: this,
error: 'Can not get the index of a non-array node: ' +
this.array.toString() + ' = ' + array.toString(),
context,
});
} else if (!index.isInteger){
return new ErrorNode({
node: this,
error: array.toString() + ' is not an integer index of the array',
context,
});
}
}
return new IndexNode({
index,
array,
previousNodes: [this],
});
}
toString(){
return `${this.array.toString()}[${this.index.toString()}]`;
}
traverse(fn){
fn(this);
this.array.traverse(fn);
this.index.traverse(fn);
}
replaceChildren(fn){
this.array = this.array.replaceNodes(fn);
this.index = this.index.replaceNodes(fn);
}
}

View File

@@ -1,34 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class NotOperatorNode extends ParseNode {
constructor({right}) {
super(...arguments);
this.right = right;
}
resolve(fn, scope, context){
let rightNode = this.right[fn](scope, context);
if (!(rightNode instanceof ConstantNode)){
return new NotOperatorNode({
right: rightNode,
});
}
let right = rightNode.value;
let result = !right;
return new ConstantNode({
value: result,
type: typeof result,
});
}
toString(){
let {right} = this;
return `!${right.toString()}`;
}
traverse(fn){
fn(this);
this.right.traverse(fn);
}
replaceChildren(fn){
this.right = this.right.replaceNodes(fn);
}
}

View File

@@ -1,71 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class OperatorNode extends ParseNode {
constructor({left, right, operator, fn}) {
super(...arguments);
this.left = left;
this.right = right;
this.fn = fn;
this.operator = operator;
}
resolve(fn, scope, context){
let leftNode = this.left[fn](scope, context);
let rightNode = this.right[fn](scope, context);
let left, right;
if (!(leftNode instanceof ConstantNode) || !(rightNode instanceof ConstantNode)){
return new OperatorNode({
left: leftNode,
right: rightNode,
operator: this.operator,
fn: this.fn,
});
} else {
left = leftNode.value;
right = rightNode.value;
}
let result;
switch(this.operator){
case '+': result = left + right; break;
case '-': result = left - right; break;
case '*': result = left * right; break;
case '/': result = left / right; break;
case '^': result = Math.pow(left, right); break;
case '%': result = left % right; break;
case '&':
case '&&': result = left && right; break;
case '|':
case '||': result = left || right; break;
case '=':
case '==': result = left == right; break;
case '===': result = left === right; break;
case '!=': result = left != right; break;
case '!==': result = left !== right; break;
case '>': result = left > right; break;
case '<': result = left < right; break;
case '>=': result = left >= right; break;
case '<=': result = left <= right; break;
}
return new ConstantNode({
value: result,
type: typeof result,
});
}
toString(){
let {left, right, operator} = this;
// special case of adding a negative number
if (operator === '+' && right.isNumber && right.value < 0){
return `${left.toString()} - ${-right.value}`
}
return `${left.toString()} ${operator} ${right.toString()}`;
}
traverse(fn){
fn(this);
this.left.traverse(fn);
this.right.traverse(fn);
}
replaceChildren(fn){
this.left = this.left.replaceNodes(fn);
this.right = this.right.replaceNodes(fn);
}
}

View File

@@ -1,30 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ParenthesisNode extends ParseNode {
constructor({content}) {
super(...arguments);
this.content = content;
}
resolve(fn, scope, context){
let content = this.content[fn](scope, context);
if (
fn === 'reduce' ||
content.constructor.name === 'ConstantNode' ||
content.constructor.name === 'ErrorNode'
){
return content;
} else {
return new ParenthesisNode({content});
}
}
toString(){
return `(${this.content.toString()})`;
}
traverse(fn){
fn(this);
this.content.traverse(fn);
}
replaceChildren(fn){
this.content = this.content.replaceNodes(fn);
}
}

View File

@@ -1,43 +0,0 @@
export default class ParseNode {
toString(){
throw new Meteor.Error('toString not implemented on ' + this.constructor.name);
}
compile(scope, context){
// Returns a ParseNode, a ConstantNode if possible
if(this.resolve) {
return this.resolve('compile', scope, context);
} else {
throw new Meteor.Error('Compile not implemented on ' + this.constructor.name);
}
}
// Compile, but turn rolls into arrays
roll(scope, context){
if (this.resolve){
return this.resolve('roll', scope, context);
} else {
return this.compile(scope, context);
}
}
// Compile, turn rolls into arrays, and reduce those arrays into single values
reduce(scope, context){
if (this.resolve){
return this.resolve('reduce', scope, context);
} else {
return this.roll(scope, context);
}
}
// If traverse isn't implemented, just apply it to the current node
traverse(fn){
fn(this);
}
// replace nodes, only replace nodes if fn returns a value
replaceNodes(fn){
let newNode = fn(this);
if (newNode) {
return newNode;
} else {
if (this.replaceChildren) this.replaceChildren(fn)
return this;
}
}
}

View File

@@ -1,22 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class RollArrayNode extends ParseNode {
constructor({values}) {
super(...arguments);
this.values = values;
}
compile(){
return this;
}
toString(){
return `[${this.values.join(', ')}]`;
}
reduce(){
let total = this.values.reduce((a, b) => a + b);
return new ConstantNode({
value: total,
type: 'number',
});
}
}

View File

@@ -1,70 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import RollArrayNode from '/imports/parser/parseTree/RollArrayNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
import roll from '/imports/parser/roll.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
export default class RollNode extends ParseNode {
constructor({left, right}) {
super(...arguments);
this.left = left;
this.right = right;
}
compile(scope, context){
let left = this.left.compile(scope, context);
let right = this.right.compile(scope, context);
return new RollNode({left, right, previousNodes: [this]});
}
toString(){
if (
this.left.isNumberNode && this.left.value === 1
){
return `d${this.right.toString()}`;
} else {
return `${this.left.toString()}d${this.right.toString()}`;
}
}
roll(scope, context){
let left = this.left.reduce(scope, context);
let right = this.right.reduce(scope, context);
if (!left.isInteger){
return new ErrorNode({
node: this,
error: 'Number of dice is not an integer',
});
}
if (!right.isInteger){
return new ErrorNode({
node: this,
error: 'Dice size is not an integer',
});
}
let number = left.value;
if (context.doubleRolls){
number *= 2;
}
if (number > STORAGE_LIMITS.diceRollValuesCount) return new ErrorNode({
node: this,
error: `Can't roll more than ${STORAGE_LIMITS.diceRollValuesCount} dice at once`,
context,
});
let diceSize = right.value;
let values = roll(number, diceSize);
if (context){
context.storeRoll({number, diceSize, values});
}
return new RollArrayNode({values});
}
reduce(scope, context){
return this.roll(scope, context).reduce(scope, context);
}
traverse(fn){
fn(this);
this.left.traverse(fn);
this.right.traverse(fn);
}
replaceChildren(fn){
this.left = this.left.replaceNodes(fn);
this.right = this.right.replaceNodes(fn);
}
}

View File

@@ -1,53 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class SymbolNode extends ParseNode {
constructor({name}){
super(...arguments);
this.name = name;
}
toString(){
return `${this.name}`
}
compile(scope, context, calledFromReduce = false){
let value = scope && scope[this.name];
let type = typeof value;
// For objects, default to their .value
if (type === 'object'){
value = value.value;
type = typeof value;
}
// For parse nodes, compile and return
if (value instanceof ParseNode){
if (calledFromReduce){
return value.reduce(scope, context);
} else {
return value.compile(scope, context);
}
}
if (type === 'string' || type === 'number' || type === 'boolean'){
return new ConstantNode({value, type});
} else if (type === 'undefined'){
return new SymbolNode({
name: this.name,
});
} else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
}
}
reduce(scope, context){
let result = this.compile(scope, context, true);
if (result instanceof SymbolNode){
if (context) context.storeError({
type: 'info',
message: `${result.toString()} not found, set to 0`
});
return new ConstantNode({
type: 'number',
value: 0,
});
} else {
return result;
}
}
}

View File

@@ -1,40 +0,0 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class UnaryOperatorNode extends ParseNode {
constructor({operator, right}) {
super(...arguments);
this.operator = operator;
this.right = right;
}
resolve(fn, scope, context){
let rightNode = this.right[fn](scope, context);
if (rightNode.type !== 'number'){
return new UnaryOperatorNode({
operator: this.operator,
right: rightNode,
});
}
let right = rightNode.value;
let result;
switch(this.operator){
case '-': result = -right; break;
case '+': result = +right; break;
}
return new ConstantNode({
value: result,
type: typeof result,
});
}
toString(){
let {right, operator} = this;
return `${operator}${right.toString()}`;
}
traverse(fn){
fn(this);
this.right.traverse(fn);
}
replaceChildren(fn){
this.right = this.right.replaceNodes(fn);
}
}

View File

@@ -1,5 +1,31 @@
import accessor from './accessor.js';
import array from './array.js';
import call from './call.js';
import constant from './constant.js';
import error from './error.js';
import ifNode from './if.js';
import index from './index.js';
import not from './not.js';
import operator from './operator.js';
import parenthesis from './parenthesis.js';
import roll from './roll.js';
import rollArray from './rollArray.js';
import symbol from './symbol.js';
import unaryOperator from './unaryOperator.js';
export default Object.freeze({
accessor,
array,
call,
constant,
error,
if: ifNode,
index,
not,
operator,
parenthesis,
roll,
rollArray,
symbol,
unaryOperator,
});

View File

@@ -3,7 +3,7 @@ import constant from './constant.js';
const accessor = {
create({name, path}) {
return {
type: 'accessor',
parseType: 'accessor',
path,
name,
};
@@ -45,12 +45,11 @@ const accessor = {
},
reduce(node, scope, context){
let { result } = accessor.compile(node, scope, context);
if (result.type === 'accessor'){
if (result.parseType === 'accessor'){
context.error(`${accessor.toString(result)} not found, set to 0`);
return {
result: constant.create({
value: 0,
valueType: 'number',
}),
context
};

View File

@@ -1,10 +1,10 @@
import constant from './constant.js';
import resolve, { toString, traverse } from '../resolve.js';
import constant from './constant.js';
const array = {
create({values}) {
return {
type: 'array',
parseType: 'array',
values,
};
},
@@ -24,7 +24,7 @@ const array = {
});
return array.create({values});
},
resolve(fn, node, scope){
resolve(fn, node, scope, context){
let values = node.values.map(node => {
let { result } = resolve(fn, node, scope, context);
return result;

View File

@@ -1,12 +1,12 @@
import error from './error.js';
import constant from './constant.js';
import functions from '/imports/parser/functions.js';
import resolve, { toString, traverse, mergeResolvedNodes } from '../resolve.js';
import resolve, { toString, traverse } from '../resolve.js';
const call = {
create({functionName, args}) {
return {
type: 'call',
parseType: 'call',
functionName,
args,
}
@@ -62,7 +62,7 @@ const call = {
// Map contant nodes to constants before attempting to run the function
let mappedArgs = resolvedArgs.map(arg => {
if (arg.type === 'constant'){
if (arg.parseType === 'constant'){
return arg.value;
} else {
return arg;
@@ -125,16 +125,10 @@ const call = {
} else {
type = argumentsExpected[index];
}
if (typeof type === 'string'){
// Type being a string means a constant node with matching type
if (node.valueType !== type) failed = true;
} else {
// Otherwise check that the node is an instance of the given type
if (!(node instanceof type)) failed = true;
}
if (node.parseType !== type && node.valueType !== type) failed = true;
if (failed && fn === 'reduce'){
let typeName = typeof type === 'string' ? type : type.constructor.name;
let nodeName = node.type;
let nodeName = node.parseType;
context.error(`Incorrect arguments to ${node.functionName} function` +
`expected ${typeName} got ${nodeName}`);
}

View File

@@ -1,14 +1,13 @@
const constant = {
create({value, valueType}){
if (!valueType) throw `Expected valueType to be set, got ${valueType}`
create({value}){
return {
type: 'constant',
valueType,
parseType: 'constant',
valueType: typeof value,
value,
}
},
compile(node){
return node;
compile(node, scope, context){
return {result: node, context};
},
toString(node){
return `${node.value}`;

View File

@@ -1,13 +1,13 @@
const error = {
create({node, error}) {
return {
type: 'error',
parseType: 'error',
node,
error,
}
},
compile(node){
return node;
compile(node, scope, context){
return {result: node, context};
},
toString(node){
return node.error.toString();

View File

@@ -1,10 +1,9 @@
import resolve, {traverse, toString, mergeResolvedNodes} from '../resolve';
import collate from '/imports/api/engine/computation/utility/collate.js';
import resolve, { traverse, toString } from '../resolve';
const ifNode = {
create({condition, consequent, alternative}){
return {
type: 'if',
parseType: 'if',
condition,
consequent,
alternative,
@@ -14,28 +13,14 @@ const ifNode = {
let {condition, consequent, alternative} = node;
return `${toString(condition)} ? ${toString(consequent)} : ${toString(alternative)}`
},
resolve(fn, node, scope){
let rest, condition, consequent, alternative;
let resolved = {};
resolve(fn, node, scope, context){
let {result: condition} = resolve(fn, node.condition, scope, context);
({result: condition, ...rest} = resolve(fn, node.condition, scope));
mergeResolvedNodes(resolved, rest);
if (condition.type === 'constant'){
if (condition.parseType === 'constant'){
if (condition.value){
({result: consequent, ...rest} = resolve(fn, node.consequent, scope));
mergeResolvedNodes(resolved, rest);
return {
result: consequent,
...resolved
};
return resolve(fn, node.consequent, scope, context);
} else {
({result: alternative, ...rest} = resolve(fn, node.alternative, scope));
mergeResolvedNodes(resolved, rest);
return {
result: alternative,
...resolved
};
return resolve(fn, node.alternative, scope, context);
}
} else {
return {
@@ -44,7 +29,7 @@ const ifNode = {
consequent: node.consequent,
alternative: node.alternative,
}),
...resolved
context,
};
}
},

View File

@@ -1,76 +1,73 @@
import resolve, {traverse, toString, mergeResolvedNodes} from '../resolve';
import resolve, { traverse, toString } from '../resolve';
import error from './error';
const index = {
create({array, index}) {
return {
type: 'index',
parseType: 'index',
array,
index,
}
},
resolve(fn, node, scope){
let index, array, rest;
let resolved = {};
({result: index, ...rest} = resolve(fn, node.index, scope));
mergeResolvedNodes(resolved, rest);
({result: array, ...rest} = resolve(fn, node.array, scope));
mergeResolvedNodes(resolved, rest);
resolve(fn, node, scope, context){
let {result: index} = resolve(fn, node.index, scope, context);
let {result: array} = resolve(fn, node.array, scope, context);
if (
index.valueType === 'number' &&
Number.isInteger(index.value) &&
array.type === 'array'
array.parseType === 'array'
){
if (index.value < 1 || index.value > array.values.length){
mergeResolvedNodes(resolved, {
errors: [{
type: 'warning',
message: `Index of ${index.value} is out of range for an array` +
` of length ${array.values.length}`,
}]
context.error({
type: 'warning',
message: `Index of ${index.value} is out of range for an array` +
` of length ${array.values.length}`,
});
}
let selection = array.values[index.value - 1];
if (selection){
let result;
({result, ...rest} = resolve(fn, selection, scope));
mergeResolvedNodes(resolved, rest)
return result;
return resolve(fn, selection, scope, context);
}
} else if (fn === 'reduce'){
if (!(array instanceof ArrayNode)){
return new ErrorNode({
node: node,
error: 'Can not get the index of a non-array node: ' +
node.array.toString() + ' = ' + array.toString(),
if (array.parseType !== 'array'){
const message = `Can not get the index of a non-array node: ${node.array.toString()} = ${array.toString()}`
context.error(message);
return {
result: error.create({
node,
error: message,
}),
context,
});
};
} else if (!index.isInteger){
return new ErrorNode({
node: node,
error: array.toString() + ' is not an integer index of the array',
const message = `${array.toString()} is not an integer index of the array`
context.error(message);
return {
result: error.create({
node,
error: message,
}),
context,
});
};
}
}
return new IndexNode({
index,
array,
previousNodes: [node],
});
return {
result: index.create({
index,
array,
}),
context,
};
},
toString(){
return `${node.array.toString()}[${node.index.toString()}]`;
toString(node){
return `${toString(node.array)}[${toString(node.index)}]`;
},
traverse(fn){
traverse(node, fn){
fn(node);
node.array.traverse(fn);
node.index.traverse(fn);
traverse(node.array, fn);
traverse(node.index, fn);
},
replaceChildren(fn){
node.array = node.array.replaceNodes(fn);
node.index = node.index.replaceNodes(fn);
}
}
export default index;

View File

@@ -0,0 +1,37 @@
import resolve, { toString, traverse } from '../resolve.js';
import constant from './constant.js';
const not = {
create({right}) {
return {
parseType: 'not',
right,
}
},
resolve(fn, node, scope, context){
const {result: right} = resolve(fn, node.right, scope, context);
if (right.parseType !== 'constant'){
return {
result: not.create({
right: right,
}),
context,
};
}
return {
result: constant.create({
value: !right.value,
}),
context,
};
},
toString(node){
return `!${toString(node.right)}`;
},
traverse(node, fn){
fn(node);
traverse(node.right, fn);
}
}
export default not;

View File

@@ -0,0 +1,76 @@
import resolve, { toString, traverse } from '../resolve.js';
import constant from './constant.js';
const operator = {
create({left, right, operator, fn}) {
return {
parseType: 'operator',
left,
right,
operator,
fn
};
},
resolve(fn, node, scope, context){
const {result: leftNode} = resolve(fn, node.left, scope, context);
const {result: rightNode} = resolve(fn, node.right, scope, context);
let left, right;
if (leftNode.parseType !== 'constant' || rightNode.parseType !== 'constant'){
return {
result: operator.create({
left: leftNode,
right: rightNode,
operator: node.operator,
fn: node.fn,
}),
context,
};
} else {
left = leftNode.value;
right = rightNode.value;
}
let result;
switch(node.operator){
case '+': result = left + right; break;
case '-': result = left - right; break;
case '*': result = left * right; break;
case '/': result = left / right; break;
case '^': result = Math.pow(left, right); break;
case '%': result = left % right; break;
case '&':
case '&&': result = left && right; break;
case '|':
case '||': result = left || right; break;
case '=':
case '==': result = left == right; break;
case '===': result = left === right; break;
case '!=': result = left != right; break;
case '!==': result = left !== right; break;
case '>': result = left > right; break;
case '<': result = left < right; break;
case '>=': result = left >= right; break;
case '<=': result = left <= right; break;
}
return {
result: constant.create({
value: result,
}),
context,
};
},
toString(node){
let {left, right, operator} = node;
// special case of adding a negative number
if (operator === '+' && right.valueType === 'number' && right.value < 0){
return `${toString(left)} - ${-right.value}`
}
return `${toString(left)} ${operator} ${toString(right)}`;
},
traverse(node, fn){
fn(node);
traverse(node.left, fn);
traverse(node.right, fn);
},
}
export default operator;

View File

@@ -0,0 +1,34 @@
import resolve, { toString, traverse } from '../resolve.js';
const parenthesis = {
create({content}) {
return {
parseType: 'parenthesis',
content,
};
},
resolve(fn, node, scope, context){
const {result: content} = resolve(fn, node.content, scope, context);
if (
fn === 'reduce' ||
content.parseType === 'constant' ||
content.parseType === 'error'
){
return {result: content, context};
} else {
return {
result: parenthesis.create({content}),
context
};
}
},
toString(node){
return `(${toString(node.content)})`;
},
traverse(node, fn){
fn(node);
traverse(node.content, fn);
}
}
export default parenthesis;

View File

@@ -0,0 +1,82 @@
import resolve, { toString, traverse } from '../resolve.js';
import error from './error.js';
import rollArray from './rollArray.js';
import roll from '/imports/parser/roll.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
const rollNode = {
create({left, right}) {
return {
parseType: 'roll',
left,
right,
};
},
compile(node, scope, context){
const {result: left} = resolve('compile', node.left, scope, context);
const {result: right} = resolve('compile', node.right, scope, context);
return {
result: rollNode.create({left, right}),
context,
};
},
toString(node){
if (
node.left.parseType === 'number' && node.left.value === 1
){
return `d${toString(node.right)}`;
} else {
return `${toString(node.left)}d${toString(node.right)}`;
}
},
roll(node, scope, context){
const {result: left} = resolve('reduce', node.left, scope, context);
const {result: right} = resolve('reduce', node.right, scope, context);
if (left.parseType !== 'number' && !Number.isInteger(left.value)){
return errorResult('Number of dice is not an integer', node, context);
}
if (!right.isInteger){
return errorResult('Dice size is not an integer', node, context);
}
let number = left.value;
if (context.doubleRolls){
number *= 2;
}
if (number > STORAGE_LIMITS.diceRollValuesCount){
const message = `Can't roll more than ${STORAGE_LIMITS.diceRollValuesCount} dice at once`;
return errorResult(message, node, context);
}
let diceSize = right.value;
let values = roll(number, diceSize);
if (context){
context.storeRoll({number, diceSize, values});
}
return {
result: rollArray.create({
values,
diceSize,
diceNum: left.value,
}),
context
};
},
reduce(node, scope, context){
const {result} = rollNode.roll(node, scope, context);
return resolve('reduce', result, scope, context);
},
traverse(node, fn){
fn(node);
traverse(node.left, fn);
traverse(node.right, fn);
},
}
function errorResult(message, node, context){
context.error(message);
return {
result: error.create({ node, error: message }),
context,
};
}
export default rollNode;

View File

@@ -0,0 +1,35 @@
import constant from './constant.js';
const rollArray = {
constructor({values, diceSize, diceNum}) {
return {
parseType: 'rollArray',
values,
diceSize,
diceNum,
};
},
compile(node, scope, context){
return {
result: node,
context
};
},
toString(node){
return `[${node.values.join(', ')}]`;
},
reduce(node, scope, context){
const total = node.values.reduce((a, b) => a + b, 0);
return {
result: constant.create({
value: total,
}),
context,
};
},
traverse(node, fn){
return fn(node);
}
}
export default rollArray;

View File

@@ -0,0 +1,61 @@
import resolve from '../resolve.js';
import constant from './constant.js';
const symbol = {
create({name}){
return {
parseType: 'symbol',
name,
};
},
toString(node){
return `${node.name}`
},
compile(node, scope, context, calledFromReduce = false){
let value = scope && scope[node.name];
let type = typeof value;
// For objects, default to their .value
if (type === 'object'){
value = value.value;
type = typeof value;
}
// For parse nodes, compile and return
if (value?.parseType){
if (calledFromReduce){
return resolve('reduce', value, scope, context);
} else {
return resolve('compile', value, scope, context);
}
}
if (type === 'string' || type === 'number' || type === 'boolean'){
return {
result: constant.create({value}),
context,
};
} else if (type === 'undefined'){
return {
result: symbol.create({name: node.name}),
context,
};
} else {
throw new Meteor.Error(`Unexpected case: ${node.name} resolved to ${value}`);
}
},
reduce(node, scope, context){
let {result} = symbol.compile(node, scope, context, true);
if (result.parseType === 'symbol'){
context.error({
type: 'info',
message: `${result.toString()} not found, set to 0`
});
return {
result: constant.create({value: 0}),
context,
};
} else {
return {result, context};
}
}
}
export default symbol;

View File

@@ -0,0 +1,46 @@
import resolve, { toString, traverse } from '../resolve.js';
import constant from './constant.js';
const unaryOperator = {
create({operator, right}) {
return {
parseType: 'unaryOperator',
operator,
right,
};
},
resolve(fn, node, scope, context){
const {result: rightNode} = resolve(fn, node.right, scope, context);
if (rightNode.parseType !== 'number'){
return {
result: unaryOperator.create({
operator: node.operator,
right: rightNode,
}),
context,
};
}
let right = rightNode.value;
let result;
switch(node.operator){
case '-': result = -right; break;
case '+': result = +right; break;
}
return {
result: constant.create({
value: result,
parseType: typeof result,
}),
context,
};
},
toString(node){
return `${node.operator}${toString(node.right)}`;
},
traverse(node, fn){
fn(node);
traverse(node.right, fn);
},
};
export default unaryOperator;