Created
November 11, 2014 00:44
-
-
Save bitnenfer/af81f93f4264fe14b6b1 to your computer and use it in GitHub Desktop.
Simple Arithmetic Lexer, Expression Parser & Tree Generator
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
/* | |
* This was created out of my own study | |
* of lexical analysis and expression | |
* parsing. | |
* | |
* Demo at: http://dev.shin.cl/binaryexpression/ | |
* | |
* @author Felipe Alfonso | |
*/ | |
var NUMS = '0123456789', | |
OPER = '*/+-', | |
isDefined = function (n) { | |
return typeof n !== 'undefined' && n !== null; | |
}, | |
Token = function (type, value) { | |
return { | |
type: type || null, | |
value: isDefined(value) ? value : null | |
}; | |
}, | |
scan = function (source) { | |
var stream = [], | |
index = 0, | |
charc, | |
token, | |
isFloat = false, | |
floatMatch = NUMS + '.'; | |
while (index < source.length) { | |
charc = source[index]; | |
if (NUMS.indexOf(charc) >= 0) { | |
isFloat = false; | |
token = Token('int', ''); | |
charc = source[index++]; | |
token.value += charc; | |
if (NUMS.indexOf(charc) !== -1) { | |
while (floatMatch.indexOf(charc) !== -1) { | |
charc = source[index++]; | |
if (typeof charc === 'string' && floatMatch.indexOf(charc) !== -1) { | |
token.value += charc; | |
if (charc === '.') { | |
if (isFloat) { | |
break; | |
} | |
isFloat = true; | |
token.type = 'float'; | |
} | |
} | |
} | |
} | |
index--; | |
stream.push(token); | |
} else if (OPER.indexOf(charc) >= 0) { | |
token = Token('operator', charc); | |
stream.push(token); | |
index++; | |
} else if (charc === '(') { | |
token = Token('lparen', charc); | |
stream.push(token); | |
index++; | |
} else if (charc === ')') { | |
token = Token('rparen', charc); | |
stream.push(token); | |
index++; | |
} else if (charc === '=') { | |
token = Token('equals', charc); | |
stream.push(token); | |
index++; | |
} else { | |
index++; | |
} | |
} | |
return stream; | |
}, | |
BinaryExpression = function (right, operator, left) { | |
var module = { | |
type: 'BinaryExpression', | |
operator: operator, | |
left: left, | |
right: right | |
}; | |
return module; | |
}, | |
IntLiteral = function (value) { | |
var module = { | |
type: 'IntLiteral', | |
value: parseInt(value) | |
}; | |
return module; | |
}, | |
FloatLiteral = function (value) { | |
var module = { | |
type: 'FloatLiteral', | |
value: parseFloat(value) | |
}; | |
return module; | |
}, | |
peek = function (n) { | |
if (n.length > 0) { | |
return n[n.length - 1]; | |
} | |
return null; | |
}, | |
presedence = function (token) { | |
if (typeof token === 'undefined' || token === null) { | |
return 0; | |
} | |
switch (token.value) { | |
case '(': | |
case ')': | |
return 3; | |
break; | |
case '*': | |
case '/': | |
return 2; | |
break; | |
case '+': | |
case '-': | |
return 1; | |
break; | |
default: | |
return 0; | |
break; | |
} | |
}, | |
infixToPostfix = function (stream) { | |
var index = 0, | |
stack = [], | |
result = [], | |
length = stream.length, | |
flush = function () { | |
while (stack.length) { | |
var token = stack.pop(); | |
if (token.type !== 'lparen' && token.type !== 'rparen') { | |
result.push(token); | |
} | |
} | |
}; | |
for (index; index < length; index++) { | |
if (stream[index].type !== 'operator' && | |
stream[index].type !== 'lparen' && | |
stream[index].type !== 'rparen') { | |
result.push(stream[index]); | |
} else { | |
if ((presedence(peek(stack)) >= presedence(stream[index]) && | |
peek(stack).type !== 'lparen') || | |
stream[index].type === 'rparen') { | |
flush(); | |
} | |
stack.push(stream[index]); | |
} | |
} | |
flush(); | |
return result; | |
}, | |
parseExpression = function (source) { | |
var stream = infixToPostfix(scan(source)), | |
index = 0, | |
length = stream.length, | |
valueStack = [], | |
exprStack = []; | |
for (index; index < length; ++index) { | |
if (stream[index].type === 'int' || stream[index].type === 'float') { | |
if (stream[index].type === 'int') { | |
valueStack.push(IntLiteral(stream[index].value)); | |
} else { | |
valueStack.push(FloatLiteral(stream[index].value)); | |
} | |
} else { | |
var expr = BinaryExpression(valueStack.pop(), stream[index].value, valueStack.pop()) | |
exprStack.push(expr); | |
valueStack.push(expr); | |
} | |
} | |
return valueStack.pop(); | |
}, | |
solveExpression = function (expression) { | |
if (expression === null || typeof expression === 'undefined') { | |
return 0; | |
} else if (expression.type === 'IntLiteral' || expression.type === 'FloatLiteral') { | |
return expression.value; | |
} else if (expression.type === 'BinaryExpression') { | |
if (expression.operator === '+') { | |
return solveExpression(expression.left) + solveExpression(expression.right); | |
} else if (expression.operator === '-') { | |
return solveExpression(expression.left) - solveExpression(expression.right); | |
} else if (expression.operator === '*') { | |
return solveExpression(expression.left) * solveExpression(expression.right); | |
} else if (expression.operator === '/') { | |
return solveExpression(expression.left) / solveExpression(expression.right); | |
} | |
} else { | |
return 0; | |
} | |
}, | |
runExpr = function (source) { | |
if (typeof source !== 'string') { | |
return; | |
} | |
var expr = parseExpression(source), | |
strJSON = JSON.stringify(expr, null, 3), | |
solution = solveExpression(expr), | |
str = '', | |
strSplit; | |
if (strJSON) { | |
strSplit = strJSON.split('\n'); | |
for (var i = 0; i < strSplit.length; ++i) { | |
str += '\n' + strSplit[i].substr(4, strSplit[i].length); | |
} | |
if (str) { | |
document.getElementById('result').innerHTML = 'Solution with parser: ' + solution + '\n' + str.replace(/{|}|,/g, '').replace(/"/g, ''); | |
} | |
} else { | |
document.getElementById('result').innerHTML = 'Empty or invalid input.'; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment