Created
March 5, 2012 02:55
-
-
Save rfink/1976187 to your computer and use it in GitHub Desktop.
Create a basic arithmetic syntax tree using javascript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function AstNode(operator, left, right) { | |
| this.operator = operator; | |
| this.left = left; | |
| this.right = right; | |
| } | |
| AstNode.prototype.evaluate = function() { | |
| return this.operator(this.left.evaluate(), this.right.evaluate()); | |
| }; | |
| function ValueNode(val) { | |
| this.value = val; | |
| } | |
| ValueNode.prototype.evaluate = function() { | |
| return this.value; | |
| }; | |
| function VariableNode(context, keyName) { | |
| this.context = context; | |
| this.keyName = keyName; | |
| } | |
| VariableNode.prototype.evaluate = function() { | |
| return this.context.get(this.keyName); | |
| }; | |
| var operators = { | |
| '*': function(left, right) { | |
| return left * right; | |
| }, | |
| '/': function(left, right) { | |
| return left / right; | |
| }, | |
| '+': function(left, right) { | |
| return left + right; | |
| }, | |
| '-': function(left, right) { | |
| return left - right; | |
| }, | |
| '^': function(left, right) { | |
| return Math.pow(left, right); | |
| }, | |
| '%': function(left, right) { | |
| return left % right; | |
| } | |
| }; | |
| var operatorDict = { | |
| '+': [operators['+'], 2, 2], | |
| '-': [operators['-'], 2, 2], | |
| '*': [operators['*'], 3, 3], | |
| '/': [operators['/'], 3, 3], | |
| '%': [operators['%'], 3, 3], | |
| '^': [operators['^'], 4, 5], | |
| '(': [function() {}, 0, 8] | |
| }; | |
| function Parser() { | |
| this.operatorStack = []; | |
| this.nodeStack = []; | |
| }; | |
| var funcDict = { | |
| valueFunc: function(value) { | |
| this.nodeStack.push(value); | |
| this.__proto__ = protoState2; | |
| }, | |
| operatorFunc: function(operator) { | |
| var operatorPrecedence = operatorDict[operator][2]; | |
| this.reduce(operatorPrecedence); | |
| this.operatorStack.push(operatorDict[operator]); | |
| this.__proto__ = protoState1; | |
| }, | |
| closeParenFunc: function() { | |
| this.reduce(1); | |
| if (this.operatorStack.length) { | |
| this.operatorStack.pop(); | |
| } else { | |
| this.syntaxErr('Error - no open parenthesis matches close parenthesis'); | |
| } | |
| this.__proto__ = protoState2; | |
| }, | |
| syntaxErr: function(err) { | |
| throw new Error(err); | |
| }, | |
| end: function() { | |
| this.reduce(0); | |
| return this.nodeStack.pop(); | |
| }, | |
| reduce: function(precedence) { | |
| while (this.operatorStack.length) { | |
| var tailOperator = this.operatorStack[this.operatorStack.length - 1]; | |
| if (tailOperator[1] < precedence) break; | |
| tailOperator = this.operatorStack.pop(); | |
| var right = this.nodeStack.pop(); | |
| var left = this.nodeStack.pop(); | |
| this.nodeStack.push(new AstNode(tailOperator[0], left, right)); | |
| } | |
| } | |
| }; | |
| var protoState1 = { | |
| valueFunc: funcDict.valueFunc, | |
| operatorFunc: funcDict.syntaxErr, | |
| openParenFunc: funcDict.operatorFunc, | |
| closeParenFunc: funcDict.syntaxErr, | |
| syntaxErr: funcDict.syntaxErr, | |
| end: funcDict.end, | |
| reduce: funcDict.reduce | |
| }; | |
| var protoState2 = { | |
| valueFunc: funcDict.syntaxErr, | |
| operatorFunc: funcDict.operatorFunc, | |
| openParenFunc: funcDict.syntaxErr, | |
| closeParenFunc: funcDict.closeParenFunc, | |
| syntaxErr: funcDict.syntaxErr, | |
| end: funcDict.end, | |
| reduce: funcDict.reduce | |
| }; | |
| Parser.prototype.__proto__ = protoState1; | |
| function Lexer(expression, parseObj, context) { | |
| if (!context) { | |
| context = { | |
| get: function(name) { | |
| return null; | |
| } | |
| }; | |
| } | |
| for (var i = 0, len = expression.length; i < len; ++i) { | |
| var character = expression.charAt(i); | |
| switch (character) { | |
| case ' ': | |
| case '\n': | |
| case '\t': | |
| continue; | |
| case '(': | |
| parseObj.openParenFunc('('); | |
| break; | |
| case ')': | |
| parseObj.closeParenFunc(); | |
| break; | |
| case '{': | |
| var name = ''; | |
| while (i < len) { | |
| var ch = expression.charAt(++i); | |
| if (ch === '}') { | |
| if (/^[\w\.]+$/.test(name)) { | |
| parseObj.valueFunc(new VariableNode(context, name)); | |
| break; | |
| } else { | |
| parseObj.syntaxErr('Invalid character in variable name at ' + (i)); | |
| } | |
| } else { | |
| name += ch; | |
| if (i === len) { | |
| parseObj.syntaxErr('Unexpected end of output'); | |
| } | |
| } | |
| } | |
| break; | |
| case '}': | |
| parseObj.syntaxErr('Unexpected } found at character' + i); | |
| break; | |
| default: | |
| if (typeof operatorDict[character] !== 'undefined') { | |
| parseObj.operatorFunc(character); | |
| } else { | |
| var numDecimals = 0, numBuffer = ''; | |
| while (i < len) { | |
| if (numDecimals > 1) parseObj.syntaxErr('Unexpected . found at character ' + i); | |
| if (character === '.') numDecimals++; | |
| numBuffer += '' + character; | |
| var nextChar = expression.charAt(i + 1); | |
| if (nextChar === '.' || !isNaN(parseFloat(nextChar))) { | |
| character = expression.charAt(++i); | |
| } else { | |
| break; | |
| } | |
| } | |
| parseObj.valueFunc(new ValueNode(parseFloat(numBuffer))); | |
| numBuffer = ''; | |
| } | |
| } | |
| } | |
| return parseObj.end(); | |
| } | |
| var expr = '4 + (5 + 18.1) * 10 / {Stuff}'; | |
| expr = '5 / 10 % 3 + {Merf}'; | |
| var context = { | |
| data: { | |
| Stuff: 5 | |
| }, | |
| get: function(name) { | |
| if (typeof this.data[name] !== 'undefined') return this.data[name]; | |
| return null; | |
| } | |
| } | |
| try { | |
| var tree = new Lexer(expr, new Parser(), context); | |
| console.log(tree, tree.evaluate()); | |
| } catch (e) { | |
| console.log(e.stack); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This has really helped me. Thank you.
Sorry to ask a question 10 years later, but what do these numbers mean here?
https://gist.github.com/rfink/1976187#file-arithsyntaxtree-js-L50-L56