Skip to content

Instantly share code, notes, and snippets.

@reu
Last active August 21, 2023 15:21
Show Gist options
  • Save reu/63b2937190eb7d5a6b70854f30d87b96 to your computer and use it in GitHub Desktop.
Save reu/63b2937190eb7d5a6b70854f30d87b96 to your computer and use it in GitHub Desktop.
Expression parser using combinators
import {
any,
char,
delimited,
int,
many0,
map,
multispace0,
nat,
parse,
Parser,
token,
tuple,
tuple3,
} from "https://raw.githubusercontent.com/reu/parser-combinators-lecture/master/combinators.ts";
type Operator = "+" | "-" | "*" | "/" | "**";
type Expression =
| { type: "NUMBER"; value: number }
| {
type: "BINARY_OPERATION";
operator: Operator;
left: Expression;
right: Expression;
};
const ws = <A>(p: Parser<A>): Parser<A> =>
delimited(multispace0, p, multispace0);
const number: Parser<Expression> = map(
any([
map(tuple3(nat, char("."), nat), ([n, _, d]) => n + d * 10 ** -1),
map(tuple3(int, char("."), nat), ([n, _, d]) => n - d * 10 ** -1),
int,
]),
(value) => ({ type: "NUMBER", value }),
);
const operand: Parser<Expression> = (input) =>
any([
delimited(ws(char("(")), expression, ws(char(")"))),
number,
])(input);
const operation = (
operators: Array<Operator>,
precedent: Parser<Expression>,
): Parser<Expression> =>
map(
tuple(
precedent,
many0(tuple(ws(any(operators.map(token))), precedent)),
),
([left, right]) =>
right.reduce((left, [operator, right]) => ({
type: "BINARY_OPERATION",
operator: operator as Operator,
left,
right,
}), left),
);
const operations = (operations: Array<Array<Operator>>): Parser<Expression> =>
operations.reduce((expr, opers) => operation(opers, expr), operand);
export const expression = operations([
["**"],
["*", "/"],
["+", "-"],
]);
export const evaluate = parse(map(
expression,
function evaluate(expression: Expression): number {
switch (expression.type) {
case "NUMBER":
return expression.value;
case "BINARY_OPERATION": {
switch (expression.operator) {
case "+":
return evaluate(expression.left) + evaluate(expression.right);
case "-":
return evaluate(expression.left) - evaluate(expression.right);
case "*":
return evaluate(expression.left) * evaluate(expression.right);
case "/":
return evaluate(expression.left) / evaluate(expression.right);
case "**":
return evaluate(expression.left) ** evaluate(expression.right);
}
}
}
},
));
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
import {
evaluate,
expression,
} from "https://gist.githubusercontent.com/reu/63b2937190eb7d5a6b70854f30d87b96/raw/expression.ts";
Deno.test(function expressionTest() {
assertEquals(evaluate("1"), 1);
assertEquals(expression("1"), [{ type: "NUMBER", value: 1 }, ""]);
assertEquals(evaluate("-1"), -1);
assertEquals(expression("-1"), [{ type: "NUMBER", value: -1 }, ""]);
assertEquals(evaluate("1.5"), 1.5);
assertEquals(expression("1.5"), [{ type: "NUMBER", value: 1.5 }, ""]);
assertEquals(evaluate("-1.5"), -1.5);
assertEquals(expression("-1.5"), [{ type: "NUMBER", value: -1.5 }, ""]);
assertEquals(evaluate("1+2"), 3);
assertEquals(expression("1+2"), [{
type: "BINARY_OPERATION",
operator: "+",
left: { type: "NUMBER", value: 1 },
right: { type: "NUMBER", value: 2 },
}, ""]);
assertEquals(evaluate("1*2"), 2);
assertEquals(expression("1*2"), [{
type: "BINARY_OPERATION",
operator: "*",
left: { type: "NUMBER", value: 1 },
right: { type: "NUMBER", value: 2 },
}, ""]);
assertEquals(evaluate("4*2+3"), 11);
assertEquals(expression("4*2+3"), [{
type: "BINARY_OPERATION",
operator: "+",
left: {
type: "BINARY_OPERATION",
operator: "*",
left: { type: "NUMBER", value: 4 },
right: { type: "NUMBER", value: 2 },
},
right: { type: "NUMBER", value: 3 },
}, ""]);
assertEquals(evaluate("4 * ( 2 + 3)"), 20);
assertEquals(expression("4*(2+3)"), [{
type: "BINARY_OPERATION",
operator: "*",
left: { type: "NUMBER", value: 4 },
right: {
type: "BINARY_OPERATION",
operator: "+",
left: { type: "NUMBER", value: 2 },
right: { type: "NUMBER", value: 3 },
},
}, ""]);
assertEquals(evaluate("4*5**2+3"), 103);
assertEquals(expression("4*5**2+3"), [{
type: "BINARY_OPERATION",
operator: "+",
left: {
type: "BINARY_OPERATION",
operator: "*",
left: { type: "NUMBER", value: 4 },
right: {
type: "BINARY_OPERATION",
operator: "**",
left: {
type: "NUMBER",
value: 5,
},
right: {
type: "NUMBER",
value: 2,
},
},
},
right: { type: "NUMBER", value: 3 },
}, ""]);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment