desmost 0.7.2 → 0.8.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.
Files changed (132) hide show
  1. package/dist/compiler/compile.d.ts.map +1 -1
  2. package/dist/compiler/compile.js.map +1 -1
  3. package/dist/compiler/evaluate.d.ts.map +1 -1
  4. package/dist/compiler/evaluate.js +2 -14
  5. package/dist/compiler/evaluate.js.map +1 -1
  6. package/dist/compiler/format.d.ts +2 -0
  7. package/dist/compiler/format.d.ts.map +1 -1
  8. package/dist/compiler/format.js +11 -0
  9. package/dist/compiler/format.js.map +1 -1
  10. package/dist/compiler/options.d.ts.map +1 -1
  11. package/dist/compiler/options.js.map +1 -1
  12. package/dist/decompiler/decompile.d.ts +9 -0
  13. package/dist/decompiler/decompile.d.ts.map +1 -0
  14. package/dist/decompiler/decompile.js +21 -0
  15. package/dist/decompiler/decompile.js.map +1 -0
  16. package/dist/decompiler/emit.d.ts +4 -0
  17. package/dist/decompiler/emit.d.ts.map +1 -0
  18. package/dist/decompiler/emit.js +28 -0
  19. package/dist/decompiler/emit.js.map +1 -0
  20. package/dist/decompiler/extract.d.ts +5 -0
  21. package/dist/decompiler/extract.d.ts.map +1 -0
  22. package/dist/decompiler/extract.js +53 -0
  23. package/dist/decompiler/extract.js.map +1 -0
  24. package/dist/decompiler/format.d.ts +3 -0
  25. package/dist/decompiler/format.d.ts.map +1 -0
  26. package/dist/decompiler/format.js +11 -0
  27. package/dist/decompiler/format.js.map +1 -0
  28. package/dist/decompiler/index.d.ts +2 -0
  29. package/dist/decompiler/index.d.ts.map +1 -0
  30. package/dist/decompiler/index.js +2 -0
  31. package/dist/decompiler/index.js.map +1 -0
  32. package/dist/desmos.d.ts.map +1 -1
  33. package/dist/desmos.js.map +1 -1
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/errors.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.internal.d.ts +1 -0
  39. package/dist/index.internal.d.ts.map +1 -1
  40. package/dist/index.internal.js +1 -0
  41. package/dist/index.internal.js.map +1 -1
  42. package/dist/index.js +1 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/magic/expr/index.js.map +1 -1
  45. package/dist/magic/expr/latex.d.ts.map +1 -1
  46. package/dist/magic/expr/latex.js.map +1 -1
  47. package/dist/magic/expr/text.d.ts.map +1 -1
  48. package/dist/magic/expr/text.js.map +1 -1
  49. package/dist/magic/global/dark-mode.d.ts.map +1 -1
  50. package/dist/magic/global/dark-mode.js.map +1 -1
  51. package/dist/magic/global/desmos.d.ts.map +1 -1
  52. package/dist/magic/global/desmos.js.map +1 -1
  53. package/dist/magic/global/index.d.ts +3 -0
  54. package/dist/magic/global/index.d.ts.map +1 -1
  55. package/dist/magic/global/index.js +3 -0
  56. package/dist/magic/global/index.js.map +1 -1
  57. package/dist/magic/global/viewport.d.ts +4 -4
  58. package/dist/magic/global/viewport.d.ts.map +1 -1
  59. package/dist/magic/global/viewport.js +3 -1
  60. package/dist/magic/global/viewport.js.map +1 -1
  61. package/dist/magic/incantation.d.ts.map +1 -1
  62. package/dist/magic/incantation.js.map +1 -1
  63. package/dist/magic/local/anim.d.ts.map +1 -1
  64. package/dist/magic/local/anim.js.map +1 -1
  65. package/dist/magic/local/colour.d.ts.map +1 -1
  66. package/dist/magic/local/colour.js.map +1 -1
  67. package/dist/magic/local/dashed.d.ts.map +1 -1
  68. package/dist/magic/local/dashed.js.map +1 -1
  69. package/dist/magic/local/fill.d.ts.map +1 -1
  70. package/dist/magic/local/fill.js.map +1 -1
  71. package/dist/magic/local/hide.d.ts.map +1 -1
  72. package/dist/magic/local/hide.js.map +1 -1
  73. package/dist/magic/local/index.js.map +1 -1
  74. package/dist/magic/local/label.d.ts.map +1 -1
  75. package/dist/magic/local/label.js.map +1 -1
  76. package/dist/magic/local/line.d.ts.map +1 -1
  77. package/dist/magic/local/line.js.map +1 -1
  78. package/dist/magic/local/no-line.d.ts.map +1 -1
  79. package/dist/magic/local/no-line.js.map +1 -1
  80. package/dist/magic/local/point.d.ts.map +1 -1
  81. package/dist/magic/local/point.js.map +1 -1
  82. package/dist/magic/local/secret.d.ts.map +1 -1
  83. package/dist/magic/local/secret.js.map +1 -1
  84. package/dist/magic/local/slider.d.ts.map +1 -1
  85. package/dist/magic/local/slider.js.map +1 -1
  86. package/dist/parser/ast.d.ts.map +1 -1
  87. package/dist/parser/ast.js.map +1 -1
  88. package/dist/parser/desmost-parser.d.ts.map +1 -1
  89. package/dist/parser/desmost-parser.js +1 -1
  90. package/dist/parser/desmost-parser.js.map +1 -1
  91. package/dist/parser/generic-parser.d.ts.map +1 -1
  92. package/dist/parser/generic-parser.js.map +1 -1
  93. package/dist/utils.d.ts.map +1 -1
  94. package/dist/utils.js.map +1 -1
  95. package/package.json +1 -1
  96. package/src/compiler/compile.ts +124 -124
  97. package/src/compiler/evaluate.ts +86 -120
  98. package/src/compiler/format.ts +39 -6
  99. package/src/compiler/options.ts +78 -75
  100. package/src/decompiler/decompile.ts +48 -0
  101. package/src/decompiler/emit.ts +47 -0
  102. package/src/decompiler/extract.ts +100 -0
  103. package/src/decompiler/format.ts +20 -0
  104. package/src/decompiler/index.ts +1 -0
  105. package/src/desmos.ts +6 -6
  106. package/src/errors.ts +12 -12
  107. package/src/index.internal.ts +2 -0
  108. package/src/index.ts +1 -0
  109. package/src/magic/expr/index.ts +2 -2
  110. package/src/magic/expr/latex.ts +16 -16
  111. package/src/magic/expr/text.ts +13 -13
  112. package/src/magic/global/dark-mode.ts +10 -10
  113. package/src/magic/global/desmos.ts +15 -15
  114. package/src/magic/global/index.ts +8 -3
  115. package/src/magic/global/viewport.ts +21 -14
  116. package/src/magic/incantation.ts +94 -94
  117. package/src/magic/local/anim.ts +10 -10
  118. package/src/magic/local/colour.ts +26 -26
  119. package/src/magic/local/dashed.ts +9 -9
  120. package/src/magic/local/fill.ts +12 -12
  121. package/src/magic/local/hide.ts +9 -9
  122. package/src/magic/local/index.ts +11 -11
  123. package/src/magic/local/label.ts +51 -51
  124. package/src/magic/local/line.ts +16 -16
  125. package/src/magic/local/no-line.ts +9 -9
  126. package/src/magic/local/point.ts +16 -16
  127. package/src/magic/local/secret.ts +9 -9
  128. package/src/magic/local/slider.ts +15 -15
  129. package/src/parser/ast.ts +45 -45
  130. package/src/parser/desmost-parser.ts +550 -547
  131. package/src/parser/generic-parser.ts +176 -176
  132. package/src/utils.ts +33 -33
@@ -1,4 +1,4 @@
1
- import { format_error } from "./format";
1
+ import { format_error, normalise_latex, prettify_latex } from "./format";
2
2
  import type { DesmostOptions } from "./options";
3
3
 
4
4
  import { Ast } from "../parser";
@@ -9,26 +9,24 @@ import { UnrecoverableError, type Unrecoverable } from "../errors";
9
9
  * Evaluate arguments (if any) to a global incantation `invocation`, then apply the incantation to `desmos`.
10
10
  */
11
11
  export function evaluate_global_incantation(
12
- invocation: Ast.IncantationInvocation,
13
- desmos: Desmos.Calculator,
14
- options: Required<DesmostOptions>,
12
+ invocation: Ast.IncantationInvocation,
13
+ desmos: Desmos.Calculator,
14
+ options: Required<DesmostOptions>,
15
15
  ): Unrecoverable<void | string>
16
16
  {
17
- let data = undefined;
18
-
19
- if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
20
- try {
21
- data = invocation.incantation.evaluate_arg(invocation.arg_raw);
22
- }
23
- catch (e) {
24
- let msg = evaluate_error(e as Error, desmos, options);
25
- if (msg != undefined) {
26
- return msg;
27
- }
28
- }
29
- }
30
-
31
- invocation.incantation.apply(desmos, data);
17
+ let data = undefined;
18
+
19
+ if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
20
+ try {
21
+ data = invocation.incantation.evaluate_arg(invocation.arg_raw);
22
+ }
23
+ catch (e) {
24
+ let msg = evaluate_error(e as Error, desmos, options);
25
+ return msg;
26
+ }
27
+ }
28
+
29
+ invocation.incantation.apply(desmos, data);
32
30
  }
33
31
 
34
32
 
@@ -36,53 +34,53 @@ export function evaluate_global_incantation(
36
34
  * Evaluate arguments (if any) to local incantation invocations on `expr`, then add `expr` to `desmos`.
37
35
  */
38
36
  export function evaluate_expr(
39
- expr: Ast.Expression,
40
- desmos: Desmos.Calculator,
41
- options: Required<DesmostOptions>,
37
+ expr: Ast.Expression,
38
+ desmos: Desmos.Calculator,
39
+ options: Required<DesmostOptions>,
42
40
  ): Unrecoverable<void | string>
43
41
  {
44
- let errors: string[] = [];
45
-
46
- for (let invocation of expr.incantations) {
47
- if (invocation.kind === Ast.Kind.INVALID_INCANTATION) {
48
- let msg = evaluate_error(invocation.error, desmos, { ...options, place_errors: "start" });
49
- if (msg != undefined) {
50
- errors.push(msg);
51
- }
52
- continue;
53
- }
54
-
55
- let data = undefined;
56
-
57
- if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
58
- try {
59
- data = invocation.incantation.evaluate_arg(invocation.arg_raw);
60
- }
61
- catch (e) {
62
- errors.push(format_error(e as Error, options));
63
- if (invocation.incantation.requires_arg) continue;
64
- }
65
- }
66
-
67
- invocation.incantation.apply(expr.data, data);
68
- }
69
-
70
- if (errors.length > 0) {
71
- desmos.setExpression({ type: "text", text: errors.join("\n\n") });
72
- }
73
-
74
- // @ts-expect-error: outdated types
75
- if (expr.data.latex != undefined) {
76
- // @ts-expect-error: outdated types
77
- expr.data.latex = normalise_latex(expr.data.latex);
78
-
79
- if (options.prettify) {
80
- // @ts-expect-error: outdated types
81
- expr.data.latex = prettify_latex(expr.data.latex);
82
- }
83
- }
84
-
85
- desmos.setExpression(expr.data);
42
+ let errors: string[] = [];
43
+
44
+ for (let invocation of expr.incantations) {
45
+ if (invocation.kind === Ast.Kind.INVALID_INCANTATION) {
46
+ let msg = evaluate_error(invocation.error, desmos, { ...options, place_errors: "start" });
47
+ if (msg != undefined) {
48
+ errors.push(msg);
49
+ }
50
+ continue;
51
+ }
52
+
53
+ let data = undefined;
54
+
55
+ if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
56
+ try {
57
+ data = invocation.incantation.evaluate_arg(invocation.arg_raw);
58
+ }
59
+ catch (e) {
60
+ errors.push(format_error(e as Error, options));
61
+ if (invocation.incantation.requires_arg) continue;
62
+ }
63
+ }
64
+
65
+ invocation.incantation.apply(expr.data, data);
66
+ }
67
+
68
+ if (errors.length > 0) {
69
+ desmos.setExpression({ type: "text", text: errors.join("\n\n") });
70
+ }
71
+
72
+ // @ts-expect-error: outdated types
73
+ if (expr.data.latex != undefined) {
74
+ // @ts-expect-error: outdated types
75
+ expr.data.latex = normalise_latex(expr.data.latex);
76
+
77
+ if (options.prettify) {
78
+ // @ts-expect-error: outdated types
79
+ expr.data.latex = prettify_latex(expr.data.latex);
80
+ }
81
+ }
82
+
83
+ desmos.setExpression(expr.data);
86
84
  }
87
85
 
88
86
 
@@ -92,62 +90,30 @@ export function evaluate_expr(
92
90
  * If the user set `place_errors: start` or `place_errors: end`, this returns the formatted error message for deferred aggregation.
93
91
  */
94
92
  export function evaluate_error(
95
- error: UnrecoverableError,
96
- desmos: Desmos.Calculator,
97
- options: Required<DesmostOptions>,
93
+ error: UnrecoverableError,
94
+ desmos: Desmos.Calculator,
95
+ options: Required<DesmostOptions>,
98
96
  ): Unrecoverable<void | string>
99
97
  {
100
- switch (options.errors) {
101
- case "crash":
102
- throw error;
103
-
104
- case "suppress":
105
- console.error(error);
106
- break;
107
-
108
- default: switch (options.place_errors) {
109
- case "end":
110
- case "start":
111
- return format_error(error, options);
112
-
113
- default:
114
- desmos.setExpression({
115
- type: "text",
116
- text: format_error(error, options),
117
- });
118
- break;
119
- }
120
- }
121
- }
122
-
123
-
124
- /**
125
- * Remove line breaks from `latex` so Desmos can properly consume it.
126
- */
127
- function normalise_latex(latex: string): string
128
- {
129
- latex = latex.replaceAll(/\s+/g, " ");
130
- return latex;
131
- }
132
-
133
-
134
- /**
135
- * Prettify `latex` to render nicely in Desmos, reflecting how you would type directly into Desmos.
136
- *
137
- * This includes:
138
- *
139
- * - Replace `()`, `[]`, etc. with `\left(\right)`
140
- * - Replace `min()`, `max()`, etc. with `\operatorname{min}()`
141
- */
142
- function prettify_latex(latex: string): string
143
- {
144
- latex = latex.replaceAll(/(?<!\\left)(\(|\[|\\\{)/g, "\\left$1");
145
- latex = latex.replaceAll(/(?<!\\right)(\)|\]|\\\})/g, "\\right$1");
146
-
147
- latex = latex.replaceAll(
148
- /(?<=[^\w]|^)\\?(mean|median|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign) ?(?=\(|\\left\()/g,
149
- "\\operatorname{$1}"
150
- );
151
-
152
- return latex;
98
+ switch (options.errors) {
99
+ case "crash":
100
+ throw error;
101
+
102
+ case "suppress":
103
+ console.error(error);
104
+ break;
105
+
106
+ default: switch (options.place_errors) {
107
+ case "end":
108
+ case "start":
109
+ return format_error(error, options);
110
+
111
+ default:
112
+ desmos.setExpression({
113
+ type: "text",
114
+ text: format_error(error, options),
115
+ });
116
+ break;
117
+ }
118
+ }
153
119
  }
@@ -4,13 +4,46 @@ import { UnrecoverableError } from "../errors";
4
4
 
5
5
 
6
6
  export function format_error(
7
- e: UnrecoverableError,
8
- options: Required<DesmostOptions>,
7
+ e: UnrecoverableError,
8
+ options: Required<DesmostOptions>,
9
9
  ): string
10
10
  {
11
- let prefix = options.error_prefix;
12
- let sep = (prefix === "" || prefix.endsWith("\n")) ? "" : " ";
11
+ let prefix = options.error_prefix;
12
+ let sep = (prefix === "" || prefix.endsWith("\n")) ? "" : " ";
13
13
 
14
- return `${prefix}${sep}${e.name}: ${e.message}`;
15
- // TODO maybe include stack? (configurable?)
14
+ return `${prefix}${sep}${e.name}: ${e.message}`;
15
+ // TODO maybe include stack? (configurable?)
16
+ }
17
+
18
+
19
+ /**
20
+ * Remove line breaks from `latex` so Desmos can properly consume it.
21
+ */
22
+ export function normalise_latex(latex: string): string
23
+ {
24
+ latex = latex.replaceAll(/\s+/g, " ");
25
+ return latex;
26
+ }
27
+
28
+
29
+ /**
30
+ * Prettify `latex` to render nicely in Desmos, reflecting how you would type directly into Desmos.
31
+ *
32
+ * This includes:
33
+ *
34
+ * - Replace `()`, `[]`, etc. with `\left(\right)`
35
+ * - Replace `min()`, `max()`, etc. with `\operatorname{min}()`
36
+ */
37
+ export function prettify_latex(latex: string): string
38
+ {
39
+ latex = latex.replaceAll(/(?<!\\left)(\(|\[|\\\{)/g, "\\left$1");
40
+ latex = latex.replaceAll(/(?<!\\right)(\)|\]|\\\})/g, "\\right$1");
41
+ latex = latex.replaceAll(/(:|,)(?!\\ ) */g, "$1\\ ");
42
+
43
+ latex = latex.replaceAll(
44
+ /(?<=[^\w]|^)\\?(mean|median|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign) ?(?=\(|\\left\()/g,
45
+ "\\operatorname{$1}"
46
+ );
47
+
48
+ return latex;
16
49
  }
@@ -9,76 +9,79 @@
9
9
  */
10
10
  export interface DesmostOptions
11
11
  {
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
- * - `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.
17
- * - `suppress`: Silently fail on the frontend (if you wish to give the illusion that everything is fine).
18
- */
19
- errors?: "surface" | "crash" | "suppress"
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
- * Where should errors be placed?
23
- *
24
- * - `inline` (default): Alongside or in place of the expression that produced it.
25
- * - `end`: All aggregated at the end of the Desmos expressions list.
26
- * - `start`: All aggregated at the start of the Desmos expressions list.
27
- */
28
- place_errors?: "inline" | "end" | "start"
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
- * The prefix to prepend to error blocks.
32
- *
33
- * Defaults to `[DESMOST ERROR]\n`.
34
- *
35
- * Provide a `""` blank string if you wish for no prefix to be added.
36
- */
37
- error_prefix?: string
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
- * Prettify LaTeX output so it renders nicely in the Desmos editor?
41
- *
42
- * Defaults to `true`, meaning LaTeX is prettified.
43
- */
44
- prettify?: boolean
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
- * Should `%` LaTeX comments be ignored, instead of turned into text expressions (notes)?
48
- *
49
- * Defaults to `false`, meaning comments are kept.
50
- */
51
- ignore_comments?: boolean
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
- * Should all line breaks be ignored, instead of kept as blank expressions?
55
- *
56
- * Defaults to `false`, meaning all blank lines are kept.
57
- */
58
- ignore_all_blanks?: boolean
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
- * Should trailing blank lines at the start of the source be kept as blank expressions?
62
- *
63
- * Defaults to `false`, meaning leading blank lines are ignored.
64
- */
65
- keep_leading_blanks?: boolean
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
- * Should trailing blank lines at the end of the source be kept as blank expressions?
69
- *
70
- * Defaults to `false`, meaning trailing blank lines are ignored.
71
- */
72
- keep_trailing_blanks?: boolean
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
- * Return debug diagnostics?
76
- *
77
- * This includes the unevaluated AST and performance diagnostics.
78
- *
79
- * Defaults to `false`, meaning `compile()` returns `void`.
80
- */
81
- debug?: boolean
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
 
@@ -87,19 +90,19 @@ export interface DesmostOptions
87
90
  */
88
91
  export function fill_defaults(options: Partial<DesmostOptions> | undefined): Required<DesmostOptions>
89
92
  {
90
- let out = Object.assign({}, options);
91
-
92
- out.errors ??= "surface";
93
- out.place_errors ??= "inline";
94
- out.error_prefix ??= "[DESMOST ERROR]\n";
93
+ let out = Object.assign({}, options);
94
+
95
+ out.errors ??= "surface";
96
+ out.place_errors ??= "inline";
97
+ out.error_prefix ??= "[DESMOST ERROR]\n";
95
98
 
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;
99
+ out.prettify ??= true;
100
+
101
+ out.ignore_comments ??= false;
102
+ out.ignore_all_blanks ??= false;
103
+ out.keep_leading_blanks ??= false;
104
+ out.keep_trailing_blanks ??= false;
105
+ out.debug ??= false;
103
106
 
104
- return out as Required<DesmostOptions>;
107
+ return out as Required<DesmostOptions>;
105
108
  }
@@ -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";