impl +
This commit is contained in:
parent
62593b0ab8
commit
bf44d84e17
2 changed files with 53 additions and 6 deletions
|
@ -4,7 +4,7 @@ import { Expr } from "./expr";
|
|||
describe("expr", () => {
|
||||
test.each([
|
||||
["1", "1"],
|
||||
// ["1 + 2", "3"],
|
||||
["1 + 2", "3"],
|
||||
// ["1 - 2", "-1"],
|
||||
// ["1 * 2", "2"],
|
||||
// ["1 / 2", "0.5"],
|
||||
|
|
|
@ -10,7 +10,7 @@ export function Expr({}, argv: TextPiece[]): ProcResult {
|
|||
const expression = argv.map(AsText).join(" ").trim();
|
||||
|
||||
const parser = new ExpressionParser(expression);
|
||||
const result = parser.parsePrefixOp();
|
||||
const result = parser.parseSubExpression(0);
|
||||
|
||||
if ("value" in result) {
|
||||
return { text: String(result.value) };
|
||||
|
@ -36,17 +36,40 @@ type Value = { value: number };
|
|||
type TokenHandler = {
|
||||
leftBindingPower: number;
|
||||
token: RegExp;
|
||||
parse: (matched: string, parser: ExpressionParser) => Value | ErrorResult;
|
||||
parse: (
|
||||
left: Value,
|
||||
matched: string,
|
||||
parser: ExpressionParser
|
||||
) => Value | ErrorResult;
|
||||
};
|
||||
|
||||
function map(
|
||||
value: Value | ErrorResult,
|
||||
op: (value: number) => Value | ErrorResult
|
||||
): Value | ErrorResult {
|
||||
if ("error" in value) {
|
||||
return value;
|
||||
} else {
|
||||
return op(value.value);
|
||||
}
|
||||
}
|
||||
|
||||
const Operators: TokenHandler[] = [
|
||||
{
|
||||
leftBindingPower: -1,
|
||||
token: NUMBER_TOKEN,
|
||||
parse: (matched) => ({ value: Number(matched) }),
|
||||
parse: (left, matched) => ({ value: Number(matched) }),
|
||||
},
|
||||
{
|
||||
leftBindingPower: 10,
|
||||
token: PLUS_TOKEN,
|
||||
parse: ({ value: left }, matched, parser) =>
|
||||
map(parser.parseSubExpression(11), (right) => ({ value: left + right })),
|
||||
},
|
||||
];
|
||||
|
||||
const ZERO = { value: 0 };
|
||||
|
||||
class ExpressionParser {
|
||||
pos: number = 0;
|
||||
|
||||
|
@ -64,11 +87,11 @@ class ExpressionParser {
|
|||
}
|
||||
|
||||
/** 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 {
|
||||
tryAll(left: Value, options: TokenHandler[]): Value | ErrorResult | null {
|
||||
for (const option of options) {
|
||||
const match = this.next(option.token);
|
||||
if (match) {
|
||||
return option.parse(match, this);
|
||||
return option.parse(left, match, this);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -77,10 +100,34 @@ class ExpressionParser {
|
|||
parsePrefixOp(): Value | ErrorResult {
|
||||
return (
|
||||
this.tryAll(
|
||||
ZERO,
|
||||
Operators.filter((operator) => operator.leftBindingPower == -1)
|
||||
) ?? { error: "Couldn't parse number" }
|
||||
);
|
||||
}
|
||||
|
||||
parseSubExpression(rightBindingPower: number): Value | ErrorResult {
|
||||
let value = this.parsePrefixOp();
|
||||
if ("error" in value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
do {
|
||||
const newValue = this.tryAll(
|
||||
value,
|
||||
Operators.filter(
|
||||
(operator) => operator.leftBindingPower > rightBindingPower
|
||||
)
|
||||
);
|
||||
|
||||
if (newValue == null) {
|
||||
break;
|
||||
}
|
||||
value = newValue;
|
||||
} while (!("error" in value));
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// parse expression:
|
||||
|
|
Loading…
Reference in a new issue