Parser now uses context to store details of the computation
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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(', ')}]`;
|
||||
|
||||
@@ -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(', ')})`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()}]`;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(){
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user