desmost 0.10.0 → 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 +6 -6
- 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 +21 -19
- 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 +13 -11
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js +21 -24
- package/dist/parser/generic-parser.js.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.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 +25 -21
- package/src/parser/errors.ts +10 -0
- package/src/parser/generic-parser.ts +23 -25
- package/src/utils.ts +1 -1
- 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/compiler/compiler.ts
CHANGED
|
@@ -77,7 +77,7 @@ export class DesmostCompiler
|
|
|
77
77
|
|
|
78
78
|
/* To aggregate errors at the start, we need a target to retroactively inject errors into */
|
|
79
79
|
if (this.options.place_errors === "start") {
|
|
80
|
-
this.desmos.setExpression({ id: "deferred",
|
|
80
|
+
this.desmos.setExpression({ id: "desmost-deferred", type: "text", text: "" });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
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. */
|
|
@@ -88,13 +88,13 @@ export class DesmostCompiler
|
|
|
88
88
|
while (!done) {
|
|
89
89
|
done = this.compile_next();
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
if (this.options.keep_trailing_blanks) {
|
|
93
|
+
this.flush_pending_blanks();
|
|
94
|
+
}
|
|
91
95
|
}
|
|
92
96
|
catch (e) {
|
|
93
|
-
this.
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (this.options.keep_trailing_blanks) {
|
|
97
|
-
this.flush_pending_blanks();
|
|
97
|
+
this.evaluate_error(e as Error);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
/* NOTE: No need to check `options.place_errors`!
|
|
@@ -105,12 +105,12 @@ export class DesmostCompiler
|
|
|
105
105
|
*/
|
|
106
106
|
if (this.errors.length > 0) {
|
|
107
107
|
this.desmos.setExpression({
|
|
108
|
-
id: "deferred",
|
|
108
|
+
id: "desmost-deferred",
|
|
109
109
|
type: "text",
|
|
110
|
-
text: this.errors.map(err => format_error(err, this.options)).join(
|
|
110
|
+
text: this.errors.map(err => format_error(err, this.options)).join(`\n\n`),
|
|
111
111
|
});
|
|
112
112
|
} else {
|
|
113
|
-
this.desmos.removeExpression({ id: "deferred" });
|
|
113
|
+
this.desmos.removeExpression({ id: "desmost-deferred" });
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
if (this.options.debug) {
|
|
@@ -244,6 +244,7 @@ export class DesmostCompiler
|
|
|
244
244
|
{
|
|
245
245
|
switch (this.options.errors) {
|
|
246
246
|
case "crash":
|
|
247
|
+
this.desmos.setBlank();
|
|
247
248
|
throw error;
|
|
248
249
|
|
|
249
250
|
case "suppress":
|
package/src/compiler/format.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function prettify_latex(latex: string): string
|
|
|
66
66
|
latex = latex.replaceAll(/(:|,)(?!\\ ) */g, "$1\\ ");
|
|
67
67
|
|
|
68
68
|
latex = latex.replaceAll(
|
|
69
|
-
/(?<=[^\w]|^)\\?(length|mean|median|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign) ?(?=\(|\\left\()/g,
|
|
69
|
+
/(?<=[^\w]|^)\\?(length|mean|median|min|max|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign) ?(?=\(|\\left\()/g,
|
|
70
70
|
"\\operatorname{$1}"
|
|
71
71
|
);
|
|
72
72
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { emit_incantation, emit_expression } from "./emit";
|
|
2
|
+
import { extract_expression } from "./extract";
|
|
3
3
|
|
|
4
|
+
import { fill_defaults, type DesmostOptions } from "../options";
|
|
4
5
|
import { Ast } from "../parser";
|
|
5
|
-
import
|
|
6
|
+
import { GLOBAL_INCANTATIONS } from "../magic";
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
export interface SemiStructuredAst
|
|
@@ -13,7 +14,9 @@ export interface SemiStructuredAst
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* Decompile Desmos into
|
|
17
|
+
* Decompile Desmos into Desmost.
|
|
18
|
+
*
|
|
19
|
+
* This extracts relevant state from the given `desmos` calculator instance, emitting the minimal raw source code that would reconstruct that calculator instance.
|
|
17
20
|
*
|
|
18
21
|
* 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.
|
|
19
22
|
*/
|
|
@@ -30,23 +33,36 @@ export function decompile(
|
|
|
30
33
|
*/
|
|
31
34
|
blank?: Desmos.Calculator,
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Decompilation options.
|
|
38
|
+
*
|
|
39
|
+
* See {@linkcode DesmostOptions} for available options.
|
|
40
|
+
*/
|
|
34
41
|
options?: Partial<DesmostOptions>,
|
|
35
42
|
): string
|
|
36
43
|
{
|
|
37
|
-
|
|
44
|
+
let opts = fill_defaults(options);
|
|
45
|
+
|
|
46
|
+
let ast = desmos_to_ast(desmos, blank ?? desmos, opts);
|
|
47
|
+
let src = ast_to_source(ast, opts);
|
|
48
|
+
|
|
49
|
+
return src;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
/**
|
|
41
53
|
* Decompile `desmos` into a semi-structured AST.
|
|
42
54
|
*/
|
|
43
|
-
export function desmos_to_ast(
|
|
55
|
+
export function desmos_to_ast(
|
|
56
|
+
desmos: Desmos.Calculator,
|
|
57
|
+
blank: Desmos.Calculator,
|
|
58
|
+
options: DesmostOptions,
|
|
59
|
+
): SemiStructuredAst
|
|
44
60
|
{
|
|
45
61
|
return {
|
|
46
62
|
globals: [
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
].filter(x => x !=
|
|
63
|
+
GLOBAL_INCANTATIONS.desmos.extract(desmos, blank),
|
|
64
|
+
GLOBAL_INCANTATIONS.viewport.extract(desmos, blank),
|
|
65
|
+
].filter(x => x != undefined),
|
|
50
66
|
locals: desmos.getExpressions().map(extract_expression),
|
|
51
67
|
};
|
|
52
68
|
}
|
|
@@ -54,11 +70,11 @@ export function desmos_to_ast(desmos: Desmos.Calculator, blank: Desmos.Calculato
|
|
|
54
70
|
/**
|
|
55
71
|
* Emit the Desmost source code representation of `ast`.
|
|
56
72
|
*/
|
|
57
|
-
export function ast_to_source(ast: SemiStructuredAst): string
|
|
73
|
+
export function ast_to_source(ast: SemiStructuredAst, options: DesmostOptions): string
|
|
58
74
|
{
|
|
59
75
|
return (
|
|
60
|
-
|
|
61
|
-
+ "\n\n"
|
|
62
|
-
+ ast.locals.map(emit_expression).join("\n")
|
|
76
|
+
ast.globals.map(inc => emit_incantation(inc, options, { expand: true })).join("\n")
|
|
77
|
+
+ (options.keep_leading_blanks ? "\n" : "\n\n")
|
|
78
|
+
+ ast.locals.map(expr => emit_expression(expr, options)).join("\n")
|
|
63
79
|
);
|
|
64
80
|
}
|
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();
|