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.
Files changed (167) hide show
  1. package/README.md +6 -6
  2. package/dist/compiler/compile.d.ts +1 -1
  3. package/dist/compiler/compile.js +1 -1
  4. package/dist/compiler/compiler.d.ts.map +1 -1
  5. package/dist/compiler/compiler.js +9 -8
  6. package/dist/compiler/compiler.js.map +1 -1
  7. package/dist/compiler/format.js +1 -1
  8. package/dist/compiler/format.js.map +1 -1
  9. package/dist/decompiler/decompile.d.ts +13 -5
  10. package/dist/decompiler/decompile.d.ts.map +1 -1
  11. package/dist/decompiler/decompile.js +24 -13
  12. package/dist/decompiler/decompile.js.map +1 -1
  13. package/dist/decompiler/emit.d.ts +5 -2
  14. package/dist/decompiler/emit.d.ts.map +1 -1
  15. package/dist/decompiler/emit.js +41 -20
  16. package/dist/decompiler/emit.js.map +1 -1
  17. package/dist/decompiler/extract.d.ts +1 -9
  18. package/dist/decompiler/extract.d.ts.map +1 -1
  19. package/dist/decompiler/extract.js +24 -58
  20. package/dist/decompiler/extract.js.map +1 -1
  21. package/dist/decompiler/format.d.ts +0 -1
  22. package/dist/decompiler/format.d.ts.map +1 -1
  23. package/dist/decompiler/format.js +3 -9
  24. package/dist/decompiler/format.js.map +1 -1
  25. package/dist/errors.d.ts +14 -7
  26. package/dist/errors.d.ts.map +1 -1
  27. package/dist/errors.js +20 -2
  28. package/dist/errors.js.map +1 -1
  29. package/dist/magic/expr/index.js +6 -6
  30. package/dist/magic/expr/index.js.map +1 -1
  31. package/dist/magic/expr/latex.d.ts +5 -2
  32. package/dist/magic/expr/latex.d.ts.map +1 -1
  33. package/dist/magic/expr/latex.js +4 -2
  34. package/dist/magic/expr/latex.js.map +1 -1
  35. package/dist/magic/expr/text.d.ts +5 -2
  36. package/dist/magic/expr/text.d.ts.map +1 -1
  37. package/dist/magic/expr/text.js +4 -2
  38. package/dist/magic/expr/text.js.map +1 -1
  39. package/dist/magic/global/{dark-mode.d.ts → dark.d.ts} +6 -2
  40. package/dist/magic/global/dark.d.ts.map +1 -0
  41. package/dist/magic/global/dark.js +20 -0
  42. package/dist/magic/global/dark.js.map +1 -0
  43. package/dist/magic/global/desmos.d.ts +4 -1
  44. package/dist/magic/global/desmos.d.ts.map +1 -1
  45. package/dist/magic/global/desmos.js +23 -0
  46. package/dist/magic/global/desmos.js.map +1 -1
  47. package/dist/magic/global/index.d.ts +5 -5
  48. package/dist/magic/global/index.d.ts.map +1 -1
  49. package/dist/magic/global/index.js +8 -11
  50. package/dist/magic/global/index.js.map +1 -1
  51. package/dist/magic/global/viewport.d.ts +3 -0
  52. package/dist/magic/global/viewport.d.ts.map +1 -1
  53. package/dist/magic/global/viewport.js +20 -0
  54. package/dist/magic/global/viewport.js.map +1 -1
  55. package/dist/magic/incantation.d.ts +15 -2
  56. package/dist/magic/incantation.d.ts.map +1 -1
  57. package/dist/magic/incantation.js +8 -1
  58. package/dist/magic/incantation.js.map +1 -1
  59. package/dist/magic/local/anim.d.ts +5 -1
  60. package/dist/magic/local/anim.d.ts.map +1 -1
  61. package/dist/magic/local/anim.js +12 -1
  62. package/dist/magic/local/anim.js.map +1 -1
  63. package/dist/magic/local/colour.d.ts +7 -3
  64. package/dist/magic/local/colour.d.ts.map +1 -1
  65. package/dist/magic/local/colour.js +13 -3
  66. package/dist/magic/local/colour.js.map +1 -1
  67. package/dist/magic/local/dashed.d.ts +5 -1
  68. package/dist/magic/local/dashed.d.ts.map +1 -1
  69. package/dist/magic/local/dashed.js +12 -1
  70. package/dist/magic/local/dashed.js.map +1 -1
  71. package/dist/magic/local/fill.d.ts +5 -1
  72. package/dist/magic/local/fill.d.ts.map +1 -1
  73. package/dist/magic/local/fill.js +15 -1
  74. package/dist/magic/local/fill.js.map +1 -1
  75. package/dist/magic/local/hide.d.ts +5 -1
  76. package/dist/magic/local/hide.d.ts.map +1 -1
  77. package/dist/magic/local/hide.js +12 -1
  78. package/dist/magic/local/hide.js.map +1 -1
  79. package/dist/magic/local/index.d.ts +13 -2
  80. package/dist/magic/local/index.d.ts.map +1 -1
  81. package/dist/magic/local/index.js +24 -24
  82. package/dist/magic/local/index.js.map +1 -1
  83. package/dist/magic/local/label.d.ts +6 -2
  84. package/dist/magic/local/label.d.ts.map +1 -1
  85. package/dist/magic/local/label.js +19 -1
  86. package/dist/magic/local/label.js.map +1 -1
  87. package/dist/magic/local/line.d.ts +5 -1
  88. package/dist/magic/local/line.d.ts.map +1 -1
  89. package/dist/magic/local/line.js +19 -1
  90. package/dist/magic/local/line.js.map +1 -1
  91. package/dist/magic/local/no-line.d.ts +5 -1
  92. package/dist/magic/local/no-line.d.ts.map +1 -1
  93. package/dist/magic/local/no-line.js +12 -1
  94. package/dist/magic/local/no-line.js.map +1 -1
  95. package/dist/magic/local/point.d.ts +5 -1
  96. package/dist/magic/local/point.d.ts.map +1 -1
  97. package/dist/magic/local/point.js +19 -1
  98. package/dist/magic/local/point.js.map +1 -1
  99. package/dist/magic/local/secret.d.ts +5 -1
  100. package/dist/magic/local/secret.d.ts.map +1 -1
  101. package/dist/magic/local/secret.js +12 -1
  102. package/dist/magic/local/secret.js.map +1 -1
  103. package/dist/magic/local/slider.d.ts +5 -1
  104. package/dist/magic/local/slider.d.ts.map +1 -1
  105. package/dist/magic/local/slider.js +13 -1
  106. package/dist/magic/local/slider.js.map +1 -1
  107. package/dist/parser/ast.d.ts +9 -3
  108. package/dist/parser/ast.d.ts.map +1 -1
  109. package/dist/parser/ast.js +3 -1
  110. package/dist/parser/ast.js.map +1 -1
  111. package/dist/parser/desmost-parser.d.ts +2 -2
  112. package/dist/parser/desmost-parser.d.ts.map +1 -1
  113. package/dist/parser/desmost-parser.js +21 -19
  114. package/dist/parser/desmost-parser.js.map +1 -1
  115. package/dist/parser/errors.d.ts +11 -0
  116. package/dist/parser/errors.d.ts.map +1 -0
  117. package/dist/parser/errors.js +4 -0
  118. package/dist/parser/errors.js.map +1 -0
  119. package/dist/parser/generic-parser.d.ts +13 -11
  120. package/dist/parser/generic-parser.d.ts.map +1 -1
  121. package/dist/parser/generic-parser.js +21 -24
  122. package/dist/parser/generic-parser.js.map +1 -1
  123. package/dist/utils.js +1 -1
  124. package/dist/utils.js.map +1 -1
  125. package/package.json +1 -1
  126. package/src/compiler/compile.ts +1 -1
  127. package/src/compiler/compiler.ts +10 -9
  128. package/src/compiler/format.ts +1 -1
  129. package/src/decompiler/decompile.ts +30 -14
  130. package/src/decompiler/emit.ts +57 -25
  131. package/src/decompiler/extract.ts +30 -89
  132. package/src/decompiler/format.ts +3 -14
  133. package/src/decompiler/readme.md +2 -2
  134. package/src/errors.ts +13 -7
  135. package/src/magic/.template.ts +8 -0
  136. package/src/magic/expr/index.ts +6 -7
  137. package/src/magic/expr/latex.ts +8 -4
  138. package/src/magic/expr/text.ts +8 -4
  139. package/src/magic/global/dark.ts +29 -0
  140. package/src/magic/global/desmos.ts +35 -5
  141. package/src/magic/global/index.ts +8 -14
  142. package/src/magic/global/viewport.ts +29 -0
  143. package/src/magic/incantation.ts +28 -6
  144. package/src/magic/local/anim.ts +16 -3
  145. package/src/magic/local/colour.ts +18 -6
  146. package/src/magic/local/dashed.ts +16 -2
  147. package/src/magic/local/fill.ts +20 -3
  148. package/src/magic/local/hide.ts +16 -2
  149. package/src/magic/local/index.ts +24 -25
  150. package/src/magic/local/label.ts +31 -8
  151. package/src/magic/local/line.ts +25 -3
  152. package/src/magic/local/no-line.ts +16 -2
  153. package/src/magic/local/point.ts +27 -3
  154. package/src/magic/local/secret.ts +16 -2
  155. package/src/magic/local/slider.ts +18 -3
  156. package/src/parser/ast.ts +10 -7
  157. package/src/parser/desmost-parser.ts +25 -21
  158. package/src/parser/errors.ts +10 -0
  159. package/src/parser/generic-parser.ts +23 -25
  160. package/src/utils.ts +1 -1
  161. package/svelte/desmost.svelte +107 -107
  162. package/dist/magic/expr/index.d.ts +0 -3
  163. package/dist/magic/expr/index.d.ts.map +0 -1
  164. package/dist/magic/global/dark-mode.d.ts.map +0 -1
  165. package/dist/magic/global/dark-mode.js +0 -9
  166. package/dist/magic/global/dark-mode.js.map +0 -1
  167. package/src/magic/global/dark-mode.ts +0 -15
@@ -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", latex: " " }); // FIXME check if ` ` needed
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.errors.push(e as Error); // FIXME Should probably override as critical
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("\n\n"),
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":
@@ -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 { emit_global_incantation, emit_expression } from "./emit";
2
- import { extract_settings, extract_viewport, extract_expression } from "./extract";
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 type { DesmostOptions } from "../options";
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 raw Desmost source code.
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
- // TODO doc
36
+ /**
37
+ * Decompilation options.
38
+ *
39
+ * See {@linkcode DesmostOptions} for available options.
40
+ */
34
41
  options?: Partial<DesmostOptions>,
35
42
  ): string
36
43
  {
37
- return ast_to_source(desmos_to_ast(desmos, blank ?? desmos));
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(desmos: Desmos.Calculator, blank: Desmos.Calculator): SemiStructuredAst
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
- extract_settings(desmos, blank),
48
- extract_viewport(desmos, blank),
49
- ].filter(x => x != null),
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
- ast.globals.map(emit_global_incantation).join("\n")
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
  }
@@ -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 emit_global_incantation(invocation?: Ast.IncantationInvocation): string
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
- let arg_raw = invocation.arg_raw;
13
-
14
- return `/${ident}{\n ${arg_raw}\n}`;
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(expr?: Ast.Expression): string
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
- if (expr.data.text.includes("\n")) {
37
- content = `/text{\n${expr.data.text}\n}`;
38
- } else {
39
- content = `% ${expr.data.text}`;
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
- if (expr.data.latex?.trim() !== "") {
49
- content = expr.data.latex ?? "[unsupported]";
50
- } else {
51
- content = "";
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
- return `${locals.join(" ")}${sep}${content}`;
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 { is_latex } from "../utils";
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(expression: Desmos.ExpressionState): Ast.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
- incantations: [], // TODO
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
- return (
100
- value === ""
101
- || typeof value === "object" && Object.values(value).every(v => v === "")
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
  }
@@ -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
  }
@@ -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 incremenetal decompilation in future
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, indentatino, etc.
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
- /** The parser failed to parse something (usually during speculative parsing), which only results in internal backtracking. */
5
- export const NO_MATCH = Symbol("no-match");
6
-
7
- /** The compiler encountered a non-fatal recoverable failure (usually during speculative parsing), which only results in internal backtracking. */
8
- export type NoMatch = typeof NO_MATCH & { readonly __brand?: unique symbol };
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"); }
@@ -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();
@@ -1,11 +1,10 @@
1
1
  import { ArgIncantation, type EXPR } from "../incantation";
2
2
 
3
- import { LatexIncantation } from "./latex";
4
- import { TextIncantation } from "./text";
3
+ import { latex } from "./latex";
4
+ import { text } from "./text";
5
5
 
6
6
 
7
- export const EXPR_INCANTATIONS: ArgIncantation<EXPR>[] =
8
- [
9
- new LatexIncantation(),
10
- new TextIncantation(),
11
- ];
7
+ export const EXPR_INCANTATIONS = {
8
+ latex,
9
+ text,
10
+ } satisfies Record<string, ArgIncantation<EXPR>>;
@@ -1,14 +1,14 @@
1
1
  import { Incantation, ArgIncantation, type EXPR } from "../incantation";
2
2
 
3
3
 
4
- export class LatexIncantation extends ArgIncantation<EXPR>
4
+ class LatexIncantation extends ArgIncantation<EXPR>
5
5
  {
6
- override readonly description
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();
@@ -1,15 +1,15 @@
1
1
  import { Incantation, ArgIncantation, type EXPR } from "../incantation";
2
2
 
3
3
 
4
- export class TextIncantation extends ArgIncantation<EXPR>
4
+ class TextIncantation extends ArgIncantation<EXPR>
5
5
  {
6
- override readonly description
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();