-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.ts
More file actions
221 lines (190 loc) · 6.52 KB
/
Copy pathtest.ts
File metadata and controls
221 lines (190 loc) · 6.52 KB
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { tokenize } from "./parser/lexer.ts";
import {
fillMissingDepartmentCodes,
removeIrrelevantParentheses,
removeRecommendations,
resolveCommas,
toSyntaxTokens,
trimLogicalOperators,
} from "./parser/normalization.ts";
import { RequirementParser } from "./parser/parser.ts";
type ExpectedRequirement =
| { kind: "course"; code: string }
| {
kind: "allOf" | "anyOf";
items: ExpectedRequirement[];
};
function course(code: string): ExpectedRequirement {
return { kind: "course", code };
}
function allOf(...items: ExpectedRequirement[]): ExpectedRequirement {
return { kind: "allOf", items };
}
function anyOf(...items: ExpectedRequirement[]): ExpectedRequirement {
return { kind: "anyOf", items };
}
function parsePrerequisite(input: string) {
const tokens = tokenize(input);
const requiredTokens = removeRecommendations(input, tokens);
const fixedTokens = fillMissingDepartmentCodes(requiredTokens);
const cleanedTokens = removeIrrelevantParentheses(fixedTokens);
const resolvedTokens = resolveCommas(cleanedTokens);
const syntaxTokens = toSyntaxTokens(resolvedTokens);
const finalSyntaxTokens = trimLogicalOperators(syntaxTokens);
return new RequirementParser(finalSyntaxTokens).parse();
}
describe("prerequisite parsing", () => {
it("CMPT 125: parses a basic OR and ignores a grade modifier", () => {
const input = "CMPT 120 or CMPT 130, with a minimum grade of C-.";
assert.deepStrictEqual(
parsePrerequisite(input),
anyOf(course("CMPT 120"), course("CMPT 130")),
);
});
it("CMPT 210: resolves Oxford commas without merging inner OR groups", () => {
const input =
"MACM 101, MATH 152, CMPT 125 or CMPT 135, and (MATH 240 or MATH 232), all with a minimum grade of C-.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(
course("MACM 101"),
course("MATH 152"),
anyOf(course("CMPT 125"), course("CMPT 135")),
anyOf(course("MATH 240"), course("MATH 232")),
),
);
});
it("CMPT 225: resolves a non-Oxford comma inside nested alternatives", () => {
const input =
"(MACM 101 and (CMPT 125, CMPT 129 or CMPT 135)) or (ENSC 251 and ENSC 252), all with a minimum grade of C-.";
assert.deepStrictEqual(
parsePrerequisite(input),
anyOf(
allOf(
course("MACM 101"),
anyOf(course("CMPT 125"), course("CMPT 129"), course("CMPT 135")),
),
allOf(course("ENSC 251"), course("ENSC 252")),
),
);
});
it("CMPT 310: removes a recommendation containing a course", () => {
const input =
"CMPT 225 and STAT 271, all with a minimum grade of C-. Recommended: MATH 251.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(course("CMPT 225"), course("STAT 271")),
);
});
it("CMPT 340: resolves a comma-only 'one of' list and fills departments", () => {
const input =
"Completion of 60 units including one of CMPT 125, 126, 128, 135, with a minimum grade of C- or CMPT 102 with a grade of B or higher.";
assert.deepStrictEqual(
parsePrerequisite(input),
anyOf(
course("CMPT 125"),
course("CMPT 126"),
course("CMPT 128"),
course("CMPT 135"),
course("CMPT 102"),
),
);
});
it("CMPT 353: resolves an Oxford OR list inside an AND expression", () => {
const input =
"CMPT 225 and (BUS 232, STAT 201, STAT 203, STAT 205, STAT 270, STAT 271, ENSC 280, MSE 210, or SEE 241), with a minimum grade of C-.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(
course("CMPT 225"),
anyOf(
course("BUS 232"),
course("STAT 201"),
course("STAT 203"),
course("STAT 205"),
course("STAT 270"),
course("STAT 271"),
course("ENSC 280"),
course("MSE 210"),
course("SEE 241"),
),
),
);
});
it("CMPT 420: binds the course choice more tightly than the comma-derived AND", () => {
const input =
"CMPT 310, CMPT 410 or CMPT 419 (Machine Learning), both with a minimum grade of C-.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(course("CMPT 310"), anyOf(course("CMPT 410"), course("CMPT 419"))),
);
});
it("CMPT 475: joins two alternative groups with a non-Oxford comma", () => {
const input =
"CMPT 275 or CMPT 276, (MACM 201 or CMPT 210), all with a minimum grade of C- and 15 units of upper division courses. Recommended: Co-op experience.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(
anyOf(course("CMPT 275"), course("CMPT 276")),
anyOf(course("MACM 201"), course("CMPT 210")),
),
);
});
it("MACM 316: fills repeated departments and trims an operator before ignored recommendation", () => {
const input =
"MATH 152 or 155 or 158, and MATH 232 or 240, and computing experience.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(
anyOf(course("MATH 152"), course("MATH 155"), course("MATH 158")),
anyOf(course("MATH 232"), course("MATH 240")),
),
);
});
it("MACM 401: preserves deeply nested alternatives and conjunctions", () => {
const input =
"CMPT 307 or ((MATH 340 or MATH 342) and (CMPT 225 or MACM 204)).";
assert.deepStrictEqual(
parsePrerequisite(input),
anyOf(
course("CMPT 307"),
allOf(
anyOf(course("MATH 340"), course("MATH 342")),
anyOf(course("CMPT 225"), course("MACM 204")),
),
),
);
});
it("MACM 442: treats semicolon OR as lower precedence than AND", () => {
const input =
"(CMPT 201 or 225) and one of (MATH 340 or 332 or 342); or CMPT 405.";
assert.deepStrictEqual(
parsePrerequisite(input),
anyOf(
allOf(
anyOf(course("CMPT 201"), course("CMPT 225")),
anyOf(course("MATH 340"), course("MATH 332"), course("MATH 342")),
),
course("CMPT 405"),
),
);
});
it("MATH 308: separates two OR lists with AND", () => {
const input =
"MATH 150, 151, 154, or 157 and MATH 240 or 232, all with a minimum grade of C-.";
assert.deepStrictEqual(
parsePrerequisite(input),
allOf(
anyOf(
course("MATH 150"),
course("MATH 151"),
course("MATH 154"),
course("MATH 157"),
),
anyOf(course("MATH 240"), course("MATH 232")),
),
);
});
});