desmost 0.2.0 → 0.3.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.
- package/README.md +23 -0
- package/package.json +3 -2
- package/src/compiler/evaluate.ts +47 -0
- package/src/compiler/options.ts +8 -0
- package/src/magic/local/index.ts +6 -4
- package/src/magic/local/label.ts +58 -0
- package/src/parser/desmost-parser.ts +5 -18
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.
|
|
4
|
+
"version": "0.3.1",
|
|
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
|
".": {
|
package/src/compiler/evaluate.ts
CHANGED
|
@@ -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>,
|
package/src/compiler/options.ts
CHANGED
|
@@ -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>;
|
package/src/magic/local/index.ts
CHANGED
|
@@ -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
|
|
7
|
-
import { HideIncantation
|
|
8
|
-
import {
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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) {
|