Skip to content

Instantly share code, notes, and snippets.

@userhooke
Created July 12, 2021 07:01
Show Gist options
  • Save userhooke/800f83c900c506469f193a1863f1480f to your computer and use it in GitHub Desktop.
Save userhooke/800f83c900c506469f193a1863f1480f to your computer and use it in GitHub Desktop.
simple lexer, parser and evaluator to run calculations
const assert = require('assert').strict;
const readline = require('readline');
const TOKEN_TYPES = {
INT: 'INT',
PLUS: 'PLUS',
MINUS: 'MINUS',
MUL: 'MUL',
DIV: 'DIV',
LPAREN: 'LPAREN',
RPAREN: 'RPAREN',
EOF: 'EOF'
}
const pipe = (...funcs) => value =>
funcs.reduce((value, func) => func(value), value);
const pop = array => array.shift();
const isInt = char => !isNaN(parseInt(char));
const isPlus = char => char === '+';
const isMinus = char => char === '-';
const isMul = char => char === '*';
const isDiv = char => char === '/';
const isLParen = char => char === '('
const isRParen = char => char === ')'
const isWhitespace = char => /\s/g.test(char);
const isTypeInt = token => token.type === TOKEN_TYPES.INT;
const isTypePlus = token => token.type === TOKEN_TYPES.PLUS;
const isTypeMinus = token => token.type === TOKEN_TYPES.MINUS;
const isTypeMul = token => token.type === TOKEN_TYPES.MUL;
const isTypeDiv = token => token.type === TOKEN_TYPES.DIV;
const isTypeLParen = token => token.type === TOKEN_TYPES.LPAREN;
const isTypeRParen = token => token.type === TOKEN_TYPES.RPAREN;
const isTypeEOF = token => token.type === TOKEN_TYPES.EOF;
const createToken = (type, value) => {
if (!TOKEN_TYPES[type]) {
throw new Error(`${type} type is not valid`)
} else {
return {
type,
value
}
}
}
/**
* takes an input and converts it into a stream of tokens
*/
const lexer = input => {
let cursor = 0;
const tokens = [];
while(cursor < input.length) {
let symbol = input[cursor]
if (isInt(symbol)) {
while(isInt(input[cursor+1])) {
symbol += input[cursor+1];
cursor++;
}
tokens.push(createToken(TOKEN_TYPES.INT, parseInt(symbol)));
cursor++;
} else if (isWhitespace(symbol)) {
cursor++;
} else if (isPlus(symbol)) {
tokens.push(createToken(TOKEN_TYPES.PLUS, '+'))
cursor++;
} else if (isMinus(symbol)) {
tokens.push(createToken(TOKEN_TYPES.MINUS, '-'))
cursor++;
} else if (isMul(symbol)) {
tokens.push(createToken(TOKEN_TYPES.MUL, '*'))
cursor++;
} else if (isDiv(symbol)) {
tokens.push(createToken(TOKEN_TYPES.DIV, '/'))
cursor++;
} else if (isLParen(symbol)) {
tokens.push(createToken(TOKEN_TYPES.LPAREN, '('))
cursor++;
} else if (isRParen(symbol)) {
tokens.push(createToken(TOKEN_TYPES.RPAREN, ')'))
cursor++;
} else {
throw new Error(`"${symbol}" is illegal character`)
}
}
tokens.push(createToken('EOF', 'EOF'))
return tokens;
}
/*
expr : term ((PLUS | MINUS) term)*
term : factor ((MUL | DIV) factor)*
factor : INTEGER | LPAREN expr RPAREN
*/
const factor = tokens => {
const token = pop(tokens)
if (isTypeInt(token)) {
return token.value
} else if (isTypeLParen(token)) {
return expr(tokens)
}
}
const term = tokens => {
let result = factor(tokens)
let cursor = 0;
while (tokens[cursor] && (isTypeMul(tokens[cursor]) || isTypeDiv(tokens[cursor]))) {
const token = pop(tokens)
if (isTypeMul(token)) {
result = result * factor(tokens)
} else if (isTypeDiv(token)) {
result = result / factor(tokens)
}
cursor++
}
return result
}
const expr = tokens => {
let result = term(tokens)
let cursor = 0;
while (tokens[cursor] && (isTypePlus(tokens[cursor]) || isTypeMinus(tokens[cursor]))) {
const token = pop(tokens)
if (isTypePlus(token)) {
result = result + term(tokens)
} else if (isTypeMinus(token)) {
result = result - term(tokens)
}
cursor++;
}
return result
}
const calc = pipe(
lexer,
expr
)
assert.strictEqual(calc('7 + 3 * (10 / (12 / (3 + 1) - 1)) / (2 + 3) - 5 - 3 + (8)'), 10)
assert.strictEqual(calc("7 + 3 * (10 / (12 / (3 + 1) - 1))"), 22)
assert.strictEqual(calc('2 + 2 * 2'), 6)
assert.strictEqual(calc('2 + 2 - 2'), 4)
assert.strictEqual(calc('7 + (((3 + 2)))'), 12)
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const recursiveAsyncReadLine = () => {
rl.question('calc> ', expr => {
const output = calc(expr);
console.log(output)
recursiveAsyncReadLine()
})
}
recursiveAsyncReadLine()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment