desmost 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/compile.d.ts.map +1 -1
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/evaluate.d.ts.map +1 -1
- package/dist/compiler/evaluate.js +2 -14
- package/dist/compiler/evaluate.js.map +1 -1
- package/dist/compiler/format.d.ts +2 -0
- package/dist/compiler/format.d.ts.map +1 -1
- package/dist/compiler/format.js +11 -0
- package/dist/compiler/format.js.map +1 -1
- package/dist/compiler/options.d.ts.map +1 -1
- package/dist/compiler/options.js.map +1 -1
- package/dist/decompiler/decompile.d.ts +9 -0
- package/dist/decompiler/decompile.d.ts.map +1 -0
- package/dist/decompiler/decompile.js +21 -0
- package/dist/decompiler/decompile.js.map +1 -0
- package/dist/decompiler/emit.d.ts +4 -0
- package/dist/decompiler/emit.d.ts.map +1 -0
- package/dist/decompiler/emit.js +28 -0
- package/dist/decompiler/emit.js.map +1 -0
- package/dist/decompiler/extract.d.ts +5 -0
- package/dist/decompiler/extract.d.ts.map +1 -0
- package/dist/decompiler/extract.js +53 -0
- package/dist/decompiler/extract.js.map +1 -0
- package/dist/decompiler/format.d.ts +3 -0
- package/dist/decompiler/format.d.ts.map +1 -0
- package/dist/decompiler/format.js +11 -0
- package/dist/decompiler/format.js.map +1 -0
- package/dist/decompiler/index.d.ts +2 -0
- package/dist/decompiler/index.d.ts.map +1 -0
- package/dist/decompiler/index.js +2 -0
- package/dist/decompiler/index.js.map +1 -0
- package/dist/desmos.d.ts.map +1 -1
- package/dist/desmos.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.internal.d.ts +1 -0
- package/dist/index.internal.d.ts.map +1 -1
- package/dist/index.internal.js +1 -0
- package/dist/index.internal.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/magic/expr/index.js.map +1 -1
- package/dist/magic/expr/latex.d.ts.map +1 -1
- package/dist/magic/expr/latex.js.map +1 -1
- package/dist/magic/expr/text.d.ts.map +1 -1
- package/dist/magic/expr/text.js.map +1 -1
- package/dist/magic/global/dark-mode.d.ts.map +1 -1
- package/dist/magic/global/dark-mode.js.map +1 -1
- package/dist/magic/global/desmos.d.ts.map +1 -1
- package/dist/magic/global/desmos.js.map +1 -1
- package/dist/magic/global/index.d.ts +3 -0
- package/dist/magic/global/index.d.ts.map +1 -1
- package/dist/magic/global/index.js +3 -0
- package/dist/magic/global/index.js.map +1 -1
- package/dist/magic/global/viewport.d.ts +4 -4
- package/dist/magic/global/viewport.d.ts.map +1 -1
- package/dist/magic/global/viewport.js +3 -1
- package/dist/magic/global/viewport.js.map +1 -1
- package/dist/magic/incantation.d.ts.map +1 -1
- package/dist/magic/incantation.js.map +1 -1
- package/dist/magic/local/anim.d.ts.map +1 -1
- package/dist/magic/local/anim.js.map +1 -1
- package/dist/magic/local/colour.d.ts.map +1 -1
- package/dist/magic/local/colour.js.map +1 -1
- package/dist/magic/local/dashed.d.ts.map +1 -1
- package/dist/magic/local/dashed.js.map +1 -1
- package/dist/magic/local/fill.d.ts.map +1 -1
- package/dist/magic/local/fill.js.map +1 -1
- package/dist/magic/local/hide.d.ts.map +1 -1
- package/dist/magic/local/hide.js.map +1 -1
- package/dist/magic/local/index.js.map +1 -1
- package/dist/magic/local/label.d.ts.map +1 -1
- package/dist/magic/local/label.js.map +1 -1
- package/dist/magic/local/line.d.ts.map +1 -1
- package/dist/magic/local/line.js.map +1 -1
- package/dist/magic/local/no-line.d.ts.map +1 -1
- package/dist/magic/local/no-line.js.map +1 -1
- package/dist/magic/local/point.d.ts.map +1 -1
- package/dist/magic/local/point.js.map +1 -1
- package/dist/magic/local/secret.d.ts.map +1 -1
- package/dist/magic/local/secret.js.map +1 -1
- package/dist/magic/local/slider.d.ts.map +1 -1
- package/dist/magic/local/slider.js.map +1 -1
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/desmost-parser.d.ts.map +1 -1
- package/dist/parser/desmost-parser.js +1 -1
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler/compile.ts +124 -124
- package/src/compiler/evaluate.ts +86 -120
- package/src/compiler/format.ts +39 -6
- package/src/compiler/options.ts +78 -75
- package/src/decompiler/decompile.ts +48 -0
- package/src/decompiler/emit.ts +47 -0
- package/src/decompiler/extract.ts +100 -0
- package/src/decompiler/format.ts +20 -0
- package/src/decompiler/index.ts +1 -0
- package/src/desmos.ts +6 -6
- package/src/errors.ts +12 -12
- package/src/index.internal.ts +2 -0
- package/src/index.ts +1 -0
- package/src/magic/expr/index.ts +2 -2
- package/src/magic/expr/latex.ts +16 -16
- package/src/magic/expr/text.ts +13 -13
- package/src/magic/global/dark-mode.ts +10 -10
- package/src/magic/global/desmos.ts +15 -15
- package/src/magic/global/index.ts +8 -3
- package/src/magic/global/viewport.ts +21 -14
- package/src/magic/incantation.ts +94 -94
- package/src/magic/local/anim.ts +10 -10
- package/src/magic/local/colour.ts +26 -26
- package/src/magic/local/dashed.ts +9 -9
- package/src/magic/local/fill.ts +12 -12
- package/src/magic/local/hide.ts +9 -9
- package/src/magic/local/index.ts +11 -11
- package/src/magic/local/label.ts +51 -51
- package/src/magic/local/line.ts +16 -16
- package/src/magic/local/no-line.ts +9 -9
- package/src/magic/local/point.ts +16 -16
- package/src/magic/local/secret.ts +9 -9
- package/src/magic/local/slider.ts +15 -15
- package/src/parser/ast.ts +45 -45
- package/src/parser/desmost-parser.ts +550 -547
- package/src/parser/generic-parser.ts +176 -176
- package/src/utils.ts +33 -33
|
@@ -7,8 +7,8 @@ import type { NoMatch, Unrecoverable } from "../errors";
|
|
|
7
7
|
import * as utils from "../utils";
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Incantation, ArgIncantation,
|
|
11
|
+
GLOBAL_INCANTATIONS, LOCAL_INCANTATIONS, EXPR_INCANTATIONS
|
|
12
12
|
} from "../magic";
|
|
13
13
|
import type { GLOBAL, LOCAL, EXPR } from "../magic";
|
|
14
14
|
|
|
@@ -18,566 +18,569 @@ import type { GLOBAL, LOCAL, EXPR } from "../magic";
|
|
|
18
18
|
*/
|
|
19
19
|
export class DesmostParser extends GenericParser
|
|
20
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
21
|
+
constructor(source: string, protected options?: DesmostOptions)
|
|
22
|
+
{
|
|
23
|
+
super(source + "\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
// == TOP-LEVEL == //
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Parse the next semantic block of source code, producing an `Ast` object, or `null` if the parser has successfully reached the end of the source code.
|
|
31
|
+
*/
|
|
32
|
+
public parse_next(): Ast | null
|
|
33
|
+
{
|
|
34
|
+
this.consume_spaces();
|
|
35
|
+
|
|
36
|
+
if (this.out_of_bounds()) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.current === "%") {
|
|
41
|
+
if (this.options?.ignore_comments) {
|
|
42
|
+
// ignore comment
|
|
43
|
+
this.parse_line();
|
|
44
|
+
this.consume_end_of_block();
|
|
45
|
+
return this.parse_next();
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// comment
|
|
49
|
+
var expr = this.parse_comment();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (this.current === "/") {
|
|
53
|
+
let r = this.parse_pre_sep();
|
|
54
|
+
|
|
55
|
+
// 1 global
|
|
56
|
+
if ("global" in r) {
|
|
57
|
+
return r.global;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 1+ locals + 1 expr
|
|
61
|
+
let incantations = r.local;
|
|
62
|
+
|
|
63
|
+
if (incantations.length > 0) {
|
|
64
|
+
this.parse_sep();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
var expr = this.parse_post_sep();
|
|
68
|
+
|
|
69
|
+
for (let invocation of incantations) {
|
|
70
|
+
expr.incantations.push(invocation);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// 1 expr
|
|
75
|
+
var expr = this.parse_post_sep();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.consume_end_of_block();
|
|
79
|
+
return expr;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parse Desmost syntax before the `::` separator, which may be:
|
|
84
|
+
*
|
|
85
|
+
* - 1 global incantation
|
|
86
|
+
* - 1+ local incantations
|
|
87
|
+
*/
|
|
88
|
+
parse_pre_sep():
|
|
89
|
+
Unrecoverable<
|
|
90
|
+
| { global: Ast.IncantationInvocation<GLOBAL> | Ast.InvalidInvocation }
|
|
91
|
+
| { local: Array<Ast.IncantationInvocation<LOCAL> | Ast.InvalidInvocation> }
|
|
92
|
+
>
|
|
93
|
+
{
|
|
94
|
+
// 1 global incantation
|
|
95
|
+
let r = this.try_parse_global_incantation();
|
|
96
|
+
|
|
97
|
+
if (r !== NO_MATCH) {
|
|
98
|
+
this.consume_end_of_block(
|
|
99
|
+
`Received excess input after global incantation /${r.incantation.identifier}`
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return { global: r };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 1+ local incantations
|
|
106
|
+
let incantations = [];
|
|
107
|
+
|
|
108
|
+
while (this.current === "/") {
|
|
109
|
+
let invocation = this.try_parse_local_incantation();
|
|
110
|
+
if (invocation === NO_MATCH) break;
|
|
111
|
+
incantations.push(invocation);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { local: incantations };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Parse the Desmost `::` separator.
|
|
119
|
+
*/
|
|
120
|
+
parse_sep(): Unrecoverable<void>
|
|
121
|
+
{
|
|
122
|
+
this.consume_whitespace();
|
|
123
|
+
this.consume("::",
|
|
124
|
+
`Expected \`::\` separator between local incantations and expression, but found: \`${this.preview()}\``
|
|
125
|
+
);
|
|
126
|
+
this.consume_whitespace();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Parse Desmost syntax after the `::` separator, which may be:
|
|
131
|
+
*
|
|
132
|
+
* - 1 line of LaTeX
|
|
133
|
+
* - 1 expression incantation
|
|
134
|
+
*/
|
|
135
|
+
parse_post_sep(): Unrecoverable<Ast.Expression>
|
|
136
|
+
{
|
|
137
|
+
this.consume_spaces();
|
|
138
|
+
|
|
139
|
+
switch (this.current) {
|
|
140
|
+
// empty block
|
|
141
|
+
case "\n":
|
|
142
|
+
return {
|
|
143
|
+
kind: Ast.Kind.EXPRESSION,
|
|
144
|
+
data: { latex: ` ` },
|
|
145
|
+
incantations: [],
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// expr incantation
|
|
149
|
+
case "/":
|
|
150
|
+
let incantation = this.try_parse_expr_incantation();
|
|
151
|
+
if (incantation !== NO_MATCH) return incantation;
|
|
152
|
+
// FALLTHROUGH: to fallback on plain LaTeX
|
|
153
|
+
|
|
154
|
+
// plain LaTeX
|
|
155
|
+
default:
|
|
156
|
+
return {
|
|
157
|
+
kind: Ast.Kind.EXPRESSION,
|
|
158
|
+
data: { latex: this.parse_line() },
|
|
159
|
+
incantations: [],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
parse_comment(): Unrecoverable<Ast.Expression>
|
|
165
|
+
{
|
|
166
|
+
this.advance();
|
|
167
|
+
let text = this.parse_line();
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
kind: Ast.Kind.EXPRESSION,
|
|
171
|
+
data: { type: "text", text },
|
|
172
|
+
incantations: [],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
// == LOW-LEVEL == //
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse a single line of arbitrary text.
|
|
181
|
+
*/
|
|
182
|
+
parse_line(): Unrecoverable<string>
|
|
183
|
+
{
|
|
184
|
+
let init = this.i;
|
|
185
|
+
|
|
186
|
+
while (!this.out_of_bounds() && this.current !== "\n") {
|
|
187
|
+
this.advance();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return this.source.slice(init, this.i).trim();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Attempt to parse a global incantation invocation.
|
|
195
|
+
*/
|
|
196
|
+
try_parse_global_incantation():
|
|
197
|
+
| Ast.IncantationInvocation<GLOBAL>
|
|
198
|
+
| Ast.InvalidInvocation
|
|
199
|
+
| NoMatch
|
|
200
|
+
{
|
|
201
|
+
let init = this.i;
|
|
202
|
+
|
|
203
|
+
if (this.try_consume("/") === NO_MATCH) return NO_MATCH;
|
|
204
|
+
|
|
205
|
+
let incantation = this.try_parse_identifier(GLOBAL_INCANTATIONS);
|
|
206
|
+
if (incantation === NO_MATCH) {
|
|
207
|
+
this.i = init;
|
|
208
|
+
return NO_MATCH;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let data = undefined;
|
|
212
|
+
|
|
213
|
+
if (incantation instanceof ArgIncantation) {
|
|
214
|
+
if (this.current === "{") {
|
|
215
|
+
data = this.parse_incantation_arg(incantation.arg_type);
|
|
216
|
+
}
|
|
217
|
+
else if (incantation.requires_arg) {
|
|
218
|
+
return {
|
|
219
|
+
kind: Ast.Kind.INVALID_INCANTATION,
|
|
220
|
+
incantation,
|
|
221
|
+
error: new UnrecoverableError.MissingInput(
|
|
222
|
+
`No argument provided for /${incantation.identifier}, which requires an argument of type: \`${incantation.arg_type}\``
|
|
223
|
+
)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.consume_spaces();
|
|
229
|
+
|
|
230
|
+
// @ts-expect-error: FIXME
|
|
231
|
+
return {
|
|
232
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
233
|
+
incantation,
|
|
234
|
+
arg_raw: data,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Attempt to parse a local incantation invocation.
|
|
240
|
+
*/
|
|
241
|
+
try_parse_local_incantation():
|
|
242
|
+
| Ast.IncantationInvocation<LOCAL>
|
|
243
|
+
| Ast.InvalidInvocation
|
|
244
|
+
| NoMatch
|
|
245
|
+
{
|
|
246
|
+
let init = this.i;
|
|
247
|
+
|
|
248
|
+
if (this.try_consume("/") === NO_MATCH) return NO_MATCH;
|
|
249
|
+
|
|
250
|
+
let incantation = this.try_parse_identifier(LOCAL_INCANTATIONS);
|
|
251
|
+
if (incantation === NO_MATCH) {
|
|
252
|
+
this.i = init;
|
|
253
|
+
return NO_MATCH;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let arg_raw = undefined;
|
|
257
|
+
|
|
258
|
+
if (incantation instanceof ArgIncantation) {
|
|
259
|
+
if (this.current === "{") {
|
|
260
|
+
arg_raw = this.parse_incantation_arg(incantation.arg_type);
|
|
261
|
+
}
|
|
262
|
+
else if (incantation.requires_arg) {
|
|
263
|
+
return {
|
|
264
|
+
kind: Ast.Kind.INVALID_INCANTATION,
|
|
265
|
+
incantation,
|
|
266
|
+
error: new UnrecoverableError.MissingInput(
|
|
267
|
+
`No argument provided for /${incantation.identifier}, which requires an argument of type: \`${incantation.arg_type}\``
|
|
268
|
+
)
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
this.consume_whitespace();
|
|
274
|
+
|
|
275
|
+
// @ts-expect-error: FIXME
|
|
276
|
+
return {
|
|
277
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
278
|
+
incantation,
|
|
279
|
+
arg_raw,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Attempt to parse an expression incantation invocation.
|
|
285
|
+
*/
|
|
286
|
+
try_parse_expr_incantation(): Unrecoverable<Ast.Expression | NoMatch>
|
|
287
|
+
{
|
|
288
|
+
let init = this.i;
|
|
289
|
+
|
|
290
|
+
if (this.try_consume("/") === NO_MATCH) return NO_MATCH;
|
|
291
|
+
|
|
292
|
+
let incantation = this.try_parse_identifier(EXPR_INCANTATIONS);
|
|
293
|
+
if (incantation === NO_MATCH) {
|
|
294
|
+
// TODO maybe flag to user
|
|
295
|
+
this.i = init;
|
|
296
|
+
return NO_MATCH;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let arg_raw = this.parse_incantation_arg((incantation as ArgIncantation<EXPR>).arg_type);
|
|
300
|
+
|
|
301
|
+
let data = {};
|
|
302
|
+
incantation.apply(data, arg_raw);
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
kind: Ast.Kind.EXPRESSION,
|
|
306
|
+
data,
|
|
307
|
+
incantations: [],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Attempt to parse an incantation identifier.
|
|
313
|
+
*/
|
|
314
|
+
try_parse_identifier<Effect extends Incantation.Effect>(
|
|
315
|
+
incantations: Incantation<Effect>[]
|
|
316
|
+
): Incantation<Effect> | NoMatch
|
|
317
|
+
{
|
|
318
|
+
for (let incantation of incantations) {
|
|
319
|
+
// yeah the duplication here is a little meh, unfortunately needing `return` means we can't extract it into a helper
|
|
320
|
+
let r = this.try_consume(incantation.identifier);
|
|
321
|
+
if (r !== NO_MATCH) return incantation;
|
|
322
|
+
|
|
323
|
+
if (incantation.alias != undefined) {
|
|
324
|
+
let r = this.try_consume(incantation.alias);
|
|
325
|
+
if (r !== NO_MATCH) return incantation;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return NO_MATCH;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Parse an argument to an incantation, enclosed in `{}`.
|
|
334
|
+
*
|
|
335
|
+
* ```ts
|
|
336
|
+
* /incantation{ arg }
|
|
337
|
+
* ^^^^^^^
|
|
338
|
+
* ```
|
|
339
|
+
*
|
|
340
|
+
* Returns the raw text without `{}`.
|
|
341
|
+
*
|
|
342
|
+
* ```ts
|
|
343
|
+
* /incantation{ arg }
|
|
344
|
+
* ^^^
|
|
345
|
+
* ```
|
|
346
|
+
*
|
|
347
|
+
* ## Notes
|
|
348
|
+
*
|
|
349
|
+
* We don't actually care for what type the argument is since we won't be evaluating it, we just want to find the closing `}`.
|
|
350
|
+
*
|
|
351
|
+
* However, the argument itself might contain one or many `}`! So we need a way to accurately identify the actual closing brace:
|
|
352
|
+
*
|
|
353
|
+
* ```ts
|
|
354
|
+
* /incantation{ field: { value: 1 } }
|
|
355
|
+
* ^ ^
|
|
356
|
+
* ```
|
|
357
|
+
*
|
|
358
|
+
* In our context, we have the guarantee that any well-formed argument always has matching pairs of `{}`. So we'll keep track of a context stack that starts with the opening `{`, and when the stack is depleted, we must've reached the end of the argument.
|
|
359
|
+
*
|
|
360
|
+
* However, there's still more edge cases:
|
|
361
|
+
*
|
|
362
|
+
* ```ts
|
|
363
|
+
* /label{ text: "This }s weird" }
|
|
364
|
+
* ^
|
|
365
|
+
* ```
|
|
366
|
+
*
|
|
367
|
+
* `{` and `}` can appear in strings, and they won't necessarily be matched, so we'll ignore them by also tracking string contexts.
|
|
368
|
+
*
|
|
369
|
+
* ```ts
|
|
370
|
+
* /latex{ \{ x, y ) }
|
|
371
|
+
* ^
|
|
372
|
+
* ```
|
|
373
|
+
*
|
|
374
|
+
* `{` and `}` in LaTeX can be escaped with `\{` or `\}`, and these won't necessarily be matched, so we'll ignore them by also tracking escapes.
|
|
375
|
+
*
|
|
376
|
+
* If the user truly mismatches `{}`, then, well ...parsing will fail catastrophically!
|
|
377
|
+
*
|
|
378
|
+
* This method also handles parsing raw enum literals by converting them to strings. So this:
|
|
379
|
+
*
|
|
380
|
+
* ```ts
|
|
381
|
+
* /line{ style: DOTTED }
|
|
382
|
+
* ^^^^^^
|
|
383
|
+
* ```
|
|
384
|
+
*
|
|
385
|
+
* Produces `style: "DOTTED"` (instead of `style: DOTTED`, which would fail to later parse as JSON).
|
|
386
|
+
*/
|
|
387
|
+
parse_incantation_arg(
|
|
388
|
+
/**
|
|
389
|
+
* The type of argument to parse.
|
|
390
|
+
*
|
|
391
|
+
* Parsing strategy varies for different types:
|
|
392
|
+
*
|
|
393
|
+
* - String / LaTeX: Track only balanced `{}` and escaped `\{\}`.
|
|
394
|
+
* - JavaScript object: The above, plus balanced string quotes.
|
|
395
|
+
*/
|
|
396
|
+
arg_type: Incantation.ArgType,
|
|
397
|
+
): Unrecoverable<string>
|
|
398
|
+
{
|
|
399
|
+
let init = this.i;
|
|
400
|
+
|
|
401
|
+
this.consume("{",
|
|
402
|
+
`Expected \`{\` to start incantation argument, but found: \`${this.preview()}\``
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
/** The context stack to keep track of when the closing `}` is encountered. */
|
|
406
|
+
let stack = new ContextStack();
|
|
407
|
+
|
|
408
|
+
/** Indices at which to later insert `"` quotes. */
|
|
409
|
+
let indices_to_insert_quotes: number[] = [];
|
|
410
|
+
|
|
411
|
+
while (stack.length > 0) {
|
|
412
|
+
// If the last character was an escape, we don't care at all what the next character is!
|
|
413
|
+
// This handles \\ double escape perfectly fine, since the first negates the second
|
|
414
|
+
if (stack.try_pop(Ctx.ESCAPE)) {
|
|
415
|
+
this.advance(
|
|
416
|
+
`Unexpected end of input while parsing incantation argument, stack: ${stack.debug()}`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let top = stack.top;
|
|
421
|
+
|
|
422
|
+
switch (this.current)
|
|
423
|
+
{
|
|
424
|
+
case Char.BACKSLASH: stack.push(Ctx.ESCAPE); break;
|
|
425
|
+
|
|
426
|
+
case "{": stack.push( Ctx.BLOCK, { unless: [Ctx.STR_1, Ctx.STR_2, Ctx.STR_F] }); break;
|
|
427
|
+
case "}": stack.force_pop(Ctx.BLOCK, { unless: [Ctx.STR_1, Ctx.STR_2, Ctx.STR_F] }); break;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (arg_type === Incantation.ArgType.OBJECT)
|
|
431
|
+
{
|
|
432
|
+
if (top === Ctx.VALUE && this.current?.match(/[a-zA-Z]/)) {
|
|
433
|
+
stack.push(Ctx.ENUM);
|
|
434
|
+
indices_to_insert_quotes.push(this.i);
|
|
435
|
+
}
|
|
436
|
+
else if (top === Ctx.ENUM && this.current?.match(/[^a-zA-Z]/)) {
|
|
437
|
+
stack.pop();
|
|
438
|
+
indices_to_insert_quotes.push(this.i);
|
|
439
|
+
|
|
440
|
+
if (indices_to_insert_quotes.length % 2 != 0) {
|
|
441
|
+
console.error(`Desmost [INTERNAL]: Logic error in \`parse_incantation_arg()\`: Mismatched inserted quotes`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
switch (this.current)
|
|
446
|
+
{
|
|
447
|
+
case ":": stack.push(Ctx.VALUE, { when: [Ctx.BLOCK] }); break;
|
|
448
|
+
case ",": stack.try_pop(Ctx.VALUE); break;
|
|
449
|
+
|
|
450
|
+
case Char.QUOTE_1:
|
|
451
|
+
stack.pop_or_push(Ctx.STR_1, { unless: [Ctx.STR_2, Ctx.STR_F] });
|
|
452
|
+
stack.try_pop(Ctx.VALUE);
|
|
453
|
+
break;
|
|
454
|
+
case Char.QUOTE_2:
|
|
455
|
+
stack.pop_or_push(Ctx.STR_2, { unless: [Ctx.STR_1, Ctx.STR_F] });
|
|
456
|
+
stack.try_pop(Ctx.VALUE);
|
|
457
|
+
break;
|
|
458
|
+
case Char.BACKTICK:
|
|
459
|
+
stack.pop_or_push(Ctx.STR_F, { unless: [Ctx.STR_1, Ctx.STR_2] });
|
|
460
|
+
stack.try_pop(Ctx.VALUE);
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
this.advance(
|
|
466
|
+
`Unexpected end of input while parsing incantation argument, stack: ${JSON.stringify(stack)}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* NOTE: Cut in by 1 on both sides to exclude {} braces */
|
|
471
|
+
let out = utils.chars(this.source, init + 1, this.i - 1);
|
|
472
|
+
|
|
473
|
+
for (let [i, j] of utils.reversing(utils.paired(indices_to_insert_quotes))) {
|
|
474
|
+
i -= init + 1;
|
|
475
|
+
j -= init + 1;
|
|
476
|
+
|
|
477
|
+
let value = out.slice(i, j).join("");
|
|
478
|
+
if (["true", "false", "null", "undefined"].includes(value)) continue;
|
|
479
|
+
|
|
480
|
+
out.splice(j, 0, `"`);
|
|
481
|
+
out.splice(i, 0, `"`);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return out.join("").trim();
|
|
485
|
+
}
|
|
483
486
|
}
|
|
484
487
|
|
|
485
488
|
|
|
486
489
|
class ContextStack
|
|
487
490
|
{
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
491
|
+
#data: Ctx[] = [Ctx.BLOCK]
|
|
492
|
+
|
|
493
|
+
get length(): number {
|
|
494
|
+
return this.#data.length;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** The currently active context. */
|
|
498
|
+
get top(): Ctx {
|
|
499
|
+
return this.#data.at(-1)!;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
push(ctx: Ctx, options?: StackOperationOptions): boolean
|
|
503
|
+
{
|
|
504
|
+
if (this.#should_skip(options)) return false;
|
|
505
|
+
|
|
506
|
+
this.#data.push(ctx);
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
pop(): void
|
|
511
|
+
{
|
|
512
|
+
this.#data.pop();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Pop `ctx` if it is the currently active context, returning `true` if so. */
|
|
516
|
+
try_pop(ctx: Ctx): boolean
|
|
517
|
+
{
|
|
518
|
+
if (this.top === ctx) {
|
|
519
|
+
this.#data.pop();
|
|
520
|
+
return true;
|
|
521
|
+
} else {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** Backtrack until `ctx` is popped. */
|
|
527
|
+
force_pop(ctx: Ctx, options?: StackOperationOptions): boolean
|
|
528
|
+
{
|
|
529
|
+
if (this.#should_skip(options)) return false;
|
|
530
|
+
|
|
531
|
+
let idx = this.#data.lastIndexOf(ctx);
|
|
532
|
+
if (idx === -1) return false;
|
|
533
|
+
|
|
534
|
+
/* NOTE: We could report errors for unterminated contexts, but we'll leave that for the actual evaluation - in case we get something wrong ;) */
|
|
535
|
+
this.#data.splice(idx);
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Pop `ctx` if it is the current context, else push it onto the stack. */
|
|
540
|
+
pop_or_push(ctx: Ctx, options?: StackOperationOptions): boolean
|
|
541
|
+
{
|
|
542
|
+
if (this.#should_skip(options)) return false;
|
|
543
|
+
|
|
544
|
+
this.try_pop(ctx) || this.push(ctx);
|
|
545
|
+
return true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
debug(): string {
|
|
549
|
+
return JSON.stringify(this.#data);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
#should_skip(options: StackOperationOptions | undefined): boolean
|
|
553
|
+
{
|
|
554
|
+
return Boolean(
|
|
555
|
+
options?.when != undefined && !options.when.includes(this.top)
|
|
556
|
+
|| options?.unless?.includes(this.top)
|
|
557
|
+
);
|
|
558
|
+
}
|
|
556
559
|
}
|
|
557
560
|
|
|
558
561
|
interface StackOperationOptions
|
|
559
562
|
{
|
|
560
|
-
|
|
561
|
-
|
|
563
|
+
/** Only perform this operation if the current context is any of these. */
|
|
564
|
+
when?: Ctx[];
|
|
562
565
|
|
|
563
|
-
|
|
564
|
-
|
|
566
|
+
/** Don't perform this operation if the current context is any of these. */
|
|
567
|
+
unless?: Ctx[];
|
|
565
568
|
}
|
|
566
569
|
|
|
567
570
|
enum Char
|
|
568
571
|
{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
572
|
+
BACKSLASH = "\\",
|
|
573
|
+
QUOTE_1 = `'`,
|
|
574
|
+
QUOTE_2 = `"`,
|
|
575
|
+
BACKTICK = "`",
|
|
573
576
|
}
|
|
574
577
|
|
|
575
578
|
enum Ctx {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
579
|
+
BLOCK = "{",
|
|
580
|
+
VALUE = ":",
|
|
581
|
+
ENUM = "<enum literal>",
|
|
582
|
+
STR_1 = Char.QUOTE_1,
|
|
583
|
+
STR_2 = Char.QUOTE_2,
|
|
584
|
+
STR_F = Char.BACKTICK,
|
|
585
|
+
ESCAPE = Char.BACKSLASH,
|
|
583
586
|
}
|