desmost 0.9.0 → 0.10.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 (104) hide show
  1. package/README.md +87 -11
  2. package/dist/compiler/compile.d.ts +1 -7
  3. package/dist/compiler/compile.d.ts.map +1 -1
  4. package/dist/compiler/compile.js +3 -94
  5. package/dist/compiler/compile.js.map +1 -1
  6. package/dist/compiler/compiler.d.ts +56 -0
  7. package/dist/compiler/compiler.d.ts.map +1 -0
  8. package/dist/compiler/compiler.js +209 -0
  9. package/dist/compiler/compiler.js.map +1 -0
  10. package/dist/compiler/format.d.ts +2 -2
  11. package/dist/compiler/format.d.ts.map +1 -1
  12. package/dist/compiler/format.js +18 -9
  13. package/dist/compiler/format.js.map +1 -1
  14. package/dist/decompiler/decompile.d.ts +1 -1
  15. package/dist/decompiler/decompile.d.ts.map +1 -1
  16. package/dist/decompiler/decompile.js +1 -3
  17. package/dist/decompiler/decompile.js.map +1 -1
  18. package/dist/decompiler/extract.d.ts.map +1 -1
  19. package/dist/decompiler/extract.js +7 -3
  20. package/dist/decompiler/extract.js.map +1 -1
  21. package/dist/decompiler/format.d.ts.map +1 -1
  22. package/dist/decompiler/format.js +0 -1
  23. package/dist/decompiler/format.js.map +1 -1
  24. package/dist/errors.d.ts +35 -27
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +46 -48
  27. package/dist/errors.js.map +1 -1
  28. package/dist/index.internal.d.ts +1 -1
  29. package/dist/index.internal.d.ts.map +1 -1
  30. package/dist/magic/global/desmos.d.ts +2 -2
  31. package/dist/magic/global/desmos.d.ts.map +1 -1
  32. package/dist/magic/global/desmos.js +3 -2
  33. package/dist/magic/global/desmos.js.map +1 -1
  34. package/dist/magic/global/viewport.d.ts +2 -2
  35. package/dist/magic/global/viewport.d.ts.map +1 -1
  36. package/dist/magic/global/viewport.js +5 -5
  37. package/dist/magic/global/viewport.js.map +1 -1
  38. package/dist/magic/incantation.d.ts +4 -4
  39. package/dist/magic/incantation.d.ts.map +1 -1
  40. package/dist/magic/incantation.js +15 -8
  41. package/dist/magic/incantation.js.map +1 -1
  42. package/dist/magic/local/colour.d.ts +3 -1
  43. package/dist/magic/local/colour.d.ts.map +1 -1
  44. package/dist/magic/local/colour.js +15 -11
  45. package/dist/magic/local/colour.js.map +1 -1
  46. package/dist/magic/local/label.d.ts +2 -2
  47. package/dist/magic/local/label.d.ts.map +1 -1
  48. package/dist/magic/local/label.js +8 -7
  49. package/dist/magic/local/label.js.map +1 -1
  50. package/dist/magic/local/line.d.ts +2 -2
  51. package/dist/magic/local/line.d.ts.map +1 -1
  52. package/dist/magic/local/line.js +0 -1
  53. package/dist/magic/local/line.js.map +1 -1
  54. package/dist/options.d.ts +59 -27
  55. package/dist/options.d.ts.map +1 -1
  56. package/dist/options.js +2 -1
  57. package/dist/options.js.map +1 -1
  58. package/dist/parser/ast.d.ts +3 -17
  59. package/dist/parser/ast.d.ts.map +1 -1
  60. package/dist/parser/ast.js +0 -2
  61. package/dist/parser/ast.js.map +1 -1
  62. package/dist/parser/desmost-parser.d.ts +33 -22
  63. package/dist/parser/desmost-parser.d.ts.map +1 -1
  64. package/dist/parser/desmost-parser.js +106 -92
  65. package/dist/parser/desmost-parser.js.map +1 -1
  66. package/dist/parser/generic-parser.d.ts +19 -10
  67. package/dist/parser/generic-parser.d.ts.map +1 -1
  68. package/dist/parser/generic-parser.js +56 -16
  69. package/dist/parser/generic-parser.js.map +1 -1
  70. package/dist/utils.d.ts +9 -2
  71. package/dist/utils.d.ts.map +1 -1
  72. package/dist/utils.js +13 -2
  73. package/dist/utils.js.map +1 -1
  74. package/package.json +4 -3
  75. package/src/compiler/compile.ts +7 -120
  76. package/src/compiler/compiler.ts +275 -0
  77. package/src/compiler/format.ts +19 -7
  78. package/src/decompiler/decompile.ts +1 -7
  79. package/src/decompiler/extract.ts +3 -1
  80. package/src/decompiler/format.ts +0 -1
  81. package/src/errors.ts +40 -24
  82. package/src/index.internal.ts +1 -1
  83. package/src/magic/.template.ts +2 -2
  84. package/src/magic/global/desmos.ts +7 -9
  85. package/src/magic/global/viewport.ts +11 -17
  86. package/src/magic/incantation.ts +21 -22
  87. package/src/magic/local/colour.ts +23 -13
  88. package/src/magic/local/label.ts +14 -19
  89. package/src/magic/local/line.ts +2 -2
  90. package/src/options.ts +83 -51
  91. package/src/parser/ast.ts +1 -21
  92. package/src/parser/desmost-parser.ts +131 -135
  93. package/src/parser/generic-parser.ts +71 -29
  94. package/src/utils.ts +25 -3
  95. package/dist/compiler/evaluate.d.ts +0 -18
  96. package/dist/compiler/evaluate.d.ts.map +0 -1
  97. package/dist/compiler/evaluate.js +0 -93
  98. package/dist/compiler/evaluate.js.map +0 -1
  99. package/dist/desmos.d.ts +0 -16
  100. package/dist/desmos.d.ts.map +0 -1
  101. package/dist/desmos.js +0 -19
  102. package/dist/desmos.js.map +0 -1
  103. package/src/compiler/evaluate.ts +0 -128
  104. package/src/desmos.ts +0 -22
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,QAAiB,CAAC;AAGnC,wBAAiB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAK/D;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAGxE;AAED,wBAAiB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAkB9E;AAGD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAGxE;AAGD,qBAAa,KAAK;IAGhB,SAAS,CAAC,KAAK,EAAE,MAAM;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM;gBADZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM;IAItB,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;CAMjD"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,QAAiB,CAAC;AAGnC;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,eAAe,GAAG,IAAI,IAAI,MAAM,CAAC,UAAU,CAGhF;AAGD,wBAAiB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAK/D;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAGxE;AAED,wBAAiB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAkB9E;AAGD,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAGxE;AAGD,qBAAa,KAAK;IAEjB,gDAAgD;IAChD,SAAgB,KAAK,EAAE,MAAM,CAAC;IAE9B,gDAAgD;IAChD,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAG5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM;IAOb,IAAI,MAAM,IAAI,MAAM,CAEnB;IAEA,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;CAMjD"}
package/dist/utils.js CHANGED
@@ -1,4 +1,10 @@
1
1
  export const LINE = '—'.repeat(10);
2
+ /**
3
+ * Is `expr` a Desmos LaTeX expression (as opposed to a note or table)?
4
+ */
5
+ export function is_latex(expr) {
6
+ return (expr.type === undefined || expr.type === "expression");
7
+ }
2
8
  export function* reversing(items) {
3
9
  for (let i = items.length - 1; i >= 0; i--) {
4
10
  yield items[i];
@@ -27,11 +33,16 @@ export function chars(s, start, stop) {
27
33
  return new Range(start ?? 0, stop ?? s.length).map(i => s[i]).toArray();
28
34
  }
29
35
  export class Range {
36
+ /** The lower bound of the range (inclusive). */
30
37
  start;
38
+ /** The upper bound of the range (exclusive). */
31
39
  stop;
32
40
  constructor(start, stop) {
33
- this.start = start;
34
- this.stop = stop;
41
+ this.start = Math.min(start, stop);
42
+ this.stop = Math.max(start, stop);
43
+ }
44
+ get length() {
45
+ return this.stop - this.start + 1;
35
46
  }
36
47
  *map(callback) {
37
48
  for (let n = this.start; n < this.stop; n++) {
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAGnC,MAAM,SAAS,CAAC,CAAC,SAAS,CAAI,KAAmB;IAEhD,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,MAAM,CAAI,KAAkB;IAE3C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,SAAS,CAAC,CAAC,OAAO,CAAI,KAAkB;IAE7C,IAAI,QAAQ,GAAG,IAAI,CAAC;IAEpB,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACvB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACV,CAAC;aACI,CAAC;YACL,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACvB,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;IACF,CAAC;IAED,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IACjB,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,KAAK,CAAC,CAAS,EAAE,KAAc,EAAE,IAAa;IAE7D,OAAO,IAAI,KAAK,CAAC,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AACzE,CAAC;AAGD,MAAM,OAAO,KAAK;IAGN;IACA;IAFX,YACW,KAAa,EACb,IAAY;QADZ,UAAK,GAAL,KAAK,CAAQ;QACb,SAAI,GAAJ,IAAI,CAAQ;IAEtB,CAAC;IAEF,CAAC,GAAG,CAAI,QAA0B;QAEjC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;CACD"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAGnC;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,IAA4B;IAEpD,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;AAChE,CAAC;AAGD,MAAM,SAAS,CAAC,CAAC,SAAS,CAAI,KAAmB;IAEhD,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5C,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC;IAChB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,MAAM,CAAI,KAAkB;IAE3C,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,SAAS,CAAC,CAAC,OAAO,CAAI,KAAkB;IAE7C,IAAI,QAAQ,GAAG,IAAI,CAAC;IAEpB,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC;QACxB,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACvB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACV,CAAC;aACI,CAAC;YACL,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YACvB,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;IACF,CAAC;IAED,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IACjB,CAAC;AACF,CAAC;AAGD,MAAM,UAAU,KAAK,CAAC,CAAS,EAAE,KAAc,EAAE,IAAa;IAE7D,OAAO,IAAI,KAAK,CAAC,KAAK,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AACzE,CAAC;AAGD,MAAM,OAAO,KAAK;IAEjB,gDAAgD;IAChC,KAAK,CAAS;IAE9B,gDAAgD;IAChC,IAAI,CAAS;IAE7B,YACC,KAAa,EACb,IAAY;QAGZ,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAI,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,CAAC,GAAG,CAAI,QAA0B;QAEjC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;QACnB,CAAC;IACF,CAAC;CACD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "desmost",
3
3
  "author": "Sup#2.0",
4
- "version": "0.9.0",
4
+ "version": "0.10.0",
5
5
  "license": "MIT",
6
6
 
7
7
  "description": "A tiny DSL for compiling LaTeX to Desmos",
@@ -54,11 +54,12 @@
54
54
  "test": "vitest run",
55
55
  "testw": "vitest",
56
56
 
57
- "check": "npx oxlint src",
57
+ "check": "npx tsc --noEmit -p tsconfig.build.json",
58
+ "lint": "npx oxlint src",
58
59
 
59
60
  "doc": "cd ../.autodoc && rake",
60
61
 
61
- "prepublishOnly": "npm run test && npm run doc && npm run buildx"
62
+ "prepublishOnly": "nu build.nu pre-publish"
62
63
  },
63
64
 
64
65
  "dependencies":
@@ -1,16 +1,6 @@
1
- import { evaluate_global_incantation, evaluate_expr, evaluate_error } from "./evaluate";
1
+ import { DesmostCompiler, type DesmostDebug } from "./compiler";
2
2
 
3
3
  import { type DesmostOptions, fill_defaults } from "../options";
4
- import { DesmostParser, Ast } from "../parser";
5
-
6
-
7
- /** Debug diagnostics returned from `compile()`. */
8
- export interface DesmostDebug
9
- {
10
- duration: number;
11
- num_blocks: number;
12
- ast: Ast[];
13
- }
14
4
 
15
5
 
16
6
  /**
@@ -55,114 +45,11 @@ export function compile(
55
45
  return;
56
46
  }
57
47
 
58
- let opts = fill_defaults(options);
59
-
60
- if (opts.debug) {
61
- var t_init = performance.now();
62
- var num_blocks = 0;
63
- var ast: Ast[] = [];
64
- }
65
-
66
- let parser = new DesmostParser(source, opts);
67
-
68
- let errors: string[] = [];
69
-
70
- /* To aggregate errors at the start, we need a target to retroactively inject errors into */
71
- if (opts.place_errors === "start") {
72
- desmos.setExpression({ id: "deferred-start", latex: " " }); // FIXME check if ` ` needed
73
- }
74
-
75
- /* Keep track of whether we're still evaluating leading blank expressions, which might be ignored. */
76
- let seen_non_blank = false;
77
-
78
- /** The number of pending blank expressions to add. */
79
- let pending_blanks: number = 0;
80
-
81
- /* 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. */
82
- /* NOTE: It also doesn't sacrifice performance, since `.setExpressions()` internally just calls `.setExpression()` in a loop anyway. Benchmarks in the frontend produce similar times for both, so there's no performance improvement. */
83
- try {
84
- while (true) {
85
- let r = parser.parse_next();
86
- if (r === null) break;
87
-
88
- if (opts.debug) {
89
- num_blocks!++;
90
- ast!.push(r);
91
- }
92
-
93
- /** An error message to leave for aggregation later. */
94
- let defer: string | void = undefined;
95
-
96
- switch (r.kind) {
97
- case Ast.Kind.INCANTATION_INVOCATION:
98
- defer = evaluate_global_incantation(r, desmos, opts);
99
- break;
100
-
101
- case Ast.Kind.EXPRESSION:
102
- // @ts-expect-error: check type-narrows
103
- if (r.data.latex === " ") {
104
- if (opts.ignore_all_blanks) break;
105
- if (!seen_non_blank && !opts.keep_leading_blanks) break;
106
- pending_blanks++;
107
- break;
108
- }
109
- else {
110
- flush_pending_blanks(desmos, pending_blanks);
111
- pending_blanks = 0;
112
-
113
- seen_non_blank = true;
114
- defer = evaluate_expr(r, desmos, opts);
115
- break;
116
- }
117
-
118
- case Ast.Kind.INVALID_INCANTATION:
119
- defer = evaluate_error(r.error, desmos, opts);
120
- break;
121
- }
122
-
123
- if (defer !== undefined) {
124
- errors.push(defer);
125
- }
126
- }
127
- }
128
- catch (e) {
129
- errors.push((e as Error).message);
130
- }
131
-
132
- if (opts.keep_trailing_blanks) {
133
- flush_pending_blanks(desmos, pending_blanks);
134
- }
135
-
136
- if (errors.length > 0) {
137
- let expr: Desmos.ExpressionState = {
138
- type: "text",
139
- text: errors.join("\n\n"),
140
- };
48
+ let compiler = new DesmostCompiler(
49
+ desmos,
50
+ source,
51
+ fill_defaults(options),
52
+ );
141
53
 
142
- switch (opts.place_errors) {
143
- case "start":
144
- desmos.setExpression({ ...expr, id: "deferred-start" });
145
- break;
146
- case "end":
147
- case "inline":
148
- desmos.setExpression(expr);
149
- break;
150
- }
151
- }
152
-
153
- if (opts.debug) {
154
- return {
155
- duration: performance.now() - t_init!,
156
- num_blocks: num_blocks!,
157
- ast: ast!,
158
- };
159
- }
160
- }
161
-
162
-
163
- function flush_pending_blanks(desmos: Desmos.Calculator, count: number)
164
- {
165
- for (let i = 0; i < count; i++) {
166
- desmos.setExpression({ latex: ` ` });
167
- }
54
+ return compiler.compile();
168
55
  }
@@ -0,0 +1,275 @@
1
+ import dedent from "dedent";
2
+
3
+ import { format_error, normalise_latex, prettify_latex } from "./format";
4
+
5
+ import type { DesmostOptions } from "../options";
6
+ import { DesmostError, type Fallible } from "../errors";
7
+ import { DesmostParser, Ast } from "../parser";
8
+ import { is_latex } from "../utils";
9
+
10
+
11
+ /** Debug diagnostics returned from `compile()`. */
12
+ export interface DesmostDebug
13
+ {
14
+ duration: number;
15
+ num_blocks: number;
16
+ ast: Ast[];
17
+ }
18
+
19
+
20
+ export class DesmostCompiler
21
+ {
22
+ private desmos: Desmos.Calculator
23
+
24
+ /** The source code to compile. */
25
+ private source: string
26
+
27
+ private options: DesmostOptions
28
+
29
+ private parser: DesmostParser
30
+
31
+ /** Accumulated errors awaiting evaluation. */
32
+ private errors: DesmostError[] = []
33
+
34
+ /**
35
+ * Have we only seen blank lines so far?
36
+ *
37
+ * This is used for excluding leading blanks.
38
+ */
39
+ private seen_non_blank = false
40
+
41
+ /**
42
+ * The number of pending blank expressions to add.
43
+ *
44
+ * This is used to excluding trailing blanks.
45
+ */
46
+ private pending_blanks: number = 0
47
+
48
+ private debug: DesmostDebug | null = null
49
+
50
+
51
+ /** Create a compiler for compiling `source` into `desmos`. */
52
+ constructor(
53
+ desmos: Desmos.Calculator,
54
+ source: string,
55
+ options: DesmostOptions,
56
+ )
57
+ {
58
+ this.desmos = desmos;
59
+ this.source = source;
60
+ this.options = options;
61
+ this.parser = new DesmostParser(this.source, this.options);
62
+ }
63
+
64
+
65
+ /**
66
+ * Run the compiler to completion.
67
+ */
68
+ public compile(): void | DesmostDebug
69
+ {
70
+ if (this.options.debug) {
71
+ this.debug = {
72
+ duration: performance.now(),
73
+ num_blocks: 0,
74
+ ast: [],
75
+ };
76
+ }
77
+
78
+ /* To aggregate errors at the start, we need a target to retroactively inject errors into */
79
+ if (this.options.place_errors === "start") {
80
+ this.desmos.setExpression({ id: "deferred", latex: " " }); // FIXME check if ` ` needed
81
+ }
82
+
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. */
84
+ /* NOTE: It also doesn't sacrifice performance, since `.setExpressions()` internally just calls `.setExpression()` in a loop anyway. Benchmarks in the frontend produce similar times for both, so there's no performance improvement. */
85
+ try {
86
+ let done = false;
87
+
88
+ while (!done) {
89
+ done = this.compile_next();
90
+ }
91
+ }
92
+ 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();
98
+ }
99
+
100
+ /* NOTE: No need to check `options.place_errors`!
101
+
102
+ - If it's `start`, `id: deferred` will replace the placeholder at the start as required.
103
+ - If it's `end`, `id: deferred` won't exist so this expression will be appended as required.
104
+ - If it's `inline`, we shouldn't have any accumulated errors at all, but if we do, then the end is where they should be anyway.
105
+ */
106
+ if (this.errors.length > 0) {
107
+ this.desmos.setExpression({
108
+ id: "deferred",
109
+ type: "text",
110
+ text: this.errors.map(err => format_error(err, this.options)).join("\n\n"),
111
+ });
112
+ } else {
113
+ this.desmos.removeExpression({ id: "deferred" });
114
+ }
115
+
116
+ if (this.options.debug) {
117
+ this.debug!.duration = performance.now() - this.debug!.duration;
118
+
119
+ return this.debug!;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Compile only the next block, returning `true` if compilation has finished.
125
+ */
126
+ compile_next(): Fallible<boolean>
127
+ {
128
+ let result = this.parser.parse_next();
129
+ if (result === undefined) return true;
130
+
131
+ let { blocks, errors } = result;
132
+
133
+ for (let block of blocks) {
134
+ this.compile_block(block);
135
+ }
136
+
137
+ for (let error of errors) {
138
+ this.evaluate_error(error);
139
+ }
140
+
141
+ return false;
142
+ }
143
+
144
+ compile_block(block: Ast): void
145
+ {
146
+ if (this.options.debug) {
147
+ this.debug!.num_blocks++;
148
+ this.debug!.ast.push(block);
149
+ }
150
+
151
+ switch (block.kind)
152
+ {
153
+ case Ast.Kind.INCANTATION_INVOCATION:
154
+ this.evaluate_global_incantation(block);
155
+ break;
156
+
157
+ case Ast.Kind.EXPRESSION:
158
+ // @ts-expect-error: check type-narrows
159
+ if (block.data.latex === " ") {
160
+ if (this.options.ignore_all_blanks) break;
161
+ if (!this.seen_non_blank && !this.options.keep_leading_blanks) break;
162
+
163
+ this.pending_blanks++;
164
+ break;
165
+ }
166
+ else {
167
+ this.flush_pending_blanks();
168
+ this.pending_blanks = 0;
169
+ this.seen_non_blank = true;
170
+
171
+ this.evaluate_expr(block);
172
+ break;
173
+ }
174
+ }
175
+ }
176
+
177
+
178
+ // == LOW-LEVEL == //
179
+
180
+ /**
181
+ * Evaluate arguments (if any) to a global incantation `invocation`, then apply the incantation to `desmos`.
182
+ */
183
+ evaluate_global_incantation(invocation: Ast.IncantationInvocation): Fallible<void>
184
+ {
185
+ let data = undefined;
186
+
187
+ if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
188
+ try {
189
+ data = invocation.incantation.evaluate_arg(invocation.arg_raw, this.options);
190
+ invocation.incantation.apply(this.desmos, data);
191
+ }
192
+ catch (e) {
193
+ this.evaluate_error(e as Error);
194
+ }
195
+ }
196
+ }
197
+
198
+
199
+ /**
200
+ * Evaluate local incantation invocations on `expr`, then add `expr` to `desmos`.
201
+ */
202
+ evaluate_expr(expr: Ast.Expression): Fallible<void>
203
+ {
204
+ for (let invocation of expr.incantations) {
205
+ let data = undefined;
206
+
207
+ if ("arg_raw" in invocation && invocation.arg_raw != undefined) {
208
+ try {
209
+ data = invocation.incantation.evaluate_arg(invocation.arg_raw, this.options);
210
+ }
211
+ catch (e) {
212
+ this.evaluate_error(e as Error);
213
+ // if it needs the arg, then we can't press on
214
+ if (invocation.incantation.requires_arg) continue;
215
+ }
216
+ }
217
+
218
+ try {
219
+ invocation.incantation.apply(expr.data, data);
220
+ }
221
+ catch (e) {
222
+ this.evaluate_error(e as Error);
223
+ }
224
+ }
225
+
226
+ if (is_latex(expr.data)) {
227
+ expr.data.latex = normalise_latex(expr.data.latex!);
228
+
229
+ if (this.options.prettify) {
230
+ expr.data.latex = prettify_latex(expr.data.latex);
231
+ }
232
+ }
233
+ else if (expr.data.type === "text" && this.options.dedent_text) {
234
+ expr.data.text = dedent(expr.data.text ?? "");
235
+ }
236
+
237
+ this.desmos.setExpression(expr.data);
238
+ }
239
+
240
+ /**
241
+ * Handle a `DesmostError`, respecting the user's options.
242
+ */
243
+ evaluate_error(error: DesmostError): Fallible<void | string>
244
+ {
245
+ switch (this.options.errors) {
246
+ case "crash":
247
+ throw error;
248
+
249
+ case "suppress":
250
+ console.error(format_error(error, this.options));
251
+ break;
252
+
253
+ default: switch (this.options.place_errors) {
254
+ case "end":
255
+ case "start":
256
+ this.errors.push(error);
257
+ break;
258
+
259
+ case "inline":
260
+ this.desmos.setExpression({
261
+ type: "text",
262
+ text: format_error(error, this.options),
263
+ });
264
+ break;
265
+ }
266
+ }
267
+ }
268
+
269
+ flush_pending_blanks(): void
270
+ {
271
+ for (let i = 0; i < this.pending_blanks; i++) {
272
+ this.desmos.setExpression({ latex: ` ` });
273
+ }
274
+ }
275
+ }
@@ -1,11 +1,11 @@
1
1
  import type { DesmostOptions } from "../options";
2
2
 
3
- import { UnrecoverableError } from "../errors";
3
+ import { DesmostError } from "../errors";
4
4
  import { LINE } from "../utils";
5
5
 
6
6
 
7
7
  export function format_error(
8
- e: UnrecoverableError,
8
+ e: DesmostError,
9
9
  options: DesmostOptions,
10
10
  ): string
11
11
  {
@@ -15,12 +15,24 @@ export function format_error(
15
15
  // TODO maybe include stack? (configurable?)
16
16
  let display = `${prefix}${sep}${e.name}: ${e.message}`;
17
17
 
18
- if (e.info != undefined && options.expand_errors) {
19
- display += `\n${LINE}`;
18
+ if (e.data == undefined) {
19
+ return display;
20
+ }
21
+
22
+ if (e.data.show != undefined) {
23
+ display += `\n${LINE}\n`;
24
+ display += e.data.show.text;
25
+
26
+ if (e.data.show.span != undefined) {
27
+ display += `\n`;
28
+ display += ' '.repeat(e.data.show.span.start);
29
+ display += '^'.repeat(e.data.show.span.length);
30
+ }
31
+ }
20
32
 
21
- if (e.info.hint != undefined) display += `\nHint: ${e.info.hint}`;
22
- if (e.info.note != undefined) display += `\n${LINE}\nNote: ${e.info.note}`;
23
- if (e.info.flagged_by != undefined) display += `\n${LINE}\nFlagged by: \`${e.info.flagged_by}\``;
33
+ if (options.expand_errors) {
34
+ if (e.data.hint != undefined) display += `\n${LINE}\nHint: ${e.data.hint}`;
35
+ if (e.data.note != undefined) display += `\n${LINE}\nNote: ${e.data.note}`;
24
36
  }
25
37
 
26
38
  return display;
@@ -1,7 +1,6 @@
1
1
  import { emit_global_incantation, emit_expression } from "./emit";
2
2
  import { extract_settings, extract_viewport, extract_expression } from "./extract";
3
3
 
4
- import { fill_defaults } from "../options";
5
4
  import { Ast } from "../parser";
6
5
  import type { DesmostOptions } from "../options";
7
6
 
@@ -35,18 +34,13 @@ export function decompile(
35
34
  options?: Partial<DesmostOptions>,
36
35
  ): string
37
36
  {
38
- let opts = fill_defaults(options);
39
37
  return ast_to_source(desmos_to_ast(desmos, blank ?? desmos));
40
38
  }
41
39
 
42
40
  /**
43
41
  * Decompile `desmos` into a semi-structured AST.
44
42
  */
45
- export function desmos_to_ast(
46
- desmos: Desmos.Calculator,
47
- blank: Desmos.Calculator,
48
- options: DesmostOptions,
49
- ): SemiStructuredAst
43
+ export function desmos_to_ast(desmos: Desmos.Calculator, blank: Desmos.Calculator): SemiStructuredAst
50
44
  {
51
45
  return {
52
46
  globals: [
@@ -1,7 +1,7 @@
1
1
  import { stringify_json5, prettify_source } from "./format";
2
2
 
3
3
  import { Ast } from "../parser";
4
- import { is_latex } from "../desmos";
4
+ import { is_latex } from "../utils";
5
5
 
6
6
  import { DesmosIncantation, ViewportIncantation } from "../magic/global";
7
7
 
@@ -19,6 +19,7 @@ export function extract_settings(
19
19
  let arg_raw = stringify_json5(
20
20
  desmos.settings,
21
21
  (k, v) => (
22
+ // @ts-expect-error: weird
22
23
  v === defaults[k]
23
24
  || k === "__observers"
24
25
  || k === "guid"
@@ -50,6 +51,7 @@ export function extract_viewport(
50
51
 
51
52
  let arg_raw = stringify_json5(
52
53
  { left, right, bottom, top },
54
+ /// @ts-expect-error: weird
53
55
  (k, v) => v === defaults[k] ? undefined : v,
54
56
  " ",
55
57
  );
@@ -15,7 +15,6 @@ export const stringify_json5: (typeof JSON.stringify) = (
15
15
  export function prettify_source(source: string): string
16
16
  {
17
17
  source = source.replaceAll(/(?<=[^ ])=(?=[^ ])/g, " = ");
18
- source = source.replaceAll(/\}\\/g, "} \\")
19
18
 
20
19
  return source;
21
20
  }
package/src/errors.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import type { Range } from "./utils";
2
+
3
+
1
4
  /** The parser failed to parse something (usually during speculative parsing), which only results in internal backtracking. */
2
5
  export const NO_MATCH = Symbol("no-match");
3
6
 
@@ -10,51 +13,64 @@ export type NoMatch = typeof NO_MATCH & { readonly __brand?: unique symbol };
10
13
  *
11
14
  * It's not *literally* unrecoverable in the sense that it will crash the compiler completely, but it means there are no further fallbacks to try.
12
15
  */
13
- export class UnrecoverableError extends Error
16
+ export class DesmostError extends Error
14
17
  {
15
- info?: UnrecoverableError.Info;
18
+ data?: DesmostError.Data;
19
+
20
+ constructor(msg: string | undefined, name: string) {
21
+ super(msg);
22
+ this.name = name;
23
+ }
16
24
  }
17
25
 
18
- export namespace UnrecoverableError
26
+ export namespace DesmostError
19
27
  {
20
- export interface Info
28
+ export interface Data
21
29
  {
22
- hint?: string,
23
- note?: string,
24
- flagged_by?: string,
30
+ msg?: string
31
+ while?: string
32
+ hint?: string
33
+ note?: string
34
+ debug?: string
35
+
36
+ show?: {
37
+ text: string
38
+ span?: Range
39
+ }
25
40
  }
26
41
 
27
- /** The parser unexpectedly reached the end of its entire input source code. */
28
- export class UnexpectedEnd extends UnrecoverableError {
29
- constructor(msg: string, override info?: Info) { super(msg); this.name = "Unexpected End"; }
42
+ /** The parser unexpectedly reached the end of its source. */
43
+ export class UnexpectedEnd extends DesmostError {
44
+ constructor(override data: Data) { super(data.msg, "Unexpected End of Source"); }
30
45
  }
31
46
 
32
47
  /** The parser did not receive input that it expected. */
33
- export class MissingInput extends UnrecoverableError {
34
- constructor(msg: string, override info?: Info) { super(msg); this.name = "Missing Input"; }
48
+ export class MissingInput extends DesmostError {
49
+ constructor(override data: Data) { super(data.msg, "Missing Input"); }
35
50
  }
36
51
 
37
52
  /** The parser received input that did not match what it expected. */
38
- export class UnexpectedInput extends UnrecoverableError {
39
- constructor(msg: string, override info?: Info) { super(msg); this.name = "Unexpected Input"; }
53
+ export class UnexpectedInput extends DesmostError {
54
+ constructor(override data: Data) { super(data.msg, "Unexpected Input"); }
40
55
  }
41
56
 
42
57
  /** The parser received excess input that it did not expect. */
43
- export class ExcessInput extends UnrecoverableError {
44
- constructor(msg: string, override info?: Info) { super(msg); this.name = "Excess Input"; }
58
+ export class ExcessInput extends DesmostError {
59
+ constructor(override data: Data) { super(data.msg, "Excess Input"); }
45
60
  }
46
61
 
47
62
  /** An incantation can't be applied here. */
48
- export class IllegalIncantation extends UnrecoverableError {
49
- constructor(msg: string, override info?: Info) { super(msg); this.name = "Illegal Incantation"; }
63
+ export class IllegalIncantation extends DesmostError {
64
+ constructor(override data: Data) { super(data.msg, "Illegal Incantation"); }
50
65
  }
51
66
 
52
- /** An incantation's argument couldn't be parsed. */
53
- export class InvalidArgument extends UnrecoverableError {
54
- constructor(msg: string, override info?: Info) { super(msg); this.name = "Invalid Argument"; }
67
+ /** An incantation's argument is invalid - could be a parsing, evaluation or validation failure. */
68
+ export class InvalidArgument extends DesmostError {
69
+ constructor(override data: Data) { super(data.msg, "Invalid Argument"); }
55
70
  }
56
71
  }
57
72
 
58
-
59
- /** Indicates that a function may throw an `UnrecoverableError`. */
60
- export type Unrecoverable<Result> = Result | Result & { readonly __brand?: unique symbol };
73
+ /** Indicates that a function may throw a `DesmostError`. */
74
+ export type Fallible<Result>
75
+ = Result
76
+ | Result & { readonly __brand?: unique symbol };