diff --git a/src/engine/parser.ts b/src/engine/parser.ts index ace0915..a8b2d06 100644 --- a/src/engine/parser.ts +++ b/src/engine/parser.ts @@ -217,136 +217,129 @@ class Parser { if (!lhs) return null; - loop: for (;;) { + for (;;) { tk = this.peek(); if (!tk) break; - switch (tk.t) { - case "percent": { - if (PREC.POSTFIX < minPrec) break loop; - this.i++; - lhs = { k: "pct", x: lhs }; - continue loop; - } - case "bang": { - if (PREC.POSTFIX < minPrec) break loop; - this.i++; - lhs = { k: "fact", x: lhs }; - continue loop; - } - case "unit": { - if (PREC.POSTFIX < minPrec) break loop; - this.i++; - lhs = { k: "unit", x: lhs, unit: tk.unit }; - continue loop; - } - case "currency": { - if (PREC.POSTFIX < minPrec) break loop; - this.i++; - lhs = { k: "curr", x: lhs, code: tk.code }; - continue loop; - } - case "scale": { - if (PREC.POSTFIX < minPrec) break loop; - this.i++; - lhs = { k: "scale", x: lhs, mult: tk.mult, label: "" }; - continue loop; - } - case "op": { - switch (tk.op) { - case "pow": { - if (PREC.POW < minPrec) break loop; - const save = this.i; - this.i++; - const r = this.parseExpr(PREC.POW); - if (!r) { this.i = save; break loop; } - lhs = { k: "bin", op: "pow", l: lhs, r }; - continue loop; - } - case "mul": - case "div": - case "mod": { - if (PREC.MUL < minPrec) break loop; - const save = this.i; - this.i++; - const r = this.parseExpr(PREC.MUL + 1); - if (!r) { this.i = save; break loop; } - lhs = { k: "bin", op: tk.op, l: lhs, r }; - continue loop; - } - case "plus": - case "minus": { - if (PREC.ADD < minPrec) break loop; - const save = this.i; - this.i++; - const r = this.parseExpr(PREC.ADD + 1); - if (!r) { this.i = save; break loop; } - lhs = { k: "bin", op: tk.op, l: lhs, r }; - continue loop; - } + const nextLhs = this.parseExprStep(tk, lhs, minPrec); + if (nextLhs === undefined) break; + lhs = nextLhs; + } + return lhs; + } + + private parseExprStep(tk: Token, lhs: Node, minPrec: number): Node | undefined { + switch (tk.t) { + case "percent": { + if (PREC.POSTFIX < minPrec) return undefined; + this.i++; + return { k: "pct", x: lhs }; + } + case "bang": { + if (PREC.POSTFIX < minPrec) return undefined; + this.i++; + return { k: "fact", x: lhs }; + } + case "unit": { + if (PREC.POSTFIX < minPrec) return undefined; + this.i++; + return { k: "unit", x: lhs, unit: tk.unit }; + } + case "currency": { + if (PREC.POSTFIX < minPrec) return undefined; + this.i++; + return { k: "curr", x: lhs, code: tk.code }; + } + case "scale": { + if (PREC.POSTFIX < minPrec) return undefined; + this.i++; + return { k: "scale", x: lhs, mult: tk.mult, label: "" }; + } + case "op": { + switch (tk.op) { + case "pow": { + if (PREC.POW < minPrec) return undefined; + const save = this.i; + this.i++; + const r = this.parseExpr(PREC.POW); + if (!r) { this.i = save; return undefined; } + return { k: "bin", op: "pow", l: lhs, r }; } - break loop; - } - case "pctop": { - if (tk.op === "of" || tk.op === "off" || tk.op === "on") { - if (!(lhs.k === "pct" || lhs.k === "var" || lhs.k === "pctop")) { - this.i++; - continue loop; - } - if (PREC.MUL < minPrec) break loop; + case "mul": + case "div": + case "mod": { + if (PREC.MUL < minPrec) return undefined; const save = this.i; this.i++; const r = this.parseExpr(PREC.MUL + 1); - if (!r) { this.i = save; break loop; } - lhs = { k: "pctop", op: tk.op, l: lhs, r }; - continue loop; + if (!r) { this.i = save; return undefined; } + return { k: "bin", op: tk.op, l: lhs, r }; } - if (tk.op.startsWith("as_pct") || tk.op.endsWith("what_is")) { - if (PREC.CONV < minPrec) break loop; + case "plus": + case "minus": { + if (PREC.ADD < minPrec) return undefined; const save = this.i; this.i++; - const r = this.parseExpr(PREC.ADD); - if (!r) { this.i = save; break loop; } - lhs = { k: "pctop", op: tk.op, l: lhs, r }; - continue loop; + const r = this.parseExpr(PREC.ADD + 1); + if (!r) { this.i = save; return undefined; } + return { k: "bin", op: tk.op, l: lhs, r }; } - break loop; - } - case "lparen": - case "const": - case "func": - case "word": { - if (tk.t === "word" && !this.vars.has(tk.raw)) break loop; - if (PREC.MUL < minPrec) break loop; - const save = this.i; - const r = this.parseExpr(PREC.MUL + 1); - if (!r) { this.i = save; break loop; } - lhs = { k: "bin", op: "mul", l: lhs, r }; - continue loop; - } - case "bitop": { - if (PREC.BIT < minPrec) break loop; - const save = this.i; - this.i++; - const r = this.parseExpr(PREC.BIT + 1); - if (!r) { this.i = save; break loop; } - lhs = { k: "bit", op: tk.op, l: lhs, r }; - continue loop; - } - case "conv": { - if (PREC.CONV < minPrec) break loop; - const save = this.i; - this.i++; - const target = this.parseTarget(); - if (!target) { this.i = save; break loop; } - lhs = { k: "conv", x: lhs, target: this.tryHistoricalDate(target) }; - continue loop; } + return undefined; } - - break; + case "pctop": { + if (tk.op === "of" || tk.op === "off" || tk.op === "on") { + if (!(lhs.k === "pct" || lhs.k === "var" || lhs.k === "pctop")) { + this.i++; + return lhs; + } + if (PREC.MUL < minPrec) return undefined; + const save = this.i; + this.i++; + const r = this.parseExpr(PREC.MUL + 1); + if (!r) { this.i = save; return undefined; } + return { k: "pctop", op: tk.op, l: lhs, r }; + } + if (tk.op.startsWith("as_pct") || tk.op.endsWith("what_is")) { + if (PREC.CONV < minPrec) return undefined; + const save = this.i; + this.i++; + const r = this.parseExpr(PREC.ADD); + if (!r) { this.i = save; return undefined; } + return { k: "pctop", op: tk.op, l: lhs, r }; + } + return undefined; + } + case "lparen": + case "const": + case "func": + case "word": { + if (tk.t === "word" && !this.vars.has(tk.raw)) return undefined; + if (PREC.MUL < minPrec) return undefined; + const save = this.i; + const r = this.parseExpr(PREC.MUL + 1); + if (!r) { this.i = save; return undefined; } + return { k: "bin", op: "mul", l: lhs, r }; + } + case "bitop": { + if (PREC.BIT < minPrec) return undefined; + const save = this.i; + this.i++; + const r = this.parseExpr(PREC.BIT + 1); + if (!r) { this.i = save; return undefined; } + return { k: "bit", op: tk.op, l: lhs, r }; + } + case "conv": { + if (PREC.CONV < minPrec) return undefined; + const save = this.i; + this.i++; + const target = this.parseTarget(); + if (!target) { this.i = save; return undefined; } + return { k: "conv", x: lhs, target: this.tryHistoricalDate(target) }; + } + default: + return undefined; } - return lhs; } /** If the current token is `pctop(on)` + `datelit`, consume them and attach `onDate`. */ private tryHistoricalDate(target: ConvTarget): ConvTarget {