-
Notifications
You must be signed in to change notification settings - Fork 18
/
TestRecursiveParser.ts
107 lines (94 loc) · 2.77 KB
/
TestRecursiveParser.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// tslint:disable:no-duplicate-imports
// tslint:disable:trailing-comma
import * as assert from 'assert';
import { Token } from 'typescript-parsec';
import { buildLexer, expectEOF, expectSingleResult, rule } from 'typescript-parsec';
import { alt, apply, kmid, lrec_sc, seq, str, tok } from 'typescript-parsec';
enum TokenKind {
Number,
Add,
Sub,
Mul,
Div,
LParen,
RParen,
Space,
}
const lexer = buildLexer([
[true, /^\d+(\.\d+)?/g, TokenKind.Number],
[true, /^\+/g, TokenKind.Add],
[true, /^\-/g, TokenKind.Sub],
[true, /^\*/g, TokenKind.Mul],
[true, /^\//g, TokenKind.Div],
[true, /^\(/g, TokenKind.LParen],
[true, /^\)/g, TokenKind.RParen],
[false, /^\s+/g, TokenKind.Space]
]);
function applyNumber(value: Token<TokenKind.Number>): number {
return +value.text;
}
function applyUnary(value: [Token<TokenKind>, number]): number {
switch (value[0].text) {
case '+': return +value[1];
case '-': return -value[1];
default: throw new Error(`Unknown unary operator: ${value[0].text}`);
}
}
function applyBinary(first: number, second: [Token<TokenKind>, number]): number {
switch (second[0].text) {
case '+': return first + second[1];
case '-': return first - second[1];
case '*': return first * second[1];
case '/': return first / second[1];
default: throw new Error(`Unknown binary operator: ${second[0].text}`);
}
}
const TERM = rule<TokenKind, number>();
const FACTOR = rule<TokenKind, number>();
const EXP = rule<TokenKind, number>();
/*
TERM
= NUMBER
= ('+' | '-') TERM
= '(' EXP ')'
*/
TERM.setPattern(
alt(
apply(tok(TokenKind.Number), applyNumber),
apply(seq(alt(str('+'), str('-')), TERM), applyUnary),
kmid(str('('), EXP, str(')'))
)
);
/*
FACTOR
= TERM
= FACTOR ('*' | '/') TERM
*/
FACTOR.setPattern(
lrec_sc(TERM, seq(alt(str('*'), str('/')), TERM), applyBinary)
);
/*
EXP
= FACTOR
= EXP ('+' | '-') FACTOR
*/
EXP.setPattern(
lrec_sc(FACTOR, seq(alt(str('+'), str('-')), FACTOR), applyBinary)
);
function evaluate(expr: string): number {
return expectSingleResult(expectEOF(EXP.parse(lexer.parse(expr))));
}
test(`Parser: calculator`, () => {
assert.strictEqual(evaluate('1'), 1);
assert.strictEqual(evaluate('+1.5'), 1.5);
assert.strictEqual(evaluate('-0.5'), -0.5);
assert.strictEqual(evaluate('1 + 2'), 3);
assert.strictEqual(evaluate('1 - 2'), -1);
assert.strictEqual(evaluate('1 * 2'), 2);
assert.strictEqual(evaluate('1 / 2'), 0.5);
assert.strictEqual(evaluate('1 + 2 * 3 + 4'), 11);
assert.strictEqual(evaluate('(1 + 2) * (3 + 4)'), 21);
assert.strictEqual(evaluate('1.2--3.4'), 4.6);
});