desmost 0.8.1 → 0.9.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 +7 -3
- package/dist/compiler/compile.d.ts.map +1 -1
- package/dist/compiler/compile.js +7 -3
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/evaluate.d.ts +5 -5
- package/dist/compiler/evaluate.d.ts.map +1 -1
- package/dist/compiler/evaluate.js +16 -8
- package/dist/compiler/evaluate.js.map +1 -1
- package/dist/compiler/format.d.ts +2 -2
- package/dist/compiler/format.d.ts.map +1 -1
- package/dist/compiler/format.js +16 -2
- package/dist/compiler/format.js.map +1 -1
- package/dist/compiler/index.d.ts +0 -1
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/decompiler/decompile.d.ts +13 -2
- package/dist/decompiler/decompile.d.ts.map +1 -1
- package/dist/decompiler/decompile.js +17 -3
- package/dist/decompiler/decompile.js.map +1 -1
- package/dist/decompiler/emit.d.ts.map +1 -1
- package/dist/decompiler/emit.js +13 -6
- package/dist/decompiler/emit.js.map +1 -1
- package/dist/decompiler/extract.d.ts.map +1 -1
- package/dist/decompiler/extract.js +3 -2
- package/dist/decompiler/extract.js.map +1 -1
- package/dist/decompiler/format.d.ts.map +1 -1
- package/dist/decompiler/format.js +2 -1
- package/dist/decompiler/format.js.map +1 -1
- package/dist/desmos.d.ts +4 -0
- package/dist/desmos.d.ts.map +1 -1
- package/dist/desmos.js +6 -0
- package/dist/desmos.js.map +1 -1
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.internal.d.ts +1 -1
- package/dist/index.internal.d.ts.map +1 -1
- package/dist/index.internal.js +1 -1
- package/dist/index.internal.js.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/magic/expr/latex.d.ts +1 -2
- package/dist/magic/expr/latex.d.ts.map +1 -1
- package/dist/magic/expr/latex.js +1 -4
- package/dist/magic/expr/latex.js.map +1 -1
- package/dist/magic/expr/text.d.ts +1 -1
- package/dist/magic/expr/text.d.ts.map +1 -1
- package/dist/magic/expr/text.js +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 +1 -3
- package/dist/magic/global/dark-mode.js.map +1 -1
- package/dist/magic/global/desmos.d.ts +4 -1
- package/dist/magic/global/desmos.d.ts.map +1 -1
- package/dist/magic/global/desmos.js +14 -1
- package/dist/magic/global/desmos.js.map +1 -1
- package/dist/magic/global/viewport.d.ts +3 -0
- package/dist/magic/global/viewport.d.ts.map +1 -1
- package/dist/magic/global/viewport.js +23 -0
- package/dist/magic/global/viewport.js.map +1 -1
- package/dist/magic/incantation.d.ts +13 -8
- package/dist/magic/incantation.d.ts.map +1 -1
- package/dist/magic/incantation.js +23 -3
- package/dist/magic/incantation.js.map +1 -1
- package/dist/magic/local/anim.d.ts.map +1 -1
- package/dist/magic/local/anim.js +0 -1
- package/dist/magic/local/anim.js.map +1 -1
- package/dist/magic/local/colour.d.ts +3 -2
- package/dist/magic/local/colour.d.ts.map +1 -1
- package/dist/magic/local/colour.js +3 -3
- package/dist/magic/local/colour.js.map +1 -1
- package/dist/magic/local/label.d.ts +2 -1
- package/dist/magic/local/label.d.ts.map +1 -1
- package/dist/magic/local/label.js +36 -16
- package/dist/magic/local/label.js.map +1 -1
- package/dist/magic/local/line.d.ts +3 -0
- package/dist/magic/local/line.d.ts.map +1 -1
- package/dist/magic/local/line.js +10 -0
- package/dist/magic/local/line.js.map +1 -1
- package/dist/magic/local/slider.d.ts +2 -2
- package/dist/magic/local/slider.d.ts.map +1 -1
- package/dist/magic/local/slider.js +0 -1
- package/dist/magic/local/slider.js.map +1 -1
- package/dist/{compiler/options.d.ts → options.d.ts} +51 -22
- package/dist/options.d.ts.map +1 -0
- package/dist/{compiler/options.js → options.js} +6 -1
- package/dist/options.js.map +1 -0
- package/dist/parser/desmost-parser.d.ts +1 -1
- package/dist/parser/desmost-parser.d.ts.map +1 -1
- package/dist/parser/desmost-parser.js +9 -5
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/generic-parser.d.ts +1 -5
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js +7 -10
- package/dist/parser/generic-parser.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -1
- package/package.json +8 -3
- package/src/compiler/compile.ts +8 -4
- package/src/compiler/evaluate.ts +22 -13
- package/src/compiler/format.ts +22 -4
- package/src/compiler/index.ts +0 -1
- package/src/decompiler/decompile.ts +25 -3
- package/src/decompiler/emit.ts +15 -6
- package/src/decompiler/extract.ts +3 -2
- package/src/decompiler/format.ts +2 -1
- package/src/decompiler/readme.md +9 -1
- package/src/desmos.ts +9 -0
- package/src/errors.ts +29 -8
- package/src/index.internal.ts +1 -1
- package/src/index.ts +2 -2
- package/src/magic/.template.ts +25 -16
- package/src/magic/expr/latex.ts +2 -7
- package/src/magic/expr/text.ts +2 -2
- package/src/magic/global/dark-mode.ts +1 -3
- package/src/magic/global/desmos.ts +23 -1
- package/src/magic/global/viewport.ts +40 -1
- package/src/magic/incantation.ts +59 -22
- package/src/magic/local/anim.ts +0 -1
- package/src/magic/local/colour.ts +4 -3
- package/src/magic/local/label.ts +49 -20
- package/src/magic/local/line.ts +20 -3
- package/src/magic/local/slider.ts +2 -3
- package/src/{compiler/options.ts → options.ts} +63 -14
- package/src/parser/desmost-parser.ts +12 -6
- package/src/parser/generic-parser.ts +12 -12
- package/src/parser/readme.md +2 -0
- package/src/utils.ts +3 -0
- package/svelte/desmost.svelte +5 -3
- package/dist/compiler/options.d.ts.map +0 -1
- package/dist/compiler/options.js.map +0 -1
package/src/compiler/format.ts
CHANGED
|
@@ -1,18 +1,29 @@
|
|
|
1
|
-
import type { DesmostOptions } from "
|
|
1
|
+
import type { DesmostOptions } from "../options";
|
|
2
2
|
|
|
3
3
|
import { UnrecoverableError } from "../errors";
|
|
4
|
+
import { LINE } from "../utils";
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
export function format_error(
|
|
7
8
|
e: UnrecoverableError,
|
|
8
|
-
options:
|
|
9
|
+
options: DesmostOptions,
|
|
9
10
|
): string
|
|
10
11
|
{
|
|
11
12
|
let prefix = options.error_prefix;
|
|
12
13
|
let sep = (prefix === "" || prefix.endsWith("\n")) ? "" : " ";
|
|
13
14
|
|
|
14
|
-
return `${prefix}${sep}${e.name}: ${e.message}`;
|
|
15
15
|
// TODO maybe include stack? (configurable?)
|
|
16
|
+
let display = `${prefix}${sep}${e.name}: ${e.message}`;
|
|
17
|
+
|
|
18
|
+
if (e.info != undefined && options.expand_errors) {
|
|
19
|
+
display += `\n${LINE}`;
|
|
20
|
+
|
|
21
|
+
if (e.info.hint != undefined) display += `\nHint: ${e.info.hint}`;
|
|
22
|
+
if (e.info.note != undefined) display += `\n${LINE}\nNote: ${e.info.note}`;
|
|
23
|
+
if (e.info.flagged_by != undefined) display += `\n${LINE}\nFlagged by: \`${e.info.flagged_by}\``;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return display;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
|
|
@@ -37,11 +48,18 @@ export function normalise_latex(latex: string): string
|
|
|
37
48
|
export function prettify_latex(latex: string): string
|
|
38
49
|
{
|
|
39
50
|
latex = latex.replaceAll(/(?<!\\left)(\(|\[|\\\{)/g, "\\left$1");
|
|
51
|
+
latex = latex.replaceAll(/(?:\\left\s?)+/g, "\\left");
|
|
40
52
|
latex = latex.replaceAll(/(?<!\\right)(\)|\]|\\\})/g, "\\right$1");
|
|
53
|
+
latex = latex.replaceAll(/(?:\\right\s?)+/g, "\\right");
|
|
41
54
|
latex = latex.replaceAll(/(:|,)(?!\\ ) */g, "$1\\ ");
|
|
42
55
|
|
|
43
56
|
latex = latex.replaceAll(
|
|
44
|
-
/(?<=[^\w]|^)\\?(mean|median|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign) ?(?=\(|\\left\()/g,
|
|
57
|
+
/(?<=[^\w]|^)\\?(length|mean|median|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign) ?(?=\(|\\left\()/g,
|
|
58
|
+
"\\operatorname{$1}"
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
latex = latex.replaceAll(
|
|
62
|
+
/\s(and|or)\s/g,
|
|
45
63
|
"\\operatorname{$1}"
|
|
46
64
|
);
|
|
47
65
|
|
package/src/compiler/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { emit_global_incantation, emit_expression } from "./emit";
|
|
2
2
|
import { extract_settings, extract_viewport, extract_expression } from "./extract";
|
|
3
3
|
|
|
4
|
+
import { fill_defaults } from "../options";
|
|
4
5
|
import { Ast } from "../parser";
|
|
6
|
+
import type { DesmostOptions } from "../options";
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
export interface SemiStructuredAst
|
|
@@ -16,15 +18,35 @@ export interface SemiStructuredAst
|
|
|
16
18
|
*
|
|
17
19
|
* The caller must also supply a `blank` calculator instance. This is used as a reference for default field values, so that emitted incantations like `/desmos{}` don't get filled with a huge amount of noise.
|
|
18
20
|
*/
|
|
19
|
-
export function decompile(
|
|
21
|
+
export function decompile(
|
|
22
|
+
/** The Desmos calculator instance to decompile. */
|
|
23
|
+
desmos: Desmos.Calculator,
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The default blank Desmos calculator instance to use as a baseline.
|
|
27
|
+
*
|
|
28
|
+
* It is **highly recommended** to include this, otherwise your decompiled source will have a bunch of unnecessary incantations!
|
|
29
|
+
*
|
|
30
|
+
* This is used in deciding what settings are noise that can be omitted. For instance, most of your calculator settings are probably set to their default values, so there's no need to include *every single one of them* in `/desmos{}`.
|
|
31
|
+
*/
|
|
32
|
+
blank?: Desmos.Calculator,
|
|
33
|
+
|
|
34
|
+
// TODO doc
|
|
35
|
+
options?: Partial<DesmostOptions>,
|
|
36
|
+
): string
|
|
20
37
|
{
|
|
21
|
-
|
|
38
|
+
let opts = fill_defaults(options);
|
|
39
|
+
return ast_to_source(desmos_to_ast(desmos, blank ?? desmos));
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
/**
|
|
25
43
|
* Decompile `desmos` into a semi-structured AST.
|
|
26
44
|
*/
|
|
27
|
-
export function desmos_to_ast(
|
|
45
|
+
export function desmos_to_ast(
|
|
46
|
+
desmos: Desmos.Calculator,
|
|
47
|
+
blank: Desmos.Calculator,
|
|
48
|
+
options: DesmostOptions,
|
|
49
|
+
): SemiStructuredAst
|
|
28
50
|
{
|
|
29
51
|
return {
|
|
30
52
|
globals: [
|
package/src/decompiler/emit.ts
CHANGED
|
@@ -31,15 +31,24 @@ export function emit_expression(expr?: Ast.Expression): string
|
|
|
31
31
|
|
|
32
32
|
switch (expr.data.type) {
|
|
33
33
|
case "text":
|
|
34
|
-
|
|
34
|
+
expr.data.text ??= "";
|
|
35
|
+
|
|
36
|
+
if (expr.data.text.includes("\n")) {
|
|
37
|
+
content = `/text{\n${expr.data.text}\n}`;
|
|
38
|
+
} else {
|
|
39
|
+
content = `% ${expr.data.text}`;
|
|
40
|
+
}
|
|
35
41
|
break;
|
|
42
|
+
|
|
43
|
+
case "table":
|
|
44
|
+
content = "[tables are currently unsupported]";
|
|
45
|
+
break;
|
|
46
|
+
|
|
36
47
|
default:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
content = "";
|
|
48
|
+
if (expr.data.latex?.trim() !== "") {
|
|
49
|
+
content = expr.data.latex ?? "[unsupported]";
|
|
40
50
|
} else {
|
|
41
|
-
|
|
42
|
-
content = expr.data.latex ?? "?";
|
|
51
|
+
content = "";
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { stringify_json5, prettify_source } from "./format";
|
|
2
2
|
|
|
3
3
|
import { Ast } from "../parser";
|
|
4
|
+
import { is_latex } from "../desmos";
|
|
4
5
|
|
|
5
6
|
import { DesmosIncantation, ViewportIncantation } from "../magic/global";
|
|
6
7
|
|
|
@@ -69,7 +70,7 @@ export function extract_expression(expression: Desmos.ExpressionState): Ast.Expr
|
|
|
69
70
|
{
|
|
70
71
|
let data;
|
|
71
72
|
|
|
72
|
-
// @ts-expect-error:
|
|
73
|
+
// @ts-expect-error: check type-narrows
|
|
73
74
|
if (expression.latex?.trim() === "") {
|
|
74
75
|
data = { latex: ` ` };
|
|
75
76
|
} else {
|
|
@@ -79,7 +80,7 @@ export function extract_expression(expression: Desmos.ExpressionState): Ast.Expr
|
|
|
79
80
|
);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
if (
|
|
83
|
+
if (is_latex(expression)) {
|
|
83
84
|
data.latex = prettify_source(data.latex);
|
|
84
85
|
}
|
|
85
86
|
|
package/src/decompiler/format.ts
CHANGED
|
@@ -3,7 +3,7 @@ export const stringify_json5: (typeof JSON.stringify) = (
|
|
|
3
3
|
) =>
|
|
4
4
|
{
|
|
5
5
|
return (
|
|
6
|
-
|
|
6
|
+
// @ts-expect-error: random `null`??
|
|
7
7
|
JSON.stringify(data, replacer, space)
|
|
8
8
|
.slice(1, -1) // strip surrounding {}
|
|
9
9
|
.trim()
|
|
@@ -15,6 +15,7 @@ export const stringify_json5: (typeof JSON.stringify) = (
|
|
|
15
15
|
export function prettify_source(source: string): string
|
|
16
16
|
{
|
|
17
17
|
source = source.replaceAll(/(?<=[^ ])=(?=[^ ])/g, " = ");
|
|
18
|
+
source = source.replaceAll(/\}\\/g, "} \\")
|
|
18
19
|
|
|
19
20
|
return source;
|
|
20
21
|
}
|
package/src/decompiler/readme.md
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
This module implements the Desmos -> Desmost decompiler.
|
|
4
4
|
|
|
5
|
-
This decompiles a `Desmos.Calculator` instance into the *minimal* raw Desmost source code that could *completely* reproduce the calculator state.
|
|
5
|
+
This decompiles a `Desmos.Calculator` instance into the *minimal* raw Desmost source code that could *completely* reproduce the calculator state. In other words, we want this to hold:[^equality]
|
|
6
|
+
|
|
7
|
+
[^equality]: Using value equality of ‘calculator state’.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
compile(decompile(calc)) == calc
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Decompilation happens through several lowering stages:
|
|
6
14
|
|
|
7
15
|
```hs
|
|
8
16
|
Desmos.Calculator -> object[] -> Ast[] -> string
|
package/src/desmos.ts
CHANGED
|
@@ -11,3 +11,12 @@ export enum DesmosColour
|
|
|
11
11
|
ORANGE = "#fa7e19",
|
|
12
12
|
BLACK = "#000000",
|
|
13
13
|
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Is `expr` a Desmos LaTeX expression (as opposed to a note or table)?
|
|
18
|
+
*/
|
|
19
|
+
export function is_latex(expr: Desmos.ExpressionState): expr is Desmos.Expression
|
|
20
|
+
{
|
|
21
|
+
return (expr.type === undefined || expr.type === "expression");
|
|
22
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -10,28 +10,49 @@ export type NoMatch = typeof NO_MATCH & { readonly __brand?: unique symbol };
|
|
|
10
10
|
*
|
|
11
11
|
* 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
12
|
*/
|
|
13
|
-
export class UnrecoverableError extends Error
|
|
13
|
+
export class UnrecoverableError extends Error
|
|
14
|
+
{
|
|
15
|
+
info?: UnrecoverableError.Info;
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
export namespace UnrecoverableError
|
|
16
19
|
{
|
|
20
|
+
export interface Info
|
|
21
|
+
{
|
|
22
|
+
hint?: string,
|
|
23
|
+
note?: string,
|
|
24
|
+
flagged_by?: string,
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
/** The parser unexpectedly reached the end of its entire input source code. */
|
|
18
|
-
export class UnexpectedEnd extends UnrecoverableError {
|
|
28
|
+
export class UnexpectedEnd extends UnrecoverableError {
|
|
29
|
+
constructor(msg: string, override info?: Info) { super(msg); this.name = "Unexpected End"; }
|
|
30
|
+
}
|
|
19
31
|
|
|
20
32
|
/** The parser did not receive input that it expected. */
|
|
21
|
-
export class MissingInput extends UnrecoverableError {
|
|
33
|
+
export class MissingInput extends UnrecoverableError {
|
|
34
|
+
constructor(msg: string, override info?: Info) { super(msg); this.name = "Missing Input"; }
|
|
35
|
+
}
|
|
22
36
|
|
|
23
37
|
/** The parser received input that did not match what it expected. */
|
|
24
|
-
export class UnexpectedInput extends UnrecoverableError {
|
|
38
|
+
export class UnexpectedInput extends UnrecoverableError {
|
|
39
|
+
constructor(msg: string, override info?: Info) { super(msg); this.name = "Unexpected Input"; }
|
|
40
|
+
}
|
|
25
41
|
|
|
26
42
|
/** The parser received excess input that it did not expect. */
|
|
27
|
-
export class ExcessInput extends UnrecoverableError {
|
|
28
|
-
|
|
43
|
+
export class ExcessInput extends UnrecoverableError {
|
|
44
|
+
constructor(msg: string, override info?: Info) { super(msg); this.name = "Excess Input"; }
|
|
45
|
+
}
|
|
29
46
|
|
|
30
47
|
/** An incantation can't be applied here. */
|
|
31
|
-
export class IllegalIncantation extends UnrecoverableError {
|
|
48
|
+
export class IllegalIncantation extends UnrecoverableError {
|
|
49
|
+
constructor(msg: string, override info?: Info) { super(msg); this.name = "Illegal Incantation"; }
|
|
50
|
+
}
|
|
32
51
|
|
|
33
52
|
/** An incantation's argument couldn't be parsed. */
|
|
34
|
-
export class InvalidArgument extends UnrecoverableError {
|
|
53
|
+
export class InvalidArgument extends UnrecoverableError {
|
|
54
|
+
constructor(msg: string, override info?: Info) { super(msg); this.name = "Invalid Argument"; }
|
|
55
|
+
}
|
|
35
56
|
}
|
|
36
57
|
|
|
37
58
|
|
package/src/index.internal.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { compile
|
|
2
|
-
export {
|
|
1
|
+
export { compile } from "./compiler";
|
|
2
|
+
export type { DesmostOptions } from "./options";
|
package/src/magic/.template.ts
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
// import { Incantation, ArgIncantation, type GLOBAL, type LOCAL, type EXPR } from "../incantation";
|
|
2
2
|
|
|
3
|
+
// import type { DesmostOptions } from "../../options";
|
|
4
|
+
// import { UnrecoverableError, type Unrecoverable } from "../../errors";
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
// export class ?Incantation extends ArgIncantation<?>
|
|
5
8
|
// {
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
9
|
+
// override readonly description
|
|
10
|
+
// = ""
|
|
11
|
+
|
|
12
|
+
// override readonly identifier = ?
|
|
13
|
+
// override readonly requires_arg = ?
|
|
14
|
+
// override readonly arg_type = Incantation.ArgType.?
|
|
15
|
+
|
|
16
|
+
// override apply(target: ?, data: ?)
|
|
17
|
+
// {
|
|
18
|
+
// // TODO
|
|
19
|
+
// }
|
|
20
|
+
|
|
21
|
+
// override evaluate_arg(raw: ?, options: DesmostOptions): Unrecoverable<?>
|
|
22
|
+
// {
|
|
23
|
+
// let out = super.evaluate_arg(raw, options) as ?;
|
|
24
|
+
|
|
25
|
+
// if (options.check_args) {
|
|
26
|
+
// // TODO
|
|
27
|
+
// }
|
|
28
|
+
|
|
29
|
+
// return out;
|
|
30
|
+
// }
|
|
22
31
|
// }
|
package/src/magic/expr/latex.ts
CHANGED
|
@@ -10,15 +10,10 @@ export class LatexIncantation extends ArgIncantation<EXPR>
|
|
|
10
10
|
override readonly requires_arg = true
|
|
11
11
|
override readonly arg_type = Incantation.ArgType.LATEX
|
|
12
12
|
|
|
13
|
-
apply(target: Desmos.ExpressionState, data: string):
|
|
13
|
+
apply(target: Desmos.ExpressionState, data: string): asserts target is Desmos.Expression
|
|
14
14
|
{
|
|
15
15
|
target.type = "expression";
|
|
16
|
-
// @ts-expect-error:
|
|
16
|
+
// @ts-expect-error: previous assignment type-narrows
|
|
17
17
|
target.latex = data === "" ? " " : data;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
override evaluate_arg(data: string): string
|
|
21
|
-
{
|
|
22
|
-
return data;
|
|
23
|
-
}
|
|
24
19
|
}
|
package/src/magic/expr/text.ts
CHANGED
|
@@ -11,10 +11,10 @@ export class TextIncantation extends ArgIncantation<EXPR>
|
|
|
11
11
|
override readonly requires_arg = true
|
|
12
12
|
override readonly arg_type = Incantation.ArgType.STRING
|
|
13
13
|
|
|
14
|
-
override apply(target: Desmos.ExpressionState, data: string)
|
|
14
|
+
override apply(target: Desmos.ExpressionState, data: string): asserts target is Desmos.Note
|
|
15
15
|
{
|
|
16
16
|
target.type = "text";
|
|
17
|
-
// @ts-expect-error:
|
|
17
|
+
// @ts-expect-error: previous assignment type-narrows
|
|
18
18
|
target.text = data;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type GLOBAL } from "../incantation";
|
|
2
2
|
|
|
3
|
+
import { UnrecoverableError, type Unrecoverable } from "../../errors";
|
|
4
|
+
import type { DesmostOptions } from "../../options";
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
type DesmosSettings = Desmos.GraphConfiguration & Desmos.GraphSettings
|
|
5
8
|
|
|
@@ -7,7 +10,7 @@ type DesmosSettings = Desmos.GraphConfiguration & Desmos.GraphSettings
|
|
|
7
10
|
export class DesmosIncantation extends ArgIncantation<GLOBAL>
|
|
8
11
|
{
|
|
9
12
|
override readonly description
|
|
10
|
-
= "
|
|
13
|
+
= "Tweak calculator settings via `Calculator.updateSettings()`."
|
|
11
14
|
|
|
12
15
|
override readonly identifier = "desmos"
|
|
13
16
|
override readonly requires_arg = false
|
|
@@ -22,4 +25,23 @@ export class DesmosIncantation extends ArgIncantation<GLOBAL>
|
|
|
22
25
|
target.updateSettings(data);
|
|
23
26
|
}
|
|
24
27
|
}
|
|
28
|
+
|
|
29
|
+
override evaluate_arg(raw: string, options: DesmostOptions): Unrecoverable<DesmosSettings>
|
|
30
|
+
{
|
|
31
|
+
let out = super.evaluate_arg(raw, options) as DesmosSettings;
|
|
32
|
+
|
|
33
|
+
if (options.check_args) {
|
|
34
|
+
if (Object.keys(out).length === 0) {
|
|
35
|
+
throw new UnrecoverableError.MissingInput(
|
|
36
|
+
`/${this.identifier} received empty settings`,
|
|
37
|
+
{
|
|
38
|
+
hint: `Provide settings like \`/desmos{expressionsCollapsed: true}\``,
|
|
39
|
+
note: `Use just /desmos (without \`{}\`) if you want it as an indicator`,
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
25
47
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type GLOBAL } from "../incantation";
|
|
2
2
|
|
|
3
|
+
import { UnrecoverableError, type Unrecoverable } from "../../errors";
|
|
4
|
+
import type { DesmostOptions } from "../../options";
|
|
5
|
+
|
|
3
6
|
|
|
4
7
|
interface ViewportBounds
|
|
5
8
|
{
|
|
@@ -7,6 +10,8 @@ interface ViewportBounds
|
|
|
7
10
|
bottom?: number; top?: number;
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
const VALID_KEYS = ["left", "right", "bottom", "top"];
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
export class ViewportIncantation extends ArgIncantation<GLOBAL>
|
|
12
17
|
{
|
|
@@ -17,7 +22,7 @@ export class ViewportIncantation extends ArgIncantation<GLOBAL>
|
|
|
17
22
|
override readonly requires_arg = true
|
|
18
23
|
override readonly arg_type = Incantation.ArgType.OBJECT
|
|
19
24
|
|
|
20
|
-
apply(target: Desmos.Calculator, data: ViewportBounds)
|
|
25
|
+
override apply(target: Desmos.Calculator, data: ViewportBounds)
|
|
21
26
|
{
|
|
22
27
|
let existing = target.graphpaperBounds.mathCoordinates;
|
|
23
28
|
|
|
@@ -30,4 +35,38 @@ export class ViewportIncantation extends ArgIncantation<GLOBAL>
|
|
|
30
35
|
|
|
31
36
|
target.setMathBounds({ left, right, bottom, top });
|
|
32
37
|
}
|
|
38
|
+
|
|
39
|
+
override evaluate_arg(raw: string, options: DesmostOptions): Unrecoverable<ViewportBounds>
|
|
40
|
+
{
|
|
41
|
+
let out = super.evaluate_arg(raw, options) as ViewportBounds;
|
|
42
|
+
|
|
43
|
+
if (options.check_args) {
|
|
44
|
+
if (Object.keys(out).length === 0) {
|
|
45
|
+
throw new UnrecoverableError.InvalidArgument(
|
|
46
|
+
`/${this.identifier} received empty viewport bounds`,
|
|
47
|
+
{
|
|
48
|
+
hint: `Provide bounds like \`/viewport{left: -8, right: 8}\``,
|
|
49
|
+
note: `Missing bounds are kept as their defaults`,
|
|
50
|
+
flagged_by: "check_args",
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let invalid_keys = Object.keys(out).filter(key => !VALID_KEYS.includes(key));
|
|
56
|
+
|
|
57
|
+
if (invalid_keys.length > 0) {
|
|
58
|
+
let plural = invalid_keys.length > 1 ? "fields" : "field";
|
|
59
|
+
|
|
60
|
+
throw new UnrecoverableError.InvalidArgument(
|
|
61
|
+
`/${this.identifier} received invalid ${plural}: [${invalid_keys.join(", ")}]`,
|
|
62
|
+
{
|
|
63
|
+
hint: `Valid fields are: [${VALID_KEYS.join(", ")}]`,
|
|
64
|
+
flagged_by: "check_args",
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
33
72
|
}
|
package/src/magic/incantation.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import Json5 from "json5";
|
|
2
|
+
import dedent from "dedent";
|
|
2
3
|
|
|
4
|
+
import type { DesmostOptions } from "../options";
|
|
3
5
|
import { UnrecoverableError, type Unrecoverable } from "../errors";
|
|
4
6
|
|
|
5
7
|
|
|
@@ -21,17 +23,17 @@ export abstract class Incantation<
|
|
|
21
23
|
>
|
|
22
24
|
{
|
|
23
25
|
/** Short user-facing description of what the incantation does. */
|
|
24
|
-
|
|
26
|
+
abstract readonly description: string
|
|
25
27
|
|
|
26
28
|
/** The raw text sequence that matches this incantation, such as `viewport` or `hidden`. */
|
|
27
|
-
|
|
29
|
+
abstract readonly identifier: string
|
|
28
30
|
|
|
29
31
|
/** An alternative `.identifier`, strictly for localisation purposes only. */
|
|
30
|
-
|
|
32
|
+
readonly alias?: string
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
/** Apply this incantation's effect to `target`, using the provided `data` if required. */
|
|
34
|
-
|
|
36
|
+
abstract apply(
|
|
35
37
|
target: Effect extends GLOBAL ? Desmos.Calculator : Desmos.ExpressionState,
|
|
36
38
|
data?: unknown,
|
|
37
39
|
): void
|
|
@@ -45,7 +47,7 @@ export abstract class Incantation<
|
|
|
45
47
|
if ((actual ?? "expression") !== required)
|
|
46
48
|
{
|
|
47
49
|
throw new UnrecoverableError.IllegalIncantation(
|
|
48
|
-
`/${this.identifier} can only applied to ${required === "expression" ? "latex" : required}
|
|
50
|
+
`/${this.identifier} can only applied to ${required === "expression" ? "latex" : required} blocks, but target block has type: ${actual}`
|
|
49
51
|
);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
@@ -61,10 +63,10 @@ export abstract class ArgIncantation<
|
|
|
61
63
|
extends Incantation<Effect>
|
|
62
64
|
{
|
|
63
65
|
/** Does this incantation always require an argument to be passed? */
|
|
64
|
-
|
|
66
|
+
readonly requires_arg: boolean = true
|
|
65
67
|
|
|
66
68
|
/** What type of argument does this incantation accept? */
|
|
67
|
-
|
|
69
|
+
abstract readonly arg_type: Incantation.ArgType
|
|
68
70
|
|
|
69
71
|
|
|
70
72
|
/**
|
|
@@ -75,14 +77,18 @@ export abstract class ArgIncantation<
|
|
|
75
77
|
* ## Notes
|
|
76
78
|
*
|
|
77
79
|
* Child incantation classes should override this method, though the defaults should cover most cases (except `ArgType.ENUM`).
|
|
80
|
+
*
|
|
81
|
+
* 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.
|
|
78
82
|
*/
|
|
79
|
-
|
|
83
|
+
evaluate_arg(raw: string, options: DesmostOptions): Unrecoverable<unknown>
|
|
80
84
|
{
|
|
81
85
|
switch (this.arg_type) {
|
|
82
|
-
case Incantation.ArgType.STRING: return raw;
|
|
83
86
|
case Incantation.ArgType.LATEX: return raw;
|
|
84
87
|
case Incantation.ArgType.ENUM: return raw;
|
|
85
88
|
|
|
89
|
+
case Incantation.ArgType.STRING:
|
|
90
|
+
return (options.dedent_text) ? dedent(raw): raw.trim();
|
|
91
|
+
|
|
86
92
|
case Incantation.ArgType.OBJECT: {
|
|
87
93
|
try {
|
|
88
94
|
return Json5.parse(`{${raw}}`);
|
|
@@ -94,9 +100,53 @@ export abstract class ArgIncantation<
|
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
}
|
|
103
|
+
|
|
104
|
+
protected require_nonempty(
|
|
105
|
+
arg: object,
|
|
106
|
+
msg?: string,
|
|
107
|
+
info?: UnrecoverableError.Info,
|
|
108
|
+
): Unrecoverable<void>
|
|
109
|
+
{
|
|
110
|
+
if (Object.entries(arg).length === 0) {
|
|
111
|
+
throw new UnrecoverableError.MissingInput(
|
|
112
|
+
msg ?? `/${this.identifier} received an empty argument {}`,
|
|
113
|
+
info,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
protected require_known(
|
|
119
|
+
arg: object,
|
|
120
|
+
valid_fields: string[],
|
|
121
|
+
info?: UnrecoverableError.Info,
|
|
122
|
+
): Unrecoverable<void>
|
|
123
|
+
{
|
|
124
|
+
// TODO accumulate
|
|
125
|
+
for (let field of Object.keys(arg)) {
|
|
126
|
+
if (!valid_fields.includes(field)) {
|
|
127
|
+
throw new UnrecoverableError.InvalidArgument(
|
|
128
|
+
`/${this.identifier} received invalid field: ${field}`,
|
|
129
|
+
{
|
|
130
|
+
hint: `Valid fields are [${valid_fields.join(", ")}]`,
|
|
131
|
+
...info,
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
97
137
|
}
|
|
98
138
|
|
|
99
139
|
|
|
140
|
+
/** An incantation that affects the entire Desmos calculator state, like `/desmos` or `/viewport`. */
|
|
141
|
+
export type GLOBAL = Incantation.Effect.GLOBAL;
|
|
142
|
+
|
|
143
|
+
/** An incantation that affects only the expression immediately following it, like `/hide` or `/slider`. */
|
|
144
|
+
export type LOCAL = Incantation.Effect.LOCAL;
|
|
145
|
+
|
|
146
|
+
/** An incantation that produces an expression, like `/latex` or `/text`. */
|
|
147
|
+
export type EXPR = Incantation.Effect.EXPR;
|
|
148
|
+
|
|
149
|
+
|
|
100
150
|
export namespace Incantation
|
|
101
151
|
{
|
|
102
152
|
/** The kind of effect an incantation produces - it either modifies only one block, or modifies the calculator as a whole. */
|
|
@@ -128,16 +178,3 @@ export namespace Incantation
|
|
|
128
178
|
OBJECT = "Object",
|
|
129
179
|
}
|
|
130
180
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// == ALIASES == //
|
|
134
|
-
|
|
135
|
-
/** An incantation that affects the entire Desmos calculator state, like `/desmos` or `/viewport`. */
|
|
136
|
-
export type GLOBAL = Incantation.Effect.GLOBAL;
|
|
137
|
-
|
|
138
|
-
/** An incantation that affects only the expression immediately following it, like `/hide` or `/slider`. */
|
|
139
|
-
export type LOCAL = Incantation.Effect.LOCAL;
|
|
140
|
-
|
|
141
|
-
/** An incantation that produces an expression, like `/latex` or `/text`. */
|
|
142
|
-
export type EXPR = Incantation.Effect.EXPR;
|
|
143
|
-
|
package/src/magic/local/anim.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type LOCAL } from "../incantation";
|
|
2
|
-
import { UnrecoverableError } from "../../errors";
|
|
3
2
|
|
|
4
3
|
import { DesmosColour } from "../../desmos";
|
|
4
|
+
import { UnrecoverableError } from "../../errors";
|
|
5
|
+
import type { DesmostOptions } from "../../options";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
export class ColourIncantation extends ArgIncantation<LOCAL>
|
|
8
9
|
{
|
|
9
10
|
override readonly description
|
|
10
|
-
= ""
|
|
11
|
+
= "Change the colour of a rendered expression, such as a line, region, polygon, etc."
|
|
11
12
|
|
|
12
13
|
override readonly identifier = "color"
|
|
13
14
|
override readonly alias = "colour"
|
|
@@ -20,7 +21,7 @@ export class ColourIncantation extends ArgIncantation<LOCAL>
|
|
|
20
21
|
target.color = data;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
override evaluate_arg(data: string): DesmosColour
|
|
24
|
+
override evaluate_arg(data: string, options: DesmostOptions): DesmosColour
|
|
24
25
|
{
|
|
25
26
|
switch (data.trim().toUpperCase()) {
|
|
26
27
|
case "RED": return DesmosColour.RED;
|