From 62593b0ab8dfaa63a788702762146803c9845a98 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Fri, 17 May 2024 20:26:54 -0400 Subject: [PATCH] Start stubbing out Pratt parser - parse integers --- src/lib/expr.test.ts | 23 +++++++++++ src/lib/expr.ts | 94 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 src/lib/expr.test.ts diff --git a/src/lib/expr.test.ts b/src/lib/expr.test.ts new file mode 100644 index 0000000..eea76f7 --- /dev/null +++ b/src/lib/expr.test.ts @@ -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 +}); diff --git a/src/lib/expr.ts b/src/lib/expr.ts index 59e01f3..27cc2fe 100644 --- a/src/lib/expr.ts +++ b/src/lib/expr.ts @@ -1,4 +1,4 @@ -import { AsText, ProcResult, TextPiece } from "../words"; +import { AsText, ErrorResult, ProcResult, TextPiece } from "../words"; export function Expr({}, argv: TextPiece[]): ProcResult { const name = argv[0]; @@ -6,7 +6,93 @@ export function Expr({}, argv: TextPiece[]): ProcResult { // being called as [expr ...], not fallback for math 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