From d21827106c831fe3af48ccc540fc8aa4fb870d0b Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Fri, 29 Mar 2019 14:08:09 +0200 Subject: [PATCH] Moved parse tree classes out of grammar.js started working on compilation. Broke the build --- app/imports/parser/compute.js | 1 + app/imports/parser/grammar.ne | 30 ++--------- app/imports/parser/parseTree.js | 96 +++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 app/imports/parser/compute.js create mode 100644 app/imports/parser/parseTree.js diff --git a/app/imports/parser/compute.js b/app/imports/parser/compute.js new file mode 100644 index 00000000..5386218b --- /dev/null +++ b/app/imports/parser/compute.js @@ -0,0 +1 @@ +// Takes a parse tree and computes it down as far as possible into a real number diff --git a/app/imports/parser/grammar.ne b/app/imports/parser/grammar.ne index cca3fa88..b50254f9 100644 --- a/app/imports/parser/grammar.ne +++ b/app/imports/parser/grammar.ne @@ -31,15 +31,6 @@ }); 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, @@ -48,26 +39,15 @@ fn }); } - - class SymbolNode { - constructor(name){ - this.name = name; - } - } - - class ConstantNode { - constructor(value, type){ - this.type = type; - this.value = value; - } - } %} # Use the Moo lexer @lexer lexer ifStatement -> - "if" _ "(" _ expression _ ")" _ ifStatement _ "else" _ ifStatement {% d => ({condition: d[4], true: d[8], false: d[12]}) %} + "if" _ "(" _ expression _ ")" _ ifStatement _ "else" _ ifStatement {% + d => new ifNode({condition: d[4], consequent: d[8], alternative: d[12]}) + %} | expression {% id %} expression -> @@ -127,13 +107,13 @@ valueExpression -> # A number or a function of a number number -> - %number {% d => new ConstantNode(d[0].value, 'number') %} + %number {% d => new ConstantNode({value: d[0].value, type 'number'}) %} name -> %name {% d => new SymbolNode(d[0].value) %} string -> - %string {% d => new ConstantNode(d[0].value, 'string') %} + %string {% d => new ConstantNode({value: d[0].value, type: 'string'}) %} _ -> null diff --git a/app/imports/parser/parseTree.js b/app/imports/parser/parseTree.js new file mode 100644 index 00000000..4002f7cc --- /dev/null +++ b/app/imports/parser/parseTree.js @@ -0,0 +1,96 @@ +// 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; + } +}