@hyperfixi/engine 7.5.0 → 7.6.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/dist/common.js CHANGED
@@ -1594,7 +1594,230 @@
1594
1594
  return indirect(p, node);
1595
1595
  }
1596
1596
 
1597
+ // src/parser.ts
1598
+ var ParseError = class extends Error {
1599
+ constructor(message, token, source, expected) {
1600
+ super(message);
1601
+ this.token = token;
1602
+ this.source = source;
1603
+ this.expected = expected;
1604
+ }
1605
+ /** What the author wrote, when a plugin rewrote the script before it was parsed. */
1606
+ written;
1607
+ };
1608
+ var createGrammar = () => ({
1609
+ commands: /* @__PURE__ */ Object.create(null),
1610
+ fallbacks: /* @__PURE__ */ Object.create(null),
1611
+ features: /* @__PURE__ */ Object.create(null),
1612
+ leaves: [],
1613
+ indirects: [],
1614
+ unaries: [],
1615
+ postfixes: [],
1616
+ collections: []
1617
+ });
1618
+ var Parser = class _Parser {
1619
+ constructor(g, tokens, src) {
1620
+ this.g = g;
1621
+ this.tokens = tokens;
1622
+ this.src = src;
1623
+ const end = src.length;
1624
+ this.eof = { type: "EOF", value: "<<<EOF>>>", start: end, end, line: 0, column: 0 };
1625
+ this.last = this.eof;
1626
+ this.skipWs();
1627
+ }
1628
+ i = 0;
1629
+ follows = [];
1630
+ eof;
1631
+ /** The last token consumed by a match. */
1632
+ last;
1633
+ child(tokens, src) {
1634
+ return new _Parser(this.g, tokens, src);
1635
+ }
1636
+ // ----- access ------------------------------------------------------------
1637
+ /** The nth token from here; whitespace is skipped unless `ws`. */
1638
+ tok(n = 0, ws = false) {
1639
+ let j = this.i;
1640
+ for (; ; ) {
1641
+ if (!ws) while (this.tokens[j]?.type === "WHITESPACE") j++;
1642
+ if (n-- === 0) return this.tokens[j] ?? this.eof;
1643
+ j++;
1644
+ }
1645
+ }
1646
+ cur() {
1647
+ return this.tokens[this.i] ?? this.eof;
1648
+ }
1649
+ hasMore() {
1650
+ return this.i < this.tokens.length;
1651
+ }
1652
+ /** Start offset of the current token — what a rule records as its node's `start`. */
1653
+ pos() {
1654
+ return this.cur().start;
1655
+ }
1656
+ /** Where the parser is, for `reset`: a command's fallback reads again from here. */
1657
+ mark() {
1658
+ return { i: this.i, last: this.last, follows: this.follows.length };
1659
+ }
1660
+ reset(mark2) {
1661
+ this.i = mark2.i;
1662
+ this.last = mark2.last;
1663
+ this.follows.length = mark2.follows;
1664
+ }
1665
+ /** End offset of the last consumed token — a node's `end`. */
1666
+ endPos() {
1667
+ return this.last.end;
1668
+ }
1669
+ text(node) {
1670
+ return this.src.substring(node.start, node.end);
1671
+ }
1672
+ // ----- matching ----------------------------------------------------------
1673
+ match(value, type = "IDENTIFIER") {
1674
+ if (this.follows.includes(value)) return;
1675
+ const t = this.cur();
1676
+ if (t.value === value && t.type === type) return this.consume();
1677
+ }
1678
+ matchOp(value) {
1679
+ const t = this.cur();
1680
+ if (t.op && t.value === value) return this.consume();
1681
+ }
1682
+ matchType(...types) {
1683
+ if (types.includes(this.cur().type)) return this.consume();
1684
+ }
1685
+ matchAny(...values) {
1686
+ for (const value of values) {
1687
+ const t = this.match(value);
1688
+ if (t) return t;
1689
+ }
1690
+ }
1691
+ matchAnyOp(...values) {
1692
+ for (const value of values) {
1693
+ const t = this.matchOp(value);
1694
+ if (t) return t;
1695
+ }
1696
+ }
1697
+ req(value) {
1698
+ return this.match(value) ?? this.expected(value);
1699
+ }
1700
+ reqOp(value) {
1701
+ return this.matchOp(value) ?? this.expected(value);
1702
+ }
1703
+ reqType(...types) {
1704
+ return this.matchType(...types) ?? this.expected(...types);
1705
+ }
1706
+ /** Is the nth token from here this identifier (or this `type`)? Consumes nothing. */
1707
+ peek(value, n = 0, type = "IDENTIFIER") {
1708
+ const t = this.tok(n);
1709
+ if (t.value === value && t.type === type) return t;
1710
+ }
1711
+ // ----- consuming ---------------------------------------------------------
1712
+ consume() {
1713
+ if (!this.hasMore()) return this.eof;
1714
+ this.last = this.tokens[this.i++];
1715
+ this.skipWs();
1716
+ return this.last;
1717
+ }
1718
+ skipWs() {
1719
+ while (this.tokens[this.i]?.type === "WHITESPACE") this.i++;
1720
+ }
1721
+ /** Raw tokens (whitespace included) up to a token with this value or type. */
1722
+ consumeUntil(value, type) {
1723
+ const out = [];
1724
+ let t = this.cur();
1725
+ while ((type == null || t.type !== type) && (value == null || t.value !== value) && t.type !== "EOF") {
1726
+ out.push(t);
1727
+ this.i++;
1728
+ t = this.cur();
1729
+ }
1730
+ this.skipWs();
1731
+ return out;
1732
+ }
1733
+ /** The whitespace just before the current token, if any. */
1734
+ lastWs() {
1735
+ const t = this.tokens[this.i - 1];
1736
+ return t?.type === "WHITESPACE" ? t.value : "";
1737
+ }
1738
+ // ----- follow set: words a nested rule must leave for its caller ----------
1739
+ pushFollow(...words) {
1740
+ this.follows.push(...words);
1741
+ return words.length;
1742
+ }
1743
+ popFollow(count = 1) {
1744
+ this.follows.length -= count;
1745
+ }
1746
+ /** Parse with `words` reserved for the caller, e.g. the `to` of `set x to y`. */
1747
+ withFollow(words, f) {
1748
+ const count = this.pushFollow(...words);
1749
+ try {
1750
+ return f();
1751
+ } finally {
1752
+ this.popFollow(count);
1753
+ }
1754
+ }
1755
+ /** Parse with no reserved words — inside parentheses the outer rule's words are free again. */
1756
+ withoutFollows(f) {
1757
+ const saved = this.follows;
1758
+ this.follows = [];
1759
+ try {
1760
+ return f();
1761
+ } finally {
1762
+ this.follows = saved;
1763
+ }
1764
+ }
1765
+ // ----- errors ------------------------------------------------------------
1766
+ err(message, expected) {
1767
+ const token = this.cur();
1768
+ throw new ParseError(message ?? "Unexpected Token : " + token.value, token, this.src, expected);
1769
+ }
1770
+ expected(...expected) {
1771
+ this.err(
1772
+ expected.length === 1 ? `Expected '${expected[0]}' but found '${this.cur().value}'` : "Expected one of: " + expected.map((e) => `'${e}'`).join(", "),
1773
+ expected
1774
+ );
1775
+ }
1776
+ // ----- statement boundaries ------------------------------------------------
1777
+ commandStart(token) {
1778
+ return token.value in this.g.commands;
1779
+ }
1780
+ featureStart(token) {
1781
+ return token.value in this.g.features;
1782
+ }
1783
+ commandBoundary(token) {
1784
+ return ["end", "then", "else", "otherwise", ")"].includes(token.value) || this.commandStart(token) || this.featureStart(token) || token.type === "EOF";
1785
+ }
1786
+ };
1787
+ function formatError(e) {
1788
+ const lines = e.source.split("\n");
1789
+ const index = e.token.line ? e.token.line - 1 : lines.length - 1;
1790
+ const line = lines[index] ?? "";
1791
+ const column = e.token.line ? e.token.column : Math.max(0, line.length - 1);
1792
+ const gutter = String(index + 1).length;
1793
+ const pad = " ".repeat(gutter + 5 + column);
1794
+ return ` ${index + 1} | ${line}
1795
+ ` + pad + "^".repeat(Math.max(1, e.token.value.length)) + "\n" + pad + e.message + "\n";
1796
+ }
1797
+
1597
1798
  // src/statements.ts
1799
+ function withFallbacks(p, rule, keyword, start) {
1800
+ const fallbacks = p.g.fallbacks[keyword];
1801
+ if (!fallbacks) return rule(p, keyword, start);
1802
+ const mark2 = p.mark();
1803
+ try {
1804
+ return rule(p, keyword, start);
1805
+ } catch (e) {
1806
+ if (!(e instanceof ParseError)) throw e;
1807
+ let error = e;
1808
+ for (const fallback of fallbacks) {
1809
+ p.reset(mark2);
1810
+ try {
1811
+ const cmd = fallback(p, keyword, start);
1812
+ if (cmd && p.pos() >= e.token.start) return cmd;
1813
+ } catch (f) {
1814
+ if (!(f instanceof ParseError)) throw f;
1815
+ if (f.token.start > error.token.start) error = f;
1816
+ }
1817
+ }
1818
+ throw error;
1819
+ }
1820
+ }
1598
1821
  function command(p) {
1599
1822
  const start = p.pos();
1600
1823
  if (p.matchOp("(")) {
@@ -1605,7 +1828,7 @@
1605
1828
  const t = p.cur();
1606
1829
  const rule = p.g.commands[t.value];
1607
1830
  let cmd;
1608
- if (rule) cmd = p.match(t.value) && rule(p, t.value, start);
1831
+ if (rule) cmd = p.match(t.value) && withFallbacks(p, rule, t.value, start);
1609
1832
  else if (t.type === "IDENTIFIER") cmd = p.g.pseudo?.(p, start);
1610
1833
  if (!cmd || !p.match("unless")) return cmd;
1611
1834
  const inner = cmd;
@@ -1950,13 +2173,7 @@
1950
2173
  node.between = [attribute2, attributeRef(p) ?? p.err("Expected attributeRef")];
1951
2174
  }
1952
2175
  } else if (!(node.classRefs = classRefs(p)) && !(node.attributeRef = attributeRef(p))) {
1953
- const element = p.g.toggleElement;
1954
- if (!element) node.target = p.withFollow(["between"], () => assignable(p));
1955
- else {
1956
- const target2 = p.withFollow(["between", "as"], () => unary(p));
1957
- if (!p.peek("between")) return element(p, target2, start);
1958
- node.target = writable(p, target2);
1959
- }
2176
+ node.target = p.withFollow(["between"], () => assignable(p));
1960
2177
  }
1961
2178
  if (!node.styleProp && !node.target) node.on = p.match("on") ? expr(p) : implicitMe(p);
1962
2179
  if (p.match("between")) {
@@ -2511,201 +2728,23 @@
2511
2728
  return result;
2512
2729
  }
2513
2730
 
2514
- // src/parser.ts
2515
- var ParseError = class extends Error {
2516
- constructor(message, token, source, expected) {
2517
- super(message);
2518
- this.token = token;
2519
- this.source = source;
2520
- this.expected = expected;
2521
- }
2522
- /** What the author wrote, when a plugin rewrote the script before it was parsed. */
2523
- written;
2524
- };
2525
- var createGrammar = () => ({
2526
- commands: /* @__PURE__ */ Object.create(null),
2527
- features: /* @__PURE__ */ Object.create(null),
2528
- leaves: [],
2529
- indirects: [],
2530
- unaries: [],
2531
- postfixes: [],
2532
- collections: []
2533
- });
2534
- var Parser = class _Parser {
2535
- constructor(g, tokens, src) {
2536
- this.g = g;
2537
- this.tokens = tokens;
2538
- this.src = src;
2539
- const end = src.length;
2540
- this.eof = { type: "EOF", value: "<<<EOF>>>", start: end, end, line: 0, column: 0 };
2541
- this.last = this.eof;
2542
- this.skipWs();
2543
- }
2544
- i = 0;
2545
- follows = [];
2546
- eof;
2547
- /** The last token consumed by a match. */
2548
- last;
2549
- child(tokens, src) {
2550
- return new _Parser(this.g, tokens, src);
2551
- }
2552
- // ----- access ------------------------------------------------------------
2553
- /** The nth token from here; whitespace is skipped unless `ws`. */
2554
- tok(n = 0, ws = false) {
2555
- let j = this.i;
2556
- for (; ; ) {
2557
- if (!ws) while (this.tokens[j]?.type === "WHITESPACE") j++;
2558
- if (n-- === 0) return this.tokens[j] ?? this.eof;
2559
- j++;
2560
- }
2561
- }
2562
- cur() {
2563
- return this.tokens[this.i] ?? this.eof;
2564
- }
2565
- hasMore() {
2566
- return this.i < this.tokens.length;
2567
- }
2568
- /** Start offset of the current token — what a rule records as its node's `start`. */
2569
- pos() {
2570
- return this.cur().start;
2571
- }
2572
- /** End offset of the last consumed token — a node's `end`. */
2573
- endPos() {
2574
- return this.last.end;
2575
- }
2576
- text(node) {
2577
- return this.src.substring(node.start, node.end);
2578
- }
2579
- // ----- matching ----------------------------------------------------------
2580
- match(value, type = "IDENTIFIER") {
2581
- if (this.follows.includes(value)) return;
2582
- const t = this.cur();
2583
- if (t.value === value && t.type === type) return this.consume();
2584
- }
2585
- matchOp(value) {
2586
- const t = this.cur();
2587
- if (t.op && t.value === value) return this.consume();
2588
- }
2589
- matchType(...types) {
2590
- if (types.includes(this.cur().type)) return this.consume();
2591
- }
2592
- matchAny(...values) {
2593
- for (const value of values) {
2594
- const t = this.match(value);
2595
- if (t) return t;
2596
- }
2597
- }
2598
- matchAnyOp(...values) {
2599
- for (const value of values) {
2600
- const t = this.matchOp(value);
2601
- if (t) return t;
2602
- }
2603
- }
2604
- req(value) {
2605
- return this.match(value) ?? this.expected(value);
2606
- }
2607
- reqOp(value) {
2608
- return this.matchOp(value) ?? this.expected(value);
2609
- }
2610
- reqType(...types) {
2611
- return this.matchType(...types) ?? this.expected(...types);
2612
- }
2613
- /** Is the nth token from here this identifier (or this `type`)? Consumes nothing. */
2614
- peek(value, n = 0, type = "IDENTIFIER") {
2615
- const t = this.tok(n);
2616
- if (t.value === value && t.type === type) return t;
2617
- }
2618
- // ----- consuming ---------------------------------------------------------
2619
- consume() {
2620
- if (!this.hasMore()) return this.eof;
2621
- this.last = this.tokens[this.i++];
2622
- this.skipWs();
2623
- return this.last;
2624
- }
2625
- skipWs() {
2626
- while (this.tokens[this.i]?.type === "WHITESPACE") this.i++;
2627
- }
2628
- /** Raw tokens (whitespace included) up to a token with this value or type. */
2629
- consumeUntil(value, type) {
2630
- const out = [];
2631
- let t = this.cur();
2632
- while ((type == null || t.type !== type) && (value == null || t.value !== value) && t.type !== "EOF") {
2633
- out.push(t);
2634
- this.i++;
2635
- t = this.cur();
2636
- }
2637
- this.skipWs();
2638
- return out;
2639
- }
2640
- /** The whitespace just before the current token, if any. */
2641
- lastWs() {
2642
- const t = this.tokens[this.i - 1];
2643
- return t?.type === "WHITESPACE" ? t.value : "";
2644
- }
2645
- // ----- follow set: words a nested rule must leave for its caller ----------
2646
- pushFollow(...words) {
2647
- this.follows.push(...words);
2648
- return words.length;
2649
- }
2650
- popFollow(count = 1) {
2651
- this.follows.length -= count;
2652
- }
2653
- /** Parse with `words` reserved for the caller, e.g. the `to` of `set x to y`. */
2654
- withFollow(words, f) {
2655
- const count = this.pushFollow(...words);
2656
- try {
2657
- return f();
2658
- } finally {
2659
- this.popFollow(count);
2660
- }
2661
- }
2662
- /** Parse with no reserved words — inside parentheses the outer rule's words are free again. */
2663
- withoutFollows(f) {
2664
- const saved = this.follows;
2665
- this.follows = [];
2666
- try {
2667
- return f();
2668
- } finally {
2669
- this.follows = saved;
2670
- }
2671
- }
2672
- // ----- errors ------------------------------------------------------------
2673
- err(message, expected) {
2674
- const token = this.cur();
2675
- throw new ParseError(message ?? "Unexpected Token : " + token.value, token, this.src, expected);
2676
- }
2677
- expected(...expected) {
2678
- this.err(
2679
- expected.length === 1 ? `Expected '${expected[0]}' but found '${this.cur().value}'` : "Expected one of: " + expected.map((e) => `'${e}'`).join(", "),
2680
- expected
2681
- );
2682
- }
2683
- // ----- statement boundaries ------------------------------------------------
2684
- commandStart(token) {
2685
- return token.value in this.g.commands;
2686
- }
2687
- featureStart(token) {
2688
- return token.value in this.g.features;
2689
- }
2690
- commandBoundary(token) {
2691
- return ["end", "then", "else", "otherwise", ")"].includes(token.value) || this.commandStart(token) || this.featureStart(token) || token.type === "EOF";
2692
- }
2693
- };
2694
- function formatError(e) {
2695
- const lines = e.source.split("\n");
2696
- const index = e.token.line ? e.token.line - 1 : lines.length - 1;
2697
- const line = lines[index] ?? "";
2698
- const column = e.token.line ? e.token.column : Math.max(0, line.length - 1);
2699
- const gutter = String(index + 1).length;
2700
- const pad = " ".repeat(gutter + 5 + column);
2701
- return ` ${index + 1} | ${line}
2702
- ` + pad + "^".repeat(Math.max(1, e.token.value.length)) + "\n" + pad + e.message + "\n";
2703
- }
2704
-
2705
2731
  // src/engine.ts
2706
2732
  var grammar = createGrammar();
2733
+ var registered = /* @__PURE__ */ new Set();
2734
+ var unread = /* @__PURE__ */ new Set();
2707
2735
  function register(...modules) {
2708
- for (const module of modules) module(grammar);
2736
+ const added = modules.filter((module) => !registered.has(module));
2737
+ for (const module of added) {
2738
+ registered.add(module);
2739
+ module(grammar);
2740
+ }
2741
+ if (!added.length) return;
2742
+ for (const elt of [...unread]) {
2743
+ unread.delete(elt);
2744
+ if (!elt.isConnected) continue;
2745
+ dropData(elt);
2746
+ initElement(elt);
2747
+ }
2709
2748
  }
2710
2749
  var parser = (src) => new Parser(grammar, tokenize(src), src);
2711
2750
  function parseProgram(src) {
@@ -2778,6 +2817,7 @@
2778
2817
  setTimeout(() => triggerEvent(target, "load", { hyperscript: true }), 1);
2779
2818
  } catch (e) {
2780
2819
  if (e instanceof ParseError) {
2820
+ unread.add(elt);
2781
2821
  if (src !== written) e.written = written;
2782
2822
  triggerEvent(elt, "hyperscript:parse-error", { errors: [e] });
2783
2823
  const from = e.written ? `
@@ -2844,6 +2884,12 @@
2844
2884
  addAfterProcessHook: (hook) => void afterProcess.push(hook),
2845
2885
  /** Not in upstream: rewrite a script as it is read, leaving the attribute as written. */
2846
2886
  addSourceTransform: (transform) => void transforms.push(transform),
2887
+ /** Not in upstream: add grammar modules, from a script tag (`dist/ext/<name>.js`) or a page. */
2888
+ register,
2889
+ // `kit` (what a module may call) is added by the script-tag bundle (`bundles/full.ts`) only,
2890
+ // so a bundle of the user's own keeps only the parts of the engine it uses.
2891
+ /** Not in upstream: the extension files (`dist/ext/<name>.js`) this page has loaded. */
2892
+ extensions: /* @__PURE__ */ new Set(),
2847
2893
  evaluate,
2848
2894
  parse: hostParse,
2849
2895
  process: processNode,
@@ -2852,7 +2898,7 @@
2852
2898
  cleanup,
2853
2899
  /** The part of upstream's `internals` that pages and tests reach for. */
2854
2900
  internals: { runtime: { cleanup, processNode } },
2855
- version: false ? "dev" : "7.5.0"
2901
+ version: false ? "dev" : "7.6.0"
2856
2902
  });
2857
2903
  function boot() {
2858
2904
  Object.assign(globalThis, { _hyperscript: api });