@rohal12/spindle 0.52.6 → 0.52.7

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.
@@ -946,6 +946,13 @@ var UNCLOSED = -1;
946
946
  var MALFORMED = -2;
947
947
  /** A frame result: the scan stops at index `s` inside it (`STOPPED - s`). */
948
948
  var STOPPED = -3;
949
+ /**
950
+ * The result of a frame that closes at `end`. `bodyStarted`: the code inside
951
+ * started a function or class body. That body replaced the one to come, and
952
+ * is gone once the frame is closed, so after the frame no body is to come —
953
+ * a scan that skips the frame must know, as must the frames around it.
954
+ */
955
+ var frameEnd = (end, bodyStarted) => end * 2 + +bodyStarted;
949
956
  /** The closers whose search for their frame a frame result may depend on. */
950
957
  var CLOSERS = [
951
958
  ")",
@@ -1178,14 +1185,30 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1178
1185
  ]] : void 0;
1179
1186
  /**
1180
1187
  * How many function or class bodies to come the scan has started, and with
1181
- * `parens`, that count when each open frame opened. Code in a `(…)` or
1182
- * `[…]` that starts one replaces the one to come, and the new one can't
1183
- * follow once the frame is closed: none is to come then.
1188
+ * `braces` or `parens`, that count when each open frame opened. Code in a
1189
+ * frame that starts one replaces the one to come, and the new one can't
1190
+ * follow once the frame is closed: none is to come then. A scan skipping
1191
+ * the frame learns this from its result (`frameEnd`, `resume`), and counts
1192
+ * the body as started too, for the frames around it to record.
1184
1193
  */
1185
1194
  let bodyStarts = 0;
1186
- const startsAt = parens ? [0] : void 0;
1195
+ const startsAt = braces || parens ? [0] : void 0;
1187
1196
  /** A `(…)` or `[…]` frame result found in `parens`, to skip to. */
1188
1197
  let parenTo;
1198
+ /** The result of the frame at index `k`, closing at `i`. */
1199
+ const closedAt = (k) => frameEnd(i, startsAt !== void 0 && bodyStarts !== startsAt[k]);
1200
+ /**
1201
+ * Go on after a frame an earlier scan lexed, whose result (`frameEnd`) is
1202
+ * `result`: returns the index it closes at. A body started in it is gone
1203
+ * after it, with the one it replaced.
1204
+ */
1205
+ function resume(result) {
1206
+ if (result % 2 === 1) {
1207
+ pendingBody = void 0;
1208
+ bodyStarts++;
1209
+ }
1210
+ return Math.floor(result / 2);
1211
+ }
1189
1212
  /** The closer `c` looked for its frame and found index `k`. */
1190
1213
  function lookedFor(c, k) {
1191
1214
  if (!lowest) return;
@@ -1333,8 +1356,8 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1333
1356
  if (lowest) {
1334
1357
  for (let j = frames.length - 1; j >= k; j--) foldLowest(j);
1335
1358
  lowest.length = k;
1336
- startsAt.length = k;
1337
1359
  }
1360
+ if (startsAt) startsAt.length = k;
1338
1361
  frames.length = k;
1339
1362
  if (stackIds) stackIds.length = k;
1340
1363
  for (const list of Object.values(closers)) while (list.length && list[list.length - 1] >= k) list.pop();
@@ -1411,6 +1434,18 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1411
1434
  stmtNext = f.kind === "block";
1412
1435
  keyNext = f.kind === "object" || f.kind === "class";
1413
1436
  }
1437
+ /**
1438
+ * Go past frame `f` if an earlier scan lexed it: to just after it, or to
1439
+ * the end of the source when it never closes. Whether it did, or
1440
+ * MALFORMED when that scan found the code malformed.
1441
+ */
1442
+ function skipLexed(f) {
1443
+ const end = known(f);
1444
+ if (end === void 0) return false;
1445
+ if (end === MALFORMED) return MALFORMED;
1446
+ i = end === UNCLOSED ? src.length : resume(end) + 1;
1447
+ return true;
1448
+ }
1414
1449
  function openBrace() {
1415
1450
  let f;
1416
1451
  if (pendingBody?.depth === frames.length) {
@@ -1454,10 +1489,10 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1454
1489
  return;
1455
1490
  }
1456
1491
  const closed = frames[k];
1492
+ const result = closedAt(k);
1457
1493
  if (lowest && c !== "}") {
1458
1494
  for (let j = frames.length - 1; j > k; j--) foldLowest(j);
1459
- const bodyStarted = bodyStarts !== startsAt[k];
1460
- recordParen(k, i * 2 + +bodyStarted);
1495
+ recordParen(k, result);
1461
1496
  }
1462
1497
  truncate(k);
1463
1498
  if (c === "]" && !ctx.lookahead) ctx.brackets.set(closed.open, i);
@@ -1466,7 +1501,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1466
1501
  stmtNext = closed.header;
1467
1502
  } else if (c === "]") endPunct(c, false);
1468
1503
  else {
1469
- record(closed, i);
1504
+ record(closed, result);
1470
1505
  afterBrace(closed);
1471
1506
  }
1472
1507
  }
@@ -1568,23 +1603,16 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1568
1603
  } else if (ch === "`") {
1569
1604
  literal(ch, i);
1570
1605
  if (frames.length === 1) return i + 1;
1571
- record(top(), i);
1606
+ record(top(), closedAt(frames.length - 1));
1572
1607
  i++;
1573
1608
  truncate(frames.length - 1);
1574
1609
  endOperand();
1575
1610
  } else if (ch === "$" && src.charAt(i + 1) === "{") {
1576
1611
  const f = frame("expr", "}", i);
1577
1612
  f.interpolation = true;
1578
- const end = known(f);
1579
- if (end === MALFORMED) return malformed();
1580
- if (end === UNCLOSED) {
1581
- i = src.length;
1582
- break;
1583
- }
1584
- if (end !== void 0) {
1585
- i = end + 1;
1586
- continue;
1587
- }
1613
+ const skipped = skipLexed(f);
1614
+ if (skipped === MALFORMED) return malformed();
1615
+ if (skipped) continue;
1588
1616
  literal("${", i);
1589
1617
  i += 2;
1590
1618
  nesting++;
@@ -1611,14 +1639,9 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1611
1639
  if (ch === "`") {
1612
1640
  endWord();
1613
1641
  const f = frame("template", "`", i);
1614
- const end = known(f);
1615
- if (end === MALFORMED) return malformed();
1616
- if (end === UNCLOSED) {
1617
- i = src.length;
1618
- break;
1619
- }
1620
- if (end !== void 0) {
1621
- i = end + 1;
1642
+ const skipped = skipLexed(f);
1643
+ if (skipped === MALFORMED) return malformed();
1644
+ if (skipped) {
1622
1645
  endOperand();
1623
1646
  continue;
1624
1647
  }
@@ -1637,7 +1660,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1637
1660
  lookedFor("}", k);
1638
1661
  if (frames[k].interpolation) {
1639
1662
  endWord();
1640
- record(frames[k], i);
1663
+ record(frames[k], closedAt(k));
1641
1664
  truncate(k);
1642
1665
  nesting--;
1643
1666
  literal(ch, i);
@@ -1719,7 +1742,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1719
1742
  continue;
1720
1743
  }
1721
1744
  }
1722
- if (ch === outer.closer && frames.length === 1) break;
1745
+ if (ch === outer.closer && (frames.length === 1 || ch === "]" && innermost("]") === 0 && innermost("}") === 0)) break;
1723
1746
  trackCode(ch);
1724
1747
  if (skipTo) {
1725
1748
  const { frame: skipped, end } = skipTo;
@@ -1729,7 +1752,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1729
1752
  i = src.length;
1730
1753
  break;
1731
1754
  }
1732
- i = end;
1755
+ i = resume(end);
1733
1756
  afterBrace(skipped);
1734
1757
  i++;
1735
1758
  continue;
@@ -1747,8 +1770,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1747
1770
  strict.stopped = true;
1748
1771
  return STOPPED - result;
1749
1772
  }
1750
- i = Math.floor(result / 2);
1751
- if (result % 2 === 1) pendingBody = void 0;
1773
+ i = resume(result);
1752
1774
  if (skipped.closer === ")") {
1753
1775
  endPunct(")", skipped.header);
1754
1776
  stmtNext = skipped.header;
@@ -248,6 +248,13 @@ var UNCLOSED = -1;
248
248
  var MALFORMED = -2;
249
249
  /** A frame result: the scan stops at index `s` inside it (`STOPPED - s`). */
250
250
  var STOPPED = -3;
251
+ /**
252
+ * The result of a frame that closes at `end`. `bodyStarted`: the code inside
253
+ * started a function or class body. That body replaced the one to come, and
254
+ * is gone once the frame is closed, so after the frame no body is to come —
255
+ * a scan that skips the frame must know, as must the frames around it.
256
+ */
257
+ var frameEnd = (end, bodyStarted) => end * 2 + +bodyStarted;
251
258
  /** The closers whose search for their frame a frame result may depend on. */
252
259
  var CLOSERS = [
253
260
  ")",
@@ -480,14 +487,30 @@ function scan(src, handlers, start, nesting, outer, ctx) {
480
487
  ]] : void 0;
481
488
  /**
482
489
  * How many function or class bodies to come the scan has started, and with
483
- * `parens`, that count when each open frame opened. Code in a `(…)` or
484
- * `[…]` that starts one replaces the one to come, and the new one can't
485
- * follow once the frame is closed: none is to come then.
490
+ * `braces` or `parens`, that count when each open frame opened. Code in a
491
+ * frame that starts one replaces the one to come, and the new one can't
492
+ * follow once the frame is closed: none is to come then. A scan skipping
493
+ * the frame learns this from its result (`frameEnd`, `resume`), and counts
494
+ * the body as started too, for the frames around it to record.
486
495
  */
487
496
  let bodyStarts = 0;
488
- const startsAt = parens ? [0] : void 0;
497
+ const startsAt = braces || parens ? [0] : void 0;
489
498
  /** A `(…)` or `[…]` frame result found in `parens`, to skip to. */
490
499
  let parenTo;
500
+ /** The result of the frame at index `k`, closing at `i`. */
501
+ const closedAt = (k) => frameEnd(i, startsAt !== void 0 && bodyStarts !== startsAt[k]);
502
+ /**
503
+ * Go on after a frame an earlier scan lexed, whose result (`frameEnd`) is
504
+ * `result`: returns the index it closes at. A body started in it is gone
505
+ * after it, with the one it replaced.
506
+ */
507
+ function resume(result) {
508
+ if (result % 2 === 1) {
509
+ pendingBody = void 0;
510
+ bodyStarts++;
511
+ }
512
+ return Math.floor(result / 2);
513
+ }
491
514
  /** The closer `c` looked for its frame and found index `k`. */
492
515
  function lookedFor(c, k) {
493
516
  if (!lowest) return;
@@ -635,8 +658,8 @@ function scan(src, handlers, start, nesting, outer, ctx) {
635
658
  if (lowest) {
636
659
  for (let j = frames.length - 1; j >= k; j--) foldLowest(j);
637
660
  lowest.length = k;
638
- startsAt.length = k;
639
661
  }
662
+ if (startsAt) startsAt.length = k;
640
663
  frames.length = k;
641
664
  if (stackIds) stackIds.length = k;
642
665
  for (const list of Object.values(closers)) while (list.length && list[list.length - 1] >= k) list.pop();
@@ -713,6 +736,18 @@ function scan(src, handlers, start, nesting, outer, ctx) {
713
736
  stmtNext = f.kind === "block";
714
737
  keyNext = f.kind === "object" || f.kind === "class";
715
738
  }
739
+ /**
740
+ * Go past frame `f` if an earlier scan lexed it: to just after it, or to
741
+ * the end of the source when it never closes. Whether it did, or
742
+ * MALFORMED when that scan found the code malformed.
743
+ */
744
+ function skipLexed(f) {
745
+ const end = known(f);
746
+ if (end === void 0) return false;
747
+ if (end === MALFORMED) return MALFORMED;
748
+ i = end === UNCLOSED ? src.length : resume(end) + 1;
749
+ return true;
750
+ }
716
751
  function openBrace() {
717
752
  let f;
718
753
  if (pendingBody?.depth === frames.length) {
@@ -756,10 +791,10 @@ function scan(src, handlers, start, nesting, outer, ctx) {
756
791
  return;
757
792
  }
758
793
  const closed = frames[k];
794
+ const result = closedAt(k);
759
795
  if (lowest && c !== "}") {
760
796
  for (let j = frames.length - 1; j > k; j--) foldLowest(j);
761
- const bodyStarted = bodyStarts !== startsAt[k];
762
- recordParen(k, i * 2 + +bodyStarted);
797
+ recordParen(k, result);
763
798
  }
764
799
  truncate(k);
765
800
  if (c === "]" && !ctx.lookahead) ctx.brackets.set(closed.open, i);
@@ -768,7 +803,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
768
803
  stmtNext = closed.header;
769
804
  } else if (c === "]") endPunct(c, false);
770
805
  else {
771
- record(closed, i);
806
+ record(closed, result);
772
807
  afterBrace(closed);
773
808
  }
774
809
  }
@@ -870,23 +905,16 @@ function scan(src, handlers, start, nesting, outer, ctx) {
870
905
  } else if (ch === "`") {
871
906
  literal(ch, i);
872
907
  if (frames.length === 1) return i + 1;
873
- record(top(), i);
908
+ record(top(), closedAt(frames.length - 1));
874
909
  i++;
875
910
  truncate(frames.length - 1);
876
911
  endOperand();
877
912
  } else if (ch === "$" && src.charAt(i + 1) === "{") {
878
913
  const f = frame("expr", "}", i);
879
914
  f.interpolation = true;
880
- const end = known(f);
881
- if (end === MALFORMED) return malformed();
882
- if (end === UNCLOSED) {
883
- i = src.length;
884
- break;
885
- }
886
- if (end !== void 0) {
887
- i = end + 1;
888
- continue;
889
- }
915
+ const skipped = skipLexed(f);
916
+ if (skipped === MALFORMED) return malformed();
917
+ if (skipped) continue;
890
918
  literal("${", i);
891
919
  i += 2;
892
920
  nesting++;
@@ -913,14 +941,9 @@ function scan(src, handlers, start, nesting, outer, ctx) {
913
941
  if (ch === "`") {
914
942
  endWord();
915
943
  const f = frame("template", "`", i);
916
- const end = known(f);
917
- if (end === MALFORMED) return malformed();
918
- if (end === UNCLOSED) {
919
- i = src.length;
920
- break;
921
- }
922
- if (end !== void 0) {
923
- i = end + 1;
944
+ const skipped = skipLexed(f);
945
+ if (skipped === MALFORMED) return malformed();
946
+ if (skipped) {
924
947
  endOperand();
925
948
  continue;
926
949
  }
@@ -939,7 +962,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
939
962
  lookedFor("}", k);
940
963
  if (frames[k].interpolation) {
941
964
  endWord();
942
- record(frames[k], i);
965
+ record(frames[k], closedAt(k));
943
966
  truncate(k);
944
967
  nesting--;
945
968
  literal(ch, i);
@@ -1021,7 +1044,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1021
1044
  continue;
1022
1045
  }
1023
1046
  }
1024
- if (ch === outer.closer && frames.length === 1) break;
1047
+ if (ch === outer.closer && (frames.length === 1 || ch === "]" && innermost("]") === 0 && innermost("}") === 0)) break;
1025
1048
  trackCode(ch);
1026
1049
  if (skipTo) {
1027
1050
  const { frame: skipped, end } = skipTo;
@@ -1031,7 +1054,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1031
1054
  i = src.length;
1032
1055
  break;
1033
1056
  }
1034
- i = end;
1057
+ i = resume(end);
1035
1058
  afterBrace(skipped);
1036
1059
  i++;
1037
1060
  continue;
@@ -1049,8 +1072,7 @@ function scan(src, handlers, start, nesting, outer, ctx) {
1049
1072
  strict.stopped = true;
1050
1073
  return STOPPED - result;
1051
1074
  }
1052
- i = Math.floor(result / 2);
1053
- if (result % 2 === 1) pendingBody = void 0;
1075
+ i = resume(result);
1054
1076
  if (skipped.closer === ")") {
1055
1077
  endPunct(")", skipped.header);
1056
1078
  stmtNext = skipped.header;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohal12/spindle",
3
- "version": "0.52.6",
3
+ "version": "0.52.7",
4
4
  "type": "module",
5
5
  "description": "A Preact-based story format for Twine 2.",
6
6
  "license": "Unlicense",
package/src/js-lexer.ts CHANGED
@@ -397,6 +397,14 @@ const MALFORMED = -2;
397
397
  /** A frame result: the scan stops at index `s` inside it (`STOPPED - s`). */
398
398
  const STOPPED = -3;
399
399
 
400
+ /**
401
+ * The result of a frame that closes at `end`. `bodyStarted`: the code inside
402
+ * started a function or class body. That body replaced the one to come, and
403
+ * is gone once the frame is closed, so after the frame no body is to come —
404
+ * a scan that skips the frame must know, as must the frames around it.
405
+ */
406
+ const frameEnd = (end: number, bodyStarted: boolean) => end * 2 + +bodyStarted;
407
+
400
408
  /** The closers whose search for their frame a frame result may depend on. */
401
409
  const CLOSERS = [')', ']', '}'] as const;
402
410
  const FRAME_KINDS: readonly Frame['kind'][] = [
@@ -423,11 +431,13 @@ function frameKey(f: Frame): number | undefined {
423
431
  */
424
432
  export interface JsScanCache {
425
433
  /**
426
- * Where each `{…}` frame, template literal or `${…}` interpolation closes:
427
- * the index of its `}` or closing backtick, `UNCLOSED` or `MALFORMED`. The
428
- * code inside such a frame lexes the same whatever surrounds it, given
429
- * where it opens and its kind — a stray `)` or `]` inside never closes it
430
- * — so a later scan entering the same frame skips to its end.
434
+ * Where each `{…}` frame, template literal or `${…}` interpolation closes,
435
+ * as a frame result (`frameEnd`): its `}` or closing backtick, and whether
436
+ * the code inside started a function or class body; or `UNCLOSED` or
437
+ * `MALFORMED`. The code inside such a frame lexes the same whatever
438
+ * surrounds it, given where it opens and its kind — a stray `)` or `]`
439
+ * inside never closes it — so a later scan entering the same frame skips
440
+ * to its end.
431
441
  */
432
442
  braces: Map<number, number>;
433
443
  /** Look-ahead bracket matches (`ScanContext.brackets`). */
@@ -457,10 +467,8 @@ export interface JsScanCache {
457
467
  stacks: Map<string, number>;
458
468
  /**
459
469
  * How `(…)` and `[…]` frames end, by the kind of scan and then by the frame
460
- * (`parenKey`): `end * 2 + 1` if it closes at `end` and the code inside
461
- * started a function or class body (which replaced the one to come, and
462
- * can't follow once the frame is closed), `end * 2` otherwise, or
463
- * `UNCLOSED`, `MALFORMED` or `STOPPED - s`. Unlike braces, a stray closer inside may close a frame
470
+ * (`parenKey`): a frame result (`frameEnd`), or `UNCLOSED`, `MALFORMED` or
471
+ * `STOPPED - s`. Unlike braces, a stray closer inside may close a frame
464
472
  * around them, so the code inside lexes the same only around frames for
465
473
  * which the closers it met find nothing to close; the key says which
466
474
  * closers met none.
@@ -748,15 +756,34 @@ function scan(
748
756
  : undefined;
749
757
  /**
750
758
  * How many function or class bodies to come the scan has started, and with
751
- * `parens`, that count when each open frame opened. Code in a `(…)` or
752
- * `[…]` that starts one replaces the one to come, and the new one can't
753
- * follow once the frame is closed: none is to come then.
759
+ * `braces` or `parens`, that count when each open frame opened. Code in a
760
+ * frame that starts one replaces the one to come, and the new one can't
761
+ * follow once the frame is closed: none is to come then. A scan skipping
762
+ * the frame learns this from its result (`frameEnd`, `resume`), and counts
763
+ * the body as started too, for the frames around it to record.
754
764
  */
755
765
  let bodyStarts = 0;
756
- const startsAt: number[] | undefined = parens ? [0] : undefined;
766
+ const startsAt: number[] | undefined = braces || parens ? [0] : undefined;
757
767
  /** A `(…)` or `[…]` frame result found in `parens`, to skip to. */
758
768
  let parenTo: { frame: Frame; result: number } | undefined;
759
769
 
770
+ /** The result of the frame at index `k`, closing at `i`. */
771
+ const closedAt = (k: number) =>
772
+ frameEnd(i, startsAt !== undefined && bodyStarts !== startsAt[k]);
773
+
774
+ /**
775
+ * Go on after a frame an earlier scan lexed, whose result (`frameEnd`) is
776
+ * `result`: returns the index it closes at. A body started in it is gone
777
+ * after it, with the one it replaced.
778
+ */
779
+ function resume(result: number): number {
780
+ if (result % 2 === 1) {
781
+ pendingBody = undefined;
782
+ bodyStarts++;
783
+ }
784
+ return Math.floor(result / 2);
785
+ }
786
+
760
787
  /** The closer `c` looked for its frame and found index `k`. */
761
788
  function lookedFor(c: (typeof CLOSERS)[number], k: number) {
762
789
  if (!lowest) return;
@@ -933,8 +960,8 @@ function scan(
933
960
  if (lowest) {
934
961
  for (let j = frames.length - 1; j >= k; j--) foldLowest(j);
935
962
  lowest.length = k;
936
- startsAt!.length = k;
937
963
  }
964
+ if (startsAt) startsAt.length = k;
938
965
  frames.length = k;
939
966
  if (stackIds) stackIds.length = k;
940
967
  for (const list of Object.values(closers)) {
@@ -1034,6 +1061,19 @@ function scan(
1034
1061
  keyNext = f.kind === 'object' || f.kind === 'class';
1035
1062
  }
1036
1063
 
1064
+ /**
1065
+ * Go past frame `f` if an earlier scan lexed it: to just after it, or to
1066
+ * the end of the source when it never closes. Whether it did, or
1067
+ * MALFORMED when that scan found the code malformed.
1068
+ */
1069
+ function skipLexed(f: Frame): boolean | typeof MALFORMED {
1070
+ const end = known(f);
1071
+ if (end === undefined) return false;
1072
+ if (end === MALFORMED) return MALFORMED;
1073
+ i = end === UNCLOSED ? src.length : resume(end) + 1;
1074
+ return true;
1075
+ }
1076
+
1037
1077
  function openBrace() {
1038
1078
  let f: Frame;
1039
1079
  if (pendingBody?.depth === frames.length) {
@@ -1079,12 +1119,11 @@ function scan(
1079
1119
  return;
1080
1120
  }
1081
1121
  const closed = frames[k]!;
1122
+ const result = closedAt(k);
1082
1123
  if (lowest && c !== '}') {
1083
1124
  // Its entry complete, with those of the frames still open in it
1084
1125
  for (let j = frames.length - 1; j > k; j--) foldLowest(j);
1085
- // Whether code in it started a function or class body: none is to come
1086
- const bodyStarted = bodyStarts !== startsAt![k];
1087
- recordParen(k, i * 2 + +bodyStarted);
1126
+ recordParen(k, result);
1088
1127
  }
1089
1128
  truncate(k);
1090
1129
  if (c === ']' && !ctx.lookahead) ctx.brackets.set(closed.open, i);
@@ -1095,7 +1134,7 @@ function scan(
1095
1134
  } else if (c === ']') {
1096
1135
  endPunct(c, false);
1097
1136
  } else {
1098
- record(closed, i);
1137
+ record(closed, result);
1099
1138
  afterBrace(closed);
1100
1139
  }
1101
1140
  }
@@ -1214,24 +1253,17 @@ function scan(
1214
1253
  } else if (ch === '`') {
1215
1254
  literal(ch, i);
1216
1255
  if (frames.length === 1) return i + 1; // the end of `lexTemplate`
1217
- record(top(), i);
1256
+ record(top(), closedAt(frames.length - 1));
1218
1257
  i++;
1219
1258
  truncate(frames.length - 1);
1220
1259
  endOperand();
1221
1260
  } else if (ch === '$' && src.charAt(i + 1) === '{') {
1222
1261
  const f = frame('expr', '}', i);
1223
1262
  f.interpolation = true;
1224
- const end = known(f);
1225
- if (end === MALFORMED) return malformed();
1226
- if (end === UNCLOSED) {
1227
- i = src.length;
1228
- break;
1229
- }
1230
- if (end !== undefined) {
1231
- // An interpolation an earlier scan lexed: on with the text after it
1232
- i = end + 1;
1233
- continue;
1234
- }
1263
+ // An interpolation an earlier scan lexed: on with the text after it
1264
+ const skipped = skipLexed(f);
1265
+ if (skipped === MALFORMED) return malformed();
1266
+ if (skipped) continue;
1235
1267
  literal('${', i);
1236
1268
  i += 2;
1237
1269
  nesting++;
@@ -1261,15 +1293,10 @@ function scan(
1261
1293
  if (ch === '`') {
1262
1294
  endWord();
1263
1295
  const f = frame('template', '`', i);
1264
- const end = known(f);
1265
- if (end === MALFORMED) return malformed();
1266
- if (end === UNCLOSED) {
1267
- i = src.length;
1268
- break;
1269
- }
1270
- if (end !== undefined) {
1271
- // A template literal an earlier scan lexed: on after it
1272
- i = end + 1;
1296
+ // A template literal an earlier scan lexed: on after it
1297
+ const skipped = skipLexed(f);
1298
+ if (skipped === MALFORMED) return malformed();
1299
+ if (skipped) {
1273
1300
  endOperand();
1274
1301
  continue;
1275
1302
  }
@@ -1291,7 +1318,7 @@ function scan(
1291
1318
  lookedFor('}', k);
1292
1319
  if (frames[k]!.interpolation) {
1293
1320
  endWord();
1294
- record(frames[k]!, i);
1321
+ record(frames[k]!, closedAt(k));
1295
1322
  truncate(k);
1296
1323
  nesting--;
1297
1324
  literal(ch, i);
@@ -1399,8 +1426,17 @@ function scan(
1399
1426
  }
1400
1427
  }
1401
1428
 
1402
- // End of an `outer` square bracket
1403
- if (ch === outer.closer && frames.length === 1) break;
1429
+ // End of an `outer` square bracket: a `]` that closes no `[` within the
1430
+ // innermost braces closes it, over any `(` left open in it, as it closes
1431
+ // a nested `[`. Look-ahead scans record where the `[`s nested in theirs
1432
+ // end (`ctx.brackets`), so one starting at a `[` must find the same.
1433
+ if (
1434
+ ch === outer.closer &&
1435
+ (frames.length === 1 ||
1436
+ (ch === ']' && innermost(']') === 0 && innermost('}') === 0))
1437
+ ) {
1438
+ break;
1439
+ }
1404
1440
 
1405
1441
  // Regular code character
1406
1442
  trackCode(ch);
@@ -1413,7 +1449,7 @@ function scan(
1413
1449
  i = src.length;
1414
1450
  break;
1415
1451
  }
1416
- i = end;
1452
+ i = resume(end);
1417
1453
  afterBrace(skipped);
1418
1454
  i++;
1419
1455
  continue;
@@ -1432,8 +1468,7 @@ function scan(
1432
1468
  strict!.stopped = true;
1433
1469
  return STOPPED - result;
1434
1470
  }
1435
- i = Math.floor(result / 2);
1436
- if (result % 2 === 1) pendingBody = undefined;
1471
+ i = resume(result);
1437
1472
  if (skipped.closer === ')') {
1438
1473
  endPunct(')', skipped.header);
1439
1474
  stmtNext = skipped.header;