desmost 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # Desmost
2
+
3
+ [**Docs**](https://github.com/Sup2point0/desmost/tree/main/docs)  ·  [**Changelog**](https://github.com/Sup2point0/desmost/blob/main/CHANGELOG.md)  ·  [**Playground**](https://sup2point0.github.io/desmost)
4
+
5
+ A tiny DSL for compiling LaTeX to Desmos.
6
+
7
+ <!-- You write this:
8
+
9
+ ```hs
10
+ /viewport{ left: -5, right: 5 }
11
+ % Drag the slider!
12
+
13
+ A = 1
14
+ y = A \sin(x - t)
15
+
16
+ /label{ text: "we love Desmos(t)!" } :: (0, 2)
17
+
18
+ /anim /slider{ min: -1, max: 1 }
19
+ :: t = 0
20
+ ```
21
+
22
+ And Desmost gives you this: -->
23
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "desmost",
3
3
  "author": "Sup#2.0",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "license": "MIT",
6
6
 
7
7
  "description": "A tiny DSL for compiling LaTeX to Desmos",
@@ -23,7 +23,8 @@
23
23
  "./src/",
24
24
  "./dist/",
25
25
  "package.json",
26
- "LICENCE"
26
+ "LICENCE",
27
+ "README.md"
27
28
  ],
28
29
  "exports": {
29
30
  ".": {
@@ -69,6 +69,18 @@ export function evaluate_expr(
69
69
  if (errors.length > 0) {
70
70
  desmos.setExpression({ type: "text", text: errors.join("\n\n") });
71
71
  }
72
+
73
+ // @ts-expect-error: outdated types
74
+ if (expr.data.latex != undefined) {
75
+ // @ts-expect-error: outdated types
76
+ expr.data.latex = normalise_latex(expr.data.latex);
77
+
78
+ if (options.prettify) {
79
+ // @ts-expect-error: outdated types
80
+ expr.data.latex = prettify_latex(expr.data.latex);
81
+ }
82
+ }
83
+
72
84
  desmos.setExpression(expr.data);
73
85
  }
74
86
 
@@ -108,6 +120,41 @@ export function evaluate_error(
108
120
  }
109
121
 
110
122
 
123
+ /**
124
+ * Remove line breaks from `latex` so Desmos can properly consume it.
125
+ */
126
+ function normalise_latex(latex: string): string
127
+ {
128
+ return latex.replaceAll("\n", " ");
129
+ }
130
+
131
+
132
+ /**
133
+ * Prettify `latex` to render nicely in Desmos, reflecting how you would type directly into Desmos.
134
+ *
135
+ * This includes:
136
+ *
137
+ * - Replace `()`, `[]`, etc. with `\left(\right)`
138
+ * - Replace `min()`, `max()`, etc. with `\operatorname{min}()`
139
+ */
140
+ function prettify_latex(latex: string): string
141
+ {
142
+ latex = latex.replaceAll(/(?<!\\left)\(/g, "\\left(");
143
+ latex = latex.replaceAll(/(?<!\\right)\)/g, "\\right)");
144
+ latex = latex.replaceAll(/(?<!\\left)\[/g, "\\left[");
145
+ latex = latex.replaceAll(/(?<!\\right)\]/g, "\\right]");
146
+ latex = latex.replaceAll("\\{", "\\left{");
147
+ latex = latex.replaceAll("\\}", "\\right}");
148
+
149
+ latex = latex.replaceAll(
150
+ /(?<=[^\w]|^)\\?(mean|median|count|total|repeat|join|sort|shuffle|unique|mod|ceil|floor|round|sign)(?=\(|\\left\()/g,
151
+ "\\operatorname{$1}"
152
+ );
153
+
154
+ return latex;
155
+ }
156
+
157
+
111
158
  function format_error(
112
159
  e: UnrecoverableError,
113
160
  options: Required<DesmostOptions>,
@@ -47,6 +47,13 @@ export interface DesmostOptions
47
47
  */
48
48
  ignore_trailing_blanks?: boolean
49
49
 
50
+ /**
51
+ * Prettify LaTeX output so it renders nicely in the Desmos editor?
52
+ *
53
+ * Defaults to `true`, meaning LaTeX is prettified.
54
+ */
55
+ prettify?: boolean
56
+
50
57
  /**
51
58
  * Return debug diagnostics? This includes the unevaluated AST, and performance diagnostics.
52
59
  *
@@ -71,6 +78,7 @@ export function set_default_options(options: Partial<DesmostOptions> | undefined
71
78
  options.ignore_line_breaks ??= false;
72
79
  options.ignore_trailing_blanks ??= false;
73
80
 
81
+ options.prettify ??= true;
74
82
  options.debug ??= false;
75
83
 
76
84
  return options as Required<DesmostOptions>;
@@ -3,11 +3,12 @@ import { AnimIncantation } from "./anim";
3
3
 
4
4
  import { ColourIncantation } from "./colour";
5
5
  import { DashedIncantation } from "./dashed";
6
- import { FillIncantation } from "./fill";
7
- import { HideIncantation } from "./hide";
8
- import { LineIncantation } from "./line";
6
+ import { FillIncantation } from "./fill";
7
+ import { HideIncantation } from "./hide";
8
+ import { LabelIncantation } from "./label";
9
+ import { LineIncantation } from "./line";
9
10
  import { NoLineIncantation } from "./no-line";
10
- import { PointIncantation } from "./point";
11
+ import { PointIncantation } from "./point";
11
12
  import { SecretIncantation } from "./secret";
12
13
  import { SliderIncantation } from "./slider";
13
14
 
@@ -19,6 +20,7 @@ export const LOCAL_INCANTATIONS: Incantation<LOCAL>[] =
19
20
  new DashedIncantation(),
20
21
  new FillIncantation(),
21
22
  new HideIncantation(),
23
+ new LabelIncantation(),
22
24
  new LineIncantation(),
23
25
  new NoLineIncantation(),
24
26
  new PointIncantation(),
@@ -0,0 +1,58 @@
1
+ import { UnrecoverableError } from "../../errors";
2
+ import { Incantation, ArgIncantation, type LOCAL } from "../incantation";
3
+
4
+
5
+ interface LabelOptions
6
+ {
7
+ text: string;
8
+ show?: boolean;
9
+ size?: number | keyof typeof Desmos.LabelSizes;
10
+ pos?: keyof typeof Desmos.LabelOrientations;
11
+ }
12
+
13
+
14
+ export class LabelIncantation extends ArgIncantation<LOCAL>
15
+ {
16
+ override readonly description
17
+ = ""
18
+
19
+ override readonly identifier = "label"
20
+ override readonly requires_arg = true
21
+ override readonly arg_type = Incantation.ArgType.OBJECT
22
+
23
+ override apply(target: Desmos.ExpressionState, data: LabelOptions)
24
+ {
25
+ super.require_expr_type(target.type, "expression");
26
+ target.label = data.text;
27
+ if (data.show != undefined) target.showLabel = data.show;
28
+ // @ts-expect-error: outdated types TODO check
29
+ if (data.size != undefined) target.labelSize = data.size;
30
+ if (data.pos != undefined) target.labelOrientation = data.pos;
31
+ }
32
+
33
+ override evaluate_arg(raw: string): LabelOptions
34
+ {
35
+ let out = super.evaluate_arg(raw) as LabelOptions;
36
+
37
+ if (!("text" in out)) {
38
+ throw new UnrecoverableError.InvalidArgument(
39
+ `/label requires a \`text\` argument, such as: \`/label{ text: "sup world!" }\``
40
+ );
41
+ }
42
+
43
+ if (typeof out.pos != "undefined") {
44
+ switch (out.pos.trim().toUpperCase()) {
45
+ case "ABOVE": out.pos = Desmos.LabelOrientations.ABOVE; break;
46
+ case "BELOW": out.pos = Desmos.LabelOrientations.BELOW; break;
47
+ case "LEFT": out.pos = Desmos.LabelOrientations.LEFT; break;
48
+ case "RIGHT": out.pos = Desmos.LabelOrientations.RIGHT; break;
49
+ default:
50
+ throw new UnrecoverableError.InvalidArgument(
51
+ `/label: Invalid label position: ${out.pos}`
52
+ );
53
+ }
54
+ }
55
+
56
+ return out;
57
+ }
58
+ }
@@ -18,9 +18,9 @@ import type { DesmostOptions } from "../compiler";
18
18
  */
19
19
  export class DesmostParser extends GenericParser
20
20
  {
21
- constructor(source: string, options?: DesmostOptions)
21
+ constructor(source: string, protected options?: DesmostOptions)
22
22
  {
23
- super(options?.ignore_trailing_blanks ? source : (source + "\n"));
23
+ super(source + "\n");
24
24
  }
25
25
 
26
26
 
@@ -379,25 +379,13 @@ export class DesmostParser extends GenericParser
379
379
 
380
380
  /**
381
381
  * Pop `ctx` if it is the currently active context, returning `true` if successful.
382
- *
383
- * If `{ force: true }`, backtrack the stack and report errors for unterminated contexts.
384
382
  */
385
- function try_pop(ctx: Ctx, options?: { force: boolean }): boolean
383
+ function try_pop(ctx: Ctx): boolean
386
384
  {
387
385
  if (stack.at(-1) === ctx) {
388
386
  stack.pop();
389
387
  return true;
390
388
  }
391
- else if (options?.force) {
392
- if (stack.includes(ctx)) {
393
- /* NOTE: We could report errors for unterminated contexts, but we'll leave that for the actual evaluation - in case we get something wrong ;) */
394
- while (stack.at(-1) !== ctx) {
395
- stack.pop();
396
- }
397
-
398
- return true;
399
- }
400
- }
401
389
 
402
390
  return false;
403
391
  }
@@ -414,14 +402,13 @@ export class DesmostParser extends GenericParser
414
402
  switch (this.current) {
415
403
  case Ctx.ESCAPE: stack.push(Ctx.ESCAPE); break;
416
404
 
417
- /* NOTE: *Currently* `{}` should be ignored in all contexts except `Ctx.BLOCK`. This might change if more contexts are added in future! */
418
405
  case Char.L_BRACE:
406
+ /* NOTE: *Currently* `{}` should be ignored in all contexts except `Ctx.BLOCK`. This might change if more contexts are added in future! */
419
407
  if (top !== Ctx.BLOCK) break;
420
408
  stack.push(Ctx.BLOCK); break;
421
409
 
422
410
  case Char.R_BRACE:
423
- if (top !== Ctx.BLOCK) break;
424
- try_pop(Ctx.BLOCK, { force: true }); break;
411
+ try_pop(Ctx.BLOCK); break;
425
412
  }
426
413
 
427
414
  if (arg_type === Incantation.ArgType.OBJECT) {