From 5dec7604525f0f7e21bf8bcff371448e1b17f3a0 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Thu, 10 Sep 2020 11:38:28 +0200 Subject: [PATCH] Parser now works with variables passed into scope --- app/imports/parser/grammar.js | 18 +++++--- app/imports/parser/grammar.ne | 14 ++++-- app/imports/parser/parseTree/AccessorNode.js | 46 ++++++++++++++++++++ app/imports/parser/parseTree/IfNode.js | 7 ++- app/imports/parser/parseTree/SymbolNode.js | 23 ++++++++-- app/imports/ui/pages/Parser.vue | 2 +- 6 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 app/imports/parser/parseTree/AccessorNode.js diff --git a/app/imports/parser/grammar.js b/app/imports/parser/grammar.js index 310a2ad8..289327de 100644 --- a/app/imports/parser/grammar.js +++ b/app/imports/parser/grammar.js @@ -2,6 +2,7 @@ // http://github.com/Hardmath123/nearley function id(x) { return x[0]; } + import AccessorNode from '/imports/parser/parseTree/AccessorNode.js'; import ArrayNode from '/imports/parser/parseTree/ArrayNode.js'; import CallNode from '/imports/parser/parseTree/CallNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; @@ -23,7 +24,7 @@ function id(x) { return x[0]; } name: { match: /[a-zA-Z]+\w*?/, type: moo.keywords({ - 'keywords': ['if', 'else', 'd'], + 'keywords': ['d'], }), }, space: { @@ -32,6 +33,7 @@ function id(x) { return x[0]; } }, separator: [',', ';'], period: ['.'], + ternaryOperator: ['?', ':'], multiplicativeOperator: ['*', '/'], exponentOperator: ['^'], additiveOperator: ['+', '-'], @@ -56,8 +58,8 @@ function id(x) { return x[0]; } let Lexer = lexer; let ParserRules = [ {"name": "expression", "symbols": ["ifStatement"], "postprocess": d => d[0]}, - {"name": "ifStatement", "symbols": ["_", "expression", "_", {"literal":"?"}, "_", "expression", "_", {"literal":":"}, "_", "expression"], "postprocess": - d => new IfNode({condition: d[4], consequent: d[8], alternative: d[12]}) + {"name": "ifStatement", "symbols": ["_", "equalityExpression", "_", {"literal":"?"}, "_", "equalityExpression", "_", {"literal":":"}, "_", "ifStatement"], "postprocess": + d => new IfNode({condition: d[1], consequent: d[5], alternative: d[9]}) }, {"name": "ifStatement", "symbols": ["equalityExpression"], "postprocess": id}, {"name": "equalityExpression", "symbols": ["equalityExpression", "_", (lexer.has("equalityOperator") ? {type: "equalityOperator"} : equalityOperator), "_", "relationalExpression"], "postprocess": d => operator(d, 'equality')}, @@ -100,11 +102,17 @@ let ParserRules = [ {"name": "arrayExpression$ebnf$2$subexpression$1", "symbols": ["_", (lexer.has("separator") ? {type: "separator"} : separator), "_", "expression"], "postprocess": d => d[3]}, {"name": "arrayExpression$ebnf$2", "symbols": ["arrayExpression$ebnf$2", "arrayExpression$ebnf$2$subexpression$1"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}}, {"name": "arrayExpression", "symbols": [{"literal":"["}, "_", "arrayExpression$ebnf$1", "arrayExpression$ebnf$2", "_", {"literal":"]"}], "postprocess": - d => new ArrayNode({values: [d[2], ...d[3]]}) + d => new ArrayNode({values: d[2] ? [d[2], ...d[3]] : []}) }, {"name": "arrayExpression", "symbols": ["parenthesizedExpression"], "postprocess": id}, {"name": "parenthesizedExpression", "symbols": [{"literal":"("}, "_", "expression", "_", {"literal":")"}], "postprocess": d => new ParenthesisNode({content: d[2]})}, - {"name": "parenthesizedExpression", "symbols": ["valueExpression"], "postprocess": id}, + {"name": "parenthesizedExpression", "symbols": ["accessorExpression"], "postprocess": id}, + {"name": "accessorExpression$ebnf$1$subexpression$1", "symbols": [{"literal":"."}, "name"], "postprocess": d => d[1].name}, + {"name": "accessorExpression$ebnf$1", "symbols": ["accessorExpression$ebnf$1$subexpression$1"]}, + {"name": "accessorExpression$ebnf$1$subexpression$2", "symbols": [{"literal":"."}, "name"], "postprocess": d => d[1].name}, + {"name": "accessorExpression$ebnf$1", "symbols": ["accessorExpression$ebnf$1", "accessorExpression$ebnf$1$subexpression$2"], "postprocess": function arrpush(d) {return d[0].concat([d[1]]);}}, + {"name": "accessorExpression", "symbols": ["name", "accessorExpression$ebnf$1"], "postprocess": d=> new AccessorNode({name: d[0], path: d[1]})}, + {"name": "accessorExpression", "symbols": ["valueExpression"], "postprocess": id}, {"name": "valueExpression", "symbols": ["name"], "postprocess": id}, {"name": "valueExpression", "symbols": ["number"], "postprocess": id}, {"name": "valueExpression", "symbols": ["string"], "postprocess": id}, diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne index 4e199c11..bac66f8a 100644 --- a/app/imports/parser/grammar.ne +++ b/app/imports/parser/grammar.ne @@ -1,5 +1,6 @@ @preprocessor esmodule @{% + import AccessorNode from '/imports/parser/parseTree/AccessorNode.js'; import ArrayNode from '/imports/parser/parseTree/ArrayNode.js'; import CallNode from '/imports/parser/parseTree/CallNode.js'; import ConstantNode from '/imports/parser/parseTree/ConstantNode.js'; @@ -21,7 +22,7 @@ name: { match: /[a-zA-Z]+\w*?/, type: moo.keywords({ - 'keywords': ['if', 'else', 'd'], + 'keywords': ['d'], }), }, space: { @@ -30,6 +31,7 @@ }, separator: [',', ';'], period: ['.'], + ternaryOperator: ['?', ':'], multiplicativeOperator: ['*', '/'], exponentOperator: ['^'], additiveOperator: ['+', '-'], @@ -60,8 +62,8 @@ expression -> ifStatement {% d => d[0] %} ifStatement -> - _ expression _ "?" _ expression _ ":" _ expression {% - d => new IfNode({condition: d[4], consequent: d[8], alternative: d[12]}) + _ equalityExpression _ "?" _ equalityExpression _ ":" _ ifStatement {% + d => new IfNode({condition: d[1], consequent: d[5], alternative: d[9]}) %} | equalityExpression {% id %} @@ -118,12 +120,16 @@ indexExpression -> arrayExpression -> "[" _ (expression {% d => d[0] %}):? ( _ %separator _ expression {% d => d[3] %} ):* _ "]" {% - d => new ArrayNode({values: [d[2], ...d[3]]}) + d => new ArrayNode({values: d[2] ? [d[2], ...d[3]] : []}) %} | parenthesizedExpression {% id %} parenthesizedExpression -> "(" _ expression _ ")" {% d => new ParenthesisNode({content: d[2]}) %} +| accessorExpression {% id %} + +accessorExpression -> + name ( "." name {% d => d[1].name %} ):+ {% d=> new AccessorNode({name: d[0], path: d[1]}) %} | valueExpression {% id %} valueExpression -> diff --git a/app/imports/parser/parseTree/AccessorNode.js b/app/imports/parser/parseTree/AccessorNode.js new file mode 100644 index 00000000..a406af09 --- /dev/null +++ b/app/imports/parser/parseTree/AccessorNode.js @@ -0,0 +1,46 @@ +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 { + constructor({name, path}) { + super(...arguments); + this.name = name; + this.path = path; + } + compile(scope){ + let value = scope && scope[this.name]; + // For objects, get their value + this.path.forEach(name => { + if (value === undefined) return; + value = value[name]; + }); + let type = typeof value; + if (type === 'string' || type === 'number' || type === 'boolean'){ + return new ConstantNode({value, type, previousNodes: [this]}); + } else if (type === 'undefined'){ + 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){ + let result = this.compile(scope); + if (result instanceof AccessorNode){ + return new ErrorNode({ + node: result, + error: `${this.toString()} could not be resolved`, + previousNodes: [result], + }); + } else { + return result; + } + } + toString(){ + return `${this.name}.${this.path.join('.')}`; + } +} diff --git a/app/imports/parser/parseTree/IfNode.js b/app/imports/parser/parseTree/IfNode.js index 66334d84..7f1c9178 100644 --- a/app/imports/parser/parseTree/IfNode.js +++ b/app/imports/parser/parseTree/IfNode.js @@ -22,13 +22,12 @@ export default class IfNode extends ParseNode { } resolve(fn, scope){ let condition = this.condition[fn](scope); - let consequent = this.consequent[fn](scope); - let alternative = this.alternative[fn](scope); - this.resolve(condition, consequent, alternative); if (condition.value){ + let consequent = this.consequent[fn](scope); consequent.inheritDetails([condition, this]); - return consequent; + return this.consequent[fn](scope); } else { + let alternative = this.alternative[fn](scope); alternative.inheritDetails([condition, this]); return alternative; } diff --git a/app/imports/parser/parseTree/SymbolNode.js b/app/imports/parser/parseTree/SymbolNode.js index 35c2ffab..73becc6f 100644 --- a/app/imports/parser/parseTree/SymbolNode.js +++ b/app/imports/parser/parseTree/SymbolNode.js @@ -13,15 +13,32 @@ export default class SymbolNode extends ParseNode { compile(scope){ let value = scope && scope[this.name]; let type = typeof value; + // For objects, get their value + if (type === 'object'){ + value = value.value; + type = typeof value; + } if (type === 'string' || type === 'number' || type === 'boolean'){ return new ConstantNode({value, type, previousNodes: [this]}); } else if (type === 'undefined'){ - return new ErrorNode({ - node: this, - error: `${this.name} could not be resolved`, + return new SymbolNode({ + name: this.name, + previousNodes: [this], }); } else { throw new Meteor.Error(`Unexpected case: ${this.name} resolved to ${value}`); } } + reduce(scope){ + let result = this.compile(scope); + if (result instanceof SymbolNode){ + return new ErrorNode({ + node: result, + error: `${this.toString()} could not be resolved`, + previousNodes: [result], + }); + } else { + return result; + } + } } diff --git a/app/imports/ui/pages/Parser.vue b/app/imports/ui/pages/Parser.vue index ba8d7683..cb3ddd92 100644 --- a/app/imports/ui/pages/Parser.vue +++ b/app/imports/ui/pages/Parser.vue @@ -59,7 +59,7 @@ export default { this.output = JSON.stringify(output, null, 2); if (!output) return; this.string = output; - let scope = {cat: 1, dog: 2, mouse: 3}; + let scope = {strength: {value: 16}, hitpoints: {value: 32, currentValue: 8}, mouse: 3}; this.compiled = output.compile(scope); this.rolled = output.roll(scope); this.reduced = output.reduce(scope);