Separated parser class nodes and began writing compile methods
This commit is contained in:
@@ -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({
|
||||
|
||||
|
||||
7
app/imports/parser/compileFunctions/index.js
Normal file
7
app/imports/parser/compileFunctions/index.js
Normal 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;
|
||||
15
app/imports/parser/compileFunctions/sum.js
Normal file
15
app/imports/parser/compileFunctions/sum.js
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
@@ -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'}) %}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
1
app/imports/parser/parseTree/CallNode.js
Normal file
1
app/imports/parser/parseTree/CallNode.js
Normal file
@@ -0,0 +1 @@
|
||||
//TODO
|
||||
21
app/imports/parser/parseTree/ConstantNode.js
Normal file
21
app/imports/parser/parseTree/ConstantNode.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
app/imports/parser/parseTree/IfNode.js
Normal file
40
app/imports/parser/parseTree/IfNode.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
app/imports/parser/parseTree/OperatorNode.js
Normal file
11
app/imports/parser/parseTree/OperatorNode.js
Normal 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;
|
||||
}
|
||||
}
|
||||
14
app/imports/parser/parseTree/ParseNode.js
Normal file
14
app/imports/parser/parseTree/ParseNode.js
Normal 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()
|
||||
}
|
||||
}
|
||||
0
app/imports/parser/parseTree/RollNode.js
Normal file
0
app/imports/parser/parseTree/RollNode.js
Normal file
24
app/imports/parser/parseTree/SymbolNode.js
Normal file
24
app/imports/parser/parseTree/SymbolNode.js
Normal 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
68
app/package-lock.json
generated
@@ -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="
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user