@thomasfosterau/effect-parse 0.5.0 → 0.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +19 -1
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +5 -3
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +5 -2
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +20 -3
  9. package/dist/Binary.js.map +1 -1
  10. package/dist/Bits.d.ts.map +1 -1
  11. package/dist/Bits.js +4 -0
  12. package/dist/Bits.js.map +1 -1
  13. package/dist/Input.d.ts +21 -3
  14. package/dist/Input.d.ts.map +1 -1
  15. package/dist/Input.js +31 -4
  16. package/dist/Input.js.map +1 -1
  17. package/dist/Nom.d.ts.map +1 -1
  18. package/dist/ParseError.d.ts +69 -11
  19. package/dist/ParseError.d.ts.map +1 -1
  20. package/dist/ParseError.js +129 -28
  21. package/dist/ParseError.js.map +1 -1
  22. package/dist/Parser.d.ts +10 -4
  23. package/dist/Parser.d.ts.map +1 -1
  24. package/dist/Parser.js +10 -24
  25. package/dist/Parser.js.map +1 -1
  26. package/dist/ParserSchema.d.ts.map +1 -1
  27. package/dist/ParserSchema.js +1 -2
  28. package/dist/ParserSchema.js.map +1 -1
  29. package/dist/ParserStream.d.ts +119 -12
  30. package/dist/ParserStream.d.ts.map +1 -1
  31. package/dist/ParserStream.js +282 -55
  32. package/dist/ParserStream.js.map +1 -1
  33. package/dist/Syntax.js +1 -1
  34. package/dist/Text.d.ts.map +1 -1
  35. package/dist/Text.js +13 -7
  36. package/dist/Text.js.map +1 -1
  37. package/dist/Token.d.ts +10 -1
  38. package/dist/Token.d.ts.map +1 -1
  39. package/dist/Token.js +14 -5
  40. package/dist/Token.js.map +1 -1
  41. package/dist/internal/compiler/closure.d.ts +1 -1
  42. package/dist/internal/compiler/closure.d.ts.map +1 -1
  43. package/dist/internal/compiler/closure.js +7 -17
  44. package/dist/internal/compiler/closure.js.map +1 -1
  45. package/dist/internal/compiler/first.d.ts.map +1 -1
  46. package/dist/internal/compiler/first.js +2 -1
  47. package/dist/internal/compiler/first.js.map +1 -1
  48. package/dist/internal/compiler/graph.d.ts +23 -1
  49. package/dist/internal/compiler/graph.d.ts.map +1 -1
  50. package/dist/internal/compiler/graph.js +27 -5
  51. package/dist/internal/compiler/graph.js.map +1 -1
  52. package/dist/internal/compiler/jit.d.ts.map +1 -1
  53. package/dist/internal/compiler/jit.js +26 -13
  54. package/dist/internal/compiler/jit.js.map +1 -1
  55. package/dist/internal/compiler/registry.d.ts +1 -1
  56. package/dist/internal/compiler/wasm.d.ts.map +1 -1
  57. package/dist/internal/compiler/wasm.js +105 -34
  58. package/dist/internal/compiler/wasm.js.map +1 -1
  59. package/dist/internal/node.d.ts +19 -1
  60. package/dist/internal/node.d.ts.map +1 -1
  61. package/dist/internal/node.js +42 -8
  62. package/dist/internal/node.js.map +1 -1
  63. package/dist/internal/run.d.ts +3 -1
  64. package/dist/internal/run.d.ts.map +1 -1
  65. package/dist/internal/run.js +4 -2
  66. package/dist/internal/run.js.map +1 -1
  67. package/dist/internal/stack.d.ts +8 -0
  68. package/dist/internal/stack.d.ts.map +1 -1
  69. package/dist/internal/stack.js +17 -1
  70. package/dist/internal/stack.js.map +1 -1
  71. package/dist/internal/tokens.d.ts +10 -3
  72. package/dist/internal/tokens.d.ts.map +1 -1
  73. package/dist/internal/tokens.js +22 -9
  74. package/dist/internal/tokens.js.map +1 -1
  75. package/dist/internal/tuple.d.ts +6 -0
  76. package/dist/internal/tuple.d.ts.map +1 -0
  77. package/dist/internal/tuple.js +170 -0
  78. package/dist/internal/tuple.js.map +1 -0
  79. package/package.json +17 -10
  80. package/src/Ascii.ts +5 -3
  81. package/src/Binary.ts +24 -3
  82. package/src/Bits.ts +4 -0
  83. package/src/Input.ts +37 -4
  84. package/src/ParseError.ts +206 -52
  85. package/src/Parser.ts +19 -29
  86. package/src/ParserSchema.ts +1 -2
  87. package/src/ParserStream.ts +336 -62
  88. package/src/Text.ts +16 -12
  89. package/src/Token.ts +14 -5
  90. package/src/internal/compiler/closure.ts +9 -18
  91. package/src/internal/compiler/first.ts +6 -0
  92. package/src/internal/compiler/graph.ts +32 -4
  93. package/src/internal/compiler/jit.ts +37 -17
  94. package/src/internal/compiler/wasm.ts +141 -68
  95. package/src/internal/node.ts +49 -3
  96. package/src/internal/run.ts +4 -2
  97. package/src/internal/stack.ts +17 -0
  98. package/src/internal/tokens.ts +20 -10
  99. package/src/internal/tuple.ts +141 -0
@@ -2,15 +2,15 @@ import * as Predicate from "effect/Predicate";
2
2
  import * as Result from "effect/Result";
3
3
  import { Fail, State } from "../../Input.js";
4
4
  import { floatTexts, toFloat } from "../numbers.js";
5
+ import { nodeOf } from "../node.js";
5
6
  import { backend as backend$1, jitSupported } from "./jit.js";
6
7
  import * as Option from "effect/Option";
7
8
  import { charOf, fastLimit, latin1, string, utf16be, utf16le, utf8 } from "../text.js";
8
9
  import * as Arr from "effect/Array";
9
10
  import { addEntry, flushEntries, fromEntries, isPlainKey } from "../entries.js";
10
- import { nodeOf } from "../node.js";
11
11
  import { buildsEntries, isDiagnostic, pairOf } from "./entries.js";
12
12
  import { TableSize, kindOfEncoding, textSet, tokenSet } from "./sets.js";
13
- import { countRefs, isLeaf, isLetter, isSmall, lowerAscii, selectorOf } from "./graph.js";
13
+ import { countInlined, countRefs, isLeaf, isLetter, isSmall, lowerAscii, selectorOf } from "./graph.js";
14
14
  import { Body, F64, I32, Op, Simd, encodeModule } from "./wasmAsm.js";
15
15
  // src/internal/compiler/wasm.ts
16
16
  /**
@@ -84,6 +84,32 @@ const Slack = 65536;
84
84
  const MaxWrites = 4096;
85
85
  /** Bytes left after the cursor beyond which a driver's parse of the same buffer runs the JIT (see `run`). */
86
86
  const LargeRest = 4096;
87
+ /**
88
+ * The most linear memory a module is grown to: 2 GiB. Every offset into the
89
+ * memory, the trace's included, is then a non-negative `i32`. A parse that
90
+ * would need more (its input copied, and four to seven times as much of
91
+ * trace), or whose `memory.grow` the engine refuses, runs on the JIT, which
92
+ * needs neither (#123).
93
+ */
94
+ const MaxMemory = 2 ** 31;
95
+ /**
96
+ * Above this much linear memory after a parse (128 MiB), the module is
97
+ * given a new memory: `WebAssembly.Memory` never shrinks, so one large
98
+ * input would otherwise hold five to ten times its size for the life of
99
+ * the process, per compiled parser and input kind.
100
+ */
101
+ const ReleaseAbove = 134217728;
102
+ /** What `grow` throws when the trace cannot grow: the parse starts again on the JIT. */
103
+ var MemoryExhausted = class {};
104
+ /**
105
+ * A repetition bound as the `i32` it is compared with, unsigned. A count
106
+ * never reaches 2^32 - 1 (the input is in a 32-bit memory), so a larger
107
+ * minimum is one that cannot be met, and a larger maximum is none at all
108
+ * (see `unbounded`); written as given, it was truncated to its low 32 bits.
109
+ */
110
+ const bound = (n) => n >= 4294967295 ? 4294967295 : n;
111
+ /** Whether a maximum can never be reached by a count (see {@link bound}). */
112
+ const unbounded = (max) => max >= 4294967295;
87
113
  /** The most literals a compiled `takeUntilAny` checks at each candidate; more are searched in JavaScript. */
88
114
  const MaxUntil = 64;
89
115
  /** The most leading bytes of each literal a SIMD search tests at once. */
@@ -341,6 +367,8 @@ var WasmCompiler = class WasmCompiler {
341
367
  rules = [/* @__PURE__ */ new WeakMap(), /* @__PURE__ */ new WeakMap()];
342
368
  pending = [];
343
369
  refs = /* @__PURE__ */ new WeakMap();
370
+ /** How many parsers have been inlined (see `countInlined`). */
371
+ inlined = 0;
344
372
  decoders = /* @__PURE__ */ new WeakMap();
345
373
  floatScanners = /* @__PURE__ */ new WeakMap();
346
374
  /** The float scanner of `Token` / `Ascii` text, which has no encoding. */
@@ -578,6 +606,7 @@ var WasmCompiler = class WasmCompiler {
578
606
  return isBuiltin(enc) && kindOfEncoding(enc) === this.kind;
579
607
  }
580
608
  inline(p, out, b, fail, bf, depth) {
609
+ this.inlined = countInlined(this.inlined);
581
610
  const node = nodeOf(p);
582
611
  const s = {
583
612
  p,
@@ -1040,7 +1069,7 @@ var WasmCompiler = class WasmCompiler {
1040
1069
  stop(b, fail, n, min, restore, done) {
1041
1070
  this.unlessBacktrack(b, fail);
1042
1071
  if (min > 0) {
1043
- b.get(n).i32(min).op(Op.i32LtU);
1072
+ b.get(n).i32(bound(min)).op(Op.i32LtU);
1044
1073
  b.if(() => b.br(fail));
1045
1074
  }
1046
1075
  restore();
@@ -1069,11 +1098,11 @@ var WasmCompiler = class WasmCompiler {
1069
1098
  b.i32(0).set(n);
1070
1099
  b.block((done) => {
1071
1100
  this.loop(b, (again) => {
1072
- if (max !== Infinity) b.get(n).i32(max).op(Op.i32GeU).brIf(done);
1101
+ if (!unbounded(max)) b.get(n).i32(bound(max)).op(Op.i32GeU).brIf(done);
1073
1102
  const saved = this.save(b);
1074
1103
  this.attempt(b, (bad) => {
1075
1104
  elementCode = element(b, bad, bf);
1076
- if (max === Infinity) this.progress(b, fail, saved[0]);
1105
+ this.progress(b, fail, saved[0]);
1077
1106
  this.nextElement(b, n, again);
1078
1107
  }, () => this.stop(b, fail, n, min, () => this.restore(b, saved), done));
1079
1108
  });
@@ -1115,10 +1144,10 @@ var WasmCompiler = class WasmCompiler {
1115
1144
  b.br(done);
1116
1145
  });
1117
1146
  this.loop(b, (again) => {
1118
- if (max !== Infinity) b.get(n).i32(max).op(Op.i32GeU).brIf(done);
1147
+ if (!unbounded(max)) b.get(n).i32(bound(max)).op(Op.i32GeU).brIf(done);
1119
1148
  mark();
1120
1149
  this.attempt(b, (bad) => this.gen(node.separator, "", b, bad, bf, d), () => this.stop(b, fail, n, min, back, done));
1121
- if (max === Infinity) this.progress(b, fail, st);
1150
+ this.progress(b, fail, st);
1122
1151
  this.attempt(b, (bad) => {
1123
1152
  element(b, bad, new Vars());
1124
1153
  this.nextElement(b, n, again);
@@ -1360,14 +1389,14 @@ var WasmCompiler = class WasmCompiler {
1360
1389
  b.loop((again) => {
1361
1390
  this.atEnd(b, 0);
1362
1391
  b.brIf(stop);
1363
- if (max !== Infinity) b.get(n).i32(max).op(Op.i32GeU).brIf(stop);
1392
+ if (!unbounded(max)) b.get(n).i32(bound(max)).op(Op.i32GeU).brIf(stop);
1364
1393
  this.whileStep(enc, b, set, w, wide, stop);
1365
1394
  if (counted) b.get(n).i32(1).op(Op.i32Add).set(n);
1366
1395
  b.br(again);
1367
1396
  });
1368
1397
  });
1369
1398
  if (min > 0) {
1370
- if (counted) b.get(n).i32(min).op(Op.i32LtU);
1399
+ if (counted) b.get(n).i32(bound(min)).op(Op.i32LtU);
1371
1400
  else b.get(0).get(st).op(Op.i32Eq);
1372
1401
  this.failIf(b, s.fail);
1373
1402
  }
@@ -2600,7 +2629,7 @@ const exportedGlobal = (exports, name) => {
2600
2629
  return g instanceof WebAssembly.Global ? Result.succeed(g) : Result.fail(`no global ${name}`);
2601
2630
  };
2602
2631
  const run = (root, c, bytes, kind, build, source) => {
2603
- const memory = new WebAssembly.Memory({ initial: 2 });
2632
+ let memory = new WebAssembly.Memory({ initial: 2 });
2604
2633
  let size = memory.buffer.byteLength;
2605
2634
  let i32 = new Int32Array(memory.buffer);
2606
2635
  let f64 = new Float64Array(memory.buffer);
@@ -2610,13 +2639,20 @@ const run = (root, c, bytes, kind, build, source) => {
2610
2639
  f64 = new Float64Array(memory.buffer);
2611
2640
  u8 = new Uint8Array(memory.buffer);
2612
2641
  };
2613
- /** Grows the memory to at least `bytes`, doubling it. */
2642
+ /**
2643
+ * Grows the memory to at least `bytes`, doubling it: false when that is
2644
+ * more than {@link MaxMemory}, or more than the engine will give.
2645
+ */
2614
2646
  const reserve = (bytes) => {
2615
2647
  const have = size;
2616
- if (have >= bytes) return;
2617
- memory.grow(Math.ceil((Math.max(bytes, have * 2) - have) / PageSize));
2648
+ if (have >= bytes) return true;
2649
+ if (bytes > MaxMemory) return false;
2650
+ const pages = Math.ceil((Math.min(Math.max(bytes, have * 2), MaxMemory) - have) / PageSize);
2651
+ const grown = Result.try(() => memory.grow(pages));
2652
+ if (Result.isFailure(grown)) return false;
2618
2653
  size = memory.buffer.byteLength;
2619
2654
  views();
2655
+ return true;
2620
2656
  };
2621
2657
  const steps = c.islands.map(({ parser }) => backend$1.build(parser, kind, false).step ?? parser.step);
2622
2658
  const used = c.islands.map(({ used }) => used);
@@ -2624,8 +2660,7 @@ const run = (root, c, bytes, kind, build, source) => {
2624
2660
  const checks = c.checks;
2625
2661
  let state = idle;
2626
2662
  const values = [];
2627
- const imports = { env: {
2628
- mem: memory,
2663
+ const env = {
2629
2664
  island: (id, pos) => {
2630
2665
  const s = state;
2631
2666
  s.pos = pos;
@@ -2635,14 +2670,14 @@ const run = (root, c, bytes, kind, build, source) => {
2635
2670
  return s.pos;
2636
2671
  },
2637
2672
  grow: () => {
2638
- reserve(size * 2);
2673
+ if (size >= MaxMemory || !reserve(Math.min(size * 2, MaxMemory))) throw new MemoryExhausted();
2639
2674
  build.bind(i32, f64, state.input, state.end, values, state);
2640
2675
  return size - Slack;
2641
2676
  },
2642
2677
  test: (id, cp) => tests[id](cp),
2643
2678
  check: (id, from) => {
2644
2679
  const s = state;
2645
- const a = checks[id](build.value(id, from >> 2));
2680
+ const a = checks[id](build.value(id, from >>> 2));
2646
2681
  if (a === Fail) {
2647
2682
  s.mode = "Backtrack";
2648
2683
  return 0;
@@ -2654,17 +2689,29 @@ const run = (root, c, bytes, kind, build, source) => {
2654
2689
  const input = state.input;
2655
2690
  return toFloat(Predicate.isString(input) ? input.slice(start, end) : latin1.text(input, start, end).replace(/\0/g, ""));
2656
2691
  }
2657
- } };
2658
- const exported = new WebAssembly.Instance(new WebAssembly.Module(bytes), imports).exports;
2659
- const rootFn = exported.root;
2660
- if (!Predicate.isFunction(rootFn)) return Result.fail("no root function");
2661
- const widenFn = kind === 0 ? exported.widen : rootFn;
2662
- if (!Predicate.isFunction(widenFn)) return Result.fail("no widen function");
2663
- const startFn = exported.start;
2664
- if (!Predicate.isFunction(startFn)) return Result.fail("no start function");
2665
- const globals = Result.all(["mode"].map((name) => exportedGlobal(exported, name)));
2666
- if (Result.isFailure(globals)) return Result.fail(globals.failure);
2667
- const [mode] = globals.success;
2692
+ };
2693
+ const module = new WebAssembly.Module(bytes);
2694
+ /** The entry points of an instance of the module over `mem`. */
2695
+ const instantiate = (mem) => {
2696
+ const exported = new WebAssembly.Instance(module, { env: {
2697
+ ...env,
2698
+ mem
2699
+ } }).exports;
2700
+ const rootFn = exported.root;
2701
+ if (!Predicate.isFunction(rootFn)) return Result.fail("no root function");
2702
+ const widenFn = kind === 0 ? exported.widen : rootFn;
2703
+ if (!Predicate.isFunction(widenFn)) return Result.fail("no widen function");
2704
+ const startFn = exported.start;
2705
+ if (!Predicate.isFunction(startFn)) return Result.fail("no start function");
2706
+ return Result.map(exportedGlobal(exported, "mode"), (mode) => ({
2707
+ widen: widenFn,
2708
+ start: startFn,
2709
+ mode
2710
+ }));
2711
+ };
2712
+ const first = instantiate(memory);
2713
+ if (Result.isFailure(first)) return Result.fail(first.failure);
2714
+ let entries = first.success;
2668
2715
  let busy = false;
2669
2716
  /** The JIT's step for the parser, built on first use. */
2670
2717
  let fallback = (s) => {
@@ -2680,7 +2727,7 @@ const run = (root, c, bytes, kind, build, source) => {
2680
2727
  */
2681
2728
  const copyString = (input, staging) => {
2682
2729
  const { written } = utf8Encoder.encodeInto(input, u8.subarray(staging, staging + 3 * input.length));
2683
- const replaced = widenFn(staging, written);
2730
+ const replaced = entries.widen(staging, written);
2684
2731
  if (replaced === 0) return;
2685
2732
  const view = new DataView(memory.buffer);
2686
2733
  const list = staging + written + 3 >> 2;
@@ -2696,7 +2743,7 @@ const run = (root, c, bytes, kind, build, source) => {
2696
2743
  const n = input.length;
2697
2744
  const inputBytes = kind === 0 ? 2 * n : n;
2698
2745
  const traceStart = Input$1 + inputBytes + 15 & -16;
2699
- reserve(traceStart + Math.max(Slack * 4, inputBytes * 4, kind === 0 ? 7 * n + 16 : 0));
2746
+ if (!reserve(traceStart + Math.max(Slack * 4, inputBytes * 4, kind === 0 ? 7 * n + 16 : 0))) throw new MemoryExhausted();
2700
2747
  if (Predicate.isString(input)) {
2701
2748
  if (input !== copied) {
2702
2749
  copied = "";
@@ -2707,13 +2754,29 @@ const run = (root, c, bytes, kind, build, source) => {
2707
2754
  values.length = 0;
2708
2755
  state = s;
2709
2756
  build.bind(i32, f64, input, s.end, values, s);
2710
- const r = startFn(s.pos, s.end, n, traceStart, size - Slack);
2757
+ const r = entries.start(s.pos, s.end, n, traceStart, size - Slack);
2711
2758
  if (r < 0) {
2712
- s.mode = mode.value === Cut ? "Cut" : "Backtrack";
2759
+ s.mode = entries.mode.value === Cut ? "Cut" : "Backtrack";
2713
2760
  return Fail;
2714
2761
  }
2715
2762
  s.pos = r;
2716
- return build.root(traceStart >> 2);
2763
+ return build.root(traceStart >>> 2);
2764
+ };
2765
+ /**
2766
+ * Gives the module a new memory of the initial size, dropping the one a
2767
+ * large parse grew (and the string it held). An instance is bound to its
2768
+ * memory, so the module is instantiated again; its data segments fill
2769
+ * the tables in.
2770
+ */
2771
+ const release = () => {
2772
+ const fresh = new WebAssembly.Memory({ initial: 2 });
2773
+ const next = instantiate(fresh);
2774
+ if (Result.isFailure(next)) return;
2775
+ memory = fresh;
2776
+ size = fresh.buffer.byteLength;
2777
+ views();
2778
+ copied = "";
2779
+ entries = next.success;
2717
2780
  };
2718
2781
  /** The bytes copied by the latest parse: parsing them again from further on is a driver at work. */
2719
2782
  let lastBytes = /* @__PURE__ */ new Uint8Array(0);
@@ -2730,13 +2793,21 @@ const run = (root, c, bytes, kind, build, source) => {
2730
2793
  }
2731
2794
  busy = true;
2732
2795
  pending = s;
2796
+ const from = s.pos;
2733
2797
  const result = Result.try(parsePending);
2734
2798
  build.unbind();
2735
2799
  state = idle;
2736
2800
  pending = idle;
2737
2801
  busy = false;
2738
2802
  values.length = 0;
2739
- if (Result.isFailure(result)) throw result.failure;
2803
+ if (size > ReleaseAbove) release();
2804
+ if (Result.isFailure(result)) {
2805
+ if (result.failure instanceof MemoryExhausted) {
2806
+ s.pos = from;
2807
+ return fallback(s);
2808
+ }
2809
+ throw result.failure;
2810
+ }
2740
2811
  return result.success;
2741
2812
  };
2742
2813
  return Result.succeed({