desmost 0.7.3 → 0.8.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/dist/compiler/compile.d.ts +25 -1
- package/dist/compiler/compile.d.ts.map +1 -1
- package/dist/compiler/compile.js +32 -2
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/evaluate.d.ts +11 -0
- package/dist/compiler/evaluate.d.ts.map +1 -1
- package/dist/compiler/evaluate.js +15 -11
- package/dist/compiler/evaluate.js.map +1 -1
- package/dist/compiler/format.d.ts +13 -0
- package/dist/compiler/format.d.ts.map +1 -1
- package/dist/compiler/format.js +23 -0
- package/dist/compiler/format.js.map +1 -1
- package/dist/compiler/index.d.ts.map +1 -1
- package/dist/compiler/index.js.map +1 -1
- package/dist/compiler/options.d.ts +79 -0
- package/dist/compiler/options.d.ts.map +1 -1
- package/dist/compiler/options.js +18 -11
- package/dist/compiler/options.js.map +1 -1
- package/dist/decompiler/decompile.d.ts +20 -0
- package/dist/decompiler/decompile.d.ts.map +1 -0
- package/dist/decompiler/decompile.js +32 -0
- package/dist/decompiler/decompile.js.map +1 -0
- package/dist/decompiler/emit.d.ts +10 -0
- package/dist/decompiler/emit.d.ts.map +1 -0
- package/dist/decompiler/emit.js +37 -0
- package/dist/decompiler/emit.js.map +1 -0
- package/dist/decompiler/extract.d.ts +14 -0
- package/dist/decompiler/extract.d.ts.map +1 -0
- package/dist/decompiler/extract.js +63 -0
- package/dist/decompiler/extract.js.map +1 -0
- package/dist/decompiler/format.d.ts +3 -0
- package/dist/decompiler/format.d.ts.map +1 -0
- package/dist/decompiler/format.js +14 -0
- package/dist/decompiler/format.js.map +1 -0
- package/dist/decompiler/index.d.ts +2 -0
- package/dist/decompiler/index.d.ts.map +1 -0
- package/dist/decompiler/index.js +2 -0
- package/dist/decompiler/index.js.map +1 -0
- package/dist/desmos.d.ts +3 -0
- package/dist/desmos.d.ts.map +1 -1
- package/dist/desmos.js +3 -0
- package/dist/desmos.js.map +1 -1
- package/dist/errors.d.ts +18 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +13 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.internal.d.ts +2 -1
- package/dist/index.internal.d.ts.map +1 -1
- package/dist/index.internal.js +2 -1
- package/dist/index.internal.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/magic/expr/index.js.map +1 -1
- package/dist/magic/expr/latex.d.ts.map +1 -1
- package/dist/magic/expr/latex.js +1 -0
- package/dist/magic/expr/latex.js.map +1 -1
- package/dist/magic/expr/text.d.ts.map +1 -1
- package/dist/magic/expr/text.js +1 -0
- 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.map +1 -1
- package/dist/magic/global/desmos.d.ts +3 -1
- package/dist/magic/global/desmos.d.ts.map +1 -1
- package/dist/magic/global/desmos.js.map +1 -1
- package/dist/magic/global/index.d.ts +3 -0
- package/dist/magic/global/index.d.ts.map +1 -1
- package/dist/magic/global/index.js +3 -0
- package/dist/magic/global/index.js.map +1 -1
- package/dist/magic/global/viewport.d.ts.map +1 -1
- package/dist/magic/global/viewport.js +0 -1
- package/dist/magic/global/viewport.js.map +1 -1
- package/dist/magic/incantation.d.ts +44 -0
- package/dist/magic/incantation.d.ts.map +1 -1
- package/dist/magic/incantation.js +38 -0
- package/dist/magic/incantation.js.map +1 -1
- package/dist/magic/index.d.ts +7 -0
- package/dist/magic/index.d.ts.map +1 -1
- package/dist/magic/index.js +7 -0
- package/dist/magic/index.js.map +1 -1
- package/dist/magic/local/anim.d.ts.map +1 -1
- package/dist/magic/local/anim.js +1 -0
- package/dist/magic/local/anim.js.map +1 -1
- package/dist/magic/local/colour.d.ts.map +1 -1
- package/dist/magic/local/colour.js.map +1 -1
- package/dist/magic/local/dashed.d.ts.map +1 -1
- package/dist/magic/local/dashed.js.map +1 -1
- package/dist/magic/local/fill.d.ts.map +1 -1
- package/dist/magic/local/fill.js.map +1 -1
- package/dist/magic/local/hide.d.ts.map +1 -1
- package/dist/magic/local/hide.js.map +1 -1
- package/dist/magic/local/index.js.map +1 -1
- package/dist/magic/local/label.d.ts.map +1 -1
- package/dist/magic/local/label.js +1 -0
- package/dist/magic/local/label.js.map +1 -1
- package/dist/magic/local/line.d.ts.map +1 -1
- package/dist/magic/local/line.js.map +1 -1
- package/dist/magic/local/no-line.d.ts.map +1 -1
- package/dist/magic/local/no-line.js.map +1 -1
- package/dist/magic/local/point.d.ts.map +1 -1
- package/dist/magic/local/point.js.map +1 -1
- package/dist/magic/local/secret.d.ts.map +1 -1
- package/dist/magic/local/secret.js.map +1 -1
- package/dist/magic/local/slider.d.ts.map +1 -1
- package/dist/magic/local/slider.js +1 -0
- package/dist/magic/local/slider.js.map +1 -1
- package/dist/parser/ast.d.ts +9 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/ast.js +1 -0
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/desmost-parser.d.ts +101 -1
- package/dist/parser/desmost-parser.d.ts.map +1 -1
- package/dist/parser/desmost-parser.js +129 -2
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/generic-parser.d.ts +47 -0
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js +50 -0
- package/dist/parser/generic-parser.js.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +5 -2
- package/src/compiler/compile.ts +124 -124
- package/src/compiler/evaluate.ts +86 -118
- package/src/compiler/format.ts +39 -6
- package/src/compiler/index.ts +0 -7
- package/src/compiler/options.ts +83 -77
- package/src/compiler/readme.md +5 -0
- package/src/decompiler/decompile.ts +48 -0
- package/src/decompiler/emit.ts +47 -0
- package/src/decompiler/extract.ts +100 -0
- package/src/decompiler/format.ts +20 -0
- package/src/decompiler/index.ts +1 -0
- package/src/decompiler/readme.md +34 -0
- package/src/desmos.ts +6 -6
- package/src/errors.ts +19 -19
- package/src/index.internal.ts +3 -1
- package/src/index.ts +1 -0
- package/src/magic/expr/index.ts +2 -2
- package/src/magic/expr/latex.ts +16 -16
- package/src/magic/expr/text.ts +13 -13
- package/src/magic/global/dark-mode.ts +10 -10
- package/src/magic/global/desmos.ts +18 -15
- package/src/magic/global/index.ts +8 -3
- package/src/magic/global/viewport.ts +19 -20
- package/src/magic/incantation.ts +94 -94
- package/src/magic/local/anim.ts +10 -10
- package/src/magic/local/colour.ts +26 -26
- package/src/magic/local/dashed.ts +9 -9
- package/src/magic/local/fill.ts +12 -12
- package/src/magic/local/hide.ts +9 -9
- package/src/magic/local/index.ts +11 -11
- package/src/magic/local/label.ts +51 -51
- package/src/magic/local/line.ts +16 -16
- package/src/magic/local/no-line.ts +9 -9
- package/src/magic/local/point.ts +16 -16
- package/src/magic/local/secret.ts +9 -9
- package/src/magic/local/slider.ts +15 -15
- package/src/magic/readme.md +5 -0
- package/src/parser/ast.ts +46 -46
- package/src/parser/desmost-parser.ts +550 -550
- package/src/parser/generic-parser.ts +176 -176
- package/src/parser/index.ts +0 -4
- package/src/parser/readme.md +21 -0
- package/src/utils.ts +33 -33
package/src/compiler/options.ts
CHANGED
|
@@ -9,97 +9,103 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export interface DesmostOptions
|
|
11
11
|
{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
/**
|
|
13
|
+
* How should errors be *surfaced* to the end user?
|
|
14
|
+
*
|
|
15
|
+
* - `surface` (default): Blocks that result in errors will become Desmos text expressions containing the error message, leaving other expressions unaffected.
|
|
16
|
+
*
|
|
17
|
+
* - `crash`: The entire compilation to Desmos will terminate with a single error message. This means you don't get any output at all, but errors are also immediately obvious.
|
|
18
|
+
* - Note that this will *clear all expressions* in the calculator instance.
|
|
19
|
+
*
|
|
20
|
+
* - `suppress`: Silently fail on the frontend (if you wish to give the illusion that everything is fine).
|
|
21
|
+
*/
|
|
22
|
+
errors?: "surface" | "crash" | "suppress"
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Where should errors be placed?
|
|
26
|
+
*
|
|
27
|
+
* - `inline` (default): Alongside or in place of the expression that produced it.
|
|
28
|
+
* - `end`: All aggregated at the end of the Desmos expressions list.
|
|
29
|
+
* - `start`: All aggregated at the start of the Desmos expressions list.
|
|
30
|
+
*/
|
|
31
|
+
place_errors?: "inline" | "end" | "start"
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The prefix to prepend to error blocks.
|
|
35
|
+
*
|
|
36
|
+
* Defaults to `[DESMOST ERROR]\n`.
|
|
37
|
+
*
|
|
38
|
+
* Provide a `""` blank string if you wish for no prefix to be added.
|
|
39
|
+
*/
|
|
40
|
+
error_prefix?: string
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Prettify LaTeX output so it renders nicely in the Desmos editor?
|
|
44
|
+
*
|
|
45
|
+
* Defaults to `true`, meaning LaTeX is prettified.
|
|
46
|
+
*/
|
|
47
|
+
prettify?: boolean
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Should `%` LaTeX comments be ignored, instead of turned into text expressions (notes)?
|
|
51
|
+
*
|
|
52
|
+
* Defaults to `false`, meaning comments are kept.
|
|
53
|
+
*/
|
|
54
|
+
ignore_comments?: boolean
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Should all line breaks be ignored, instead of kept as blank expressions?
|
|
58
|
+
*
|
|
59
|
+
* Defaults to `false`, meaning all blank lines are kept.
|
|
60
|
+
*/
|
|
61
|
+
ignore_all_blanks?: boolean
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Should trailing blank lines at the start of the source be kept as blank expressions?
|
|
65
|
+
*
|
|
66
|
+
* Defaults to `false`, meaning leading blank lines are ignored.
|
|
67
|
+
*/
|
|
68
|
+
keep_leading_blanks?: boolean
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Should trailing blank lines at the end of the source be kept as blank expressions?
|
|
72
|
+
*
|
|
73
|
+
* Defaults to `false`, meaning trailing blank lines are ignored.
|
|
74
|
+
*/
|
|
75
|
+
keep_trailing_blanks?: boolean
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Return debug diagnostics?
|
|
79
|
+
*
|
|
80
|
+
* This includes the unevaluated AST and performance diagnostics.
|
|
81
|
+
*
|
|
82
|
+
* Defaults to `false`, meaning `compile()` returns `void`.
|
|
83
|
+
*/
|
|
84
|
+
debug?: boolean
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
|
|
88
|
+
export const DEFAULT_OPTIONS =
|
|
89
|
+
{
|
|
90
|
+
errors: "surface",
|
|
91
|
+
place_errors: "inline",
|
|
92
|
+
error_prefix: "[DESMOST ERROR]\n",
|
|
93
|
+
prettify: true,
|
|
94
|
+
ignore_comments: false,
|
|
95
|
+
ignore_all_blanks: false,
|
|
96
|
+
keep_leading_blanks: false,
|
|
97
|
+
keep_trailing_blanks: false,
|
|
98
|
+
debug: false,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
|
|
85
102
|
/**
|
|
86
103
|
* Fill in `options` with Desmost's defaults to produce a complete `DesmostOptions` config.
|
|
87
104
|
*/
|
|
88
105
|
export function fill_defaults(options: Partial<DesmostOptions> | undefined): Required<DesmostOptions>
|
|
89
106
|
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
out.error_prefix ??= "[DESMOST ERROR]\n";
|
|
95
|
-
|
|
96
|
-
out.prettify ??= true;
|
|
97
|
-
|
|
98
|
-
out.ignore_comments ??= false;
|
|
99
|
-
out.ignore_all_blanks ??= false;
|
|
100
|
-
out.keep_leading_blanks ??= false;
|
|
101
|
-
out.keep_trailing_blanks ??= false;
|
|
102
|
-
out.debug ??= false;
|
|
103
|
-
|
|
104
|
-
return out as Required<DesmostOptions>;
|
|
107
|
+
return {
|
|
108
|
+
...DEFAULT_OPTIONS,
|
|
109
|
+
...options,
|
|
110
|
+
} as Required<DesmostOptions>;
|
|
105
111
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { emit_global_incantation, emit_expression } from "./emit";
|
|
2
|
+
import { extract_settings, extract_viewport, extract_expression } from "./extract";
|
|
3
|
+
|
|
4
|
+
import { Ast } from "../parser";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export interface SemiStructuredAst
|
|
8
|
+
{
|
|
9
|
+
globals: Ast.IncantationInvocation[];
|
|
10
|
+
locals: Ast.Expression[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Decompile Desmos into raw Desmost source code.
|
|
16
|
+
*
|
|
17
|
+
* 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
|
+
*/
|
|
19
|
+
export function decompile(desmos: Desmos.Calculator, blank: Desmos.Calculator): string
|
|
20
|
+
{
|
|
21
|
+
return ast_to_source(desmos_to_ast(desmos, blank));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decompile `desmos` into a semi-structured AST.
|
|
26
|
+
*/
|
|
27
|
+
export function desmos_to_ast(desmos: Desmos.Calculator, blank: Desmos.Calculator): SemiStructuredAst
|
|
28
|
+
{
|
|
29
|
+
return {
|
|
30
|
+
globals: [
|
|
31
|
+
extract_settings(desmos, blank),
|
|
32
|
+
extract_viewport(desmos, blank),
|
|
33
|
+
].filter(x => x != null),
|
|
34
|
+
locals: desmos.getExpressions().map(extract_expression),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Emit the Desmost source code representation of `ast`.
|
|
40
|
+
*/
|
|
41
|
+
export function ast_to_source(ast: SemiStructuredAst): string
|
|
42
|
+
{
|
|
43
|
+
return (
|
|
44
|
+
ast.globals.map(emit_global_incantation).join("\n")
|
|
45
|
+
+ "\n\n"
|
|
46
|
+
+ ast.locals.map(emit_expression).join("\n")
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Ast } from "../parser";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Emit the source code for a global incantation `invocation`.
|
|
6
|
+
*/
|
|
7
|
+
export function emit_global_incantation(invocation?: Ast.IncantationInvocation): string
|
|
8
|
+
{
|
|
9
|
+
if (invocation == undefined) return "";
|
|
10
|
+
|
|
11
|
+
let ident = invocation.incantation.identifier;
|
|
12
|
+
let arg_raw = invocation.arg_raw;
|
|
13
|
+
|
|
14
|
+
return `/${ident}{\n ${arg_raw}\n}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Emit the source code for an `expr`.
|
|
20
|
+
*/
|
|
21
|
+
export function emit_expression(expr?: Ast.Expression): string
|
|
22
|
+
{
|
|
23
|
+
if (expr == undefined) return "";
|
|
24
|
+
|
|
25
|
+
// TODO
|
|
26
|
+
let locals: string[] = [];
|
|
27
|
+
|
|
28
|
+
let sep = expr.incantations.length > 0 ? " :: " : "";
|
|
29
|
+
|
|
30
|
+
let content;
|
|
31
|
+
|
|
32
|
+
switch (expr.data.type) {
|
|
33
|
+
case "text":
|
|
34
|
+
content = `/text{ ${expr.data.text} }`;
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
// @ts-expect-error: outdated types
|
|
38
|
+
if (expr.data.latex?.trim() === "") {
|
|
39
|
+
content = "";
|
|
40
|
+
} else {
|
|
41
|
+
// @ts-expect-error: outdated types
|
|
42
|
+
content = expr.data.latex ?? "?";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return `${locals.join(" ")}${sep}${content}`;
|
|
47
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { stringify_json5, prettify_source } from "./format";
|
|
2
|
+
|
|
3
|
+
import { Ast } from "../parser";
|
|
4
|
+
|
|
5
|
+
import { DesmosIncantation, ViewportIncantation } from "../magic/global";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract a `/desmos` incantation from the settings of `desmos`.
|
|
10
|
+
*/
|
|
11
|
+
export function extract_settings(
|
|
12
|
+
desmos: Desmos.Calculator,
|
|
13
|
+
blank: Desmos.Calculator,
|
|
14
|
+
): Ast.IncantationInvocation | null
|
|
15
|
+
{
|
|
16
|
+
let defaults = blank.settings;
|
|
17
|
+
|
|
18
|
+
let arg_raw = stringify_json5(
|
|
19
|
+
desmos.settings,
|
|
20
|
+
(k, v) => (
|
|
21
|
+
v === defaults[k]
|
|
22
|
+
|| k === "__observers"
|
|
23
|
+
|| k === "guid"
|
|
24
|
+
|| k === "randomSeed"
|
|
25
|
+
|| k === "colors"
|
|
26
|
+
) ? undefined : v,
|
|
27
|
+
" ",
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (arg_raw.trim() === "") return null;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
34
|
+
incantation: new DesmosIncantation(),
|
|
35
|
+
arg_raw,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extract a `/viewport` incantation from the viewport bounds of `desmos`.
|
|
41
|
+
*/
|
|
42
|
+
export function extract_viewport(
|
|
43
|
+
desmos: Desmos.Calculator,
|
|
44
|
+
blank: Desmos.Calculator,
|
|
45
|
+
): Ast.IncantationInvocation | null
|
|
46
|
+
{
|
|
47
|
+
let defaults = blank.graphpaperBounds.mathCoordinates;
|
|
48
|
+
let { left, right, bottom, top } = desmos.graphpaperBounds.mathCoordinates;
|
|
49
|
+
|
|
50
|
+
let arg_raw = stringify_json5(
|
|
51
|
+
{ left, right, bottom, top },
|
|
52
|
+
(k, v) => v === defaults[k] ? undefined : v,
|
|
53
|
+
" ",
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (arg_raw.trim() === "") return null;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
60
|
+
incantation: new ViewportIncantation(),
|
|
61
|
+
arg_raw,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract an expression and the local incantations that reproduce its state.
|
|
67
|
+
*/
|
|
68
|
+
export function extract_expression(expression: Desmos.ExpressionState): Ast.Expression
|
|
69
|
+
{
|
|
70
|
+
let data;
|
|
71
|
+
|
|
72
|
+
// @ts-expect-error: outdated types
|
|
73
|
+
if (expression.latex?.trim() === "") {
|
|
74
|
+
data = { latex: ` ` };
|
|
75
|
+
} else {
|
|
76
|
+
data = Object.fromEntries(
|
|
77
|
+
Object.entries(expression)
|
|
78
|
+
.filter(([key, value]) => !is_insignificant(key, value))
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if ("latex" in data) {
|
|
83
|
+
data.latex = prettify_source(data.latex);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
kind: Ast.Kind.EXPRESSION,
|
|
88
|
+
data,
|
|
89
|
+
incantations: [], // TODO
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
function is_insignificant(key: string, value: any): boolean
|
|
95
|
+
{
|
|
96
|
+
return (
|
|
97
|
+
value === ""
|
|
98
|
+
|| typeof value === "object" && Object.values(value).every(v => v === "")
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
export function prettify_source(source: string): string
|
|
16
|
+
{
|
|
17
|
+
source = source.replaceAll(/(?<=[^ ])=(?=[^ ])/g, " = ");
|
|
18
|
+
|
|
19
|
+
return source;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { decompile } from "./decompile";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Decompiler
|
|
2
|
+
|
|
3
|
+
This module implements the Desmos -> Desmost decompiler.
|
|
4
|
+
|
|
5
|
+
This decompiles a `Desmos.Calculator` instance into the *minimal* raw Desmost source code that could *completely* reproduce the calculator state. Decompilation happens through several lowering stages:
|
|
6
|
+
|
|
7
|
+
```hs
|
|
8
|
+
Desmos.Calculator -> object[] -> Ast[] -> string
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The first 2 stages are closely intertwined, so they're merged into one `desmos_to_ast()` function.
|
|
12
|
+
|
|
13
|
+
Technically it'd be more performant to directly emit instead of constructing the intermediate AST, but doing it this way means:
|
|
14
|
+
|
|
15
|
+
- We can access the AST for debug information (in Playground)
|
|
16
|
+
- We can diff the AST instead of the source when doing incremenetal decompilation in future
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Extracting the AST
|
|
20
|
+
`Desmos.Calculator -> object[] -> Ast[]`
|
|
21
|
+
|
|
22
|
+
We need to not only extract the expressions in the editor with `.getExpressions()`, but also other global calculator state like `.graphpaperBounds`.
|
|
23
|
+
|
|
24
|
+
We then transform those raw objects into `Ast` nodes.
|
|
25
|
+
|
|
26
|
+
The main challenge here is removing unnecessary noise - we need to pick up on fields that *wouldn't* be covered by defaults and include them as incantations, but ignore everything else to avoid producing humongous `/desmos{ p, q, r, s, t }` slop.
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Emitting the Source
|
|
30
|
+
`Ast[] -> string`
|
|
31
|
+
|
|
32
|
+
The main concern here is ensuring the emitted source code looks like something the user would write, which means nice formatting, line breaks, spaces, indentatino, etc.
|
|
33
|
+
|
|
34
|
+
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/desmos.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
export enum DesmosColour
|
|
6
6
|
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
RED = "#c74440",
|
|
8
|
+
BLUE = "#2d70b3",
|
|
9
|
+
GREEN = "#388c46",
|
|
10
|
+
PURPLE = "#6042a6",
|
|
11
|
+
ORANGE = "#fa7e19",
|
|
12
|
+
BLACK = "#000000",
|
|
13
13
|
}
|
package/src/errors.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/** The parser failed to parse something (usually during speculative parsing), which only results in internal backtracking. */
|
|
2
|
+
export const NO_MATCH = Symbol("no-match");
|
|
3
|
+
|
|
4
|
+
/** The compiler encountered a non-fatal recoverable failure (usually during speculative parsing), which only results in internal backtracking. */
|
|
5
|
+
export type NoMatch = typeof NO_MATCH & { readonly __brand?: unique symbol };
|
|
6
|
+
|
|
7
|
+
|
|
1
8
|
/**
|
|
2
9
|
* The compiler encountered a fatal unrecoverable error, which will reach the end user.
|
|
3
10
|
*
|
|
@@ -7,33 +14,26 @@ export class UnrecoverableError extends Error {}
|
|
|
7
14
|
|
|
8
15
|
export namespace UnrecoverableError
|
|
9
16
|
{
|
|
10
|
-
|
|
11
|
-
|
|
17
|
+
/** The parser unexpectedly reached the end of its entire input source code. */
|
|
18
|
+
export class UnexpectedEnd extends UnrecoverableError {}
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
/** The parser did not receive input that it expected. */
|
|
21
|
+
export class MissingInput extends UnrecoverableError {}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
/** The parser received input that did not match what it expected. */
|
|
24
|
+
export class UnexpectedInput extends UnrecoverableError {}
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
/** The parser received excess input that it did not expect. */
|
|
27
|
+
export class ExcessInput extends UnrecoverableError {}
|
|
21
28
|
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
/** An incantation can't be applied here. */
|
|
31
|
+
export class IllegalIncantation extends UnrecoverableError {}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
/** An incantation's argument couldn't be parsed. */
|
|
34
|
+
export class InvalidArgument extends UnrecoverableError {}
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
|
|
31
|
-
/** The parser failed to parse something (usually during speculative parsing), which only results in internal backtracking. */
|
|
32
|
-
export const NO_MATCH = Symbol("no-match");
|
|
33
|
-
|
|
34
|
-
/** The compiler encountered a non-fatal recoverable failure (usually during speculative parsing), which only results in internal backtracking. */
|
|
35
|
-
export type NoMatch = typeof NO_MATCH & { readonly __brand?: unique symbol };
|
|
36
|
-
|
|
37
|
-
|
|
38
38
|
/** Indicates that a function may throw an `UnrecoverableError`. */
|
|
39
39
|
export type Unrecoverable<Result> = Result | Result & { readonly __brand?: unique symbol };
|
package/src/index.internal.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { fill_defaults } from "./compiler/options";
|
|
1
|
+
export { fill_defaults, DEFAULT_OPTIONS } from "./compiler/options";
|
|
2
2
|
export type { DesmostDebug } from "./compiler/compile";
|
|
3
3
|
|
|
4
4
|
export { Ast } from "./parser";
|
|
5
|
+
|
|
6
|
+
export { desmos_to_ast, ast_to_source } from "./decompiler/decompile";
|
package/src/index.ts
CHANGED
package/src/magic/expr/index.ts
CHANGED
package/src/magic/expr/latex.ts
CHANGED
|
@@ -3,22 +3,22 @@ import { Incantation, ArgIncantation, type EXPR } from "../incantation";
|
|
|
3
3
|
|
|
4
4
|
export class LatexIncantation extends ArgIncantation<EXPR>
|
|
5
5
|
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
override readonly description
|
|
7
|
+
= "Produce a LaTeX expression. This allows the input to span multiple lines."
|
|
8
|
+
|
|
9
|
+
override readonly identifier = "latex"
|
|
10
|
+
override readonly requires_arg = true
|
|
11
|
+
override readonly arg_type = Incantation.ArgType.LATEX
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
apply(target: Desmos.ExpressionState, data: string): void
|
|
14
|
+
{
|
|
15
|
+
target.type = "expression";
|
|
16
|
+
// @ts-expect-error: outdated types
|
|
17
|
+
target.latex = data === "" ? " " : data;
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
override evaluate_arg(data: string): string
|
|
21
|
+
{
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
24
|
}
|
package/src/magic/expr/text.ts
CHANGED
|
@@ -3,18 +3,18 @@ import { Incantation, ArgIncantation, type EXPR } from "../incantation";
|
|
|
3
3
|
|
|
4
4
|
export class TextIncantation extends ArgIncantation<EXPR>
|
|
5
5
|
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
override readonly description
|
|
7
|
+
= "Produce a text expression (“Add Note” in the Desmos GUI)."
|
|
8
|
+
|
|
9
|
+
override readonly identifier = "text"
|
|
10
|
+
override readonly alias = "note"
|
|
11
|
+
override readonly requires_arg = true
|
|
12
|
+
override readonly arg_type = Incantation.ArgType.STRING
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
override apply(target: Desmos.ExpressionState, data: string)
|
|
15
|
+
{
|
|
16
|
+
target.type = "text";
|
|
17
|
+
// @ts-expect-error: outdated types
|
|
18
|
+
target.text = data;
|
|
19
|
+
}
|
|
20
20
|
}
|
|
@@ -3,15 +3,15 @@ import { Incantation, type GLOBAL } from "../incantation";
|
|
|
3
3
|
|
|
4
4
|
export class DarkModeIncantation extends Incantation<GLOBAL>
|
|
5
5
|
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
override readonly description
|
|
7
|
+
= "Enable dark mode for the calculator, which inverts all colours."
|
|
8
|
+
|
|
9
|
+
override readonly identifier = "dark"
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
apply(target: Desmos.Calculator)
|
|
12
|
+
{
|
|
13
|
+
target.updateSettings({
|
|
14
|
+
invertedColors: true,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
17
|
}
|