desmost 0.1.2 → 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 +23 -0
- package/dist/compiler/evaluate.js.map +1 -1
- package/dist/errors.d.ts +6 -7
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -3
- package/dist/errors.js.map +1 -1
- package/dist/magic/expr/latex.d.ts.map +1 -1
- package/dist/magic/expr/text.d.ts.map +1 -1
- package/dist/magic/global/dark-mode.d.ts.map +1 -1
- package/dist/magic/global/desmos.d.ts.map +1 -1
- package/dist/magic/global/viewport.d.ts.map +1 -1
- package/dist/magic/incantation.d.ts.map +1 -1
- package/dist/magic/incantation.js.map +1 -1
- package/dist/magic/local/anim.d.ts.map +1 -1
- package/dist/magic/local/colour.d.ts.map +1 -1
- package/dist/magic/local/colour.js.map +1 -1
- package/dist/magic/local/dashed.d.ts.map +1 -1
- package/dist/magic/local/fill.d.ts.map +1 -1
- package/dist/magic/local/hide.d.ts.map +1 -1
- package/dist/magic/local/line.d.ts.map +1 -1
- package/dist/magic/local/no-line.d.ts.map +1 -1
- package/dist/magic/local/point.d.ts.map +1 -1
- package/dist/magic/local/secret.d.ts.map +1 -1
- package/dist/magic/local/slider.d.ts.map +1 -1
- package/dist/parser/ast.js.map +1 -1
- package/dist/parser/desmost-parser.d.ts +4 -4
- package/dist/parser/desmost-parser.d.ts.map +1 -1
- package/dist/parser/desmost-parser.js +50 -66
- package/dist/parser/desmost-parser.js.map +1 -1
- package/dist/parser/generic-parser.d.ts +5 -4
- package/dist/parser/generic-parser.d.ts.map +1 -1
- package/dist/parser/generic-parser.js +20 -23
- package/dist/parser/generic-parser.js.map +1 -1
- package/package.json +9 -3
- package/src/compiler/evaluate.ts +47 -0
- package/src/compiler/options.ts +8 -0
- package/src/errors.ts +6 -6
- package/src/magic/local/index.ts +6 -4
- package/src/magic/local/label.ts +58 -0
- package/src/parser/desmost-parser.ts +66 -96
- package/src/parser/generic-parser.ts +33 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generic-parser.js","sourceRoot":"","sources":["../../src/parser/generic-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"generic-parser.js","sourceRoot":"","sources":["../../src/parser/generic-parser.ts"],"names":[],"mappings":"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"}
|
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.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
|
".": {
|
|
@@ -51,6 +52,11 @@
|
|
|
51
52
|
"json5": "^2.2"
|
|
52
53
|
},
|
|
53
54
|
|
|
55
|
+
"peerDependencies":
|
|
56
|
+
{
|
|
57
|
+
"@types/desmos": "1.11"
|
|
58
|
+
},
|
|
59
|
+
|
|
54
60
|
"devDependencies":
|
|
55
61
|
{
|
|
56
62
|
"@types/desmos": "1.11",
|
|
@@ -58,7 +64,7 @@
|
|
|
58
64
|
"oxlint": "^1.78",
|
|
59
65
|
"oxlint-tsgolint": "^7.0",
|
|
60
66
|
"tsc-alias": "^1.9",
|
|
61
|
-
"typescript": "^
|
|
67
|
+
"typescript": "^6.0",
|
|
62
68
|
"vitest": "^4.0"
|
|
63
69
|
}
|
|
64
70
|
}
|
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/errors.ts
CHANGED
|
@@ -29,18 +29,18 @@ export namespace UnrecoverableError
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
/** The compiler encountered a non-fatal recoverable failure (usually during speculative parsing), which only results in internal backtracking. */
|
|
32
|
-
export
|
|
32
|
+
export const FAIL = Symbol("fail");
|
|
33
33
|
|
|
34
|
+
/** The compiler encountered a non-fatal recoverable failure (usually during speculative parsing), which only results in internal backtracking. */
|
|
35
|
+
export type RecoverableFail = typeof FAIL & { readonly __brand?: unique symbol };
|
|
34
36
|
|
|
35
|
-
/** Indicates that a function may throw a `RecoverableFail`. */
|
|
36
|
-
export type Recoverable<Result> = Result | Result & { readonly __brand?: unique symbol };
|
|
37
37
|
|
|
38
38
|
/** Indicates that a function may throw an `UnrecoverableError`. */
|
|
39
|
-
export type Unrecoverable<Result> = Result | Result & { readonly __brand?: unique symbol }
|
|
39
|
+
export type Unrecoverable<Result> = Result | Result & { readonly __brand?: unique symbol };
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Indicates that a function may
|
|
42
|
+
* Indicates that a function may either return a `RecoverableFail` *or* throw an `UnrecoverableError`.
|
|
43
43
|
*
|
|
44
44
|
* This is used in potentially ambiguous cases. For instance, the user might have invoked an unknown incantation, in which case we might want to `RecoverableFail` and fallback to parsing it as LaTeX; on the other hand, if they invoked a valid incantation, but did not supply the required argument, that's a definite `UnrecoverableError`.
|
|
45
45
|
*/
|
|
46
|
-
export type MaybeRecoverable<Result> =
|
|
46
|
+
export type MaybeRecoverable<Result> = RecoverableFail | Unrecoverable<Result>;
|
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
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { GenericParser } from "./generic-parser";
|
|
2
2
|
import { Ast } from "./ast";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import type {
|
|
4
|
+
import { FAIL, UnrecoverableError } from "../errors";
|
|
5
|
+
import type { RecoverableFail, Unrecoverable, MaybeRecoverable } from "../errors";
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
Incantation, ArgIncantation,
|
|
@@ -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
|
|
|
@@ -48,6 +48,10 @@ export class DesmostParser extends GenericParser
|
|
|
48
48
|
// 1+ locals + 1 expr
|
|
49
49
|
let incantations = r.local;
|
|
50
50
|
|
|
51
|
+
if (incantations.length > 0) {
|
|
52
|
+
this.parse_sep();
|
|
53
|
+
}
|
|
54
|
+
|
|
51
55
|
var expr = this.parse_post_sep();
|
|
52
56
|
|
|
53
57
|
for (let invocation of incantations) {
|
|
@@ -80,34 +84,23 @@ export class DesmostParser extends GenericParser
|
|
|
80
84
|
>
|
|
81
85
|
{
|
|
82
86
|
// 1 global incantation
|
|
83
|
-
|
|
84
|
-
let r = this.try_parse_global_incantation();
|
|
87
|
+
let r = this.try_parse_global_incantation();
|
|
85
88
|
|
|
89
|
+
if (r !== FAIL) {
|
|
86
90
|
this.consume_end_of_block(
|
|
87
91
|
`Received excess input after global incantation /${r.incantation.identifier}`
|
|
88
92
|
);
|
|
89
93
|
|
|
90
94
|
return { global: r };
|
|
91
95
|
}
|
|
92
|
-
catch (e) {
|
|
93
|
-
if (!(e instanceof RecoverableFail)) throw e;
|
|
94
|
-
}
|
|
95
96
|
|
|
97
|
+
// 1+ local incantations
|
|
96
98
|
let incantations = [];
|
|
97
99
|
|
|
98
|
-
// 1+ local incantations
|
|
99
100
|
while (this.current === "/") {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
catch (e) {
|
|
104
|
-
if (!(e instanceof RecoverableFail)) throw e;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (incantations.length > 0) {
|
|
110
|
-
this.parse_sep();
|
|
101
|
+
let invocation = this.try_parse_local_incantation();
|
|
102
|
+
if (invocation === FAIL) break;
|
|
103
|
+
incantations.push(invocation);
|
|
111
104
|
}
|
|
112
105
|
|
|
113
106
|
return { local: incantations };
|
|
@@ -122,6 +115,7 @@ export class DesmostParser extends GenericParser
|
|
|
122
115
|
this.consume("::",
|
|
123
116
|
`Expected \`::\` separator between local incantations and expression, but found: \`${this.preview()}\``
|
|
124
117
|
);
|
|
118
|
+
this.consume_whitespace();
|
|
125
119
|
}
|
|
126
120
|
|
|
127
121
|
/**
|
|
@@ -145,11 +139,9 @@ export class DesmostParser extends GenericParser
|
|
|
145
139
|
|
|
146
140
|
// expr incantation
|
|
147
141
|
case "/":
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
catch (e) {
|
|
152
|
-
if (!(e instanceof RecoverableFail)) throw e;
|
|
142
|
+
let incantation = this.try_parse_expr_incantation();
|
|
143
|
+
if (incantation !== FAIL) return incantation;
|
|
144
|
+
else {
|
|
153
145
|
// plain LaTeX (fallback)
|
|
154
146
|
return this.parse_latex_line();
|
|
155
147
|
}
|
|
@@ -176,7 +168,7 @@ export class DesmostParser extends GenericParser
|
|
|
176
168
|
|
|
177
169
|
return {
|
|
178
170
|
kind: Ast.Kind.EXPRESSION,
|
|
179
|
-
data: { latex: this.source.slice(init, this.i) },
|
|
171
|
+
data: { latex: this.source.slice(init, this.i).trimEnd() },
|
|
180
172
|
incantations: [],
|
|
181
173
|
};
|
|
182
174
|
}
|
|
@@ -186,23 +178,18 @@ export class DesmostParser extends GenericParser
|
|
|
186
178
|
* Attempt to parse a global incantation invocation.
|
|
187
179
|
*/
|
|
188
180
|
try_parse_global_incantation():
|
|
189
|
-
Recoverable<
|
|
190
181
|
| Ast.IncantationInvocation<GLOBAL>
|
|
191
182
|
| Ast.InvalidInvocation
|
|
192
|
-
|
|
183
|
+
| RecoverableFail
|
|
193
184
|
{
|
|
194
185
|
let init = this.i;
|
|
195
186
|
|
|
196
|
-
this.try_consume("/");
|
|
187
|
+
if (this.try_consume("/") === FAIL) return FAIL;
|
|
197
188
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (e instanceof RecoverableFail) {
|
|
203
|
-
this.i = init;
|
|
204
|
-
}
|
|
205
|
-
throw e;
|
|
189
|
+
let incantation = this.try_parse_identifier(GLOBAL_INCANTATIONS);
|
|
190
|
+
if (incantation === FAIL) {
|
|
191
|
+
this.i = init;
|
|
192
|
+
return FAIL;
|
|
206
193
|
}
|
|
207
194
|
|
|
208
195
|
let data = undefined;
|
|
@@ -224,6 +211,7 @@ export class DesmostParser extends GenericParser
|
|
|
224
211
|
|
|
225
212
|
this.consume_spaces();
|
|
226
213
|
|
|
214
|
+
// @ts-expect-error: FIXME
|
|
227
215
|
return {
|
|
228
216
|
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
229
217
|
incantation,
|
|
@@ -235,23 +223,18 @@ export class DesmostParser extends GenericParser
|
|
|
235
223
|
* Attempt to parse a local incantation invocation.
|
|
236
224
|
*/
|
|
237
225
|
try_parse_local_incantation():
|
|
238
|
-
Recoverable<
|
|
239
226
|
| Ast.IncantationInvocation<LOCAL>
|
|
240
227
|
| Ast.InvalidInvocation
|
|
241
|
-
|
|
228
|
+
| RecoverableFail
|
|
242
229
|
{
|
|
243
230
|
let init = this.i;
|
|
244
231
|
|
|
245
|
-
this.try_consume("/");
|
|
232
|
+
if (this.try_consume("/") === FAIL) return FAIL;
|
|
246
233
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (e instanceof RecoverableFail) {
|
|
252
|
-
this.i = init;
|
|
253
|
-
}
|
|
254
|
-
throw e;
|
|
234
|
+
let incantation = this.try_parse_identifier(LOCAL_INCANTATIONS);
|
|
235
|
+
if (incantation === FAIL) {
|
|
236
|
+
this.i = init;
|
|
237
|
+
return FAIL;
|
|
255
238
|
}
|
|
256
239
|
|
|
257
240
|
let arg_raw = undefined;
|
|
@@ -273,6 +256,7 @@ export class DesmostParser extends GenericParser
|
|
|
273
256
|
|
|
274
257
|
this.consume_whitespace();
|
|
275
258
|
|
|
259
|
+
// @ts-expect-error: FIXME
|
|
276
260
|
return {
|
|
277
261
|
kind: Ast.Kind.INCANTATION_INVOCATION,
|
|
278
262
|
incantation,
|
|
@@ -282,23 +266,18 @@ export class DesmostParser extends GenericParser
|
|
|
282
266
|
|
|
283
267
|
try_parse_expr_incantation(): MaybeRecoverable<Ast.Expression>
|
|
284
268
|
{
|
|
285
|
-
// TODO refactor with `backtrack()` helper
|
|
286
269
|
let init = this.i;
|
|
287
270
|
|
|
288
|
-
this.try_consume("/");
|
|
271
|
+
if (this.try_consume("/") === FAIL) return FAIL;
|
|
289
272
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// TODO maybe flag to user
|
|
296
|
-
this.i = init;
|
|
297
|
-
}
|
|
298
|
-
throw e;
|
|
273
|
+
let incantation = this.try_parse_identifier(EXPR_INCANTATIONS);
|
|
274
|
+
if (incantation === FAIL) {
|
|
275
|
+
// TODO maybe flag to user
|
|
276
|
+
this.i = init;
|
|
277
|
+
return FAIL;
|
|
299
278
|
}
|
|
300
279
|
|
|
301
|
-
let arg_raw = this.parse_incantation_arg(incantation.arg_type);
|
|
280
|
+
let arg_raw = this.parse_incantation_arg((incantation as ArgIncantation<EXPR>).arg_type);
|
|
302
281
|
|
|
303
282
|
let data = {};
|
|
304
283
|
incantation.apply(data, arg_raw);
|
|
@@ -315,30 +294,20 @@ export class DesmostParser extends GenericParser
|
|
|
315
294
|
*/
|
|
316
295
|
try_parse_identifier<Effect extends Incantation.Effect>(
|
|
317
296
|
incantations: Incantation<Effect>[]
|
|
318
|
-
):
|
|
297
|
+
): Incantation<Effect> | RecoverableFail
|
|
319
298
|
{
|
|
320
299
|
for (let incantation of incantations) {
|
|
321
300
|
// yeah the duplication here is a little meh, unfortunately needing `return` means we can't extract it into a helper
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return incantation;
|
|
325
|
-
}
|
|
326
|
-
catch (e) {
|
|
327
|
-
if (!(e instanceof RecoverableFail)) throw e;
|
|
328
|
-
}
|
|
301
|
+
let r = this.try_consume(incantation.identifier);
|
|
302
|
+
if (r !== FAIL) return incantation;
|
|
329
303
|
|
|
330
304
|
if (incantation.alias != undefined) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return incantation;
|
|
334
|
-
}
|
|
335
|
-
catch (e) {
|
|
336
|
-
if (!(e instanceof RecoverableFail)) throw e;
|
|
337
|
-
}
|
|
305
|
+
let r = this.try_consume(incantation.alias);
|
|
306
|
+
if (r !== FAIL) return incantation;
|
|
338
307
|
}
|
|
339
308
|
}
|
|
340
309
|
|
|
341
|
-
|
|
310
|
+
return FAIL;
|
|
342
311
|
}
|
|
343
312
|
|
|
344
313
|
/**
|
|
@@ -410,25 +379,13 @@ export class DesmostParser extends GenericParser
|
|
|
410
379
|
|
|
411
380
|
/**
|
|
412
381
|
* Pop `ctx` if it is the currently active context, returning `true` if successful.
|
|
413
|
-
*
|
|
414
|
-
* If `{ force: true }`, backtrack the stack and report errors for unterminated contexts.
|
|
415
382
|
*/
|
|
416
|
-
function try_pop(ctx: Ctx
|
|
383
|
+
function try_pop(ctx: Ctx): boolean
|
|
417
384
|
{
|
|
418
385
|
if (stack.at(-1) === ctx) {
|
|
419
386
|
stack.pop();
|
|
420
387
|
return true;
|
|
421
388
|
}
|
|
422
|
-
else if (options?.force) {
|
|
423
|
-
if (stack.includes(ctx)) {
|
|
424
|
-
/* NOTE: We could report errors for unterminated contexts, but we'll leave that for the actual evaluation - in case we get something wrong ;) */
|
|
425
|
-
while (stack.at(-1) !== ctx) {
|
|
426
|
-
stack.pop();
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return true;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
389
|
|
|
433
390
|
return false;
|
|
434
391
|
}
|
|
@@ -445,21 +402,34 @@ export class DesmostParser extends GenericParser
|
|
|
445
402
|
switch (this.current) {
|
|
446
403
|
case Ctx.ESCAPE: stack.push(Ctx.ESCAPE); break;
|
|
447
404
|
|
|
448
|
-
/* NOTE: *Currently* `{}` should be ignored in all contexts except `Ctx.BLOCK`. This might change if more contexts are added in future! */
|
|
449
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! */
|
|
450
407
|
if (top !== Ctx.BLOCK) break;
|
|
451
408
|
stack.push(Ctx.BLOCK); break;
|
|
452
409
|
|
|
453
410
|
case Char.R_BRACE:
|
|
454
|
-
|
|
455
|
-
try_pop(Ctx.BLOCK, { force: true }); break;
|
|
411
|
+
try_pop(Ctx.BLOCK); break;
|
|
456
412
|
}
|
|
457
413
|
|
|
458
414
|
if (arg_type === Incantation.ArgType.OBJECT) {
|
|
459
415
|
switch (this.current) {
|
|
460
|
-
case Ctx.STR_1:
|
|
461
|
-
|
|
462
|
-
|
|
416
|
+
case Ctx.STR_1:
|
|
417
|
+
if (top === Ctx.STR_2) break;
|
|
418
|
+
if (top === Ctx.STR_F) break;
|
|
419
|
+
try_pop(Ctx.STR_1) || stack.push(Ctx.STR_1);
|
|
420
|
+
break;
|
|
421
|
+
|
|
422
|
+
case Ctx.STR_2:
|
|
423
|
+
if (top === Ctx.STR_1) break;
|
|
424
|
+
if (top === Ctx.STR_F) break;
|
|
425
|
+
try_pop(Ctx.STR_2) || stack.push(Ctx.STR_2);
|
|
426
|
+
break;
|
|
427
|
+
|
|
428
|
+
case Ctx.STR_F:
|
|
429
|
+
if (top === Ctx.STR_1) break;
|
|
430
|
+
if (top === Ctx.STR_2) break;
|
|
431
|
+
try_pop(Ctx.STR_F) || stack.push(Ctx.STR_F);
|
|
432
|
+
break;
|
|
463
433
|
}
|
|
464
434
|
}
|
|
465
435
|
|