desmost 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +80 -13
  2. package/dist/compiler/compile.d.ts +7 -3
  3. package/dist/compiler/compile.d.ts.map +1 -1
  4. package/dist/compiler/compile.js +7 -3
  5. package/dist/compiler/compile.js.map +1 -1
  6. package/dist/compiler/evaluate.d.ts +5 -5
  7. package/dist/compiler/evaluate.d.ts.map +1 -1
  8. package/dist/compiler/evaluate.js +16 -8
  9. package/dist/compiler/evaluate.js.map +1 -1
  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 +14 -1
  13. package/dist/compiler/format.js.map +1 -1
  14. package/dist/compiler/index.d.ts +0 -1
  15. package/dist/compiler/index.d.ts.map +1 -1
  16. package/dist/decompiler/decompile.d.ts +12 -1
  17. package/dist/decompiler/decompile.d.ts.map +1 -1
  18. package/dist/decompiler/decompile.js +14 -2
  19. package/dist/decompiler/decompile.js.map +1 -1
  20. package/dist/decompiler/emit.d.ts.map +1 -1
  21. package/dist/decompiler/emit.js +13 -6
  22. package/dist/decompiler/emit.js.map +1 -1
  23. package/dist/decompiler/extract.d.ts.map +1 -1
  24. package/dist/decompiler/extract.js +3 -2
  25. package/dist/decompiler/extract.js.map +1 -1
  26. package/dist/decompiler/format.js +1 -1
  27. package/dist/decompiler/format.js.map +1 -1
  28. package/dist/desmos.d.ts +4 -0
  29. package/dist/desmos.d.ts.map +1 -1
  30. package/dist/desmos.js +6 -0
  31. package/dist/desmos.js.map +1 -1
  32. package/dist/errors.d.ts +18 -0
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js +37 -0
  35. package/dist/errors.js.map +1 -1
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.internal.d.ts +1 -1
  39. package/dist/index.internal.d.ts.map +1 -1
  40. package/dist/index.internal.js +1 -1
  41. package/dist/index.internal.js.map +1 -1
  42. package/dist/index.js +0 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/magic/expr/latex.d.ts +1 -2
  45. package/dist/magic/expr/latex.d.ts.map +1 -1
  46. package/dist/magic/expr/latex.js +1 -4
  47. package/dist/magic/expr/latex.js.map +1 -1
  48. package/dist/magic/expr/text.d.ts +1 -1
  49. package/dist/magic/expr/text.d.ts.map +1 -1
  50. package/dist/magic/expr/text.js +1 -1
  51. package/dist/magic/expr/text.js.map +1 -1
  52. package/dist/magic/global/dark-mode.d.ts.map +1 -1
  53. package/dist/magic/global/dark-mode.js +1 -3
  54. package/dist/magic/global/dark-mode.js.map +1 -1
  55. package/dist/magic/global/desmos.d.ts +4 -1
  56. package/dist/magic/global/desmos.d.ts.map +1 -1
  57. package/dist/magic/global/desmos.js +14 -1
  58. package/dist/magic/global/desmos.js.map +1 -1
  59. package/dist/magic/global/viewport.d.ts +3 -0
  60. package/dist/magic/global/viewport.d.ts.map +1 -1
  61. package/dist/magic/global/viewport.js +23 -0
  62. package/dist/magic/global/viewport.js.map +1 -1
  63. package/dist/magic/incantation.d.ts +13 -8
  64. package/dist/magic/incantation.d.ts.map +1 -1
  65. package/dist/magic/incantation.js +23 -3
  66. package/dist/magic/incantation.js.map +1 -1
  67. package/dist/magic/local/anim.d.ts.map +1 -1
  68. package/dist/magic/local/anim.js +0 -1
  69. package/dist/magic/local/anim.js.map +1 -1
  70. package/dist/magic/local/colour.d.ts +3 -2
  71. package/dist/magic/local/colour.d.ts.map +1 -1
  72. package/dist/magic/local/colour.js +3 -3
  73. package/dist/magic/local/colour.js.map +1 -1
  74. package/dist/magic/local/label.d.ts +4 -3
  75. package/dist/magic/local/label.d.ts.map +1 -1
  76. package/dist/magic/local/label.js +37 -16
  77. package/dist/magic/local/label.js.map +1 -1
  78. package/dist/magic/local/line.d.ts +3 -0
  79. package/dist/magic/local/line.d.ts.map +1 -1
  80. package/dist/magic/local/line.js +10 -0
  81. package/dist/magic/local/line.js.map +1 -1
  82. package/dist/magic/local/slider.d.ts +2 -2
  83. package/dist/magic/local/slider.d.ts.map +1 -1
  84. package/dist/magic/local/slider.js +0 -1
  85. package/dist/magic/local/slider.js.map +1 -1
  86. package/dist/{compiler/options.d.ts → options.d.ts} +49 -22
  87. package/dist/options.d.ts.map +1 -0
  88. package/dist/{compiler/options.js → options.js} +6 -1
  89. package/dist/options.js.map +1 -0
  90. package/dist/parser/desmost-parser.d.ts +1 -1
  91. package/dist/parser/desmost-parser.d.ts.map +1 -1
  92. package/dist/parser/desmost-parser.js +9 -5
  93. package/dist/parser/desmost-parser.js.map +1 -1
  94. package/dist/parser/generic-parser.d.ts +1 -5
  95. package/dist/parser/generic-parser.d.ts.map +1 -1
  96. package/dist/parser/generic-parser.js +7 -10
  97. package/dist/parser/generic-parser.js.map +1 -1
  98. package/dist/utils.d.ts +1 -0
  99. package/dist/utils.d.ts.map +1 -1
  100. package/dist/utils.js +1 -0
  101. package/dist/utils.js.map +1 -1
  102. package/package.json +9 -6
  103. package/src/compiler/compile.ts +8 -4
  104. package/src/compiler/evaluate.ts +22 -13
  105. package/src/compiler/format.ts +16 -3
  106. package/src/compiler/index.ts +0 -1
  107. package/src/decompiler/decompile.ts +18 -2
  108. package/src/decompiler/emit.ts +15 -6
  109. package/src/decompiler/extract.ts +3 -2
  110. package/src/decompiler/format.ts +1 -1
  111. package/src/decompiler/readme.md +9 -1
  112. package/src/desmos.ts +9 -0
  113. package/src/errors.ts +29 -8
  114. package/src/index.internal.ts +1 -1
  115. package/src/index.ts +2 -2
  116. package/src/magic/.template.ts +25 -16
  117. package/src/magic/expr/latex.ts +2 -7
  118. package/src/magic/expr/text.ts +2 -2
  119. package/src/magic/global/dark-mode.ts +1 -3
  120. package/src/magic/global/desmos.ts +23 -1
  121. package/src/magic/global/viewport.ts +40 -1
  122. package/src/magic/incantation.ts +59 -22
  123. package/src/magic/local/anim.ts +0 -1
  124. package/src/magic/local/colour.ts +4 -3
  125. package/src/magic/local/label.ts +52 -22
  126. package/src/magic/local/line.ts +20 -3
  127. package/src/magic/local/slider.ts +2 -3
  128. package/src/{compiler/options.ts → options.ts} +61 -14
  129. package/src/parser/desmost-parser.ts +12 -6
  130. package/src/parser/generic-parser.ts +12 -12
  131. package/src/parser/readme.md +2 -0
  132. package/src/utils.ts +3 -0
  133. package/svelte/desmost.svelte +5 -3
  134. package/dist/compiler/options.d.ts.map +0 -1
  135. package/dist/compiler/options.js.map +0 -1
@@ -1,5 +1,7 @@
1
1
  import Json5 from "json5";
2
+ import dedent from "dedent";
2
3
 
4
+ import type { DesmostOptions } from "../options";
3
5
  import { UnrecoverableError, type Unrecoverable } from "../errors";
4
6
 
5
7
 
@@ -21,17 +23,17 @@ export abstract class Incantation<
21
23
  >
22
24
  {
23
25
  /** Short user-facing description of what the incantation does. */
24
- public abstract readonly description: string
26
+ abstract readonly description: string
25
27
 
26
28
  /** The raw text sequence that matches this incantation, such as `viewport` or `hidden`. */
27
- public abstract readonly identifier: string
29
+ abstract readonly identifier: string
28
30
 
29
31
  /** An alternative `.identifier`, strictly for localisation purposes only. */
30
- public readonly alias?: string
32
+ readonly alias?: string
31
33
 
32
34
 
33
35
  /** Apply this incantation's effect to `target`, using the provided `data` if required. */
34
- public abstract apply(
36
+ abstract apply(
35
37
  target: Effect extends GLOBAL ? Desmos.Calculator : Desmos.ExpressionState,
36
38
  data?: unknown,
37
39
  ): void
@@ -45,7 +47,7 @@ export abstract class Incantation<
45
47
  if ((actual ?? "expression") !== required)
46
48
  {
47
49
  throw new UnrecoverableError.IllegalIncantation(
48
- `/${this.identifier} can only applied to ${required === "expression" ? "latex" : required} expressions, but target block has type: ${actual}`
50
+ `/${this.identifier} can only applied to ${required === "expression" ? "latex" : required} blocks, but target block has type: ${actual}`
49
51
  );
50
52
  }
51
53
  }
@@ -61,10 +63,10 @@ export abstract class ArgIncantation<
61
63
  extends Incantation<Effect>
62
64
  {
63
65
  /** Does this incantation always require an argument to be passed? */
64
- public readonly requires_arg: boolean = true
66
+ readonly requires_arg: boolean = true
65
67
 
66
68
  /** What type of argument does this incantation accept? */
67
- public abstract readonly arg_type: Incantation.ArgType
69
+ abstract readonly arg_type: Incantation.ArgType
68
70
 
69
71
 
70
72
  /**
@@ -75,14 +77,18 @@ export abstract class ArgIncantation<
75
77
  * ## Notes
76
78
  *
77
79
  * Child incantation classes should override this method, though the defaults should cover most cases (except `ArgType.ENUM`).
80
+ *
81
+ * Validity checks for missing or invalid values, incorrect types, and unknown fields should be performed here, so that `.apply()` can assume the data it receives is guaranteed to be valid.
78
82
  */
79
- public evaluate_arg(raw: string): Unrecoverable<unknown>
83
+ evaluate_arg(raw: string, options: DesmostOptions): Unrecoverable<unknown>
80
84
  {
81
85
  switch (this.arg_type) {
82
- case Incantation.ArgType.STRING: return raw;
83
86
  case Incantation.ArgType.LATEX: return raw;
84
87
  case Incantation.ArgType.ENUM: return raw;
85
88
 
89
+ case Incantation.ArgType.STRING:
90
+ return (options.dedent_text) ? dedent(raw): raw.trim();
91
+
86
92
  case Incantation.ArgType.OBJECT: {
87
93
  try {
88
94
  return Json5.parse(`{${raw}}`);
@@ -94,9 +100,53 @@ export abstract class ArgIncantation<
94
100
  }
95
101
  }
96
102
  }
103
+
104
+ protected require_nonempty(
105
+ arg: object,
106
+ msg?: string,
107
+ info?: UnrecoverableError.Info,
108
+ ): Unrecoverable<void>
109
+ {
110
+ if (Object.entries(arg).length === 0) {
111
+ throw new UnrecoverableError.MissingInput(
112
+ msg ?? `/${this.identifier} received an empty argument {}`,
113
+ info,
114
+ );
115
+ }
116
+ }
117
+
118
+ protected require_known(
119
+ arg: object,
120
+ valid_fields: string[],
121
+ info?: UnrecoverableError.Info,
122
+ ): Unrecoverable<void>
123
+ {
124
+ // TODO accumulate
125
+ for (let field of Object.keys(arg)) {
126
+ if (!valid_fields.includes(field)) {
127
+ throw new UnrecoverableError.InvalidArgument(
128
+ `/${this.identifier} received invalid field: ${field}`,
129
+ {
130
+ hint: `Valid fields are [${valid_fields.join(", ")}]`,
131
+ ...info,
132
+ }
133
+ );
134
+ }
135
+ }
136
+ }
97
137
  }
98
138
 
99
139
 
140
+ /** An incantation that affects the entire Desmos calculator state, like `/desmos` or `/viewport`. */
141
+ export type GLOBAL = Incantation.Effect.GLOBAL;
142
+
143
+ /** An incantation that affects only the expression immediately following it, like `/hide` or `/slider`. */
144
+ export type LOCAL = Incantation.Effect.LOCAL;
145
+
146
+ /** An incantation that produces an expression, like `/latex` or `/text`. */
147
+ export type EXPR = Incantation.Effect.EXPR;
148
+
149
+
100
150
  export namespace Incantation
101
151
  {
102
152
  /** The kind of effect an incantation produces - it either modifies only one block, or modifies the calculator as a whole. */
@@ -128,16 +178,3 @@ export namespace Incantation
128
178
  OBJECT = "Object",
129
179
  }
130
180
  }
131
-
132
-
133
- // == ALIASES == //
134
-
135
- /** An incantation that affects the entire Desmos calculator state, like `/desmos` or `/viewport`. */
136
- export type GLOBAL = Incantation.Effect.GLOBAL;
137
-
138
- /** An incantation that affects only the expression immediately following it, like `/hide` or `/slider`. */
139
- export type LOCAL = Incantation.Effect.LOCAL;
140
-
141
- /** An incantation that produces an expression, like `/latex` or `/text`. */
142
- export type EXPR = Incantation.Effect.EXPR;
143
-
@@ -11,7 +11,6 @@ export class AnimIncantation extends Incantation<LOCAL>
11
11
  override apply(target: Desmos.ExpressionState)
12
12
  {
13
13
  super.require_expr_type(target.type, "expression");
14
- // @ts-expect-error: outdated types
15
14
  target.playing = true;
16
15
  }
17
16
  }
@@ -1,13 +1,14 @@
1
1
  import { Incantation, ArgIncantation, type LOCAL } from "../incantation";
2
- import { UnrecoverableError } from "../../errors";
3
2
 
4
3
  import { DesmosColour } from "../../desmos";
4
+ import { UnrecoverableError } from "../../errors";
5
+ import type { DesmostOptions } from "../../options";
5
6
 
6
7
 
7
8
  export class ColourIncantation extends ArgIncantation<LOCAL>
8
9
  {
9
10
  override readonly description
10
- = ""
11
+ = "Change the colour of a rendered expression, such as a line, region, polygon, etc."
11
12
 
12
13
  override readonly identifier = "color"
13
14
  override readonly alias = "colour"
@@ -20,7 +21,7 @@ export class ColourIncantation extends ArgIncantation<LOCAL>
20
21
  target.color = data;
21
22
  }
22
23
 
23
- override evaluate_arg(data: string): DesmosColour
24
+ override evaluate_arg(data: string, options: DesmostOptions): DesmosColour
24
25
  {
25
26
  switch (data.trim().toUpperCase()) {
26
27
  case "RED": return DesmosColour.RED;
@@ -1,15 +1,24 @@
1
- import { UnrecoverableError } from "../../errors";
2
1
  import { Incantation, ArgIncantation, type LOCAL } from "../incantation";
3
2
 
3
+ import { UnrecoverableError } from "../../errors";
4
+ import type { DesmostOptions } from "../../options";
5
+
4
6
 
5
7
  interface LabelOptions
6
8
  {
7
9
  text: string;
8
10
  show?: boolean;
9
- size?: number | keyof typeof Desmos.LabelSizes;
10
- pos?: keyof typeof Desmos.LabelOrientations;
11
+ size?: number;
12
+ pos?: "ABOVE" | "BELOW" | "LEFT" | "RIGHT" | "ABOVE_LEFT" | "ABOVE_RIGHT" | "BELOW_LEFT" | "BELOW_RIGHT";
11
13
  }
12
14
 
15
+ const VALID_FIELDS = ["text", "show", "size", "pos"];
16
+
17
+ const VALID_POSITIONS = [
18
+ "ABOVE", "BELOW", "LEFT", "RIGHT",
19
+ "ABOVE_LEFT", "ABOVE_RIGHT", "BELOW_LEFT", "BELOW_RIGHT",
20
+ ];
21
+
13
22
 
14
23
  export class LabelIncantation extends ArgIncantation<LOCAL>
15
24
  {
@@ -25,36 +34,57 @@ export class LabelIncantation extends ArgIncantation<LOCAL>
25
34
  super.require_expr_type(target.type, "expression");
26
35
  target.label = data.text;
27
36
  target.showLabel = data.show ?? true;
28
- // @ts-expect-error: outdated types TODO check
37
+ // @ts-expect-error: outdated types
29
38
  if (data.size != undefined) target.labelSize = data.size;
39
+ // @ts-expect-error: outdated types
30
40
  if (data.pos != undefined) target.labelOrientation = data.pos;
31
41
  }
32
42
 
33
- override evaluate_arg(raw: string): LabelOptions
43
+ override evaluate_arg(raw: string, options: DesmostOptions): LabelOptions
34
44
  {
35
- let out = super.evaluate_arg(raw) as LabelOptions;
45
+ let out = super.evaluate_arg(raw, options) as LabelOptions;
46
+
47
+ if (options.check_args) {
48
+ super.require_nonempty(out, `/label received empty argument`, {
49
+ hint: `You can provide [${VALID_FIELDS.join(", ")}]`,
50
+ flagged_by: "check_args",
51
+ });
36
52
 
37
- if (!("text" in out)) {
38
- throw new UnrecoverableError.InvalidArgument(
39
- `/label requires a \`text\` argument, such as: \`/label{ text: "sup world!" }\``
40
- );
53
+ if (!("text" in out)) {
54
+ throw new UnrecoverableError.MissingInput(
55
+ `/label is missing label text`,
56
+ {
57
+ hint: `Provide text for the label: \`/label{text: "sup world!"}\``,
58
+ flagged_by: "check_args",
59
+ }
60
+ );
61
+ }
62
+
63
+ super.require_known(out, VALID_FIELDS);
41
64
  }
42
65
 
43
66
  if (typeof out.pos != "undefined") {
44
- let pos = out.pos.trim().toUpperCase();
45
-
46
- switch (pos) {
47
- case "ABOVE":
48
- case "BELOW":
49
- case "LEFT":
50
- case "RIGHT":
51
- out.pos = pos;
52
- break;
53
-
54
- default:
67
+ let pos = out.pos.trim().toUpperCase().replaceAll("-", "_");
68
+
69
+ if (VALID_POSITIONS.includes(pos)) {
70
+ // @ts-expect-error: validated
71
+ out.pos = pos;
72
+ }
73
+ else {
74
+ // @ts-expect-error: indexing
75
+ let orientation = Desmos.LabelOrientations[pos];
76
+
77
+ if (options.check_args && orientation == undefined) {
55
78
  throw new UnrecoverableError.InvalidArgument(
56
- `/label: Invalid label position: ${pos}`
79
+ `/label received invalid label position: ${pos}`,
80
+ {
81
+ hint: `Valid label positions are ${VALID_POSITIONS.join(", ")}`,
82
+ flagged_by: "check_args"
83
+ }
57
84
  );
85
+ }
86
+
87
+ out.pos = orientation;
58
88
  }
59
89
  }
60
90
 
@@ -1,13 +1,18 @@
1
1
  import { Incantation, ArgIncantation, type LOCAL } from "../incantation";
2
2
 
3
+ import type { DesmostOptions } from "../../options";
4
+ import { type Unrecoverable } from "../../errors";
5
+
3
6
 
4
7
  interface LineOptions
5
8
  {
6
- style?: keyof typeof Desmos.Styles; // TODO allow raw enum
9
+ style?: keyof typeof Desmos.Styles;
7
10
  width?: number;
8
11
  opacity?: number;
9
12
  }
10
13
 
14
+ const VALID_FIELDS = ["style", "width", "opacity"];
15
+
11
16
 
12
17
  export class LineIncantation extends ArgIncantation<LOCAL>
13
18
  {
@@ -22,8 +27,20 @@ export class LineIncantation extends ArgIncantation<LOCAL>
22
27
  {
23
28
  super.require_expr_type(target.type, "expression");
24
29
 
25
- if (data.style != undefined) target.lineStyle = data.style;
26
- if (data.width != undefined) target.lineWidth = data.width;
30
+ if (data.style != undefined) target.lineStyle = data.style;
31
+ if (data.width != undefined) target.lineWidth = data.width;
27
32
  if (data.opacity != undefined) target.lineOpacity = data.opacity;
28
33
  }
34
+
35
+ override evaluate_arg(raw: string, options: DesmostOptions): Unrecoverable<LineOptions>
36
+ {
37
+ let out = super.evaluate_arg(raw, options) as LineOptions;
38
+
39
+ if (options.check_args) {
40
+ super.require_nonempty(out);
41
+ super.require_known(out, VALID_FIELDS);
42
+ }
43
+
44
+ return out;
45
+ }
29
46
  }
@@ -3,8 +3,8 @@ import { Incantation, ArgIncantation, type LOCAL } from "../incantation";
3
3
 
4
4
  interface SliderBounds
5
5
  {
6
- min?: number | string;
7
- max?: number | string;
6
+ min: number | string;
7
+ max: number | string;
8
8
  step?: number | string;
9
9
  }
10
10
 
@@ -21,7 +21,6 @@ export class SliderIncantation extends ArgIncantation<LOCAL>
21
21
  override apply(target: Desmos.ExpressionState, data: SliderBounds)
22
22
  {
23
23
  super.require_expr_type(target.type, "expression");
24
- // @ts-expect-error: outdated types
25
24
  target.sliderBounds = data;
26
25
  }
27
26
  }
@@ -1,11 +1,30 @@
1
+ import { LINE } from "./utils";
2
+ import { compile } from "./compiler/compile";
3
+
4
+
1
5
  /**
2
6
  * Options to customise Desmost compilation.
3
7
  *
4
- * To customise compilation, pass in a `DesmostOptions` object as the last argument to `compile()`:
8
+ * To customise compilation, pass in a `DesmostOptions` object as the last argument to {@linkcode compile()}:
5
9
  *
6
10
  * ```ts
7
11
  * compile(calc, source, { errors: "crash" });
8
12
  * ```
13
+ *
14
+ * ## Decompilation
15
+ *
16
+ * The same options can also be applied to decompilation:
17
+ *
18
+ * ```ts
19
+ * decompile(calc, blank, { prettify: false });
20
+ * ```
21
+ *
22
+ * Here, this means “decompile into source code that would reproduce `calc`, *when compiled with these options*”. As an identity:
23
+ *
24
+ * ```ts
25
+ * let options = { ... };
26
+ * compile(_, decompile(calc, _ options), options) == calc // (value equality)
27
+ * ```
9
28
  */
10
29
  export interface DesmostOptions
11
30
  {
@@ -19,7 +38,7 @@ export interface DesmostOptions
19
38
  *
20
39
  * - `suppress`: Silently fail on the frontend (if you wish to give the illusion that everything is fine).
21
40
  */
22
- errors?: "surface" | "crash" | "suppress"
41
+ errors: "surface" | "crash" | "suppress"
23
42
 
24
43
  /**
25
44
  * Where should errors be placed?
@@ -28,7 +47,7 @@ export interface DesmostOptions
28
47
  * - `end`: All aggregated at the end of the Desmos expressions list.
29
48
  * - `start`: All aggregated at the start of the Desmos expressions list.
30
49
  */
31
- place_errors?: "inline" | "end" | "start"
50
+ place_errors: "inline" | "end" | "start"
32
51
 
33
52
  /**
34
53
  * The prefix to prepend to error blocks.
@@ -37,42 +56,67 @@ export interface DesmostOptions
37
56
  *
38
57
  * Provide a `""` blank string if you wish for no prefix to be added.
39
58
  */
40
- error_prefix?: string
59
+ error_prefix: string
60
+
61
+ /**
62
+ * Show all the available diagnostics for errors, including hints and debug information?
63
+ *
64
+ * Defaults to `true`, meaning all output is shown.
65
+ */
66
+ expand_errors: boolean
67
+
68
+ /**
69
+ * Error if an incantation receives a `{}` object argument with unknown fields, or no fields at all?
70
+ *
71
+ * For instance, `/label{position: LEFT}` is invalid; it should be `/label{pos: LEFT}`. With `check_args: false`, this silently no-ops.
72
+ *
73
+ * Desmost doesn’t remove unknown fields, it just passes them directly to the Desmos API. If you know what you’re doing and a field you know exists isn’t supported by Desmos, you can disable this to avoid erroring.
74
+ *
75
+ * Defaults to `true`, meaning arguments are checked. The Desmos API happily accepts and ignores invalid fields, so Desmost provides a safety net for you ;)
76
+ */
77
+ check_args: boolean
41
78
 
42
79
  /**
43
80
  * Prettify LaTeX output so it renders nicely in the Desmos editor?
44
81
  *
45
82
  * Defaults to `true`, meaning LaTeX is prettified.
46
83
  */
47
- prettify?: boolean
84
+ prettify: boolean
85
+
86
+ /**
87
+ * Strip common indentation from `/text{}` blocks?
88
+ *
89
+ * Defaults to `true`, meaning text is dedented.
90
+ */
91
+ dedent_text: boolean
48
92
 
49
93
  /**
50
94
  * Should `%` LaTeX comments be ignored, instead of turned into text expressions (notes)?
51
95
  *
52
96
  * Defaults to `false`, meaning comments are kept.
53
97
  */
54
- ignore_comments?: boolean
98
+ ignore_comments: boolean
55
99
 
56
100
  /**
57
101
  * Should all line breaks be ignored, instead of kept as blank expressions?
58
102
  *
59
103
  * Defaults to `false`, meaning all blank lines are kept.
60
104
  */
61
- ignore_all_blanks?: boolean
105
+ ignore_all_blanks: boolean
62
106
 
63
107
  /**
64
108
  * Should trailing blank lines at the start of the source be kept as blank expressions?
65
109
  *
66
110
  * Defaults to `false`, meaning leading blank lines are ignored.
67
111
  */
68
- keep_leading_blanks?: boolean
112
+ keep_leading_blanks: boolean
69
113
 
70
114
  /**
71
115
  * Should trailing blank lines at the end of the source be kept as blank expressions?
72
116
  *
73
117
  * Defaults to `false`, meaning trailing blank lines are ignored.
74
118
  */
75
- keep_trailing_blanks?: boolean
119
+ keep_trailing_blanks: boolean
76
120
 
77
121
  /**
78
122
  * Return debug diagnostics?
@@ -81,16 +125,19 @@ export interface DesmostOptions
81
125
  *
82
126
  * Defaults to `false`, meaning `compile()` returns `void`.
83
127
  */
84
- debug?: boolean
128
+ debug: boolean
85
129
  }
86
130
 
87
131
 
88
- export const DEFAULT_OPTIONS =
132
+ export const DEFAULT_OPTIONS: DesmostOptions =
89
133
  {
90
134
  errors: "surface",
91
135
  place_errors: "inline",
92
- error_prefix: "[DESMOST ERROR]\n",
136
+ error_prefix: `[DESMOST ERROR]\n${LINE}\n`,
137
+ expand_errors: true,
93
138
  prettify: true,
139
+ dedent_text: true,
140
+ check_args: false,
94
141
  ignore_comments: false,
95
142
  ignore_all_blanks: false,
96
143
  keep_leading_blanks: false,
@@ -102,10 +149,10 @@ export const DEFAULT_OPTIONS =
102
149
  /**
103
150
  * Fill in `options` with Desmost's defaults to produce a complete `DesmostOptions` config.
104
151
  */
105
- export function fill_defaults(options: Partial<DesmostOptions> | undefined): Required<DesmostOptions>
152
+ export function fill_defaults(options: Partial<DesmostOptions> | undefined): DesmostOptions
106
153
  {
107
154
  return {
108
155
  ...DEFAULT_OPTIONS,
109
156
  ...options,
110
- } as Required<DesmostOptions>;
157
+ };
111
158
  }
@@ -1,7 +1,7 @@
1
1
  import { GenericParser } from "./generic-parser";
2
2
  import { Ast } from "./ast";
3
3
 
4
- import type { DesmostOptions } from "../compiler";
4
+ import type { DesmostOptions } from "../options";
5
5
  import { NO_MATCH, UnrecoverableError } from "../errors";
6
6
  import type { NoMatch, Unrecoverable } from "../errors";
7
7
  import * as utils from "../utils";
@@ -468,20 +468,26 @@ export class DesmostParser extends GenericParser
468
468
  }
469
469
 
470
470
  /* NOTE: Cut in by 1 on both sides to exclude {} braces */
471
- let out = utils.chars(this.source, init + 1, this.i - 1);
471
+ let raw = utils.chars(this.source, init + 1, this.i - 1);
472
472
 
473
473
  for (let [i, j] of utils.reversing(utils.paired(indices_to_insert_quotes))) {
474
474
  i -= init + 1;
475
475
  j -= init + 1;
476
476
 
477
- let value = out.slice(i, j).join("");
477
+ let value = raw.slice(i, j).join("");
478
478
  if (["true", "false", "null", "undefined"].includes(value)) continue;
479
479
 
480
- out.splice(j, 0, `"`);
481
- out.splice(i, 0, `"`);
480
+ raw.splice(j, 0, `"`);
481
+ raw.splice(i, 0, `"`);
482
482
  }
483
483
 
484
- return out.join("").trim();
484
+ let out = raw.join("");
485
+
486
+ if (arg_type !== Incantation.ArgType.STRING) {
487
+ out = out.trim();
488
+ }
489
+
490
+ return out;
485
491
  }
486
492
  }
487
493
 
@@ -48,14 +48,6 @@ export class GenericParser
48
48
  return this.source.at(this.i);
49
49
  }
50
50
 
51
- /**
52
- * Get the character *after* the current one that the parser points at.
53
- */
54
- protected next(): string | undefined
55
- {
56
- return this.source.at(this.i + 1);
57
- }
58
-
59
51
  /**
60
52
  * Peek a snippet of the upcoming source text.
61
53
  *
@@ -64,7 +56,11 @@ export class GenericParser
64
56
  public preview(idx?: number): string
65
57
  {
66
58
  idx ??= this.i;
67
- return this.source.slice(idx, idx + 20) + "...";
59
+
60
+ return (
61
+ this.source.slice(idx, idx + 20).replaceAll("\n", " ")
62
+ + (idx + 20 >= this.length ? "⏎" : "...")
63
+ );
68
64
  }
69
65
 
70
66
  /**
@@ -140,7 +136,7 @@ export class GenericParser
140
136
 
141
137
  while (this.current === raw[ii]) {
142
138
  let r = this.try_advance();
143
- if (r === NO_MATCH) return NO_MATCH;
139
+ if (r === NO_MATCH) return NO_MATCH; // FIXME?
144
140
 
145
141
  ii++;
146
142
  if (ii === raw.length) return;
@@ -167,14 +163,18 @@ export class GenericParser
167
163
  /**
168
164
  * Consume 0 or more whitespace characters.
169
165
  *
170
- * Same as `.consume_spaces()`, but allows newlines.
166
+ * Same as `.consume_spaces()`, but allows tabs and newlines.
171
167
  */
172
168
  protected consume_whitespace(): void
173
169
  {
174
170
  /* NOTE: Callers should be able to assume this is safe to call even when at end of input, since it should just match 0 characters */
175
171
  if (this.i >= this.length) return;
176
172
 
177
- while (this.current === " " || this.current === "\n") {
173
+ while (
174
+ this.current === " "
175
+ || this.current === "\n"
176
+ || this.current === "\t"
177
+ ) {
178
178
  this.advance();
179
179
  }
180
180
  }
@@ -11,6 +11,8 @@ The parser is split into `DesmostParser` and `GenericParser`.
11
11
 
12
12
  The parser is stateful, tracking its current position in the source text with its `.i` field.
13
13
 
14
+ `parse_next()` is the only method consumers need to be aware of, the rest is implementation detail. Would love to make them `private`, unfortunately we need them public for unit testing ;)
15
+
14
16
 
15
17
  ## Backtracking
16
18
 
package/src/utils.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export const LINE = '—'.repeat(10);
2
+
3
+
1
4
  export function* reversing<T>(items: ArrayLike<T>): Generator<T>
2
5
  {
3
6
  for (let i = items.length - 1; i >= 0; i--) {
@@ -141,9 +141,11 @@ onMount(() =>
141
141
  inject(el_desmos, source.textContent);
142
142
  }
143
143
  else {
144
- let observer = new IntersectionObserver(() => {
145
- inject(el_desmos, source.textContent);
146
- observer.disconnect();
144
+ let observer = new IntersectionObserver(([entry]) => {
145
+ if (entry.isIntersecting) {
146
+ inject(el_desmos, source.textContent);
147
+ observer.disconnect();
148
+ }
147
149
  });
148
150
  observer.observe(el_desmos);
149
151
  lazy_observers.push(observer);
@@ -1 +0,0 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/compiler/options.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAE9B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAA;IAEzC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAA;IAEzC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAE3B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAE7B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAE9B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CACf;AAGD,eAAO,MAAM,eAAe;;;;;;;;;;CAW3B,CAAC;AAGF;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,cAAc,CAAC,CAMpG"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"options.js","sourceRoot":"","sources":["../../src/compiler/options.ts"],"names":[],"mappings":"AAuFA,MAAM,CAAC,MAAM,eAAe,GAC5B;IACG,MAAM,EAAE,SAAS;IACjB,YAAY,EAAE,QAAQ;IACtB,YAAY,EAAE,mBAAmB;IACjC,QAAQ,EAAE,IAAI;IACd,eAAe,EAAE,KAAK;IACtB,iBAAiB,EAAE,KAAK;IACxB,mBAAmB,EAAE,KAAK;IAC1B,oBAAoB,EAAE,KAAK;IAC3B,KAAK,EAAE,KAAK;CACd,CAAC;AAGF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,OAA4C;IAEvE,OAAO;QACJ,GAAG,eAAe;QAClB,GAAG,OAAO;KACgB,CAAC;AACjC,CAAC"}