Start stubbing out Pratt parser - parse integers
This commit is contained in:
parent
20b6624ed7
commit
62593b0ab8
2 changed files with 113 additions and 4 deletions
23
src/lib/expr.test.ts
Normal file
23
src/lib/expr.test.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { AsText, TextPiece } from "../words";
|
||||||
|
import { Expr } from "./expr";
|
||||||
|
|
||||||
|
describe("expr", () => {
|
||||||
|
test.each([
|
||||||
|
["1", "1"],
|
||||||
|
// ["1 + 2", "3"],
|
||||||
|
// ["1 - 2", "-1"],
|
||||||
|
// ["1 * 2", "2"],
|
||||||
|
// ["1 / 2", "0.5"],
|
||||||
|
// ["1 // 2", "0"],
|
||||||
|
// TODO: operator precedence
|
||||||
|
// TODO: parentheses
|
||||||
|
// TODO; eror reporting
|
||||||
|
])("Expr does math: %s", (expression, result) => {
|
||||||
|
const actualResult = Expr({}, [{ text: expression }]);
|
||||||
|
console.log(actualResult)
|
||||||
|
expect("error" in actualResult).toBeFalsy();
|
||||||
|
expect(AsText(actualResult as TextPiece)).toEqual(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: handle expr prefix
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
import { AsText, ProcResult, TextPiece } from "../words";
|
import { AsText, ErrorResult, ProcResult, TextPiece } from "../words";
|
||||||
|
|
||||||
export function Expr({}, argv: TextPiece[]): ProcResult {
|
export function Expr({}, argv: TextPiece[]): ProcResult {
|
||||||
const name = argv[0];
|
const name = argv[0];
|
||||||
|
@ -6,7 +6,93 @@ export function Expr({}, argv: TextPiece[]): ProcResult {
|
||||||
// being called as [expr ...], not fallback for math
|
// being called as [expr ...], not fallback for math
|
||||||
argv.splice(0, 1);
|
argv.splice(0, 1);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
text: `Will do math to solve ${argv.map(AsText).join(" ")} eventually`,
|
const expression = argv.map(AsText).join(" ").trim();
|
||||||
};
|
|
||||||
|
const parser = new ExpressionParser(expression);
|
||||||
|
const result = parser.parsePrefixOp();
|
||||||
|
|
||||||
|
if ("value" in result) {
|
||||||
|
return { text: String(result.value) };
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
|
// Pratt parser for evaluating arithmatic expressions
|
||||||
|
|
||||||
|
const PLUS_TOKEN = /\s*(\+)/y;
|
||||||
|
const MINUS_TOKEN = /\s*(\-)/y;
|
||||||
|
const TIMES_TOKEN = /\s*(\*)/y;
|
||||||
|
const FLOOR_TOKEN = /\s*(\/\/)/y;
|
||||||
|
const DIV_TOKEN = /\s*(\/)/y;
|
||||||
|
|
||||||
|
const NUMBER_TOKEN = /\s*(\d+)/y;
|
||||||
|
|
||||||
|
type Value = { value: number };
|
||||||
|
|
||||||
|
type TokenHandler = {
|
||||||
|
leftBindingPower: number;
|
||||||
|
token: RegExp;
|
||||||
|
parse: (matched: string, parser: ExpressionParser) => Value | ErrorResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Operators: TokenHandler[] = [
|
||||||
|
{
|
||||||
|
leftBindingPower: -1,
|
||||||
|
token: NUMBER_TOKEN,
|
||||||
|
parse: (matched) => ({ value: Number(matched) }),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
class ExpressionParser {
|
||||||
|
pos: number = 0;
|
||||||
|
|
||||||
|
constructor(public text: string) {}
|
||||||
|
|
||||||
|
next(token: RegExp): string | null {
|
||||||
|
token.lastIndex = this.pos;
|
||||||
|
const matches = token.exec(this.text);
|
||||||
|
if (matches) {
|
||||||
|
this.pos = token.lastIndex;
|
||||||
|
return matches[1];
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tries to match one of the given tokens & returns the value of the handler. If none match, returns null. */
|
||||||
|
tryAll(options: TokenHandler[]): Value | ErrorResult | null {
|
||||||
|
for (const option of options) {
|
||||||
|
const match = this.next(option.token);
|
||||||
|
if (match) {
|
||||||
|
return option.parse(match, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsePrefixOp(): Value | ErrorResult {
|
||||||
|
return (
|
||||||
|
this.tryAll(
|
||||||
|
Operators.filter((operator) => operator.leftBindingPower == -1)
|
||||||
|
) ?? { error: "Couldn't parse number" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse expression:
|
||||||
|
// must parse sub-expression with right binding power 0
|
||||||
|
// must be at EOF
|
||||||
|
|
||||||
|
// parse sub-expression with right binding power :
|
||||||
|
// must parse a prefix op
|
||||||
|
// parse as many postfix ops w/ left binding power > right binding power
|
||||||
|
|
||||||
|
// parse op:
|
||||||
|
// must parse associated token (if success- failing the rest of the op fails *everything*)
|
||||||
|
// depending on op:
|
||||||
|
// maybe parse sub-expressions with op's right-binding power
|
||||||
|
// maybe consume other tokens
|
||||||
|
|
Loading…
Reference in a new issue