Skip to content

Instantly share code, notes, and snippets.

@aulisius
Last active October 18, 2018 11:38
Show Gist options
  • Save aulisius/724995312f71113da10dd3b76cbae121 to your computer and use it in GitHub Desktop.
Save aulisius/724995312f71113da10dd3b76cbae121 to your computer and use it in GitHub Desktop.
Evaluate mathematical expression
function getPrecedence(token) {
const Precedence = { HIGH: 3, MED: 2, LOW: 1, ZERO: 0 }
switch (token) {
case '+':
case '-':
return Precedence.ZERO
case '*':
case '/':
case '%':
return Precedence.LOW
case '_':
case '$':
return Precedence.MED
default:
return Precedence.HIGH
}
}
class NotANumber {
constructor(token) {
this.token = token
this.precedence = getPrecedence(token)
}
hasHigherPrecedence(token) { return this.precedence >= token.precedence }
}
const isOneOf = b => a => b.includes(a)
const eitherOneOf = (...functions) => token => functions.some(fn => fn(token))
const isDigit = isOneOf('0123456789.')
const isOperator = isOneOf('+-*/%_$')
const isMinus = isOneOf('-')
const isPlus = isOneOf('+')
const isUnary = isOneOf('_$')
const isLeftParens = isOneOf('(')
const isRightParens = isOneOf(')')
const isParens = eitherOneOf(isLeftParens, isRightParens)
const isWhitespace = isOneOf(' ')
const isNotANumber = eitherOneOf(isOperator, isLeftParens, isRightParens)
const isUnaryOperator = eitherOneOf(isMinus, isPlus)
const precedesUnary = eitherOneOf(isOperator, isLeftParens)
const getUnary = token => isMinus(token) ? '_' : '$'
const listEnd = list => list[list.length - 1]
const cleanInput = input => input.filter(token => !isWhitespace(token))
const formatInput = (input = []) =>
input
.reduce((value, token) =>
([listEnd(value), token].every(isDigit))
? value + token
: isUnaryOperator(token) && precedesUnary(listEnd(value))
? value + ' ' + getUnary(token)
: value + ' ' + token
)
.split(' ')
const parseInput = (formattedInput = []) => formattedInput.map(token => isNotANumber(token) ? new NotANumber(token) : new Number(token))
const insertToken = (stack = [], list = [], tokenObject) =>
listEnd(stack) &&
!isLeftParens(listEnd(stack).token) &&
listEnd(stack).hasHigherPrecedence(tokenObject)
? insertToken(stack.slice(0, stack.length - 1), list.concat(listEnd(stack)), tokenObject)
: { stack: stack.concat(tokenObject), list }
const dropTokens = (stack = [], list = []) =>
listEnd(stack) &&
isLeftParens(listEnd(stack).token)
? { stack: stack.slice(0, stack.length - 1), list }
: dropTokens(stack.slice(0, stack.length - 1), list.concat(listEnd(stack)))
const convertPostfix = (parsedInput = []) => {
const { stack, list } = parsedInput.reduce(
({ stack, list }, tokenObject) =>
tokenObject instanceof Number
? { stack, list: list.concat(tokenObject) }
: isRightParens(tokenObject.token)
? dropTokens(stack, list)
: insertToken(stack, list, tokenObject)
, { stack: [], list: [] }
)
return list.concat(stack.reverse())
}
const doBinary = (rOperand = 0, lOperand = 0, operation = '+') => {
switch (operation) {
case '+': return lOperand + rOperand
case '-': return lOperand - rOperand
case '*': return lOperand * rOperand
case '/': return lOperand / rOperand
case '%': return lOperand % rOperand
}
}
const doUnary = (operand = 0, operation = '+') => operation === '_' ? -operand : operand
const evaluatePostfix = (postfixInput = []) => postfixInput
.reduce((stack, tokenObject) =>
tokenObject instanceof Number
? stack.concat(tokenObject)
: isUnary(tokenObject.token)
? stack.concat(doUnary(stack.pop(), tokenObject.token))
: stack.concat(doBinary(stack.pop(), stack.pop(), tokenObject.token))
, [])
.pop().valueOf()
const flow = (...functions) => originalInput => functions.reduce((input, fn) => fn(input), originalInput)
const calculateExpression = flow(Array.from, cleanInput, formatInput, parseInput, convertPostfix, evaluatePostfix)
let expressionsStore = new Map();
function storeExpression(fullExpression) {
let [ref, expression] = fullExpression.trim().split("=").map(_ => _.trim())
if (expression) {
expressionsStore.set(ref, expression);
} else {
return calculateWithReferences(fullExpression)
}
}
function calculateWithReferences(_expr) {
let references = _expr.split("").filter(input => expressionsStore.has(input));
while (references.length > 0) {
let expressions = references.map(ref => expressionsStore.get(ref));
_expr = references.reduce((acc, ref, index) => acc.replace(ref, `(${expressions[index]})`), _expr)
references = _expr.split("").filter(input => expressionsStore.has(input));
}
console.log(calculateExpression(_expr))
return calculateExpression(_expr)
}
storeExpression("a = 3")
storeExpression("b = a + 1")
storeExpression("c = b+3")
storeExpression("2 * 5 / 2") // 2
storeExpression("c") // 7
storeExpression("a = 1")
storeExpression("c") // 8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment