diff --git a/app/imports/parser/parseTree/AccessorNode.js b/app/imports/parser/parseTree/AccessorNode.js index a406af09..2f594e50 100644 --- a/app/imports/parser/parseTree/AccessorNode.js +++ b/app/imports/parser/parseTree/AccessorNode.js @@ -1,5 +1,4 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js'; -import ErrorNode from '/imports/parser/parseTree/ErrorNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; export default class AccessorNode extends ParseNode { @@ -22,19 +21,21 @@ export default class AccessorNode extends ParseNode { return new AccessorNode({ name: this.name, path: this.path, - previousNodes: [this], }); } else { throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`); } } - reduce(scope){ + reduce(scope, context){ let result = this.compile(scope); if (result instanceof AccessorNode){ - return new ErrorNode({ - node: result, - error: `${this.toString()} could not be resolved`, - previousNodes: [result], + context.storeError({ + type: 'info', + message: `${result.toString()} not found, set to 0` + }); + return new ConstantNode({ + type: 'number', + value: 0, }); } else { return result; diff --git a/app/imports/parser/parseTree/ArrayNode.js b/app/imports/parser/parseTree/ArrayNode.js index b74fc455..76e20a4f 100644 --- a/app/imports/parser/parseTree/ArrayNode.js +++ b/app/imports/parser/parseTree/ArrayNode.js @@ -5,9 +5,9 @@ export default class ArrayNode extends ParseNode { super(...arguments); this.values = values; } - compile(scope){ - let values = this.values.map(node => node.compile(scope)); - return new ArrayNode({values, previousNodes: [this]}); + resolve(fn, scope, context){ + let values = this.values.map(node => node[fn](scope, context)); + return new ArrayNode({values}); } toString(){ return `[${this.values.map(node => node.toString()).join(', ')}]`; diff --git a/app/imports/parser/parseTree/CallNode.js b/app/imports/parser/parseTree/CallNode.js index 52ab227c..31522dfa 100644 --- a/app/imports/parser/parseTree/CallNode.js +++ b/app/imports/parser/parseTree/CallNode.js @@ -8,21 +8,12 @@ export default class CallNode extends ParseNode { this.functionName = functionName; this.args = args; } - compile(scope){ - return this.resolve('compile', scope); - } - roll(scope){ - return this.resolve('roll', scope); - } - reduce(scope){ - return this.resolve('reduce', scope); - } - resolve(fn, scope){ + resolve(fn, scope, context){ let func = functions[this.functionName]; if (!func) return new ErrorNode({ node: this, error: `${this.functionName} is not a function`, - previousNodes: [this], + context, }); let args = castArgsToType({fn, scope, args: this.args, type: func.argumentType}); if (args.failed){ @@ -30,25 +21,30 @@ export default class CallNode extends ParseNode { return new ErrorNode({ node: this, error: 'Could not convert all arguments to the correct type', - previousNodes: [this], + context, }); } else { return new CallNode({ functionName: this.functionName, args: args, - previousNodes: [this], }); } } else { - let value = func.fn.apply(null, args); - console.log({args}) - return new ConstantNode({ - value, - type: 'number', - previousNodes: [this], - }); + try { + let value = func.fn.apply(null, args); + return new ConstantNode({ + value, + type: 'number', + previousNodes: [this], + }); + } catch (error) { + return new ErrorNode({ + node: this, + error, + context, + }); + } } - } toString(){ return `${this.functionName}(${this.args.map(node => node.toString()).join(', ')})`; diff --git a/app/imports/parser/parseTree/ConstantNode.js b/app/imports/parser/parseTree/ConstantNode.js index 268011eb..708bfaba 100644 --- a/app/imports/parser/parseTree/ConstantNode.js +++ b/app/imports/parser/parseTree/ConstantNode.js @@ -1,12 +1,11 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js'; export default class ConstantNode extends ParseNode { - constructor({value, type, rollTable}){ + constructor({value, type}){ super(...arguments); - // string, number, boolean, numberArray, uncompiledNode + // string, number, boolean, uncompiledNode this.type = type; this.value = value; - if (rollTable) this.rollTable = rollTable; } compile(){ return this; diff --git a/app/imports/parser/parseTree/ErrorNode.js b/app/imports/parser/parseTree/ErrorNode.js index 44fe2434..fd23b666 100644 --- a/app/imports/parser/parseTree/ErrorNode.js +++ b/app/imports/parser/parseTree/ErrorNode.js @@ -1,10 +1,16 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js'; export default class ErrorNode extends ParseNode { - constructor({node, error}) { + constructor({node, error, context}) { super(...arguments); this.node = node; this.error = error; + if (context){ + context.storeError({ + type: 'error', + message: error, + }); + } } compile(){ return this; diff --git a/app/imports/parser/parseTree/IfNode.js b/app/imports/parser/parseTree/IfNode.js index 7f1c9178..d088bae7 100644 --- a/app/imports/parser/parseTree/IfNode.js +++ b/app/imports/parser/parseTree/IfNode.js @@ -1,4 +1,5 @@ import ParseNode from '/imports/parser/parseTree/ParseNode.js'; +import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; export default class IfNode extends ParseNode { constructor({condition, consequent, alternative}){ @@ -11,25 +12,24 @@ export default class IfNode extends ParseNode { let {condition, consequent, alternative} = this; return `${condition.toString()} ? ${consequent.toString()} : ${alternative.toString()}` } - compile(scope){ - return this.resolve('compile', scope); - } - roll(scope){ - return this.resolve('roll', scope); - } - reduce(scope){ - return this.resolve('reduce', scope); - } resolve(fn, scope){ let condition = this.condition[fn](scope); - if (condition.value){ - let consequent = this.consequent[fn](scope); - consequent.inheritDetails([condition, this]); - return this.consequent[fn](scope); + if (condition instanceof ConstantNode){ + if (condition.value){ + let consequent = this.consequent[fn](scope); + consequent.inheritDetails([condition, this]); + return this.consequent[fn](scope); + } else { + let alternative = this.alternative[fn](scope); + alternative.inheritDetails([condition, this]); + return alternative; + } } else { - let alternative = this.alternative[fn](scope); - alternative.inheritDetails([condition, this]); - return alternative; + return new IfNode({ + condition: condition, + consequent: this.consequent, + alternative: this.alternative, + }); } } } diff --git a/app/imports/parser/parseTree/IndexNode.js b/app/imports/parser/parseTree/IndexNode.js index 8014d942..4359ae56 100644 --- a/app/imports/parser/parseTree/IndexNode.js +++ b/app/imports/parser/parseTree/IndexNode.js @@ -22,15 +22,6 @@ export default class IndexNode extends ParseNode { previousNodes: [this], }); } - compile(scope){ - return this.resolve('compile', scope); - } - roll(scope){ - return this.resolve('roll', scope); - } - reduce(scope){ - return this.resolve('reduce', scope); - } toString(){ return `${this.array.toString()}[${this.index.toString()}]`; } diff --git a/app/imports/parser/parseTree/OperatorNode.js b/app/imports/parser/parseTree/OperatorNode.js index 5b6830d8..14935a0f 100644 --- a/app/imports/parser/parseTree/OperatorNode.js +++ b/app/imports/parser/parseTree/OperatorNode.js @@ -9,15 +9,6 @@ export default class OperatorNode extends ParseNode { this.fn = fn; this.operator = operator; } - compile(scope){ - return this.resolve('compile', scope); - } - roll(scope){ - return this.resolve('roll', scope); - } - reduce(scope){ - return this.resolve('reduce', scope); - } resolve(fn, scope){ let leftNode = this.left[fn](scope); let rightNode = this.right[fn](scope); diff --git a/app/imports/parser/parseTree/ParenthesisNode.js b/app/imports/parser/parseTree/ParenthesisNode.js index 2df81c3e..afb8acac 100644 --- a/app/imports/parser/parseTree/ParenthesisNode.js +++ b/app/imports/parser/parseTree/ParenthesisNode.js @@ -5,21 +5,15 @@ export default class ParenthesisNode extends ParseNode { super(...arguments); this.content = content; } - compile(scope){ - return this.resolve('compile', scope); - } - roll(scope){ - return this.resolve('roll', scope); - } - reduce(scope){ - return this.resolve('reduce', scope); - } resolve(fn, scope){ let content = this.content[fn](scope); - if (content.constructor.name === 'ConstantNode'){ - return content; - } else { + if ( + content.constructor.name === 'IfNode' || + content.constructor.name === 'OperatorNode' + ){ return new ParenthesisNode({content, previousNodes: [this]}); + } else { + return content; } } toString(){ diff --git a/app/imports/parser/parseTree/ParseNode.js b/app/imports/parser/parseTree/ParseNode.js index 03f054ac..8f3ebd23 100644 --- a/app/imports/parser/parseTree/ParseNode.js +++ b/app/imports/parser/parseTree/ParseNode.js @@ -1,30 +1,29 @@ export default class ParseNode { - constructor({previousNodes, detail}){ - this.inheritDetails(previousNodes); - if (detail) this.pushDetails([detail]); - } - inheritDetails(nodes){ - if (!nodes || !nodes.length) return; - nodes.forEach(node => this.pushDetails(node.details)); - } - pushDetails(details){ - if (!details || !details.length) return; - if (!this.details) this.details = []; - details.forEach(detail => this.details.push(detail)); - } - compile(){ - // Returns a ParseNode, a ConstantNode if possible - throw new Meteor.Error('Compile not implemented on ' + this.constructor.name); - } 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){ - return this.compile(scope); + 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){ - return this.roll(scope); + reduce(scope, context){ + if (this.resolve){ + return this.resolve('reduce', scope, context); + } else { + return this.roll(scope, context); + } } } diff --git a/app/imports/parser/parseTree/RollNode.js b/app/imports/parser/parseTree/RollNode.js index 8f141832..070ac165 100644 --- a/app/imports/parser/parseTree/RollNode.js +++ b/app/imports/parser/parseTree/RollNode.js @@ -22,7 +22,7 @@ export default class RollNode extends ParseNode { return `${this.left.toString()}d${this.right.toString()}`; } } - roll(scope){ + roll(scope, context){ let left = this.left.reduce(scope); let right = this.right.reduce(scope); if (!left.isInteger){ @@ -51,11 +51,10 @@ export default class RollNode extends ParseNode { let roll = ~~(randomSrc.fraction() * diceSize) + 1 values.push(roll); } - return new RollArrayNode({ - values, - detail: {number, diceSize, values}, - previousNodes: [this, left, right], - }); + if (context){ + context.storeRoll({number, diceSize, values}); + } + return new RollArrayNode({values}); } reduce(scope){ return this.roll(scope).reduce(scope); diff --git a/app/imports/parser/parser.js b/app/imports/parser/parser.js index 12328243..d51b2733 100644 --- a/app/imports/parser/parser.js +++ b/app/imports/parser/parser.js @@ -6,3 +6,31 @@ const nearleyGrammar = nearley.Grammar.fromCompiled(grammar); export default function parser(){ return new nearley.Parser(nearleyGrammar); } + +export class CompilationContext { + constructor(){ + this.errors = []; + this.rolls = []; + } + 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); + let results = parser.results; + if (results.length === 1){ + return results[0]; + } else if (results.length === 0){ + // Valid parsing up until now, but need more. Unexpected end of input. + return null; + } else { + console.warn('Grammar is ambiguous!', {string, results}); + return results[0]; + } +}