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.
Files changed (41) hide show
  1. package/README.md +23 -0
  2. package/dist/compiler/evaluate.js.map +1 -1
  3. package/dist/errors.d.ts +6 -7
  4. package/dist/errors.d.ts.map +1 -1
  5. package/dist/errors.js +1 -3
  6. package/dist/errors.js.map +1 -1
  7. package/dist/magic/expr/latex.d.ts.map +1 -1
  8. package/dist/magic/expr/text.d.ts.map +1 -1
  9. package/dist/magic/global/dark-mode.d.ts.map +1 -1
  10. package/dist/magic/global/desmos.d.ts.map +1 -1
  11. package/dist/magic/global/viewport.d.ts.map +1 -1
  12. package/dist/magic/incantation.d.ts.map +1 -1
  13. package/dist/magic/incantation.js.map +1 -1
  14. package/dist/magic/local/anim.d.ts.map +1 -1
  15. package/dist/magic/local/colour.d.ts.map +1 -1
  16. package/dist/magic/local/colour.js.map +1 -1
  17. package/dist/magic/local/dashed.d.ts.map +1 -1
  18. package/dist/magic/local/fill.d.ts.map +1 -1
  19. package/dist/magic/local/hide.d.ts.map +1 -1
  20. package/dist/magic/local/line.d.ts.map +1 -1
  21. package/dist/magic/local/no-line.d.ts.map +1 -1
  22. package/dist/magic/local/point.d.ts.map +1 -1
  23. package/dist/magic/local/secret.d.ts.map +1 -1
  24. package/dist/magic/local/slider.d.ts.map +1 -1
  25. package/dist/parser/ast.js.map +1 -1
  26. package/dist/parser/desmost-parser.d.ts +4 -4
  27. package/dist/parser/desmost-parser.d.ts.map +1 -1
  28. package/dist/parser/desmost-parser.js +50 -66
  29. package/dist/parser/desmost-parser.js.map +1 -1
  30. package/dist/parser/generic-parser.d.ts +5 -4
  31. package/dist/parser/generic-parser.d.ts.map +1 -1
  32. package/dist/parser/generic-parser.js +20 -23
  33. package/dist/parser/generic-parser.js.map +1 -1
  34. package/package.json +9 -3
  35. package/src/compiler/evaluate.ts +47 -0
  36. package/src/compiler/options.ts +8 -0
  37. package/src/errors.ts +6 -6
  38. package/src/magic/local/index.ts +6 -4
  39. package/src/magic/local/label.ts +58 -0
  40. package/src/parser/desmost-parser.ts +66 -96
  41. 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":"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"}
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.1.2",
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": "^7.0",
67
+ "typescript": "^6.0",
62
68
  "vitest": "^4.0"
63
69
  }
64
70
  }
@@ -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>;
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 class RecoverableFail {}
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 throw either a `RecoverableFail` *or* an `UnrecoverableError`.
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> = Recoverable<Result> | Unrecoverable<Result>;
46
+ export type MaybeRecoverable<Result> = RecoverableFail | Unrecoverable<Result>;
@@ -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
+ }
@@ -1,8 +1,8 @@
1
1
  import { GenericParser } from "./generic-parser";
2
2
  import { Ast } from "./ast";
3
3
 
4
- import { RecoverableFail, UnrecoverableError } from "../errors";
5
- import type { Recoverable, Unrecoverable, MaybeRecoverable } from "../errors";
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(options?.ignore_trailing_blanks ? source : (source + "\n"));
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
- try {
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
- try {
101
- incantations.push(this.try_parse_local_incantation());
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
- try {
149
- return this.try_parse_expr_incantation();
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
- try {
199
- var incantation = this.try_parse_identifier(GLOBAL_INCANTATIONS);
200
- }
201
- catch (e) {
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
- try {
248
- var incantation = this.try_parse_identifier(LOCAL_INCANTATIONS);
249
- }
250
- catch (e) {
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
- try {
291
- var incantation = this.try_parse_identifier(EXPR_INCANTATIONS) as ArgIncantation<EXPR>;
292
- }
293
- catch (e) {
294
- if (e instanceof RecoverableFail) {
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
- ): Recoverable<Incantation<Effect>>
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
- try {
323
- this.try_consume(incantation.identifier);
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
- try {
332
- this.try_consume(incantation.alias);
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
- throw new RecoverableFail();
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, options?: { force: boolean }): boolean
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
- if (top !== Ctx.BLOCK) break;
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: try_pop(Ctx.STR_1) || stack.push(Ctx.STR_1); break;
461
- case Ctx.STR_2: try_pop(Ctx.STR_2) || stack.push(Ctx.STR_2); break;
462
- case Ctx.STR_F: try_pop(Ctx.STR_F) || stack.push(Ctx.STR_F); break;
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