desmost 0.10.1 → 0.11.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 +5 -5
- package/dist/compiler/compile.d.ts +1 -1
- package/dist/compiler/compile.js +1 -1
- package/dist/compiler/compiler.d.ts.map +1 -1
- package/dist/compiler/compiler.js +9 -8
- package/dist/compiler/compiler.js.map +1 -1
- package/dist/compiler/format.js +1 -1
- package/dist/compiler/format.js.map +1 -1
- package/dist/decompiler/decompile.d.ts +13 -5
- package/dist/decompiler/decompile.d.ts.map +1 -1
- package/dist/decompiler/decompile.js +24 -13
- package/dist/decompiler/decompile.js.map +1 -1
- package/dist/decompiler/emit.d.ts +5 -2
- package/dist/decompiler/emit.d.ts.map +1 -1
- package/dist/decompiler/emit.js +41 -20
- package/dist/decompiler/emit.js.map +1 -1
- package/dist/decompiler/extract.d.ts +1 -9
- package/dist/decompiler/extract.d.ts.map +1 -1
- package/dist/decompiler/extract.js +24 -58
- package/dist/decompiler/extract.js.map +1 -1
- package/dist/decompiler/format.d.ts +0 -1
- package/dist/decompiler/format.d.ts.map +1 -1
- package/dist/decompiler/format.js +3 -9
- package/dist/decompiler/format.js.map +1 -1
- package/dist/errors.d.ts +14 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -2
- package/dist/errors.js.map +1 -1
- package/dist/magic/expr/index.js +6 -6
- package/dist/magic/expr/index.js.map +1 -1
- package/dist/magic/expr/latex.d.ts +5 -2
- package/dist/magic/expr/latex.d.ts.map +1 -1
- package/dist/magic/expr/latex.js +4 -2
- package/dist/magic/expr/latex.js.map +1 -1
- package/dist/magic/expr/text.d.ts +5 -2
- package/dist/magic/expr/text.d.ts.map +1 -1
- package/dist/magic/expr/text.js +4 -2
- package/dist/magic/expr/text.js.map +1 -1
- package/dist/magic/global/{dark-mode.d.ts → dark.d.ts} +6 -2
- package/dist/magic/global/dark.d.ts.map +1 -0
- package/dist/magic/global/dark.js +20 -0
- package/dist/magic/global/dark.js.map +1 -0
- 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 +23 -0
- package/dist/magic/global/desmos.js.map +1 -1
- package/dist/magic/global/index.d.ts +5 -5
- package/dist/magic/global/index.d.ts.map +1 -1
- package/dist/magic/global/index.js +8 -11
- package/dist/magic/global/index.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 +20 -0
- package/dist/magic/global/viewport.js.map +1 -1
- package/dist/magic/incantation.d.ts +15 -2
- package/dist/magic/incantation.d.ts.map +1 -1
- package/dist/magic/incantation.js +8 -1
- package/dist/magic/incantation.js.map +1 -1
- package/dist/magic/local/anim.d.ts +5 -1
- package/dist/magic/local/anim.d.ts.map +1 -1
- package/dist/magic/local/anim.js +12 -1
- package/dist/magic/local/anim.js.map +1 -1
- package/dist/magic/local/colour.d.ts +7 -3
- package/dist/magic/local/colour.d.ts.map +1 -1
- package/dist/magic/local/colour.js +13 -3
- package/dist/magic/local/colour.js.map +1 -1
- package/dist/magic/local/dashed.d.ts +5 -1
- package/dist/magic/local/dashed.d.ts.map +1 -1
- package/dist/magic/local/dashed.js +12 -1
- package/dist/magic/local/dashed.js.map +1 -1
- package/dist/magic/local/fill.d.ts +5 -1
- package/dist/magic/local/fill.d.ts.map +1 -1
- package/dist/magic/local/fill.js +15 -1
- package/dist/magic/local/fill.js.map +1 -1
- package/dist/magic/local/hide.d.ts +5 -1
- package/dist/magic/local/hide.d.ts.map +1 -1
- package/dist/magic/local/hide.js +12 -1
- package/dist/magic/local/hide.js.map +1 -1
- package/dist/magic/local/index.d.ts +13 -2
- package/dist/magic/local/index.d.ts.map +1 -1
- package/dist/magic/local/index.js +24 -24
- package/dist/magic/local/index.js.map +1 -1
- package/dist/magic/local/label.d.ts +6 -2
- package/dist/magic/local/label.d.ts.map +1 -1
- package/dist/magic/local/label.js +19 -1
- package/dist/magic/local/label.js.map +1 -1
- package/dist/magic/local/line.d.ts +5 -1
- package/dist/magic/local/line.d.ts.map +1 -1
- package/dist/magic/local/line.js +19 -1
- package/dist/magic/local/line.js.map +1 -1
- package/dist/magic/local/no-line.d.ts +5 -1
- package/dist/magic/local/no-line.d.ts.map +1 -1
- package/dist/magic/local/no-line.js +12 -1
- package/dist/magic/local/no-line.js.map +1 -1
- package/dist/magic/local/point.d.ts +5 -1
- package/dist/magic/local/point.d.ts.map +1 -1
- package/dist/magic/local/point.js +19 -1
- package/dist/magic/local/point.js.map +1 -1
- package/dist/magic/local/secret.d.ts +5 -1
- package/dist/magic/local/secret.d.ts.map +1 -1
- package/dist/magic/local/secret.js +12 -1
- package/dist/magic/local/secret.js.map +1 -1
- package/dist/magic/local/slider.d.ts +5 -1
- package/dist/magic/local/slider.d.ts.map +1 -1
- package/dist/magic/local/slider.js +13 -1
- package/dist/magic/local/slider.js.map +1 -1
- package/dist/parser/ast.d.ts +9 -3
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js +3 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/desmost-parser.d.ts +2 -2
- package/dist/parser/desmost-parser.d.ts.map +1 -1
- package/dist/parser/desmost-parser.js +6 -4
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/errors.d.ts +11 -0
- package/dist/parser/errors.d.ts.map +1 -0
- package/dist/parser/errors.js +4 -0
- package/dist/parser/errors.js.map +1 -0
- package/dist/parser/generic-parser.d.ts +7 -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/package.json +1 -1
- package/src/compiler/compile.ts +1 -1
- package/src/compiler/compiler.ts +10 -9
- package/src/compiler/format.ts +1 -1
- package/src/decompiler/decompile.ts +30 -14
- package/src/decompiler/emit.ts +57 -25
- package/src/decompiler/extract.ts +30 -89
- package/src/decompiler/format.ts +3 -14
- package/src/decompiler/readme.md +2 -2
- package/src/errors.ts +13 -7
- package/src/magic/.template.ts +8 -0
- package/src/magic/expr/index.ts +6 -7
- package/src/magic/expr/latex.ts +8 -4
- package/src/magic/expr/text.ts +8 -4
- package/src/magic/global/dark.ts +29 -0
- package/src/magic/global/desmos.ts +35 -5
- package/src/magic/global/index.ts +8 -14
- package/src/magic/global/viewport.ts +29 -0
- package/src/magic/incantation.ts +28 -6
- package/src/magic/local/anim.ts +16 -3
- package/src/magic/local/colour.ts +18 -6
- package/src/magic/local/dashed.ts +16 -2
- package/src/magic/local/fill.ts +20 -3
- package/src/magic/local/hide.ts +16 -2
- package/src/magic/local/index.ts +24 -25
- package/src/magic/local/label.ts +31 -8
- package/src/magic/local/line.ts +25 -3
- package/src/magic/local/no-line.ts +16 -2
- package/src/magic/local/point.ts +27 -3
- package/src/magic/local/secret.ts +16 -2
- package/src/magic/local/slider.ts +18 -3
- package/src/parser/ast.ts +10 -7
- package/src/parser/desmost-parser.ts +10 -6
- package/src/parser/errors.ts +10 -0
- package/src/parser/generic-parser.ts +9 -11
- package/svelte/desmost.svelte +107 -107
- package/dist/magic/expr/index.d.ts +0 -3
- package/dist/magic/expr/index.d.ts.map +0 -1
- package/dist/magic/global/dark-mode.d.ts.map +0 -1
- package/dist/magic/global/dark-mode.js +0 -9
- package/dist/magic/global/dark-mode.js.map +0 -1
- package/src/magic/global/dark-mode.ts +0 -15
package/src/decompiler/emit.ts
CHANGED
|
@@ -1,56 +1,88 @@
|
|
|
1
|
+
import { prettify_source } from "./format";
|
|
2
|
+
|
|
3
|
+
import { DEFAULT_OPTIONS, type DesmostOptions } from "../options";
|
|
4
|
+
import { Incantation } from "../magic";
|
|
1
5
|
import type { Ast } from "../parser";
|
|
2
6
|
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* Emit the source code for a global incantation `invocation`.
|
|
6
10
|
*/
|
|
7
|
-
export function
|
|
11
|
+
export function emit_incantation(
|
|
12
|
+
invocation: Ast.IncantationInvocation,
|
|
13
|
+
options: DesmostOptions,
|
|
14
|
+
_?: { expand: boolean },
|
|
15
|
+
): string
|
|
8
16
|
{
|
|
9
17
|
if (invocation == undefined) return "";
|
|
10
18
|
|
|
11
19
|
let ident = invocation.incantation.identifier;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
|
|
21
|
+
if ("arg_raw" in invocation) {
|
|
22
|
+
let arg_raw = invocation.arg_raw;
|
|
23
|
+
|
|
24
|
+
if (_?.expand && invocation.incantation.arg_type === Incantation.ArgType.OBJECT) {
|
|
25
|
+
return `/${ident}{\n ${arg_raw}\n}`;
|
|
26
|
+
} else {
|
|
27
|
+
return `/${ident}{${arg_raw}}`;
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
return `/${ident}`
|
|
31
|
+
}
|
|
15
32
|
}
|
|
16
33
|
|
|
17
34
|
|
|
18
35
|
/**
|
|
19
36
|
* Emit the source code for an `expr`.
|
|
20
37
|
*/
|
|
21
|
-
export function emit_expression(
|
|
38
|
+
export function emit_expression(
|
|
39
|
+
expr: Ast.Expression,
|
|
40
|
+
options: DesmostOptions,
|
|
41
|
+
): string
|
|
22
42
|
{
|
|
23
43
|
if (expr == undefined) return "";
|
|
24
|
-
|
|
25
|
-
// TODO
|
|
26
|
-
let locals: string[] = [];
|
|
27
|
-
|
|
28
|
-
let sep = expr.incantations.length > 0 ? " :: " : "";
|
|
29
44
|
|
|
30
|
-
let content;
|
|
31
|
-
|
|
32
45
|
switch (expr.data.type) {
|
|
33
46
|
case "text":
|
|
34
|
-
expr.data.text
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
if (expr.data.text?.startsWith(DEFAULT_OPTIONS.error_prefix)) {
|
|
48
|
+
var content = `% [invalid]`;
|
|
49
|
+
}
|
|
50
|
+
else if (expr.data.text?.includes("\n")) {
|
|
51
|
+
if (options.prettify) {
|
|
52
|
+
let indented = (
|
|
53
|
+
expr.data.text
|
|
54
|
+
.split("\n")
|
|
55
|
+
.map(line => ` ${line}`)
|
|
56
|
+
.join("\n")
|
|
57
|
+
);
|
|
58
|
+
var content = `/text{\n${indented}\n}`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
var content = `/text{\n${expr.data.text}\n}`
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
var content = `% ${expr.data.text ?? ""}`;
|
|
40
66
|
}
|
|
41
67
|
break;
|
|
42
68
|
|
|
43
69
|
case "table":
|
|
44
|
-
content = "[tables are currently unsupported]";
|
|
70
|
+
var content = "% [tables are currently unsupported]";
|
|
45
71
|
break;
|
|
46
72
|
|
|
47
73
|
default:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
74
|
+
var content = (
|
|
75
|
+
expr.data.latex?.trim() === "" ? ""
|
|
76
|
+
: expr.data.latex == undefined ? "% [unsupported]"
|
|
77
|
+
: options.prettify ? prettify_source(expr.data.latex)
|
|
78
|
+
: expr.data.latex
|
|
79
|
+
);
|
|
53
80
|
}
|
|
54
81
|
|
|
55
|
-
|
|
82
|
+
let locals = expr.incantations.map(inc => emit_incantation(inc, options));
|
|
83
|
+
|
|
84
|
+
let sep = (locals.length > 0) ? " :: " : "";
|
|
85
|
+
let space = (locals.length >= 3 ) ? '\n' : ' ';
|
|
86
|
+
|
|
87
|
+
return `${locals.join(space)}${sep}${content}`;
|
|
56
88
|
}
|
|
@@ -1,103 +1,44 @@
|
|
|
1
|
-
import { stringify_json5, prettify_source } from "./format";
|
|
2
|
-
|
|
3
1
|
import { Ast } from "../parser";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { DesmosIncantation, ViewportIncantation } from "../magic/global";
|
|
2
|
+
import { LOCAL_INCANTATIONS } from "../magic";
|
|
3
|
+
import type { LOCAL } from "../magic";
|
|
7
4
|
|
|
8
5
|
|
|
9
|
-
/**
|
|
10
|
-
* Extract a `/desmos` incantation from the settings of `desmos`.
|
|
11
|
-
*/
|
|
12
|
-
export function extract_settings(
|
|
13
|
-
desmos: Desmos.Calculator,
|
|
14
|
-
blank: Desmos.Calculator,
|
|
15
|
-
): Ast.IncantationInvocation | null
|
|
16
|
-
{
|
|
17
|
-
let defaults = blank.settings;
|
|
18
|
-
|
|
19
|
-
let arg_raw = stringify_json5(
|
|
20
|
-
desmos.settings,
|
|
21
|
-
(k, v) => (
|
|
22
|
-
// @ts-expect-error: weird
|
|
23
|
-
v === defaults[k]
|
|
24
|
-
|| k === "__observers"
|
|
25
|
-
|| k === "guid"
|
|
26
|
-
|| k === "randomSeed"
|
|
27
|
-
|| k === "colors"
|
|
28
|
-
) ? undefined : v,
|
|
29
|
-
" ",
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
if (arg_raw.trim() === "") return null;
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
36
|
-
incantation: new DesmosIncantation(),
|
|
37
|
-
arg_raw,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Extract a `/viewport` incantation from the viewport bounds of `desmos`.
|
|
43
|
-
*/
|
|
44
|
-
export function extract_viewport(
|
|
45
|
-
desmos: Desmos.Calculator,
|
|
46
|
-
blank: Desmos.Calculator,
|
|
47
|
-
): Ast.IncantationInvocation | null
|
|
48
|
-
{
|
|
49
|
-
let defaults = blank.graphpaperBounds.mathCoordinates;
|
|
50
|
-
let { left, right, bottom, top } = desmos.graphpaperBounds.mathCoordinates;
|
|
51
|
-
|
|
52
|
-
let arg_raw = stringify_json5(
|
|
53
|
-
{ left, right, bottom, top },
|
|
54
|
-
/// @ts-expect-error: weird
|
|
55
|
-
(k, v) => v === defaults[k] ? undefined : v,
|
|
56
|
-
" ",
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (arg_raw.trim() === "") return null;
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
63
|
-
incantation: new ViewportIncantation(),
|
|
64
|
-
arg_raw,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
6
|
/**
|
|
69
7
|
* Extract an expression and the local incantations that reproduce its state.
|
|
70
8
|
*/
|
|
71
|
-
export function extract_expression(
|
|
9
|
+
export function extract_expression(expr: Desmos.ExpressionState): Ast.Expression
|
|
72
10
|
{
|
|
73
|
-
let data;
|
|
74
|
-
|
|
75
|
-
// @ts-expect-error: check type-narrows
|
|
76
|
-
if (expression.latex?.trim() === "") {
|
|
77
|
-
data = { latex: ` ` };
|
|
78
|
-
} else {
|
|
79
|
-
data = Object.fromEntries(
|
|
80
|
-
Object.entries(expression)
|
|
81
|
-
.filter(([key, value]) => !is_insignificant(key, value))
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (is_latex(expression)) {
|
|
86
|
-
data.latex = prettify_source(data.latex);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
11
|
return {
|
|
90
12
|
kind: Ast.Kind.EXPRESSION,
|
|
91
|
-
data
|
|
92
|
-
|
|
13
|
+
data: (
|
|
14
|
+
expr.latex?.trim() === "" ? { latex: ` ` }
|
|
15
|
+
: {
|
|
16
|
+
type: expr.type,
|
|
17
|
+
latex: expr.latex,
|
|
18
|
+
text: expr.text,
|
|
19
|
+
} as Desmos.ExpressionState
|
|
20
|
+
),
|
|
21
|
+
incantations: extract_locals(expr),
|
|
93
22
|
};
|
|
94
23
|
}
|
|
95
24
|
|
|
96
|
-
|
|
97
|
-
function is_insignificant(key: string, value: any): boolean
|
|
25
|
+
function extract_locals(expr: Desmos.ExpressionState): Ast.IncantationInvocation<LOCAL>[]
|
|
98
26
|
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
27
|
+
/* NOTE: Currently only LaTeX expressions can have local incantations applied to them, this will change in future */
|
|
28
|
+
switch (expr.type)
|
|
29
|
+
{
|
|
30
|
+
case "text": return [];
|
|
31
|
+
case "table": return [];
|
|
32
|
+
default:
|
|
33
|
+
if (expr.latex?.trim() === "") {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return (
|
|
38
|
+
Object.values(LOCAL_INCANTATIONS)
|
|
39
|
+
.map(inc => inc.extract(expr))
|
|
40
|
+
.filter(each => each != undefined)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
103
44
|
}
|
package/src/decompiler/format.ts
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
export const stringify_json5: (typeof JSON.stringify) = (
|
|
2
|
-
data, replacer, space
|
|
3
|
-
) =>
|
|
4
|
-
{
|
|
5
|
-
return (
|
|
6
|
-
// @ts-expect-error: random `null`??
|
|
7
|
-
JSON.stringify(data, replacer, space)
|
|
8
|
-
.slice(1, -1) // strip surrounding {}
|
|
9
|
-
.trim()
|
|
10
|
-
.replaceAll(/"([^"]+)":/g, "$1:") // "key": <value> -> key: <value>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
1
|
export function prettify_source(source: string): string
|
|
16
2
|
{
|
|
17
3
|
source = source.replaceAll(/(?<=[^ ])=(?=[^ ])/g, " = ");
|
|
4
|
+
source = source.replaceAll(/\\left\s*(\(|\[|\\\{)/g, "$1");
|
|
5
|
+
source = source.replaceAll(/\\right\s*(\)|\]|\\\})/g, "$1");
|
|
6
|
+
source = source.replaceAll(/(:|,)\\ /g, "$1 ");
|
|
18
7
|
|
|
19
8
|
return source;
|
|
20
9
|
}
|
package/src/decompiler/readme.md
CHANGED
|
@@ -21,7 +21,7 @@ The first 2 stages are closely intertwined, so they're merged into one `desmos_t
|
|
|
21
21
|
Technically it'd be more performant to directly emit instead of constructing the intermediate AST, but doing it this way means:
|
|
22
22
|
|
|
23
23
|
- We can access the AST for debug information (in Playground)
|
|
24
|
-
- We can diff the AST instead of the source when doing
|
|
24
|
+
- We can diff the AST instead of the source when doing incremental decompilation in future
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
## Extracting the AST
|
|
@@ -37,6 +37,6 @@ The main challenge here is removing unnecessary noise - we need to pick up on fi
|
|
|
37
37
|
## Emitting the Source
|
|
38
38
|
`Ast[] -> string`
|
|
39
39
|
|
|
40
|
-
The main concern here is ensuring the emitted source code looks like something the user would write, which means nice formatting, line breaks, spaces,
|
|
40
|
+
The main concern here is ensuring the emitted source code looks like something the user would write, which means nice formatting, line breaks, spaces, indentation, etc.
|
|
41
41
|
|
|
42
42
|
Once we try implementing incremental decompilation, this will be the hardest part because we need to reconcile changes with the user's existing source code, respecting where their formatting deviates from the autogenerated output (currently their entire source code may be shuffled around when decompiling, which is terrible UX).
|
package/src/errors.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Range } from "./utils";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Multiple aggregated errors.
|
|
6
|
+
*
|
|
7
|
+
* Since the compiler does its best to recover from errors, it often needs to aggregate errors to report later, instead of immediately throwing.
|
|
8
|
+
*/
|
|
9
|
+
export class DesmostErrors {
|
|
10
|
+
constructor(public errors: DesmostError[] = []) {}
|
|
11
|
+
}
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* The compiler encountered a fatal unrecoverable error, which will reach the end user.
|
|
@@ -28,7 +30,6 @@ export namespace DesmostError
|
|
|
28
30
|
export interface Data
|
|
29
31
|
{
|
|
30
32
|
msg?: string
|
|
31
|
-
while?: string
|
|
32
33
|
hint?: string
|
|
33
34
|
note?: string
|
|
34
35
|
debug?: string
|
|
@@ -59,6 +60,11 @@ export namespace DesmostError
|
|
|
59
60
|
constructor(override data: Data) { super(data.msg, "Excess Input"); }
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/** No incantation with this identifier exists. */
|
|
64
|
+
export class UnknownIncantation extends DesmostError {
|
|
65
|
+
constructor(override data: Data) { super(data.msg, "Unknown Incantation"); }
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
/** An incantation can't be applied here. */
|
|
63
69
|
export class IllegalIncantation extends DesmostError {
|
|
64
70
|
constructor(override data: Data) { super(data.msg, "Illegal Incantation"); }
|
package/src/magic/.template.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// import type { DesmostOptions } from "../../options";
|
|
4
4
|
// import { DesmostError, type Fallible } from "../../errors";
|
|
5
|
+
// import { Ast } from "../../parser";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
// export class ?Incantation extends ArgIncantation<?>
|
|
@@ -28,4 +29,11 @@
|
|
|
28
29
|
|
|
29
30
|
// return out;
|
|
30
31
|
// }
|
|
32
|
+
|
|
33
|
+
// override extract(target: ?): Ast.IncantationInvocation<?> | undefined
|
|
34
|
+
// {
|
|
35
|
+
// // TODO
|
|
36
|
+
// }
|
|
31
37
|
// }
|
|
38
|
+
|
|
39
|
+
// export const ? = new ?Incantation();
|
package/src/magic/expr/index.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { ArgIncantation, type EXPR } from "../incantation";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { latex } from "./latex";
|
|
4
|
+
import { text } from "./text";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
export const EXPR_INCANTATIONS
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
];
|
|
7
|
+
export const EXPR_INCANTATIONS = {
|
|
8
|
+
latex,
|
|
9
|
+
text,
|
|
10
|
+
} satisfies Record<string, ArgIncantation<EXPR>>;
|
package/src/magic/expr/latex.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type EXPR } from "../incantation";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
class LatexIncantation extends ArgIncantation<EXPR>
|
|
5
5
|
{
|
|
6
|
-
override readonly
|
|
7
|
-
= "Produce a LaTeX expression. This allows the input to span multiple lines."
|
|
8
|
-
|
|
6
|
+
override readonly effect = Incantation.Effect.EXPR
|
|
9
7
|
override readonly identifier = "latex"
|
|
10
8
|
override readonly requires_arg = true
|
|
11
9
|
override readonly arg_type = Incantation.ArgType.LATEX
|
|
10
|
+
override readonly description
|
|
11
|
+
= "Produce a LaTeX expression. This allows the input to span multiple lines."
|
|
12
12
|
|
|
13
13
|
apply(target: Desmos.ExpressionState, data: string): asserts target is Desmos.Expression
|
|
14
14
|
{
|
|
@@ -16,4 +16,8 @@ export class LatexIncantation extends ArgIncantation<EXPR>
|
|
|
16
16
|
// @ts-expect-error: previous assignment type-narrows
|
|
17
17
|
target.latex = data === "" ? " " : data;
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
// TODO extract
|
|
19
21
|
}
|
|
22
|
+
|
|
23
|
+
export const latex = new LatexIncantation();
|
package/src/magic/expr/text.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type EXPR } from "../incantation";
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
class TextIncantation extends ArgIncantation<EXPR>
|
|
5
5
|
{
|
|
6
|
-
override readonly
|
|
7
|
-
= "Produce a text expression (“Add Note” in the Desmos GUI)."
|
|
8
|
-
|
|
6
|
+
override readonly effect = Incantation.Effect.EXPR
|
|
9
7
|
override readonly identifier = "text"
|
|
10
8
|
override readonly alias = "note"
|
|
11
9
|
override readonly requires_arg = true
|
|
12
10
|
override readonly arg_type = Incantation.ArgType.STRING
|
|
11
|
+
override readonly description
|
|
12
|
+
= "Produce a text expression (“Add Note” in the Desmos GUI)."
|
|
13
13
|
|
|
14
14
|
override apply(target: Desmos.ExpressionState, data: string): asserts target is Desmos.Note
|
|
15
15
|
{
|
|
@@ -17,4 +17,8 @@ export class TextIncantation extends ArgIncantation<EXPR>
|
|
|
17
17
|
// @ts-expect-error: previous assignment type-narrows
|
|
18
18
|
target.text = data;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
// TODO extract
|
|
20
22
|
}
|
|
23
|
+
|
|
24
|
+
export const text = new TextIncantation();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Incantation, type GLOBAL } from "../incantation";
|
|
2
|
+
|
|
3
|
+
import { Ast } from "../../parser";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class DarkModeIncantation extends Incantation<GLOBAL>
|
|
7
|
+
{
|
|
8
|
+
override readonly effect = Incantation.Effect.GLOBAL
|
|
9
|
+
override readonly identifier = "dark"
|
|
10
|
+
override readonly description
|
|
11
|
+
= "Enable dark mode for the calculator, which inverts all colours."
|
|
12
|
+
|
|
13
|
+
apply(target: Desmos.Calculator)
|
|
14
|
+
{
|
|
15
|
+
target.updateSettings({ invertedColors: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override extract(target: Desmos.Calculator): Ast.IncantationInvocation<Incantation.Effect.GLOBAL> | void
|
|
19
|
+
{
|
|
20
|
+
if (target.settings.invertedColors) {
|
|
21
|
+
return {
|
|
22
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
23
|
+
incantation: dark,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const dark = new DarkModeIncantation();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Incantation, ArgIncantation, type GLOBAL } from "../incantation";
|
|
2
2
|
|
|
3
|
-
import { DesmostError, type Fallible } from "../../errors";
|
|
4
3
|
import type { DesmostOptions } from "../../options";
|
|
4
|
+
import { DesmostError, type Fallible } from "../../errors";
|
|
5
|
+
import { Ast } from "../../parser";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
type DesmosSettings = Desmos.GraphConfiguration & Desmos.GraphSettings
|
|
@@ -16,10 +17,7 @@ export class DesmosIncantation extends ArgIncantation<GLOBAL>
|
|
|
16
17
|
override readonly requires_arg = false
|
|
17
18
|
override readonly arg_type = Incantation.ArgType.OBJECT
|
|
18
19
|
|
|
19
|
-
override apply(
|
|
20
|
-
target: Desmos.Calculator,
|
|
21
|
-
data?: DesmosSettings,
|
|
22
|
-
): void
|
|
20
|
+
override apply(target: Desmos.Calculator, data?: DesmosSettings): void
|
|
23
21
|
{
|
|
24
22
|
if (data != undefined) {
|
|
25
23
|
target.updateSettings(data);
|
|
@@ -42,4 +40,36 @@ export class DesmosIncantation extends ArgIncantation<GLOBAL>
|
|
|
42
40
|
|
|
43
41
|
return out;
|
|
44
42
|
}
|
|
43
|
+
|
|
44
|
+
override extract(
|
|
45
|
+
target: Desmos.Calculator,
|
|
46
|
+
blank: Desmos.Calculator,
|
|
47
|
+
): Ast.IncantationInvocation<Incantation.Effect.GLOBAL> | void
|
|
48
|
+
{
|
|
49
|
+
let settings = { ...target.settings };
|
|
50
|
+
let defaults = blank.settings;
|
|
51
|
+
|
|
52
|
+
for (let [key, val] of Object.entries(settings)) {
|
|
53
|
+
if (
|
|
54
|
+
val === defaults[key]
|
|
55
|
+
|| key === "__observers"
|
|
56
|
+
|| key === "guid"
|
|
57
|
+
|| key === "randomSeed"
|
|
58
|
+
|| key === "colors"
|
|
59
|
+
) {
|
|
60
|
+
delete settings[key];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let arg_raw = super.emit_object_arg(settings);
|
|
65
|
+
if (arg_raw.trim() === "") return;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
69
|
+
incantation: desmos,
|
|
70
|
+
arg_raw,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
45
73
|
}
|
|
74
|
+
|
|
75
|
+
export const desmos = new DesmosIncantation();
|
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
export { DesmosIncantation } from "./desmos";
|
|
2
|
-
export { ViewportIncantation } from "./viewport";
|
|
3
|
-
export { DarkModeIncantation } from "./dark-mode";
|
|
4
|
-
|
|
5
|
-
|
|
6
1
|
import { Incantation, type GLOBAL } from "../incantation";
|
|
7
2
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
3
|
+
import { desmos } from "./desmos";
|
|
4
|
+
import { viewport } from "./viewport";
|
|
5
|
+
import { dark } from "./dark";
|
|
11
6
|
|
|
12
7
|
|
|
13
|
-
export const GLOBAL_INCANTATIONS
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
];
|
|
8
|
+
export const GLOBAL_INCANTATIONS = {
|
|
9
|
+
desmos,
|
|
10
|
+
viewport,
|
|
11
|
+
dark,
|
|
12
|
+
} satisfies Record<string, Incantation<GLOBAL>>;
|
|
@@ -2,6 +2,7 @@ import { Incantation, ArgIncantation, type GLOBAL } from "../incantation";
|
|
|
2
2
|
|
|
3
3
|
import { DesmostError, type Fallible } from "../../errors";
|
|
4
4
|
import type { DesmostOptions } from "../../options";
|
|
5
|
+
import { Ast } from "../../parser";
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
interface ViewportBounds
|
|
@@ -63,4 +64,32 @@ export class ViewportIncantation extends ArgIncantation<GLOBAL>
|
|
|
63
64
|
|
|
64
65
|
return out;
|
|
65
66
|
}
|
|
67
|
+
|
|
68
|
+
override extract(
|
|
69
|
+
target: Desmos.Calculator,
|
|
70
|
+
blank: Desmos.Calculator,
|
|
71
|
+
): Ast.IncantationInvocation<Incantation.Effect.GLOBAL> | void
|
|
72
|
+
{
|
|
73
|
+
let { left, right, bottom, top } = target.graphpaperBounds.mathCoordinates;
|
|
74
|
+
let bounds = { left, right, bottom, top };
|
|
75
|
+
|
|
76
|
+
let defaults = blank.graphpaperBounds.mathCoordinates;
|
|
77
|
+
|
|
78
|
+
for (let [key, val] of Object.entries(bounds)) {
|
|
79
|
+
if (Math.abs(val - defaults[key]) < 0.001) {
|
|
80
|
+
delete bounds[key];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let arg_raw = super.emit_object_arg(bounds);
|
|
85
|
+
if (arg_raw.trim() === "") return;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
89
|
+
incantation: viewport,
|
|
90
|
+
arg_raw,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
66
93
|
}
|
|
94
|
+
|
|
95
|
+
export const viewport = new ViewportIncantation();
|
package/src/magic/incantation.ts
CHANGED
|
@@ -2,6 +2,7 @@ import Json5 from "json5";
|
|
|
2
2
|
import dedent from "dedent";
|
|
3
3
|
|
|
4
4
|
import type { DesmostOptions } from "../options";
|
|
5
|
+
import type { Ast } from "../parser";
|
|
5
6
|
import { DesmostError, type Fallible } from "../errors";
|
|
6
7
|
|
|
7
8
|
|
|
@@ -18,12 +19,10 @@ import { DesmostError, type Fallible } from "../errors";
|
|
|
18
19
|
*
|
|
19
20
|
* Incantations that accept an argument derive from `ArgIncantation`.
|
|
20
21
|
*/
|
|
21
|
-
export abstract class Incantation<
|
|
22
|
-
Effect extends Incantation.Effect = Incantation.Effect
|
|
23
|
-
>
|
|
22
|
+
export abstract class Incantation<Effect extends Incantation.Effect = Incantation.Effect>
|
|
24
23
|
{
|
|
25
|
-
/**
|
|
26
|
-
abstract readonly
|
|
24
|
+
/** Whether the incantation is a global, local or expression incantation. */
|
|
25
|
+
abstract readonly effect: Effect
|
|
27
26
|
|
|
28
27
|
/** The raw text sequence that matches this incantation, such as `viewport` or `hidden`. */
|
|
29
28
|
abstract readonly identifier: string
|
|
@@ -31,6 +30,9 @@ export abstract class Incantation<
|
|
|
31
30
|
/** An alternative `.identifier`, strictly for localisation purposes only. */
|
|
32
31
|
readonly alias?: string
|
|
33
32
|
|
|
33
|
+
/** Short user-facing description of what the incantation does. */
|
|
34
|
+
abstract readonly description: string
|
|
35
|
+
|
|
34
36
|
|
|
35
37
|
/** Apply this incantation's effect to `target`, using the provided `data` if required. */
|
|
36
38
|
abstract apply(
|
|
@@ -49,10 +51,20 @@ export abstract class Incantation<
|
|
|
49
51
|
let req = required === "expression" ? "latex" : required;
|
|
50
52
|
|
|
51
53
|
throw new DesmostError.IllegalIncantation({
|
|
52
|
-
msg: `/${this.identifier} can only applied to
|
|
54
|
+
msg: `/${this.identifier} can only applied to \`${req}\` blocks, but target block has type: \`${actual}\``,
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Extract the AST representation of this incantation from `target`.
|
|
61
|
+
*
|
|
62
|
+
* (The opposite of {@linkcode apply()}.)
|
|
63
|
+
*/
|
|
64
|
+
abstract extract(
|
|
65
|
+
target: Effect extends GLOBAL ? Desmos.Calculator : Desmos.ExpressionState,
|
|
66
|
+
blank?: Effect extends GLOBAL ? Desmos.Calculator : never,
|
|
67
|
+
): Ast.IncantationInvocation<Effect> | void
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
|
|
@@ -133,6 +145,16 @@ export abstract class ArgIncantation<
|
|
|
133
145
|
}
|
|
134
146
|
}
|
|
135
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Stringify an object argument to its raw source text.
|
|
151
|
+
*/
|
|
152
|
+
protected emit_object_arg(data: object): string
|
|
153
|
+
{
|
|
154
|
+
let defined = Object.fromEntries(Object.entries(data).filter((k, v) => Boolean(v)));
|
|
155
|
+
|
|
156
|
+
return Json5.stringify(defined).slice(1, -1);
|
|
157
|
+
}
|
|
136
158
|
}
|
|
137
159
|
|
|
138
160
|
|