Separated parser class nodes and began writing compile methods

This commit is contained in:
Thaum Rystra
2019-04-06 10:56:53 +02:00
parent d21827106c
commit b7b0ac9c00
14 changed files with 167 additions and 207 deletions

View File

@@ -12,6 +12,8 @@ import Proficiencies from "/imports/api/creature/properties/Proficiencies.js";
import DamageMultipliers from "/imports/api/creature/properties/DamageMultipliers.js";
import Classes from "/imports/api/creature/properties/Classes.js";
import * as math from 'mathjs';
import parser from '/imports/parser/parser.js';
if (Meteor.isClient) console.log({parser});
export const recomputeCreature = new ValidatedMethod({

View File

@@ -0,0 +1,7 @@
// All of the compile functions are provided for use in compiling parse trees
// Every compile function takes in ParseNodes as arguements and returns a single
// ConstantNode as a result
const compileFunctions = {};
export compileFunctions;

View File

@@ -0,0 +1,15 @@
export default function sum(inputNode) {
let node = inputNode.roll();
if (node.type === 'numberArray'){
let total = node.value.reduce((total, num) => total + num, 0);
return new ConstantNode({type: 'number', value: total});
} else {
let errors = node.errors || [];
errors.push(`Could not sum ${node.value}`);
return new ConstantNode({
type: 'uncompiledNode',
value: node.value,
errors,
});
}
}

View File

@@ -1,9 +1,13 @@
// Generated automatically by nearley, version 2.16.0
// http://github.com/Hardmath123/nearley
(function () {
function id(x) { return x[0]; }
const moo = require("moo");
import CallNode from '/imports/parser/parseTree/CallNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import IfNode from '/imports/parser/parseTree/IfNode.js';
import OperatorNode from '/imports/parser/parseTree/OperatorNode.js';
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
import moo from 'moo';
const lexer = moo.compile({
number: /[0-9]+(?:\.[0-9]+)?/,
@@ -35,15 +39,6 @@ function id(x) { return x[0]; }
});
function nuller() { return null; }
class OperatorNode {
constructor({left, right, operator, fn}) {
this.left = left;
this.right = right;
this.fn = fn;
this.operator = operator;
}
}
function operator([left, _1, operator, _2, right], fn){
return new OperatorNode({
left,
@@ -52,23 +47,11 @@ function id(x) { return x[0]; }
fn
});
}
class SymbolNode {
constructor(name){
this.name = name;
}
}
class ConstantNode {
constructor(value, type){
this.type = type;
this.value = value;
}
}
var grammar = {
Lexer: lexer,
ParserRules: [
{"name": "ifStatement", "symbols": [{"literal":"if"}, "_", {"literal":"("}, "_", "expression", "_", {"literal":")"}, "_", "ifStatement", "_", {"literal":"else"}, "_", "ifStatement"], "postprocess": d => ({condition: d[4], true: d[8], false: d[12]})},
let Lexer = lexer;
let ParserRules = [
{"name": "ifStatement", "symbols": [{"literal":"if"}, "_", {"literal":"("}, "_", "expression", "_", {"literal":")"}, "_", "ifStatement", "_", {"literal":"else"}, "_", "ifStatement"], "postprocess":
d => new IfNode({condition: d[4], consequent: d[8], alternative: d[12]})
},
{"name": "ifStatement", "symbols": ["expression"], "postprocess": id},
{"name": "expression", "symbols": ["equalityExpression"], "postprocess": d => d[0]},
{"name": "equalityExpression", "symbols": ["equalityExpression", "_", (lexer.has("equalityOperator") ? {type: "equalityOperator"} : equalityOperator), "_", "relationalExpression"], "postprocess": d => operator(d, 'equality')},
@@ -88,7 +71,7 @@ var grammar = {
{"name": "exponentExpression", "symbols": ["callExpression", "_", (lexer.has("exponentOperator") ? {type: "exponentOperator"} : exponentOperator), "_", "exponentExpression"], "postprocess": d => operator(d, 'exponent')},
{"name": "exponentExpression", "symbols": ["callExpression"], "postprocess": id},
{"name": "callExpression", "symbols": ["name", "_", "arguments"], "postprocess":
d => ({type: "call", function: d[0], arguments: d[2]})
d => new CallNode ({type: "call", fn: d[0], arguments: d[2]})
},
{"name": "callExpression", "symbols": ["parenthesizedExpression"], "postprocess": id},
{"name": "arguments$ebnf$1$subexpression$1", "symbols": ["expression"], "postprocess": d => d[0]},
@@ -105,17 +88,11 @@ var grammar = {
{"name": "valueExpression", "symbols": ["name"], "postprocess": id},
{"name": "valueExpression", "symbols": ["number"], "postprocess": id},
{"name": "valueExpression", "symbols": ["string"], "postprocess": id},
{"name": "number", "symbols": [(lexer.has("number") ? {type: "number"} : number)], "postprocess": d => new ConstantNode(d[0].value, 'number')},
{"name": "name", "symbols": [(lexer.has("name") ? {type: "name"} : name)], "postprocess": d => new SymbolNode(d[0].value)},
{"name": "string", "symbols": [(lexer.has("string") ? {type: "string"} : string)], "postprocess": d => new ConstantNode(d[0].value, 'string')},
{"name": "number", "symbols": [(lexer.has("number") ? {type: "number"} : number)], "postprocess": d => new ConstantNode({value: d[0].value, type: 'number'})},
{"name": "name", "symbols": [(lexer.has("name") ? {type: "name"} : name)], "postprocess": d => new SymbolNode({name: d[0].value})},
{"name": "string", "symbols": [(lexer.has("string") ? {type: "string"} : string)], "postprocess": d => new ConstantNode({value: d[0].value, type: 'string'})},
{"name": "_", "symbols": []},
{"name": "_", "symbols": [(lexer.has("space") ? {type: "space"} : space)], "postprocess": nuller}
]
, ParserStart: "ifStatement"
}
if (typeof module !== 'undefined'&& typeof module.exports !== 'undefined') {
module.exports = grammar;
} else {
window.grammar = grammar;
}
})();
];
let ParserStart = "ifStatement";
export default { Lexer, ParserRules, ParserStart };

View File

@@ -1,5 +1,11 @@
@preprocessor esmodule
@{%
const moo = require("moo");
import CallNode from '/imports/parser/parseTree/CallNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import IfNode from '/imports/parser/parseTree/IfNode.js';
import OperatorNode from '/imports/parser/parseTree/OperatorNode.js';
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
import moo from 'moo';
const lexer = moo.compile({
number: /[0-9]+(?:\.[0-9]+)?/,
@@ -46,7 +52,7 @@
ifStatement ->
"if" _ "(" _ expression _ ")" _ ifStatement _ "else" _ ifStatement {%
d => new ifNode({condition: d[4], consequent: d[8], alternative: d[12]})
d => new IfNode({condition: d[4], consequent: d[8], alternative: d[12]})
%}
| expression {% id %}
@@ -87,7 +93,7 @@ exponentExpression ->
callExpression ->
name _ arguments {%
d => ({type: "call", function: d[0], arguments: d[2]})
d => new CallNode ({type: "call", fn: d[0], arguments: d[2]})
%}
| parenthesizedExpression {% id %}
@@ -107,10 +113,10 @@ valueExpression ->
# A number or a function of a number
number ->
%number {% d => new ConstantNode({value: d[0].value, type 'number'}) %}
%number {% d => new ConstantNode({value: d[0].value, type: 'number'}) %}
name ->
%name {% d => new SymbolNode(d[0].value) %}
%name {% d => new SymbolNode({name: d[0].value}) %}
string ->
%string {% d => new ConstantNode({value: d[0].value, type: 'string'}) %}

View File

@@ -1,96 +0,0 @@
// All the classes that make up a parse tree
class ParseNode {
// Compiling a node must return a ConstantNode
compile(){
throw new Meteor.Error('Compile not implemented on ' + this);
}
compileToSingleValue(){
return this.compile();
}
}
function sum(total, num) {
return total + num;
}
class ConstantNode extends ParseNode {
constructor({value, type, errors}){
// string, number, boolean, numberArray, uncompiledNode
this.type = type;
this.value = value;
if (errors) this.errors = errors;
}
compile(){
return this;
}
compileToSingleValue(){
if (this.type !== 'numberArray') return this;
return this.value.reduce(sum, 0);
}
}
class SymbolNode extends ParseNode {
constructor({name}){
this.name = name;
}
compile(scope){
let value = scope[this.name];
let type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean'){
return new ConstantNode({value, type});
} else if (type === 'undefined'){
return new ConstantNode({
value: this.name,
type: 'uncompiledNode',
errors: [`${this.name} could not be resolved`]
});
} else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
}
}
}
class ifNode extends ParseNode {
constructor({condition, consequent, alternative}){
this.condition = condition;
this.consequent = consequent;
this.alternative = alternative;
}
compile(){
let condition = this.condition.compile();
let consequent = this.consequent.compile();
let alternative = this.alternative.compile();
if (
condition.type === 'uncompiledNode' ||
consequent.type === 'uncompiledNode' ||
alternative.type === 'uncompiledNode'
){
// Handle uncompiled child nodes
return new ConstantNode({
value: `if (${condition.value}) ${consequent.value} else ${alternative.value}`,
type: 'uncompiledNode',
errors: [
...condition.errors,
...consequent.errors,
...alternative.errors,
],
});
} else {
if (condition.value){
return consequent;
} else {
return alternative;
}
}
}
}
class OperatorNode extends ParseNode {
constructor({left, right, operator, fn}) {
this.left = left;
this.right = right;
this.fn = fn;
this.operator = operator;
}
}

View File

@@ -0,0 +1 @@
//TODO

View File

@@ -0,0 +1,21 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class ConstantNode extends ParseNode {
constructor({value, type, errors}){
super();
// string, number, boolean, numberArray, uncompiledNode
this.type = type;
this.value = value;
if (errors) this.errors = errors;
}
compile(){
return this;
}
reduce(){
if (this.type === 'numberArray'){
return this.value.reduce((total, num) => total + num, 0);
} else {
return this;
}
}
}

View File

@@ -0,0 +1,40 @@
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}){
super();
this.condition = condition;
this.consequent = consequent;
this.alternative = alternative;
}
compile(){
let condition = this.condition.compile();
let consequent = this.consequent.compile();
let alternative = this.alternative.compile();
if (
condition.type !== 'string' &&
condition.type !== 'number' &&
condition.type !== 'boolean'
){
// Handle unresolved condition
return new ConstantNode({
value: `if (${condition.value}) ${consequent.value} else ${alternative.value}`,
type: 'uncompiledNode',
errors: [
...condition.errors,
...consequent.errors,
...alternative.errors,
],
});
} else {
// So long as the condition reolves, return the correct alternative,
// even if it's unresolved
if (condition.value){
return consequent;
} else {
return alternative;
}
}
}
}

View File

@@ -0,0 +1,11 @@
import ParseNode from '/imports/parser/parseTree/ParseNode.js';
export default class OperatorNode extends ParseNode {
constructor({left, right, operator, fn}) {
super();
this.left = left;
this.right = right;
this.fn = fn;
this.operator = operator;
}
}

View File

@@ -0,0 +1,14 @@
export default class ParseNode {
// Compiling a node must return a ConstantNode
compile(){
throw new Meteor.Error('Compile not implemented on ' + this);
}
// Compile, but turn rolls into arrays
roll(){
return this.compile();
}
// Compile, turn rolls into arrays, and reduce those arrays into single values
reduce(){
return this.compileAndRoll()
}
}

View File

View File

@@ -0,0 +1,24 @@
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();
this.name = name;
}
compile(scope){
let value = scope && scope[this.name];
let type = typeof value;
if (type === 'string' || type === 'number' || type === 'boolean'){
return new ConstantNode({value, type});
} else if (type === 'undefined'){
return new ConstantNode({
value: this.name,
type: 'uncompiledNode',
errors: [`${this.name} could not be resolved`]
});
} else {
throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`);
}
}
}

68
app/package-lock.json generated
View File

@@ -104,7 +104,7 @@
},
"block-stream": {
"version": "0.0.9",
"resolved": false,
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"requires": {
"inherits": "~2.0.0"
@@ -513,7 +513,7 @@
},
"inherits": {
"version": "2.0.3",
"resolved": false,
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
@@ -1456,37 +1456,6 @@
"requires": {
"inherits": "~2.0.1",
"readable-stream": "^2.0.2"
},
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"stream-http": {
@@ -1499,37 +1468,6 @@
"readable-stream": "^2.3.3",
"to-arraybuffer": "^1.0.0",
"xtend": "^4.0.0"
},
"dependencies": {
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"string_decoder": {
@@ -1928,7 +1866,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": false,
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}