Skip to content

Instantly share code, notes, and snippets.

@bitnenfer
Created November 11, 2014 00:44
Show Gist options
  • Save bitnenfer/af81f93f4264fe14b6b1 to your computer and use it in GitHub Desktop.
Save bitnenfer/af81f93f4264fe14b6b1 to your computer and use it in GitHub Desktop.
Simple Arithmetic Lexer, Expression Parser & Tree Generator
/*
* 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