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.
Files changed (104) hide show
  1. package/README.md +87 -11
  2. package/dist/compiler/compile.d.ts +1 -7
  3. package/dist/compiler/compile.d.ts.map +1 -1
  4. package/dist/compiler/compile.js +3 -94
  5. package/dist/compiler/compile.js.map +1 -1
  6. package/dist/compiler/compiler.d.ts +56 -0
  7. package/dist/compiler/compiler.d.ts.map +1 -0
  8. package/dist/compiler/compiler.js +209 -0
  9. package/dist/compiler/compiler.js.map +1 -0
  10. package/dist/compiler/format.d.ts +2 -2
  11. package/dist/compiler/format.d.ts.map +1 -1
  12. package/dist/compiler/format.js +18 -9
  13. package/dist/compiler/format.js.map +1 -1
  14. package/dist/decompiler/decompile.d.ts +1 -1
  15. package/dist/decompiler/decompile.d.ts.map +1 -1
  16. package/dist/decompiler/decompile.js +1 -3
  17. package/dist/decompiler/decompile.js.map +1 -1
  18. package/dist/decompiler/extract.d.ts.map +1 -1
  19. package/dist/decompiler/extract.js +7 -3
  20. package/dist/decompiler/extract.js.map +1 -1
  21. package/dist/decompiler/format.d.ts.map +1 -1
  22. package/dist/decompiler/format.js +0 -1
  23. package/dist/decompiler/format.js.map +1 -1
  24. package/dist/errors.d.ts +35 -27
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +46 -48
  27. package/dist/errors.js.map +1 -1
  28. package/dist/index.internal.d.ts +1 -1
  29. package/dist/index.internal.d.ts.map +1 -1
  30. package/dist/magic/global/desmos.d.ts +2 -2
  31. package/dist/magic/global/desmos.d.ts.map +1 -1
  32. package/dist/magic/global/desmos.js +3 -2
  33. package/dist/magic/global/desmos.js.map +1 -1
  34. package/dist/magic/global/viewport.d.ts +2 -2
  35. package/dist/magic/global/viewport.d.ts.map +1 -1
  36. package/dist/magic/global/viewport.js +5 -5
  37. package/dist/magic/global/viewport.js.map +1 -1
  38. package/dist/magic/incantation.d.ts +4 -4
  39. package/dist/magic/incantation.d.ts.map +1 -1
  40. package/dist/magic/incantation.js +15 -8
  41. package/dist/magic/incantation.js.map +1 -1
  42. package/dist/magic/local/colour.d.ts +3 -1
  43. package/dist/magic/local/colour.d.ts.map +1 -1
  44. package/dist/magic/local/colour.js +15 -11
  45. package/dist/magic/local/colour.js.map +1 -1
  46. package/dist/magic/local/label.d.ts +2 -2
  47. package/dist/magic/local/label.d.ts.map +1 -1
  48. package/dist/magic/local/label.js +8 -7
  49. package/dist/magic/local/label.js.map +1 -1
  50. package/dist/magic/local/line.d.ts +2 -2
  51. package/dist/magic/local/line.d.ts.map +1 -1
  52. package/dist/magic/local/line.js +0 -1
  53. package/dist/magic/local/line.js.map +1 -1
  54. package/dist/options.d.ts +59 -27
  55. package/dist/options.d.ts.map +1 -1
  56. package/dist/options.js +2 -1
  57. package/dist/options.js.map +1 -1
  58. package/dist/parser/ast.d.ts +3 -17
  59. package/dist/parser/ast.d.ts.map +1 -1
  60. package/dist/parser/ast.js +0 -2
  61. package/dist/parser/ast.js.map +1 -1
  62. package/dist/parser/desmost-parser.d.ts +33 -22
  63. package/dist/parser/desmost-parser.d.ts.map +1 -1
  64. package/dist/parser/desmost-parser.js +106 -92
  65. package/dist/parser/desmost-parser.js.map +1 -1
  66. package/dist/parser/generic-parser.d.ts +19 -10
  67. package/dist/parser/generic-parser.d.ts.map +1 -1
  68. package/dist/parser/generic-parser.js +56 -16
  69. package/dist/parser/generic-parser.js.map +1 -1
  70. package/dist/utils.d.ts +9 -2
  71. package/dist/utils.d.ts.map +1 -1
  72. package/dist/utils.js +13 -2
  73. package/dist/utils.js.map +1 -1
  74. package/package.json +4 -3
  75. package/src/compiler/compile.ts +7 -120
  76. package/src/compiler/compiler.ts +275 -0
  77. package/src/compiler/format.ts +19 -7
  78. package/src/decompiler/decompile.ts +1 -7
  79. package/src/decompiler/extract.ts +3 -1
  80. package/src/decompiler/format.ts +0 -1
  81. package/src/errors.ts +40 -24
  82. package/src/index.internal.ts +1 -1
  83. package/src/magic/.template.ts +2 -2
  84. package/src/magic/global/desmos.ts +7 -9
  85. package/src/magic/global/viewport.ts +11 -17
  86. package/src/magic/incantation.ts +21 -22
  87. package/src/magic/local/colour.ts +23 -13
  88. package/src/magic/local/label.ts +14 -19
  89. package/src/magic/local/line.ts +2 -2
  90. package/src/options.ts +83 -51
  91. package/src/parser/ast.ts +1 -21
  92. package/src/parser/desmost-parser.ts +131 -135
  93. package/src/parser/generic-parser.ts +71 -29
  94. package/src/utils.ts +25 -3
  95. package/dist/compiler/evaluate.d.ts +0 -18
  96. package/dist/compiler/evaluate.d.ts.map +0 -1
  97. package/dist/compiler/evaluate.js +0 -93
  98. package/dist/compiler/evaluate.js.map +0 -1
  99. package/dist/desmos.d.ts +0 -16
  100. package/dist/desmos.d.ts.map +0 -1
  101. package/dist/desmos.js +0 -19
  102. package/dist/desmos.js.map +0 -1
  103. package/src/compiler/evaluate.ts +0 -128
  104. 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, UnrecoverableError } from "../errors";
6
- import type { NoMatch, Unrecoverable } from "../errors";
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, producing an `Ast` object, or `null` if the parser has successfully reached the end of the 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(): Ast | null
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 null;
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
- // comment
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
- return r.global;
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
- // 1+ locals + 1 expr
61
- let incantations = r.local;
82
+ if (incantations.length > 0) {
83
+ this.parse_sep();
84
+ }
62
85
 
63
- if (incantations.length > 0) {
64
- this.parse_sep();
65
- }
86
+ let block = this.parse_post_sep();
66
87
 
67
- var expr = this.parse_post_sep();
88
+ for (let invocation of incantations) {
89
+ block.incantations.push(invocation);
90
+ }
68
91
 
69
- for (let invocation of incantations) {
70
- expr.incantations.push(invocation);
92
+ blocks.push(block);
71
93
  }
72
94
  }
73
95
  else {
74
96
  // 1 expr
75
- var expr = this.parse_post_sep();
97
+ blocks.push(this.parse_post_sep());
76
98
  }
77
99
 
78
100
  this.consume_end_of_block();
79
- return expr;
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
- Unrecoverable<
90
- | { global: Ast.IncantationInvocation<GLOBAL> | Ast.InvalidInvocation }
91
- | { local: Array<Ast.IncantationInvocation<LOCAL> | Ast.InvalidInvocation> }
112
+ Fallible<
113
+ | { global: Ast.IncantationInvocation<GLOBAL> | null }
114
+ | { locals: Ast.IncantationInvocation<LOCAL>[] }
92
115
  >
93
116
  {
94
117
  // 1 global incantation
95
- let r = this.try_parse_global_incantation();
118
+ try {
119
+ let r = this.try_parse_incantation(GLOBAL_INCANTATIONS);
96
120
 
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 };
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
- let invocation = this.try_parse_local_incantation();
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 { local: incantations };
148
+ return { locals: incantations };
115
149
  }
116
150
 
117
151
  /**
118
152
  * Parse the Desmost `::` separator.
119
153
  */
120
- parse_sep(): Unrecoverable<void>
154
+ parse_sep(): Fallible<void>
121
155
  {
122
156
  this.consume_whitespace();
123
- this.consume("::",
124
- `Expected \`::\` separator between local incantations and expression, but found: \`${this.preview()}\``
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(): Unrecoverable<Ast.Expression>
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(): Unrecoverable<Ast.Expression>
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(): Unrecoverable<string>
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 a global incantation invocation.
255
+ * Attempt to parse an incantation invocation allowed by `incantations`.
195
256
  */
196
- try_parse_global_incantation():
197
- | Ast.IncantationInvocation<GLOBAL>
198
- | Ast.InvalidInvocation
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 incantation = this.try_parse_identifier(GLOBAL_INCANTATIONS);
206
- if (incantation === NO_MATCH) {
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 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) {
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
- 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
- };
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(): Unrecoverable<Ast.Expression | NoMatch>
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 incantation = this.try_parse_identifier(EXPR_INCANTATIONS);
293
- if (incantation === NO_MATCH) {
294
- // TODO maybe flag to user
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
- ): Unrecoverable<string>
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
- /** Backtrack until `ctx` is popped. */
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, UnrecoverableError } from "../errors";
2
- import type { NoMatch, Unrecoverable } from "../errors";
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
- "\r",
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 `UnrecoverableError`, but the latter instead backtracks and only returns a `NoMatch`. The former is for expected parses, while the latter is for speculative parsing.
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 this parser is parsing. */
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 the out-of-bounds *before* advancing.
86
+ * Errors if the parser is already out-of-bounds *before* advancing.
70
87
  */
71
- protected advance(error_msg?: string): Unrecoverable<void>
88
+ protected advance(error_data?: DesmostError.Data): Fallible<void>
72
89
  {
73
90
  if (this.out_of_bounds()) {
74
- throw new UnrecoverableError.UnexpectedEnd(error_msg ?? "Unexpected end of input");
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()) return NO_MATCH;
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, error_msg?: string): Unrecoverable<void>
122
+ protected consume(raw: string, error_data?: DesmostError.Data): Fallible<void>
105
123
  {
106
124
  if (this.out_of_bounds()) {
107
- throw new UnrecoverableError.UnexpectedEnd(
108
- `Unexpected end of input while trying to consume: \`${raw}\``
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
- `Unexpected end of input while trying to consume: \`${raw}\`, at: \`${this.preview(init)}\``
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 UnrecoverableError.UnexpectedInput(
125
- error_msg ?? `Expected: \`${raw}\`, but found: \`${this.preview(init)}\``
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) return NO_MATCH; // FIXME?
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(error_msg?: string): Unrecoverable<void>
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
- error_msg ?? `Expected end of block, but found: \`${this.preview()}\``
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
- protected start: number,
47
- protected stop: number,
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"}