desmost 0.9.1 → 0.10.1
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 +12 -3
- 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/extract.d.ts.map +1 -1
- package/dist/decompiler/extract.js +7 -3
- package/dist/decompiler/extract.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.map +1 -1
- package/dist/magic/local/label.js +7 -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 +57 -23
- 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 +114 -100
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/generic-parser.d.ts +23 -14
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js +67 -27
- 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 +5 -2
- 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 -1
- package/src/decompiler/extract.ts +3 -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 +11 -17
- package/src/magic/local/line.ts +2 -2
- package/src/options.ts +81 -47
- package/src/parser/ast.ts +1 -21
- package/src/parser/desmost-parser.ts +139 -143
- package/src/parser/generic-parser.ts +82 -40
- 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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import dedent from "dedent";
|
|
2
|
+
|
|
3
|
+
import { format_error, normalise_latex, prettify_latex } from "./format";
|
|
4
|
+
|
|
5
|
+
import type { DesmostOptions } from "../options";
|
|
6
|
+
import { DesmostError, type Fallible } from "../errors";
|
|
7
|
+
import { DesmostParser, Ast } from "../parser";
|
|
8
|
+
import { is_latex } from "../utils";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/** Debug diagnostics returned from `compile()`. */
|
|
12
|
+
export interface DesmostDebug
|
|
13
|
+
{
|
|
14
|
+
duration: number;
|
|
15
|
+
num_blocks: number;
|
|
16
|
+
ast: Ast[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
export class DesmostCompiler
|
|
21
|
+
{
|
|
22
|
+
private desmos: Desmos.Calculator
|
|
23
|
+
|
|
24
|
+
/** The source code to compile. */
|
|
25
|
+
private source: string
|
|
26
|
+
|
|
27
|
+
private options: DesmostOptions
|
|
28
|
+
|
|
29
|
+
private parser: DesmostParser
|
|
30
|
+
|
|
31
|
+
/** Accumulated errors awaiting evaluation. */
|
|
32
|
+
private errors: DesmostError[] = []
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Have we only seen blank lines so far?
|
|
36
|
+
*
|
|
37
|
+
* This is used for excluding leading blanks.
|
|
38
|
+
*/
|
|
39
|
+
private seen_non_blank = false
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The number of pending blank expressions to add.
|
|
43
|
+
*
|
|
44
|
+
* This is used to excluding trailing blanks.
|
|
45
|
+
*/
|
|
46
|
+
private pending_blanks: number = 0
|
|
47
|
+
|
|
48
|
+
private debug: DesmostDebug | null = null
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/** Create a compiler for compiling `source` into `desmos`. */
|
|
52
|
+
constructor(
|
|
53
|
+
desmos: Desmos.Calculator,
|
|
54
|
+
source: string,
|
|
55
|
+
options: DesmostOptions,
|
|
56
|
+
)
|
|
57
|
+
{
|
|
58
|
+
this.desmos = desmos;
|
|
59
|
+
this.source = source;
|
|
60
|
+
this.options = options;
|
|
61
|
+
this.parser = new DesmostParser(this.source, this.options);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Run the compiler to completion.
|
|
67
|
+
*/
|
|
68
|
+
public compile(): void | DesmostDebug
|
|
69
|
+
{
|
|
70
|
+
if (this.options.debug) {
|
|
71
|
+
this.debug = {
|
|
72
|
+
duration: performance.now(),
|
|
73
|
+
num_blocks: 0,
|
|
74
|
+
ast: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* To aggregate errors at the start, we need a target to retroactively inject errors into */
|
|
79
|
+
if (this.options.place_errors === "start") {
|
|
80
|
+
this.desmos.setExpression({ id: "deferred", latex: " " }); // FIXME check if ` ` needed
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* The compiler is lazy, parsing and evaluating one block at a time (as opposed to first parsing the entire AST). We don't need the whole AST, so this saves memory. */
|
|
84
|
+
/* NOTE: It also doesn't sacrifice performance, since `.setExpressions()` internally just calls `.setExpression()` in a loop anyway. Benchmarks in the frontend produce similar times for both, so there's no performance improvement. */
|
|
85
|
+
try {
|
|
86
|
+
let done = false;
|
|
87
|
+
|
|
88
|
+
while (!done) {
|
|
89
|
+
done = this.compile_next();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
this.errors.push(e as Error); // FIXME Should probably override as critical
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.options.keep_trailing_blanks) {
|
|
97
|
+
this.flush_pending_blanks();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* NOTE: No need to check `options.place_errors`!
|
|
101
|
+
|
|
102
|
+
- If it's `start`, `id: deferred` will replace the placeholder at the start as required.
|
|
103
|
+
- If it's `end`, `id: deferred` won't exist so this expression will be appended as required.
|
|
104
|
+
- If it's `inline`, we shouldn't have any accumulated errors at all, but if we do, then the end is where they should be anyway.
|
|
105
|
+
*/
|
|
106
|
+
if (this.errors.length > 0) {
|
|
107
|
+
this.desmos.setExpression({
|
|
108
|
+
id: "deferred",
|
|
109
|
+
type: "text",
|
|
110
|
+
text: this.errors.map(err => format_error(err, this.options)).join("\n\n"),
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
this.desmos.removeExpression({ id: "deferred" });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (this.options.debug) {
|
|
117
|
+
this.debug!.duration = performance.now() - this.debug!.duration;
|
|
118
|
+
|
|
119
|
+
return this.debug!;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compile only the next block, returning `true` if compilation has finished.
|
|
125
|
+
*/
|
|
126
|
+
compile_next(): Fallible<boolean>
|
|
127
|
+
{
|
|
128
|
+
let result = this.parser.parse_next();
|
|
129
|
+
if (result === undefined) return true;
|
|
130
|
+
|
|
131
|
+
let { blocks, errors } = result;
|
|
132
|
+
|
|
133
|
+
for (let block of blocks) {
|
|
134
|
+
this.compile_block(block);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (let error of errors) {
|
|
138
|
+
this.evaluate_error(error);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
compile_block(block: Ast): void
|
|
145
|
+
{
|
|
146
|
+
if (this.options.debug) {
|
|
147
|
+
this.debug!.num_blocks++;
|
|
148
|
+
this.debug!.ast.push(block);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
switch (block.kind)
|
|
152
|
+
{
|
|
153
|
+
case Ast.Kind.INCANTATION_INVOCATION:
|
|
154
|
+
this.evaluate_global_incantation(block);
|
|
155
|
+
break;
|
|
156
|
+
|
|
157
|
+
case Ast.Kind.EXPRESSION:
|
|
158
|
+
// @ts-expect-error: check type-narrows
|
|
159
|
+
if (block.data.latex === " ") {
|
|
160
|
+
if (this.options.ignore_all_blanks) break;
|
|
161
|
+
if (!this.seen_non_blank && !this.options.keep_leading_blanks) break;
|
|
162
|
+
|
|
163
|
+
this.pending_blanks++;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
this.flush_pending_blanks();
|
|
168
|
+
this.pending_blanks = 0;
|
|
169
|
+
this.seen_non_blank = true;
|
|
170
|
+
|
|
171
|
+
this.evaluate_expr(block);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
// == LOW-LEVEL == //
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Evaluate arguments (if any) to a global incantation `invocation`, then apply the incantation to `desmos`.
|
|
182
|
+
*/
|
|
183
|
+
evaluate_global_incantation(invocation: Ast.IncantationInvocation): Fallible<void>
|
|
184
|
+
{
|
|
185
|
+
let data = undefined;
|
|
186
|
+
|
|
187
|
+
if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
|
|
188
|
+
try {
|
|
189
|
+
data = invocation.incantation.evaluate_arg(invocation.arg_raw, this.options);
|
|
190
|
+
invocation.incantation.apply(this.desmos, data);
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
this.evaluate_error(e as Error);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Evaluate local incantation invocations on `expr`, then add `expr` to `desmos`.
|
|
201
|
+
*/
|
|
202
|
+
evaluate_expr(expr: Ast.Expression): Fallible<void>
|
|
203
|
+
{
|
|
204
|
+
for (let invocation of expr.incantations) {
|
|
205
|
+
let data = undefined;
|
|
206
|
+
|
|
207
|
+
if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
|
|
208
|
+
try {
|
|
209
|
+
data = invocation.incantation.evaluate_arg(invocation.arg_raw, this.options);
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
this.evaluate_error(e as Error);
|
|
213
|
+
// if it needs the arg, then we can't press on
|
|
214
|
+
if (invocation.incantation.requires_arg) continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
invocation.incantation.apply(expr.data, data);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
this.evaluate_error(e as Error);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (is_latex(expr.data)) {
|
|
227
|
+
expr.data.latex = normalise_latex(expr.data.latex!);
|
|
228
|
+
|
|
229
|
+
if (this.options.prettify) {
|
|
230
|
+
expr.data.latex = prettify_latex(expr.data.latex);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (expr.data.type === "text" && this.options.dedent_text) {
|
|
234
|
+
expr.data.text = dedent(expr.data.text ?? "");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.desmos.setExpression(expr.data);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle a `DesmostError`, respecting the user's options.
|
|
242
|
+
*/
|
|
243
|
+
evaluate_error(error: DesmostError): Fallible<void | string>
|
|
244
|
+
{
|
|
245
|
+
switch (this.options.errors) {
|
|
246
|
+
case "crash":
|
|
247
|
+
throw error;
|
|
248
|
+
|
|
249
|
+
case "suppress":
|
|
250
|
+
console.error(format_error(error, this.options));
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
default: switch (this.options.place_errors) {
|
|
254
|
+
case "end":
|
|
255
|
+
case "start":
|
|
256
|
+
this.errors.push(error);
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
case "inline":
|
|
260
|
+
this.desmos.setExpression({
|
|
261
|
+
type: "text",
|
|
262
|
+
text: format_error(error, this.options),
|
|
263
|
+
});
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
flush_pending_blanks(): void
|
|
270
|
+
{
|
|
271
|
+
for (let i = 0; i < this.pending_blanks; i++) {
|
|
272
|
+
this.desmos.setExpression({ latex: ` ` });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
package/src/compiler/format.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { DesmostOptions } from "../options";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DesmostError } from "../errors";
|
|
4
4
|
import { LINE } from "../utils";
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export function format_error(
|
|
8
|
-
e:
|
|
8
|
+
e: DesmostError,
|
|
9
9
|
options: DesmostOptions,
|
|
10
10
|
): string
|
|
11
11
|
{
|
|
@@ -15,12 +15,24 @@ export function format_error(
|
|
|
15
15
|
// TODO maybe include stack? (configurable?)
|
|
16
16
|
let display = `${prefix}${sep}${e.name}: ${e.message}`;
|
|
17
17
|
|
|
18
|
-
if (e.
|
|
19
|
-
display
|
|
18
|
+
if (e.data == undefined) {
|
|
19
|
+
return display;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (e.data.show != undefined) {
|
|
23
|
+
display += `\n${LINE}\n`;
|
|
24
|
+
display += e.data.show.text;
|
|
25
|
+
|
|
26
|
+
if (e.data.show.span != undefined) {
|
|
27
|
+
display += `\n`;
|
|
28
|
+
display += ' '.repeat(e.data.show.span.start);
|
|
29
|
+
display += '^'.repeat(e.data.show.span.length);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
20
32
|
|
|
21
|
-
|
|
22
|
-
if (e.
|
|
23
|
-
if (e.
|
|
33
|
+
if (options.expand_errors) {
|
|
34
|
+
if (e.data.hint != undefined) display += `\n${LINE}\nHint: ${e.data.hint}`;
|
|
35
|
+
if (e.data.note != undefined) display += `\n${LINE}\nNote: ${e.data.note}`;
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
return display;
|
|
@@ -2,7 +2,7 @@ import { emit_global_incantation, emit_expression } from "./emit";
|
|
|
2
2
|
import { extract_settings, extract_viewport, extract_expression } from "./extract";
|
|
3
3
|
|
|
4
4
|
import { Ast } from "../parser";
|
|
5
|
-
import type { DesmostOptions } from "../
|
|
5
|
+
import type { DesmostOptions } from "../options";
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
export interface SemiStructuredAst
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { stringify_json5, prettify_source } from "./format";
|
|
2
2
|
|
|
3
3
|
import { Ast } from "../parser";
|
|
4
|
-
import { is_latex } from "../
|
|
4
|
+
import { is_latex } from "../utils";
|
|
5
5
|
|
|
6
6
|
import { DesmosIncantation, ViewportIncantation } from "../magic/global";
|
|
7
7
|
|
|
@@ -19,6 +19,7 @@ export function extract_settings(
|
|
|
19
19
|
let arg_raw = stringify_json5(
|
|
20
20
|
desmos.settings,
|
|
21
21
|
(k, v) => (
|
|
22
|
+
// @ts-expect-error: weird
|
|
22
23
|
v === defaults[k]
|
|
23
24
|
|| k === "__observers"
|
|
24
25
|
|| k === "guid"
|
|
@@ -50,6 +51,7 @@ export function extract_viewport(
|
|
|
50
51
|
|
|
51
52
|
let arg_raw = stringify_json5(
|
|
52
53
|
{ left, right, bottom, top },
|
|
54
|
+
/// @ts-expect-error: weird
|
|
53
55
|
(k, v) => v === defaults[k] ? undefined : v,
|
|
54
56
|
" ",
|
|
55
57
|
);
|
package/src/errors.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { Range } from "./utils";
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
/** The parser failed to parse something (usually during speculative parsing), which only results in internal backtracking. */
|
|
2
5
|
export const NO_MATCH = Symbol("no-match");
|
|
3
6
|
|
|
@@ -10,51 +13,64 @@ export type NoMatch = typeof NO_MATCH & { readonly __brand?: unique symbol };
|
|
|
10
13
|
*
|
|
11
14
|
* It's not *literally* unrecoverable in the sense that it will crash the compiler completely, but it means there are no further fallbacks to try.
|
|
12
15
|
*/
|
|
13
|
-
export class
|
|
16
|
+
export class DesmostError extends Error
|
|
14
17
|
{
|
|
15
|
-
|
|
18
|
+
data?: DesmostError.Data;
|
|
19
|
+
|
|
20
|
+
constructor(msg: string | undefined, name: string) {
|
|
21
|
+
super(msg);
|
|
22
|
+
this.name = name;
|
|
23
|
+
}
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
export namespace
|
|
26
|
+
export namespace DesmostError
|
|
19
27
|
{
|
|
20
|
-
export interface
|
|
28
|
+
export interface Data
|
|
21
29
|
{
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
msg?: string
|
|
31
|
+
while?: string
|
|
32
|
+
hint?: string
|
|
33
|
+
note?: string
|
|
34
|
+
debug?: string
|
|
35
|
+
|
|
36
|
+
show?: {
|
|
37
|
+
text: string
|
|
38
|
+
span?: Range
|
|
39
|
+
}
|
|
25
40
|
}
|
|
26
41
|
|
|
27
|
-
/** The parser unexpectedly reached the end of its
|
|
28
|
-
export class UnexpectedEnd extends
|
|
29
|
-
constructor(
|
|
42
|
+
/** The parser unexpectedly reached the end of its source. */
|
|
43
|
+
export class UnexpectedEnd extends DesmostError {
|
|
44
|
+
constructor(override data: Data) { super(data.msg, "Unexpected End of Source"); }
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
/** The parser did not receive input that it expected. */
|
|
33
|
-
export class MissingInput extends
|
|
34
|
-
constructor(
|
|
48
|
+
export class MissingInput extends DesmostError {
|
|
49
|
+
constructor(override data: Data) { super(data.msg, "Missing Input"); }
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/** The parser received input that did not match what it expected. */
|
|
38
|
-
export class UnexpectedInput extends
|
|
39
|
-
constructor(
|
|
53
|
+
export class UnexpectedInput extends DesmostError {
|
|
54
|
+
constructor(override data: Data) { super(data.msg, "Unexpected Input"); }
|
|
40
55
|
}
|
|
41
56
|
|
|
42
57
|
/** The parser received excess input that it did not expect. */
|
|
43
|
-
export class ExcessInput extends
|
|
44
|
-
constructor(
|
|
58
|
+
export class ExcessInput extends DesmostError {
|
|
59
|
+
constructor(override data: Data) { super(data.msg, "Excess Input"); }
|
|
45
60
|
}
|
|
46
61
|
|
|
47
62
|
/** An incantation can't be applied here. */
|
|
48
|
-
export class IllegalIncantation extends
|
|
49
|
-
constructor(
|
|
63
|
+
export class IllegalIncantation extends DesmostError {
|
|
64
|
+
constructor(override data: Data) { super(data.msg, "Illegal Incantation"); }
|
|
50
65
|
}
|
|
51
66
|
|
|
52
|
-
/** An incantation's argument
|
|
53
|
-
export class InvalidArgument extends
|
|
54
|
-
constructor(
|
|
67
|
+
/** An incantation's argument is invalid - could be a parsing, evaluation or validation failure. */
|
|
68
|
+
export class InvalidArgument extends DesmostError {
|
|
69
|
+
constructor(override data: Data) { super(data.msg, "Invalid Argument"); }
|
|
55
70
|
}
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
/** Indicates that a function may throw a `DesmostError`. */
|
|
74
|
+
export type Fallible<Result>
|
|
75
|
+
= Result
|
|
76
|
+
| Result & { readonly __brand?: unique symbol };
|
package/src/index.internal.ts
CHANGED
package/src/magic/.template.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// import { Incantation, ArgIncantation, type GLOBAL, type LOCAL, type EXPR } from "../incantation";
|
|
2
2
|
|
|
3
3
|
// import type { DesmostOptions } from "../../options";
|
|
4
|
-
// import {
|
|
4
|
+
// import { DesmostError, type Fallible } from "../../errors";
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
// export class ?Incantation extends ArgIncantation<?>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// // TODO
|
|
19
19
|
// }
|
|
20
20
|
|
|
21
|
-
// override evaluate_arg(raw: ?, options: DesmostOptions):
|
|
21
|
+
// override evaluate_arg(raw: ?, options: DesmostOptions): Fallible<?>
|
|
22
22
|
// {
|
|
23
23
|
// let out = super.evaluate_arg(raw, options) as ?;
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type GLOBAL } from "../incantation";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DesmostError, type Fallible } from "../../errors";
|
|
4
4
|
import type { DesmostOptions } from "../../options";
|
|
5
5
|
|
|
6
6
|
|
|
@@ -26,19 +26,17 @@ export class DesmosIncantation extends ArgIncantation<GLOBAL>
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
override evaluate_arg(raw: string, options: DesmostOptions):
|
|
29
|
+
override evaluate_arg(raw: string, options: DesmostOptions): Fallible<DesmosSettings>
|
|
30
30
|
{
|
|
31
31
|
let out = super.evaluate_arg(raw, options) as DesmosSettings;
|
|
32
32
|
|
|
33
33
|
if (options.check_args) {
|
|
34
34
|
if (Object.keys(out).length === 0) {
|
|
35
|
-
throw new
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
);
|
|
35
|
+
throw new DesmostError.MissingInput({
|
|
36
|
+
msg: `/desmos received empty settings`,
|
|
37
|
+
hint: `Provide settings like \`/desmos{expressionsCollapsed: true}\``,
|
|
38
|
+
note: `Use just /desmos (without \`{}\`) if you want it as an indicator`,
|
|
39
|
+
});
|
|
42
40
|
}
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type GLOBAL } from "../incantation";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DesmostError, type Fallible } from "../../errors";
|
|
4
4
|
import type { DesmostOptions } from "../../options";
|
|
5
5
|
|
|
6
6
|
|
|
@@ -36,20 +36,17 @@ export class ViewportIncantation extends ArgIncantation<GLOBAL>
|
|
|
36
36
|
target.setMathBounds({ left, right, bottom, top });
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
override evaluate_arg(raw: string, options: DesmostOptions):
|
|
39
|
+
override evaluate_arg(raw: string, options: DesmostOptions): Fallible<ViewportBounds>
|
|
40
40
|
{
|
|
41
41
|
let out = super.evaluate_arg(raw, options) as ViewportBounds;
|
|
42
42
|
|
|
43
43
|
if (options.check_args) {
|
|
44
44
|
if (Object.keys(out).length === 0) {
|
|
45
|
-
throw new
|
|
46
|
-
`/${this.identifier} received empty viewport bounds`,
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
flagged_by: "check_args",
|
|
51
|
-
}
|
|
52
|
-
);
|
|
45
|
+
throw new DesmostError.InvalidArgument({
|
|
46
|
+
msg: `/${this.identifier} received empty viewport bounds`,
|
|
47
|
+
hint: `Provide bounds like \`/viewport{left: -8, right: 8}\``,
|
|
48
|
+
note: `Missing bounds are kept as their defaults`,
|
|
49
|
+
});
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
let invalid_keys = Object.keys(out).filter(key => !VALID_KEYS.includes(key));
|
|
@@ -57,13 +54,10 @@ export class ViewportIncantation extends ArgIncantation<GLOBAL>
|
|
|
57
54
|
if (invalid_keys.length > 0) {
|
|
58
55
|
let plural = invalid_keys.length > 1 ? "fields" : "field";
|
|
59
56
|
|
|
60
|
-
throw new
|
|
61
|
-
`/${this.identifier} received invalid ${plural}: [${invalid_keys.join(", ")}]`,
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
flagged_by: "check_args",
|
|
65
|
-
}
|
|
66
|
-
);
|
|
57
|
+
throw new DesmostError.InvalidArgument({
|
|
58
|
+
msg: `/${this.identifier} received invalid ${plural}: [${invalid_keys.join(", ")}]`,
|
|
59
|
+
hint: `Valid fields are: [${VALID_KEYS.join(", ")}]`,
|
|
60
|
+
});
|
|
67
61
|
}
|
|
68
62
|
}
|
|
69
63
|
|
package/src/magic/incantation.ts
CHANGED
|
@@ -2,7 +2,7 @@ import Json5 from "json5";
|
|
|
2
2
|
import dedent from "dedent";
|
|
3
3
|
|
|
4
4
|
import type { DesmostOptions } from "../options";
|
|
5
|
-
import {
|
|
5
|
+
import { DesmostError, type Fallible } from "../errors";
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -46,9 +46,11 @@ export abstract class Incantation<
|
|
|
46
46
|
{
|
|
47
47
|
if ((actual ?? "expression") !== required)
|
|
48
48
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
let req = required === "expression" ? "latex" : required;
|
|
50
|
+
|
|
51
|
+
throw new DesmostError.IllegalIncantation({
|
|
52
|
+
msg: `/${this.identifier} can only applied to ${req} blocks, but target block has type: ${actual}`,
|
|
53
|
+
});
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
}
|
|
@@ -80,7 +82,7 @@ export abstract class ArgIncantation<
|
|
|
80
82
|
*
|
|
81
83
|
* Validity checks for missing or invalid values, incorrect types, and unknown fields should be performed here, so that `.apply()` can assume the data it receives is guaranteed to be valid.
|
|
82
84
|
*/
|
|
83
|
-
evaluate_arg(raw: string, options: DesmostOptions):
|
|
85
|
+
evaluate_arg(raw: string, options: DesmostOptions): Fallible<unknown>
|
|
84
86
|
{
|
|
85
87
|
switch (this.arg_type) {
|
|
86
88
|
case Incantation.ArgType.LATEX: return raw;
|
|
@@ -95,7 +97,7 @@ export abstract class ArgIncantation<
|
|
|
95
97
|
}
|
|
96
98
|
catch (e) {
|
|
97
99
|
// @ts-expect-error: fine
|
|
98
|
-
throw new
|
|
100
|
+
throw new DesmostError.InvalidArgument({ msg: e.message });
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
103
|
}
|
|
@@ -103,34 +105,31 @@ export abstract class ArgIncantation<
|
|
|
103
105
|
|
|
104
106
|
protected require_nonempty(
|
|
105
107
|
arg: object,
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
): Unrecoverable<void>
|
|
108
|
+
error_data?: DesmostError.Data,
|
|
109
|
+
): Fallible<void>
|
|
109
110
|
{
|
|
110
111
|
if (Object.entries(arg).length === 0) {
|
|
111
|
-
throw new
|
|
112
|
-
msg
|
|
113
|
-
|
|
114
|
-
);
|
|
112
|
+
throw new DesmostError.MissingInput({
|
|
113
|
+
msg: `/${this.identifier} received an empty argument {}`,
|
|
114
|
+
...error_data
|
|
115
|
+
});
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
protected require_known(
|
|
119
120
|
arg: object,
|
|
120
121
|
valid_fields: string[],
|
|
121
|
-
|
|
122
|
-
):
|
|
122
|
+
error_data?: DesmostError.Data,
|
|
123
|
+
): Fallible<void>
|
|
123
124
|
{
|
|
124
125
|
// TODO accumulate
|
|
125
126
|
for (let field of Object.keys(arg)) {
|
|
126
127
|
if (!valid_fields.includes(field)) {
|
|
127
|
-
throw new
|
|
128
|
-
`/${this.identifier} received invalid field: ${field}`,
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
);
|
|
128
|
+
throw new DesmostError.InvalidArgument({
|
|
129
|
+
msg: `/${this.identifier} received invalid field: ${field}`,
|
|
130
|
+
hint: `Valid fields are [${valid_fields.join(", ")}]`,
|
|
131
|
+
...error_data,
|
|
132
|
+
});
|
|
134
133
|
}
|
|
135
134
|
}
|
|
136
135
|
}
|