Parser now uses context to store details of the computation

This commit is contained in:
Stefan Zermatten
2020-09-18 10:14:53 +02:00
parent b69ad6c306
commit 06f17a6d33
12 changed files with 112 additions and 108 deletions

View File

@@ -1,5 +1,4 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class AccessorNode extends ParseNode { export default class AccessorNode extends ParseNode {
@@ -22,19 +21,21 @@ export default class AccessorNode extends ParseNode {
return new AccessorNode({ return new AccessorNode({
name: this.name, name: this.name,
path: this.path, path: this.path,
previousNodes: [this],
}); });
} else { } else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`); throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
} }
} }
reduce(scope){ reduce(scope, context){
let result = this.compile(scope); let result = this.compile(scope);
if (result instanceof AccessorNode){ if (result instanceof AccessorNode){
return new ErrorNode({ context.storeError({
node: result, type: 'info',
error: `${this.toString()} could not be resolved`, message: `${result.toString()} not found, set to 0`
previousNodes: [result], });
return new ConstantNode({
type: 'number',
value: 0,
}); });
} else { } else {
return result; return result;

View File

@@ -5,9 +5,9 @@ export default class ArrayNode extends ParseNode {
super(...arguments); super(...arguments);
this.values = values; this.values = values;
} }
compile(scope){ resolve(fn, scope, context){
let values = this.values.map(node => node.compile(scope)); let values = this.values.map(node => node[fn](scope, context));
return new ArrayNode({values, previousNodes: [this]}); return new ArrayNode({values});
} }
toString(){ toString(){
return `[${this.values.map(node => node.toString()).join(', ')}]`; return `[${this.values.map(node => node.toString()).join(', ')}]`;

View File

@@ -8,21 +8,12 @@ export default class CallNode extends ParseNode {
this.functionName = functionName; this.functionName = functionName;
this.args = args; this.args = args;
} }
compile(scope){ resolve(fn, scope, context){
return this.resolve('compile', scope);
}
roll(scope){
return this.resolve('roll', scope);
}
reduce(scope){
return this.resolve('reduce', scope);
}
resolve(fn, scope){
let func = functions[this.functionName]; let func = functions[this.functionName];
if (!func) return new ErrorNode({ if (!func) return new ErrorNode({
node: this, node: this,
error: `${this.functionName} is not a function`, error: `${this.functionName} is not a function`,
previousNodes: [this], context,
}); });
let args = castArgsToType({fn, scope, args: this.args, type: func.argumentType}); let args = castArgsToType({fn, scope, args: this.args, type: func.argumentType});
if (args.failed){ if (args.failed){
@@ -30,25 +21,30 @@ export default class CallNode extends ParseNode {
return new ErrorNode({ return new ErrorNode({
node: this, node: this,
error: 'Could not convert all arguments to the correct type', error: 'Could not convert all arguments to the correct type',
previousNodes: [this], context,
}); });
} else { } else {
return new CallNode({ return new CallNode({
functionName: this.functionName, functionName: this.functionName,
args: args, args: args,
previousNodes: [this],
}); });
} }
} else { } else {
let value = func.fn.apply(null, args); try {
console.log({args}) let value = func.fn.apply(null, args);
return new ConstantNode({ return new ConstantNode({
value, value,
type: 'number', type: 'number',
previousNodes: [this], previousNodes: [this],
}); });
} catch (error) {
return new ErrorNode({
node: this,
error,
context,
});
}
} }
} }
toString(){ toString(){
return `${this.functionName}(${this.args.map(node => node.toString()).join(', ')})`; return `${this.functionName}(${this.args.map(node => node.toString()).join(', ')})`;

View File

@@ -1,12 +1,11 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ConstantNode extends ParseNode { export default class ConstantNode extends ParseNode {
constructor({value, type, rollTable}){ constructor({value, type}){
super(...arguments); super(...arguments);
// string, number, boolean, numberArray, uncompiledNode // string, number, boolean, uncompiledNode
this.type = type; this.type = type;
this.value = value; this.value = value;
if (rollTable) this.rollTable = rollTable;
} }
compile(){ compile(){
return this; return this;

View File

@@ -1,10 +1,16 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ErrorNode extends ParseNode { export default class ErrorNode extends ParseNode {
constructor({node, error}) { constructor({node, error, context}) {
super(...arguments); super(...arguments);
this.node = node; this.node = node;
this.error = error; this.error = error;
if (context){
context.storeError({
type: 'error',
message: error,
});
}
} }
compile(){ compile(){
return this; return this;

View File

@@ -1,4 +1,5 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js'; import ParseNode from '/imports/parser/parseTree/ParseNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
export default class IfNode extends ParseNode { export default class IfNode extends ParseNode {
constructor({condition, consequent, alternative}){ constructor({condition, consequent, alternative}){
@@ -11,25 +12,24 @@ export default class IfNode extends ParseNode {
let {condition, consequent, alternative} = this; let {condition, consequent, alternative} = this;
return `${condition.toString()} ? ${consequent.toString()} : ${alternative.toString()}` 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){ resolve(fn, scope){
let condition = this.condition[fn](scope); let condition = this.condition[fn](scope);
if (condition.value){ if (condition instanceof ConstantNode){
let consequent = this.consequent[fn](scope); if (condition.value){
consequent.inheritDetails([condition, this]); let consequent = this.consequent[fn](scope);
return 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 { } else {
let alternative = this.alternative[fn](scope); return new IfNode({
alternative.inheritDetails([condition, this]); condition: condition,
return alternative; consequent: this.consequent,
alternative: this.alternative,
});
} }
} }
} }

View File

@@ -22,15 +22,6 @@ export default class IndexNode extends ParseNode {
previousNodes: [this], previousNodes: [this],
}); });
} }
compile(scope){
return this.resolve('compile', scope);
}
roll(scope){
return this.resolve('roll', scope);
}
reduce(scope){
return this.resolve('reduce', scope);
}
toString(){ toString(){
return `${this.array.toString()}[${this.index.toString()}]`; return `${this.array.toString()}[${this.index.toString()}]`;
} }

View File

@@ -9,15 +9,6 @@ export default class OperatorNode extends ParseNode {
this.fn = fn; this.fn = fn;
this.operator = operator; 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){ resolve(fn, scope){
let leftNode = this.left[fn](scope); let leftNode = this.left[fn](scope);
let rightNode = this.right[fn](scope); let rightNode = this.right[fn](scope);

View File

@@ -5,21 +5,15 @@ export default class ParenthesisNode extends ParseNode {
super(...arguments); super(...arguments);
this.content = content; 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){ resolve(fn, scope){
let content = this.content[fn](scope); let content = this.content[fn](scope);
if (content.constructor.name === 'ConstantNode'){ if (
return content; content.constructor.name === 'IfNode' ||
} else { content.constructor.name === 'OperatorNode'
){
return new ParenthesisNode({content, previousNodes: [this]}); return new ParenthesisNode({content, previousNodes: [this]});
} else {
return content;
} }
} }
toString(){ toString(){

View File

@@ -1,30 +1,29 @@
export default class ParseNode { 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(){ toString(){
throw new Meteor.Error('toString not implemented on ' + this.constructor.name); 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 // Compile, but turn rolls into arrays
roll(scope){ roll(scope, context){
return this.compile(scope); 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 // Compile, turn rolls into arrays, and reduce those arrays into single values
reduce(scope){ reduce(scope, context){
return this.roll(scope); if (this.resolve){
return this.resolve('reduce', scope, context);
} else {
return this.roll(scope, context);
}
} }
} }

View File

@@ -22,7 +22,7 @@ export default class RollNode extends ParseNode {
return `${this.left.toString()}d${this.right.toString()}`; return `${this.left.toString()}d${this.right.toString()}`;
} }
} }
roll(scope){ roll(scope, context){
let left = this.left.reduce(scope); let left = this.left.reduce(scope);
let right = this.right.reduce(scope); let right = this.right.reduce(scope);
if (!left.isInteger){ if (!left.isInteger){
@@ -51,11 +51,10 @@ export default class RollNode extends ParseNode {
let roll = ~~(randomSrc.fraction() * diceSize) + 1 let roll = ~~(randomSrc.fraction() * diceSize) + 1
values.push(roll); values.push(roll);
} }
return new RollArrayNode({ if (context){
values, context.storeRoll({number, diceSize, values});
detail: {number, diceSize, values}, }
previousNodes: [this, left, right], return new RollArrayNode({values});
});
} }
reduce(scope){ reduce(scope){
return this.roll(scope).reduce(scope); return this.roll(scope).reduce(scope);

View File

@@ -6,3 +6,31 @@ const nearleyGrammar = nearley.Grammar.fromCompiled(grammar);
export default function parser(){ export default function parser(){
return new nearley.Parser(nearleyGrammar); 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];
}
}