desmost 0.9.0 → 0.10.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/README.md +87 -11
- package/dist/compiler/compile.d.ts +1 -7
- package/dist/compiler/compile.d.ts.map +1 -1
- package/dist/compiler/compile.js +3 -94
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/compiler.d.ts +56 -0
- package/dist/compiler/compiler.d.ts.map +1 -0
- package/dist/compiler/compiler.js +209 -0
- package/dist/compiler/compiler.js.map +1 -0
- package/dist/compiler/format.d.ts +2 -2
- package/dist/compiler/format.d.ts.map +1 -1
- package/dist/compiler/format.js +18 -9
- package/dist/compiler/format.js.map +1 -1
- package/dist/decompiler/decompile.d.ts +1 -1
- package/dist/decompiler/decompile.d.ts.map +1 -1
- package/dist/decompiler/decompile.js +1 -3
- package/dist/decompiler/decompile.js.map +1 -1
- package/dist/decompiler/extract.d.ts.map +1 -1
- package/dist/decompiler/extract.js +7 -3
- package/dist/decompiler/extract.js.map +1 -1
- package/dist/decompiler/format.d.ts.map +1 -1
- package/dist/decompiler/format.js +0 -1
- package/dist/decompiler/format.js.map +1 -1
- package/dist/errors.d.ts +35 -27
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +46 -48
- package/dist/errors.js.map +1 -1
- package/dist/index.internal.d.ts +1 -1
- package/dist/index.internal.d.ts.map +1 -1
- package/dist/magic/global/desmos.d.ts +2 -2
- package/dist/magic/global/desmos.d.ts.map +1 -1
- package/dist/magic/global/desmos.js +3 -2
- package/dist/magic/global/desmos.js.map +1 -1
- package/dist/magic/global/viewport.d.ts +2 -2
- package/dist/magic/global/viewport.d.ts.map +1 -1
- package/dist/magic/global/viewport.js +5 -5
- package/dist/magic/global/viewport.js.map +1 -1
- package/dist/magic/incantation.d.ts +4 -4
- package/dist/magic/incantation.d.ts.map +1 -1
- package/dist/magic/incantation.js +15 -8
- package/dist/magic/incantation.js.map +1 -1
- package/dist/magic/local/colour.d.ts +3 -1
- package/dist/magic/local/colour.d.ts.map +1 -1
- package/dist/magic/local/colour.js +15 -11
- package/dist/magic/local/colour.js.map +1 -1
- package/dist/magic/local/label.d.ts +2 -2
- package/dist/magic/local/label.d.ts.map +1 -1
- package/dist/magic/local/label.js +8 -7
- package/dist/magic/local/label.js.map +1 -1
- package/dist/magic/local/line.d.ts +2 -2
- package/dist/magic/local/line.d.ts.map +1 -1
- package/dist/magic/local/line.js +0 -1
- package/dist/magic/local/line.js.map +1 -1
- package/dist/options.d.ts +59 -27
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +2 -1
- package/dist/options.js.map +1 -1
- package/dist/parser/ast.d.ts +3 -17
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js +0 -2
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/desmost-parser.d.ts +33 -22
- package/dist/parser/desmost-parser.d.ts.map +1 -1
- package/dist/parser/desmost-parser.js +106 -92
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/generic-parser.d.ts +19 -10
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js +56 -16
- package/dist/parser/generic-parser.js.map +1 -1
- package/dist/utils.d.ts +9 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +13 -2
- package/dist/utils.js.map +1 -1
- package/package.json +4 -3
- package/src/compiler/compile.ts +7 -120
- package/src/compiler/compiler.ts +275 -0
- package/src/compiler/format.ts +19 -7
- package/src/decompiler/decompile.ts +1 -7
- package/src/decompiler/extract.ts +3 -1
- package/src/decompiler/format.ts +0 -1
- package/src/errors.ts +40 -24
- package/src/index.internal.ts +1 -1
- package/src/magic/.template.ts +2 -2
- package/src/magic/global/desmos.ts +7 -9
- package/src/magic/global/viewport.ts +11 -17
- package/src/magic/incantation.ts +21 -22
- package/src/magic/local/colour.ts +23 -13
- package/src/magic/local/label.ts +14 -19
- package/src/magic/local/line.ts +2 -2
- package/src/options.ts +83 -51
- package/src/parser/ast.ts +1 -21
- package/src/parser/desmost-parser.ts +131 -135
- package/src/parser/generic-parser.ts +71 -29
- package/src/utils.ts +25 -3
- package/dist/compiler/evaluate.d.ts +0 -18
- package/dist/compiler/evaluate.d.ts.map +0 -1
- package/dist/compiler/evaluate.js +0 -93
- package/dist/compiler/evaluate.js.map +0 -1
- package/dist/desmos.d.ts +0 -16
- package/dist/desmos.d.ts.map +0 -1
- package/dist/desmos.js +0 -19
- package/dist/desmos.js.map +0 -1
- package/src/compiler/evaluate.ts +0 -128
- package/src/desmos.ts +0 -22
|
@@ -2,8 +2,8 @@ import { GenericParser } from "./generic-parser";
|
|
|
2
2
|
import { Ast } from "./ast";
|
|
3
3
|
|
|
4
4
|
import type { DesmostOptions } from "../options";
|
|
5
|
-
import { NO_MATCH,
|
|
6
|
-
import type { NoMatch,
|
|
5
|
+
import { NO_MATCH, DesmostError } from "../errors";
|
|
6
|
+
import type { NoMatch, Fallible } from "../errors";
|
|
7
7
|
import * as utils from "../utils";
|
|
8
8
|
|
|
9
9
|
import {
|
|
@@ -27,56 +27,79 @@ export class DesmostParser extends GenericParser
|
|
|
27
27
|
// == TOP-LEVEL == //
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Parse the next semantic block of source code
|
|
30
|
+
* Parse the next semantic block of source code.
|
|
31
|
+
*
|
|
32
|
+
* This returns:
|
|
33
|
+
*
|
|
34
|
+
* ```ts
|
|
35
|
+
* // an AST block(s) + any errors encountered
|
|
36
|
+
* { blocks: Ast[], errors: [...] }
|
|
37
|
+
*
|
|
38
|
+
* // ignored block + any errors encountered
|
|
39
|
+
* { blocks: [], errors: [...] }
|
|
40
|
+
*
|
|
41
|
+
* // parser has reached the end of the source
|
|
42
|
+
* undefined
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* `block: null` could be ignored content like comments, or it could be a parse that couldn't recover and so failed to produce a block.
|
|
31
46
|
*/
|
|
32
|
-
public parse_next():
|
|
47
|
+
public parse_next():
|
|
48
|
+
Fallible<{ blocks: Ast[], errors: DesmostError[] } | undefined>
|
|
33
49
|
{
|
|
50
|
+
this.errors = [];
|
|
51
|
+
|
|
34
52
|
this.consume_spaces();
|
|
35
53
|
|
|
36
54
|
if (this.out_of_bounds()) {
|
|
37
|
-
return
|
|
55
|
+
return undefined;
|
|
38
56
|
}
|
|
39
57
|
|
|
58
|
+
let blocks = [];
|
|
59
|
+
|
|
40
60
|
if (this.current === "%") {
|
|
61
|
+
// comment
|
|
41
62
|
if (this.options?.ignore_comments) {
|
|
42
|
-
// ignore comment
|
|
43
63
|
this.parse_line();
|
|
44
|
-
this.consume_end_of_block();
|
|
45
|
-
return this.parse_next();
|
|
46
64
|
}
|
|
47
65
|
else {
|
|
48
|
-
|
|
49
|
-
var expr = this.parse_comment();
|
|
66
|
+
blocks.push(this.parse_comment());
|
|
50
67
|
}
|
|
51
68
|
}
|
|
52
69
|
else if (this.current === "/") {
|
|
53
70
|
let r = this.parse_pre_sep();
|
|
54
71
|
|
|
55
|
-
// 1 global
|
|
56
72
|
if ("global" in r) {
|
|
57
|
-
|
|
73
|
+
// 1 global
|
|
74
|
+
if (r.global !== null) {
|
|
75
|
+
blocks.push(r.global);
|
|
76
|
+
}
|
|
58
77
|
}
|
|
78
|
+
else {
|
|
79
|
+
// 1+ locals + 1 expr
|
|
80
|
+
let incantations = r.locals;
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
if (incantations.length > 0) {
|
|
83
|
+
this.parse_sep();
|
|
84
|
+
}
|
|
62
85
|
|
|
63
|
-
|
|
64
|
-
this.parse_sep();
|
|
65
|
-
}
|
|
86
|
+
let block = this.parse_post_sep();
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
for (let invocation of incantations) {
|
|
89
|
+
block.incantations.push(invocation);
|
|
90
|
+
}
|
|
68
91
|
|
|
69
|
-
|
|
70
|
-
expr.incantations.push(invocation);
|
|
92
|
+
blocks.push(block);
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
95
|
else {
|
|
74
96
|
// 1 expr
|
|
75
|
-
|
|
97
|
+
blocks.push(this.parse_post_sep());
|
|
76
98
|
}
|
|
77
99
|
|
|
78
100
|
this.consume_end_of_block();
|
|
79
|
-
|
|
101
|
+
|
|
102
|
+
return { blocks, errors: this.errors };
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
/**
|
|
@@ -86,43 +109,81 @@ export class DesmostParser extends GenericParser
|
|
|
86
109
|
* - 1+ local incantations
|
|
87
110
|
*/
|
|
88
111
|
parse_pre_sep():
|
|
89
|
-
|
|
90
|
-
| { global: Ast.IncantationInvocation<GLOBAL> |
|
|
91
|
-
| {
|
|
112
|
+
Fallible<
|
|
113
|
+
| { global: Ast.IncantationInvocation<GLOBAL> | null }
|
|
114
|
+
| { locals: Ast.IncantationInvocation<LOCAL>[] }
|
|
92
115
|
>
|
|
93
116
|
{
|
|
94
117
|
// 1 global incantation
|
|
95
|
-
|
|
118
|
+
try {
|
|
119
|
+
let r = this.try_parse_incantation(GLOBAL_INCANTATIONS);
|
|
96
120
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
121
|
+
if (r !== NO_MATCH) {
|
|
122
|
+
this.consume_spaces();
|
|
123
|
+
return { global: r };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
this.errors.push(e as Error);
|
|
128
|
+
return { global: null };
|
|
103
129
|
}
|
|
104
130
|
|
|
105
131
|
// 1+ local incantations
|
|
106
132
|
let incantations = [];
|
|
107
133
|
|
|
108
134
|
while (this.current === "/") {
|
|
109
|
-
|
|
135
|
+
try {
|
|
136
|
+
var invocation = this.try_parse_incantation(LOCAL_INCANTATIONS);
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
this.errors.push(e as Error);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
110
143
|
if (invocation === NO_MATCH) break;
|
|
111
144
|
incantations.push(invocation);
|
|
145
|
+
this.consume_whitespace();
|
|
112
146
|
}
|
|
113
147
|
|
|
114
|
-
return {
|
|
148
|
+
return { locals: incantations };
|
|
115
149
|
}
|
|
116
150
|
|
|
117
151
|
/**
|
|
118
152
|
* Parse the Desmost `::` separator.
|
|
119
153
|
*/
|
|
120
|
-
parse_sep():
|
|
154
|
+
parse_sep(): Fallible<void>
|
|
121
155
|
{
|
|
122
156
|
this.consume_whitespace();
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
this.consume("::", {
|
|
160
|
+
msg: `Expected \`::\` separator between local incantations and expression, but found: \`${this.preview()}\``,
|
|
161
|
+
hint: `Use \`/incantation :: ${this.preview()}\``,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
if (!this.peek_line().includes("::")) throw e;
|
|
166
|
+
|
|
167
|
+
let init = this.i;
|
|
168
|
+
|
|
169
|
+
while (this.current !== ":" || this.peek() !== ":") {
|
|
170
|
+
this.advance();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const peek = 7;
|
|
174
|
+
const start = "...".length + Math.round(1.5 * peek);
|
|
175
|
+
|
|
176
|
+
this.errors.push(new DesmostError.ExcessInput({
|
|
177
|
+
msg: `While consuming \`::\` separator`,
|
|
178
|
+
show: {
|
|
179
|
+
text: `...${this.source.slice(init - peek, this.i + peek)}...`,
|
|
180
|
+
span: new utils.Range(start, start + this.i - init)
|
|
181
|
+
}
|
|
182
|
+
}));
|
|
183
|
+
|
|
184
|
+
this.consume("::");
|
|
185
|
+
}
|
|
186
|
+
|
|
126
187
|
this.consume_whitespace();
|
|
127
188
|
}
|
|
128
189
|
|
|
@@ -132,7 +193,7 @@ export class DesmostParser extends GenericParser
|
|
|
132
193
|
* - 1 line of LaTeX
|
|
133
194
|
* - 1 expression incantation
|
|
134
195
|
*/
|
|
135
|
-
parse_post_sep():
|
|
196
|
+
parse_post_sep(): Fallible<Ast.Expression>
|
|
136
197
|
{
|
|
137
198
|
this.consume_spaces();
|
|
138
199
|
|
|
@@ -161,7 +222,7 @@ export class DesmostParser extends GenericParser
|
|
|
161
222
|
}
|
|
162
223
|
}
|
|
163
224
|
|
|
164
|
-
parse_comment():
|
|
225
|
+
parse_comment(): Fallible<Ast.Expression>
|
|
165
226
|
{
|
|
166
227
|
this.advance();
|
|
167
228
|
let text = this.parse_line();
|
|
@@ -179,7 +240,7 @@ export class DesmostParser extends GenericParser
|
|
|
179
240
|
/**
|
|
180
241
|
* Parse a single line of arbitrary text.
|
|
181
242
|
*/
|
|
182
|
-
parse_line():
|
|
243
|
+
parse_line(): Fallible<string>
|
|
183
244
|
{
|
|
184
245
|
let init = this.i;
|
|
185
246
|
|
|
@@ -191,64 +252,24 @@ export class DesmostParser extends GenericParser
|
|
|
191
252
|
}
|
|
192
253
|
|
|
193
254
|
/**
|
|
194
|
-
* Attempt to parse
|
|
255
|
+
* Attempt to parse an incantation invocation allowed by `incantations`.
|
|
195
256
|
*/
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
| NoMatch
|
|
257
|
+
try_parse_incantation<Effect extends Incantation.Effect>(
|
|
258
|
+
incantations: Incantation<Effect>[],
|
|
259
|
+
): Fallible<Ast.IncantationInvocation<Effect> | NoMatch>
|
|
200
260
|
{
|
|
201
261
|
let init = this.i;
|
|
202
262
|
|
|
203
263
|
if (this.try_consume("/") === NO_MATCH) return NO_MATCH;
|
|
204
264
|
|
|
205
|
-
let
|
|
206
|
-
if (
|
|
265
|
+
let ident = this.try_consume_identifier();
|
|
266
|
+
if (ident === NO_MATCH) {
|
|
207
267
|
this.i = init;
|
|
208
268
|
return NO_MATCH;
|
|
209
269
|
}
|
|
210
270
|
|
|
211
|
-
let
|
|
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) {
|
|
271
|
+
let incantation = incantations.find(inc => inc.identifier === ident || inc.alias === ident);
|
|
272
|
+
if (incantation == undefined) {
|
|
252
273
|
this.i = init;
|
|
253
274
|
return NO_MATCH;
|
|
254
275
|
}
|
|
@@ -260,19 +281,13 @@ export class DesmostParser extends GenericParser
|
|
|
260
281
|
arg_raw = this.parse_incantation_arg(incantation.arg_type);
|
|
261
282
|
}
|
|
262
283
|
else if (incantation.requires_arg) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
incantation
|
|
266
|
-
|
|
267
|
-
`No argument provided for /${incantation.identifier}, which requires an argument of type: \`${incantation.arg_type}\``
|
|
268
|
-
)
|
|
269
|
-
};
|
|
284
|
+
throw new DesmostError.MissingInput({
|
|
285
|
+
msg: `No argument provided for /${incantation.identifier}`,
|
|
286
|
+
hint: `/${incantation.identifier} requires an argument of type: \`${incantation.arg_type}\``,
|
|
287
|
+
});
|
|
270
288
|
}
|
|
271
289
|
}
|
|
272
290
|
|
|
273
|
-
this.consume_whitespace();
|
|
274
|
-
|
|
275
|
-
// @ts-expect-error: FIXME
|
|
276
291
|
return {
|
|
277
292
|
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
278
293
|
incantation,
|
|
@@ -283,19 +298,25 @@ export class DesmostParser extends GenericParser
|
|
|
283
298
|
/**
|
|
284
299
|
* Attempt to parse an expression incantation invocation.
|
|
285
300
|
*/
|
|
286
|
-
try_parse_expr_incantation():
|
|
301
|
+
try_parse_expr_incantation(): Fallible<Ast.Expression | NoMatch>
|
|
287
302
|
{
|
|
288
303
|
let init = this.i;
|
|
289
304
|
|
|
290
305
|
if (this.try_consume("/") === NO_MATCH) return NO_MATCH;
|
|
291
306
|
|
|
292
|
-
let
|
|
293
|
-
if (
|
|
294
|
-
|
|
307
|
+
let ident = this.try_consume_identifier();
|
|
308
|
+
if (ident === NO_MATCH) {
|
|
309
|
+
this.i = init;
|
|
310
|
+
return NO_MATCH;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let incantation = EXPR_INCANTATIONS.find(inc => inc.identifier === ident || inc.alias === ident);
|
|
314
|
+
if (incantation == undefined) {
|
|
295
315
|
this.i = init;
|
|
296
316
|
return NO_MATCH;
|
|
297
317
|
}
|
|
298
318
|
|
|
319
|
+
/* NOTE: All expression incantations currently require arguments, and all for the foreseeable future will too */
|
|
299
320
|
let arg_raw = this.parse_incantation_arg((incantation as ArgIncantation<EXPR>).arg_type);
|
|
300
321
|
|
|
301
322
|
let data = {};
|
|
@@ -308,27 +329,6 @@ export class DesmostParser extends GenericParser
|
|
|
308
329
|
};
|
|
309
330
|
}
|
|
310
331
|
|
|
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
332
|
/**
|
|
333
333
|
* Parse an argument to an incantation, enclosed in `{}`.
|
|
334
334
|
*
|
|
@@ -394,13 +394,13 @@ export class DesmostParser extends GenericParser
|
|
|
394
394
|
* - JavaScript object: The above, plus balanced string quotes.
|
|
395
395
|
*/
|
|
396
396
|
arg_type: Incantation.ArgType,
|
|
397
|
-
):
|
|
397
|
+
): Fallible<string>
|
|
398
398
|
{
|
|
399
399
|
let init = this.i;
|
|
400
400
|
|
|
401
|
-
this.consume("{",
|
|
402
|
-
`Expected \`{\` to start incantation argument, but found: \`${this.preview()}\``
|
|
403
|
-
);
|
|
401
|
+
this.consume("{", {
|
|
402
|
+
msg: `Expected \`{\` to start incantation argument, but found: \`${this.preview()}\``
|
|
403
|
+
});
|
|
404
404
|
|
|
405
405
|
/** The context stack to keep track of when the closing `}` is encountered. */
|
|
406
406
|
let stack = new ContextStack();
|
|
@@ -412,9 +412,7 @@ export class DesmostParser extends GenericParser
|
|
|
412
412
|
// If the last character was an escape, we don't care at all what the next character is!
|
|
413
413
|
// This handles \\ double escape perfectly fine, since the first negates the second
|
|
414
414
|
if (stack.try_pop(Ctx.ESCAPE)) {
|
|
415
|
-
this.advance(
|
|
416
|
-
`Unexpected end of input while parsing incantation argument, stack: ${stack.debug()}`
|
|
417
|
-
);
|
|
415
|
+
this.advance({ msg: `While parsing incantation argument`, debug: stack.debug() });
|
|
418
416
|
}
|
|
419
417
|
|
|
420
418
|
let top = stack.top;
|
|
@@ -462,9 +460,7 @@ export class DesmostParser extends GenericParser
|
|
|
462
460
|
}
|
|
463
461
|
}
|
|
464
462
|
|
|
465
|
-
this.advance(
|
|
466
|
-
`Unexpected end of input while parsing incantation argument, stack: ${JSON.stringify(stack)}`
|
|
467
|
-
);
|
|
463
|
+
this.advance({ msg: `While parsing incantation argument`, debug: stack.debug() });
|
|
468
464
|
}
|
|
469
465
|
|
|
470
466
|
/* NOTE: Cut in by 1 on both sides to exclude {} braces */
|
|
@@ -529,7 +525,7 @@ class ContextStack
|
|
|
529
525
|
}
|
|
530
526
|
}
|
|
531
527
|
|
|
532
|
-
/**
|
|
528
|
+
/** If `ctx` is in the context stack, backtrack until it is popped, returning `true` if so. */
|
|
533
529
|
force_pop(ctx: Ctx, options?: StackOperationOptions): boolean
|
|
534
530
|
{
|
|
535
531
|
if (this.#should_skip(options)) return false;
|
|
@@ -1,27 +1,30 @@
|
|
|
1
|
-
import { NO_MATCH,
|
|
2
|
-
import type { NoMatch,
|
|
1
|
+
import { NO_MATCH, DesmostError } from "../errors";
|
|
2
|
+
import type { NoMatch, Fallible } from "../errors";
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
const IGNORED_CHARACTERS = new Set([
|
|
6
|
-
|
|
6
|
+
'\r',
|
|
7
7
|
]);
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A stateful lazy parser, providing core (non-Desmost-specific) methods for parsing.
|
|
12
12
|
*
|
|
13
|
-
* Many methods come in `<verb>` and `try_<verb>` pairs. On parsing failure, the former throws an `
|
|
13
|
+
* Many methods come in `<verb>` and `try_<verb>` pairs. On parsing failure, the former throws an `DesmostError`, but the latter instead backtracks and only returns a `NoMatch`. The former is for expected parses, while the latter is for speculative parsing.
|
|
14
14
|
*/
|
|
15
15
|
export class GenericParser
|
|
16
16
|
{
|
|
17
|
-
/** The source code
|
|
18
|
-
protected source: string
|
|
17
|
+
/** The source code to parse. */
|
|
18
|
+
protected readonly source: string
|
|
19
19
|
|
|
20
20
|
/** The current position in the source code the parser is pointing to. */
|
|
21
|
-
protected i: number = 0
|
|
21
|
+
protected i: number = 0
|
|
22
22
|
|
|
23
23
|
/** The number of characters in the source code. */
|
|
24
|
-
protected length: number
|
|
24
|
+
protected readonly length: number
|
|
25
|
+
|
|
26
|
+
/** Accumulated errors in the current block being parsed. */
|
|
27
|
+
errors: DesmostError[]
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
/** Create a parser for parsing `source`. */
|
|
@@ -29,6 +32,7 @@ export class GenericParser
|
|
|
29
32
|
{
|
|
30
33
|
this.source = source;
|
|
31
34
|
this.length = source.length;
|
|
35
|
+
this.errors = [];
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
|
|
@@ -48,8 +52,21 @@ export class GenericParser
|
|
|
48
52
|
return this.source.at(this.i);
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
protected peek(): string | undefined
|
|
56
|
+
{
|
|
57
|
+
return this.source.at(this.i + 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Peek the rest of the content in the current line.
|
|
62
|
+
*/
|
|
63
|
+
protected peek_line(): string
|
|
64
|
+
{
|
|
65
|
+
return this.source.slice(this.i, this.source.indexOf("\n", this.i));
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
/**
|
|
52
|
-
* Peek a snippet of the upcoming source text.
|
|
69
|
+
* Peek a snippet of the upcoming source text for display purposes.
|
|
53
70
|
*
|
|
54
71
|
* Optionally start from the given `idx`.
|
|
55
72
|
*/
|
|
@@ -66,14 +83,13 @@ export class GenericParser
|
|
|
66
83
|
/**
|
|
67
84
|
* Advance to the next character, skipping ignored characters.
|
|
68
85
|
*
|
|
69
|
-
* Errors if the parser is
|
|
86
|
+
* Errors if the parser is already out-of-bounds *before* advancing.
|
|
70
87
|
*/
|
|
71
|
-
protected advance(
|
|
88
|
+
protected advance(error_data?: DesmostError.Data): Fallible<void>
|
|
72
89
|
{
|
|
73
90
|
if (this.out_of_bounds()) {
|
|
74
|
-
throw new
|
|
91
|
+
throw new DesmostError.UnexpectedEnd(error_data ?? {});
|
|
75
92
|
}
|
|
76
|
-
|
|
77
93
|
this.#advance();
|
|
78
94
|
}
|
|
79
95
|
|
|
@@ -84,7 +100,9 @@ export class GenericParser
|
|
|
84
100
|
*/
|
|
85
101
|
protected try_advance(): void | NoMatch
|
|
86
102
|
{
|
|
87
|
-
if (this.out_of_bounds())
|
|
103
|
+
if (this.out_of_bounds()) {
|
|
104
|
+
return NO_MATCH;
|
|
105
|
+
}
|
|
88
106
|
this.#advance();
|
|
89
107
|
}
|
|
90
108
|
|
|
@@ -101,29 +119,36 @@ export class GenericParser
|
|
|
101
119
|
/**
|
|
102
120
|
* Consume `raw`, erroring if `raw` was not found.
|
|
103
121
|
*/
|
|
104
|
-
protected consume(raw: string,
|
|
122
|
+
protected consume(raw: string, error_data?: DesmostError.Data): Fallible<void>
|
|
105
123
|
{
|
|
106
124
|
if (this.out_of_bounds()) {
|
|
107
|
-
throw new
|
|
108
|
-
`
|
|
109
|
-
|
|
125
|
+
throw new DesmostError.UnexpectedEnd({
|
|
126
|
+
msg: `While trying to consume: \`${raw}\``,
|
|
127
|
+
show: {
|
|
128
|
+
text: this.preview(this.i - 5),
|
|
129
|
+
}
|
|
130
|
+
});
|
|
110
131
|
}
|
|
111
132
|
|
|
112
133
|
let init = this.i;
|
|
113
134
|
let ii = 0;
|
|
114
135
|
|
|
115
136
|
while (this.current === raw[ii]) {
|
|
116
|
-
this.advance(
|
|
117
|
-
`
|
|
118
|
-
|
|
137
|
+
this.advance({
|
|
138
|
+
msg: `While trying to consume: \`${raw}\``,
|
|
139
|
+
show: {
|
|
140
|
+
text: `\`${this.preview(init)}\``,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
119
143
|
|
|
120
144
|
ii++;
|
|
121
145
|
if (ii === raw.length) return;
|
|
122
146
|
}
|
|
123
147
|
|
|
124
|
-
throw new
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
throw new DesmostError.UnexpectedInput({
|
|
149
|
+
msg: `Expected: \`${raw}\`, but found: \`${this.preview(init)}\``,
|
|
150
|
+
...error_data,
|
|
151
|
+
});
|
|
127
152
|
}
|
|
128
153
|
|
|
129
154
|
/**
|
|
@@ -136,7 +161,7 @@ export class GenericParser
|
|
|
136
161
|
|
|
137
162
|
while (this.current === raw[ii]) {
|
|
138
163
|
let r = this.try_advance();
|
|
139
|
-
if (r === NO_MATCH)
|
|
164
|
+
if (r === NO_MATCH) break;
|
|
140
165
|
|
|
141
166
|
ii++;
|
|
142
167
|
if (ii === raw.length) return;
|
|
@@ -147,6 +172,22 @@ export class GenericParser
|
|
|
147
172
|
return NO_MATCH;
|
|
148
173
|
}
|
|
149
174
|
|
|
175
|
+
protected try_consume_identifier(): string | NoMatch
|
|
176
|
+
{
|
|
177
|
+
let init = this.i;
|
|
178
|
+
|
|
179
|
+
while (/[a-zA-Z-]/.test(this.current ?? "")) {
|
|
180
|
+
let r = this.try_advance();
|
|
181
|
+
|
|
182
|
+
if (r === NO_MATCH) {
|
|
183
|
+
this.i = init;
|
|
184
|
+
return NO_MATCH;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return this.source.slice(init, this.i);
|
|
189
|
+
}
|
|
190
|
+
|
|
150
191
|
/**
|
|
151
192
|
* Consume 0 or more space characters.
|
|
152
193
|
*/
|
|
@@ -179,14 +220,15 @@ export class GenericParser
|
|
|
179
220
|
}
|
|
180
221
|
}
|
|
181
222
|
|
|
182
|
-
protected consume_end_of_block(
|
|
223
|
+
protected consume_end_of_block(error_data?: DesmostError.Data): Fallible<void>
|
|
183
224
|
{
|
|
184
225
|
if (this.i >= this.length) return;
|
|
185
226
|
|
|
186
227
|
this.consume_spaces();
|
|
187
228
|
|
|
188
|
-
this.consume("\n",
|
|
189
|
-
|
|
190
|
-
|
|
229
|
+
this.consume("\n", {
|
|
230
|
+
msg: `Expected a newline at the end of a block, but found: \`${this.preview()}\``,
|
|
231
|
+
...error_data
|
|
232
|
+
});
|
|
191
233
|
}
|
|
192
234
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
export const LINE = '—'.repeat(10);
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Is `expr` a Desmos LaTeX expression (as opposed to a note or table)?
|
|
6
|
+
*/
|
|
7
|
+
export function is_latex(expr: Desmos.ExpressionState): expr is Desmos.Expression
|
|
8
|
+
{
|
|
9
|
+
return (expr.type === undefined || expr.type === "expression");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
4
13
|
export function* reversing<T>(items: ArrayLike<T>): Generator<T>
|
|
5
14
|
{
|
|
6
15
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
@@ -42,11 +51,24 @@ export function chars(s: string, start?: number, stop?: number): string[]
|
|
|
42
51
|
|
|
43
52
|
export class Range
|
|
44
53
|
{
|
|
54
|
+
/** The lower bound of the range (inclusive). */
|
|
55
|
+
public readonly start: number;
|
|
56
|
+
|
|
57
|
+
/** The upper bound of the range (exclusive). */
|
|
58
|
+
public readonly stop: number;
|
|
59
|
+
|
|
45
60
|
constructor(
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
start: number,
|
|
62
|
+
stop: number,
|
|
48
63
|
)
|
|
49
|
-
{
|
|
64
|
+
{
|
|
65
|
+
this.start = Math.min(start, stop);
|
|
66
|
+
this.stop = Math.max(start, stop);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
get length(): number {
|
|
70
|
+
return this.stop - this.start + 1;
|
|
71
|
+
}
|
|
50
72
|
|
|
51
73
|
*map<T>(callback: (n: number) => T): Generator<T>
|
|
52
74
|
{
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { DesmostOptions } from "../options.js";
|
|
2
|
-
import { Ast } from "../parser/index.js";
|
|
3
|
-
import { UnrecoverableError, type Unrecoverable } from "../errors.js";
|
|
4
|
-
/**
|
|
5
|
-
* Evaluate arguments (if any) to a global incantation `invocation`, then apply the incantation to `desmos`.
|
|
6
|
-
*/
|
|
7
|
-
export declare function evaluate_global_incantation(invocation: Ast.IncantationInvocation, desmos: Desmos.Calculator, options: DesmostOptions): Unrecoverable<void | string>;
|
|
8
|
-
/**
|
|
9
|
-
* Evaluate local incantation invocations on `expr`, then add `expr` to `desmos`.
|
|
10
|
-
*/
|
|
11
|
-
export declare function evaluate_expr(expr: Ast.Expression, desmos: Desmos.Calculator, options: DesmostOptions): Unrecoverable<void | string>;
|
|
12
|
-
/**
|
|
13
|
-
* Handle an `InvalidInvocation`, respecting the user's preferences in `options`.
|
|
14
|
-
*
|
|
15
|
-
* If the user set `place_errors: start` or `place_errors: end`, this returns the formatted error message for deferred aggregation.
|
|
16
|
-
*/
|
|
17
|
-
export declare function evaluate_error(error: UnrecoverableError, desmos: Desmos.Calculator, options: DesmostOptions): Unrecoverable<void | string>;
|
|
18
|
-
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../../src/compiler/evaluate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAInE;;GAEG;AACH,wBAAgB,2BAA2B,CAC1C,UAAU,EAAE,GAAG,CAAC,qBAAqB,EACrC,MAAM,EAAE,MAAM,CAAC,UAAU,EACzB,OAAO,EAAE,cAAc,GACrB,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAe9B;AAGD;;GAEG;AACH,wBAAgB,aAAa,CAC5B,IAAI,EAAE,GAAG,CAAC,UAAU,EACpB,MAAM,EAAE,MAAM,CAAC,UAAU,EACzB,OAAO,EAAE,cAAc,GACrB,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAkD9B;AAGD;;;;GAIG;AACH,wBAAgB,cAAc,CAC7B,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,MAAM,CAAC,UAAU,EACzB,OAAO,EAAE,cAAc,GACrB,aAAa,CAAC,IAAI,GAAG,MAAM,CAAC,CAuB9B"}
|