@thomasfosterau/effect-parse 0.2.0 → 0.4.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 (144) hide show
  1. package/README.md +44 -1
  2. package/dist/Ascii.d.ts +0 -2
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +6 -6
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +0 -2
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +1 -2
  9. package/dist/Bits.d.ts +0 -2
  10. package/dist/Bits.d.ts.map +1 -1
  11. package/dist/Bits.js +2 -3
  12. package/dist/Bits.js.map +1 -1
  13. package/dist/Compiler.d.ts +0 -2
  14. package/dist/Compiler.d.ts.map +1 -1
  15. package/dist/Compiler.js +1 -2
  16. package/dist/Encoding.d.ts +0 -2
  17. package/dist/Encoding.d.ts.map +1 -1
  18. package/dist/Encoding.js +1 -2
  19. package/dist/Expression.d.ts +0 -2
  20. package/dist/Expression.d.ts.map +1 -1
  21. package/dist/Expression.js +61 -18
  22. package/dist/Expression.js.map +1 -1
  23. package/dist/Input.d.ts +80 -7
  24. package/dist/Input.d.ts.map +1 -1
  25. package/dist/Input.js +149 -18
  26. package/dist/Input.js.map +1 -1
  27. package/dist/Nom.d.ts +1 -3
  28. package/dist/Nom.d.ts.map +1 -1
  29. package/dist/Nom.js +1 -2
  30. package/dist/ParseError.d.ts +0 -2
  31. package/dist/ParseError.d.ts.map +1 -1
  32. package/dist/ParseError.js +6 -10
  33. package/dist/ParseError.js.map +1 -1
  34. package/dist/Parser.d.ts +2 -2
  35. package/dist/Parser.d.ts.map +1 -1
  36. package/dist/Parser.js +58 -32
  37. package/dist/Parser.js.map +1 -1
  38. package/dist/ParserSchema.d.ts +198 -2
  39. package/dist/ParserSchema.d.ts.map +1 -1
  40. package/dist/ParserSchema.js +646 -18
  41. package/dist/ParserSchema.js.map +1 -1
  42. package/dist/ParserStream.d.ts +0 -2
  43. package/dist/ParserStream.d.ts.map +1 -1
  44. package/dist/ParserStream.js +15 -9
  45. package/dist/ParserStream.js.map +1 -1
  46. package/dist/Syntax.d.ts +0 -2
  47. package/dist/Syntax.d.ts.map +1 -1
  48. package/dist/Syntax.js +13 -11
  49. package/dist/Syntax.js.map +1 -1
  50. package/dist/Text.d.ts +0 -2
  51. package/dist/Text.d.ts.map +1 -1
  52. package/dist/Text.js +1 -2
  53. package/dist/Token.d.ts +0 -2
  54. package/dist/Token.d.ts.map +1 -1
  55. package/dist/Token.js +1 -2
  56. package/dist/internal/compiler/closure.d.ts +5 -2
  57. package/dist/internal/compiler/closure.d.ts.map +1 -1
  58. package/dist/internal/compiler/closure.js +160 -92
  59. package/dist/internal/compiler/closure.js.map +1 -1
  60. package/dist/internal/compiler/entries.d.ts +0 -2
  61. package/dist/internal/compiler/entries.d.ts.map +1 -1
  62. package/dist/internal/compiler/entries.js +6 -7
  63. package/dist/internal/compiler/entries.js.map +1 -1
  64. package/dist/internal/compiler/first.d.ts +0 -2
  65. package/dist/internal/compiler/first.d.ts.map +1 -1
  66. package/dist/internal/compiler/first.js +10 -10
  67. package/dist/internal/compiler/first.js.map +1 -1
  68. package/dist/internal/compiler/jit.d.ts +0 -2
  69. package/dist/internal/compiler/jit.d.ts.map +1 -1
  70. package/dist/internal/compiler/jit.js +52 -51
  71. package/dist/internal/compiler/jit.js.map +1 -1
  72. package/dist/internal/compiler/registry.d.ts +0 -2
  73. package/dist/internal/compiler/registry.d.ts.map +1 -1
  74. package/dist/internal/compiler/registry.js +1 -2
  75. package/dist/internal/compiler/sets.d.ts +2 -2
  76. package/dist/internal/compiler/sets.d.ts.map +1 -1
  77. package/dist/internal/compiler/sets.js +8 -3
  78. package/dist/internal/compiler/sets.js.map +1 -1
  79. package/dist/internal/entries.d.ts +0 -2
  80. package/dist/internal/entries.d.ts.map +1 -1
  81. package/dist/internal/entries.js +1 -2
  82. package/dist/internal/key.d.ts +10 -0
  83. package/dist/internal/key.d.ts.map +1 -0
  84. package/dist/internal/key.js +11 -0
  85. package/dist/internal/key.js.map +1 -0
  86. package/dist/internal/node.d.ts +11 -4
  87. package/dist/internal/node.d.ts.map +1 -1
  88. package/dist/internal/node.js +15 -3
  89. package/dist/internal/node.js.map +1 -1
  90. package/dist/internal/numbers.d.ts +0 -2
  91. package/dist/internal/numbers.d.ts.map +1 -1
  92. package/dist/internal/numbers.js +1 -2
  93. package/dist/internal/resume.d.ts +5 -6
  94. package/dist/internal/resume.d.ts.map +1 -1
  95. package/dist/internal/resume.js +1 -2
  96. package/dist/internal/resume.js.map +1 -1
  97. package/dist/internal/run.d.ts +0 -2
  98. package/dist/internal/run.d.ts.map +1 -1
  99. package/dist/internal/run.js +2 -3
  100. package/dist/internal/run.js.map +1 -1
  101. package/dist/internal/stack.d.ts +0 -2
  102. package/dist/internal/stack.d.ts.map +1 -1
  103. package/dist/internal/stack.js +12 -3
  104. package/dist/internal/stack.js.map +1 -1
  105. package/dist/internal/syntax.d.ts +30 -19
  106. package/dist/internal/syntax.d.ts.map +1 -1
  107. package/dist/internal/syntax.js +74 -63
  108. package/dist/internal/syntax.js.map +1 -1
  109. package/dist/internal/text.d.ts +5 -2
  110. package/dist/internal/text.d.ts.map +1 -1
  111. package/dist/internal/text.js +14 -5
  112. package/dist/internal/text.js.map +1 -1
  113. package/dist/internal/ties.d.ts +28 -0
  114. package/dist/internal/ties.d.ts.map +1 -0
  115. package/dist/internal/ties.js +66 -0
  116. package/dist/internal/ties.js.map +1 -0
  117. package/dist/internal/tokens.d.ts +10 -9
  118. package/dist/internal/tokens.d.ts.map +1 -1
  119. package/dist/internal/tokens.js +4 -8
  120. package/dist/internal/tokens.js.map +1 -1
  121. package/package.json +6 -6
  122. package/src/Ascii.ts +6 -5
  123. package/src/Bits.ts +1 -1
  124. package/src/Expression.ts +27 -23
  125. package/src/Input.ts +157 -21
  126. package/src/ParseError.ts +10 -8
  127. package/src/Parser.ts +67 -32
  128. package/src/ParserSchema.ts +982 -23
  129. package/src/ParserStream.ts +20 -8
  130. package/src/Syntax.ts +12 -9
  131. package/src/internal/compiler/closure.ts +202 -90
  132. package/src/internal/compiler/entries.ts +14 -11
  133. package/src/internal/compiler/first.ts +9 -8
  134. package/src/internal/compiler/jit.ts +95 -74
  135. package/src/internal/compiler/sets.ts +7 -0
  136. package/src/internal/key.ts +11 -0
  137. package/src/internal/node.ts +19 -2
  138. package/src/internal/resume.ts +5 -4
  139. package/src/internal/run.ts +1 -1
  140. package/src/internal/stack.ts +6 -1
  141. package/src/internal/syntax.ts +80 -84
  142. package/src/internal/text.ts +24 -4
  143. package/src/internal/ties.ts +83 -0
  144. package/src/internal/tokens.ts +20 -13
@@ -1,6 +1,44 @@
1
- import { Fail, mergeFailures } from "./Input.js";
1
+ import * as Predicate from "effect/Predicate";
2
+ import { Fail } from "./Input.js";
2
3
  import { make as make$1 } from "./Parser.js";
3
- //#region src/Expression.ts
4
+ import { TiedFailures } from "./internal/ties.js";
5
+ // src/Expression.ts
6
+ /**
7
+ * Operator-precedence expression parsing with a Pratt parser (from
8
+ * `nom-language`'s `precedence`, and winnow's expression parser).
9
+ *
10
+ * Describe the operand parser and a table of prefix, postfix and infix
11
+ * operators with their precedence (higher binds tighter) and associativity.
12
+ * Each operator carries a function that folds its operands into a result.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { Ascii, Expression, Parser, Token } from "@thomasfosterau/effect-parse"
17
+ *
18
+ * const op = (s: string) => Parser.delimited(Ascii.space0, Token.literal(s), Ascii.space0)
19
+ *
20
+ * const arithmetic: Parser.Parser<number, string> = Expression.make({
21
+ * operand: Parser.alt(
22
+ * Ascii.decInt,
23
+ * Parser.delimited(op("("), Parser.suspend(() => arithmetic), op(")"))
24
+ * ),
25
+ * operators: [
26
+ * Expression.prefix(op("-"), 4, (_, x) => -x),
27
+ * Expression.postfix(op("!"), 5, (x) => factorial(x)),
28
+ * Expression.infixRight(op("^"), 3, (l, _, r) => l ** r),
29
+ * Expression.infixLeft(op("*"), 2, (l, _, r) => l * r),
30
+ * Expression.infixLeft(op("/"), 2, (l, _, r) => l / r),
31
+ * Expression.infixLeft(op("+"), 1, (l, _, r) => l + r),
32
+ * Expression.infixLeft(op("-"), 1, (l, _, r) => l - r)
33
+ * ]
34
+ * })
35
+ *
36
+ * declare const factorial: (n: number) => number
37
+ * Parser.parse(arithmetic, "1 + 2 * 3 ^ 2") // Result.succeed(19)
38
+ * ```
39
+ *
40
+ * @since 0.1.0
41
+ */
4
42
  /**
5
43
  * A prefix operator, e.g. unary minus.
6
44
  *
@@ -66,11 +104,11 @@ const infixRight = (parser, precedence, fold) => ({
66
104
  */
67
105
  const make = (options) => {
68
106
  const operand = options.operand.step;
69
- const prefixes = options.operators.filter((o) => o._tag === "Prefix");
70
- const postfixes = options.operators.filter((o) => o._tag === "Postfix");
71
- const infixes = options.operators.filter((o) => o._tag === "Infix");
107
+ const prefixes = options.operators.filter((o) => Predicate.isTagged(o, "Prefix"));
108
+ const postfixes = options.operators.filter((o) => Predicate.isTagged(o, "Postfix"));
109
+ const infixes = options.operators.filter((o) => Predicate.isTagged(o, "Infix"));
72
110
  let matched;
73
- let failure;
111
+ const failures = new TiedFailures();
74
112
  const tryOps = (state, ops, mustConsume) => {
75
113
  const start = state.pos;
76
114
  for (let i = 0; i < ops.length; i++) {
@@ -84,14 +122,14 @@ const make = (options) => {
84
122
  return i;
85
123
  }
86
124
  if (state.mode !== "Backtrack") return -2;
87
- failure = failure === void 0 ? state.failure : mergeFailures(failure, state.failure);
88
- state.pos = start;
125
+ state.rewind(start);
126
+ failures.add(state);
89
127
  }
90
128
  return -1;
91
129
  };
92
130
  const expr = (state, minPower) => {
93
131
  let lhs;
94
- failure = void 0;
132
+ failures.clear();
95
133
  const p = tryOps(state, prefixes, true);
96
134
  if (p === -2) return Fail;
97
135
  if (p >= 0) {
@@ -101,39 +139,45 @@ const make = (options) => {
101
139
  if (operandValue === Fail) return Fail;
102
140
  lhs = op.fold(opOut, operandValue);
103
141
  } else {
104
- const prefixFailure = failure;
142
+ const prefixFailure = failures.failure;
143
+ const prefixTie1 = failures.tie1;
144
+ const prefixTie2 = failures.tie2;
105
145
  lhs = operand(state);
106
146
  if (lhs === Fail) {
107
- if (prefixFailure !== void 0 && state.mode === "Backtrack") state.failWith("Backtrack", mergeFailures(prefixFailure, state.failure));
147
+ if (prefixFailure !== void 0 && state.mode === "Backtrack") {
148
+ failures.failure = prefixFailure;
149
+ failures.tie1 = prefixTie1;
150
+ failures.tie2 = prefixTie2;
151
+ failures.add(state);
152
+ failures.fail(state);
153
+ }
108
154
  return Fail;
109
155
  }
110
156
  }
111
157
  while (true) {
112
158
  const start = state.pos;
113
- failure = void 0;
159
+ failures.clear();
114
160
  const q = tryOps(state, postfixes, true);
115
161
  if (q === -2) return Fail;
116
162
  if (q >= 0) {
117
163
  const op = postfixes[q];
118
164
  if (op.precedence * 2 < minPower) {
119
- state.pos = start;
165
+ state.rewind(start);
120
166
  break;
121
167
  }
122
168
  lhs = op.fold(lhs, matched);
123
169
  continue;
124
170
  }
125
- const postfixFailure = failure;
126
171
  const i = tryOps(state, infixes, false);
127
172
  if (i === -2) return Fail;
128
173
  if (i < 0) {
129
- const f = postfixFailure === void 0 ? failure : failure === void 0 ? postfixFailure : mergeFailures(postfixFailure, failure);
130
- if (f !== void 0 && f.offset === state.pos) state.setHint(f.offset, f.expected);
174
+ failures.hint(state);
131
175
  break;
132
176
  }
133
177
  const op = infixes[i];
134
178
  const leftPower = op.precedence * 2;
135
179
  if (leftPower < minPower) {
136
- state.pos = start;
180
+ state.rewind(start);
137
181
  break;
138
182
  }
139
183
  const opOut = matched;
@@ -145,7 +189,6 @@ const make = (options) => {
145
189
  };
146
190
  return make$1((state) => expr(state, 0), "expression");
147
191
  };
148
- //#endregion
149
192
  export { infixLeft, infixRight, make, postfix, prefix };
150
193
 
151
194
  //# sourceMappingURL=Expression.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Expression.js","names":["makeParser"],"sources":["../src/Expression.ts"],"sourcesContent":["/**\n * Operator-precedence expression parsing with a Pratt parser (from\n * `nom-language`'s `precedence`, and winnow's expression parser).\n *\n * Describe the operand parser and a table of prefix, postfix and infix\n * operators with their precedence (higher binds tighter) and associativity.\n * Each operator carries a function that folds its operands into a result.\n *\n * @example\n * ```ts\n * import { Ascii, Expression, Parser, Token } from \"@thomasfosterau/effect-parse\"\n *\n * const op = (s: string) => Parser.delimited(Ascii.space0, Token.literal(s), Ascii.space0)\n *\n * const arithmetic: Parser.Parser<number, string> = Expression.make({\n * operand: Parser.alt(\n * Ascii.decInt,\n * Parser.delimited(op(\"(\"), Parser.suspend(() => arithmetic), op(\")\"))\n * ),\n * operators: [\n * Expression.prefix(op(\"-\"), 4, (_, x) => -x),\n * Expression.postfix(op(\"!\"), 5, (x) => factorial(x)),\n * Expression.infixRight(op(\"^\"), 3, (l, _, r) => l ** r),\n * Expression.infixLeft(op(\"*\"), 2, (l, _, r) => l * r),\n * Expression.infixLeft(op(\"/\"), 2, (l, _, r) => l / r),\n * Expression.infixLeft(op(\"+\"), 1, (l, _, r) => l + r),\n * Expression.infixLeft(op(\"-\"), 1, (l, _, r) => l - r)\n * ]\n * })\n *\n * declare const factorial: (n: number) => number\n * Parser.parse(arithmetic, \"1 + 2 * 3 ^ 2\") // Result.succeed(19)\n * ```\n *\n * @since 0.1.0\n */\nimport type { Input, State } from \"./Input.js\"\nimport { Fail, mergeFailures } from \"./Input.js\"\nimport type { Failure } from \"./Input.js\"\nimport type { Parser } from \"./Parser.js\"\nimport { make as makeParser } from \"./Parser.js\"\n\n/**\n * An operator in an expression grammar.\n *\n * @since 0.1.0\n * @category models\n */\nexport type Operator<A, E = never, R = never, I extends Input = string> =\n | Prefix<A, E, R, I>\n | Postfix<A, E, R, I>\n | Infix<A, E, R, I>\n\ninterface OperatorBase<E, R, I extends Input> {\n readonly parser: Parser<unknown, E, R, I>\n readonly precedence: number\n}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport interface Prefix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {\n readonly _tag: \"Prefix\"\n readonly fold: (op: any, operand: A) => A\n}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport interface Postfix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {\n readonly _tag: \"Postfix\"\n readonly fold: (operand: A, op: any) => A\n}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport interface Infix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {\n readonly _tag: \"Infix\"\n readonly associativity: \"left\" | \"right\"\n readonly fold: (left: A, op: any, right: A) => A\n}\n\n/**\n * A prefix operator, e.g. unary minus.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const prefix = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (op: O, operand: A) => A\n): Prefix<A, E, R, I> => ({ _tag: \"Prefix\", parser, precedence, fold })\n\n/**\n * A postfix operator, e.g. factorial or a function call suffix.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const postfix = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (operand: A, op: O) => A\n): Postfix<A, E, R, I> => ({ _tag: \"Postfix\", parser, precedence, fold })\n\n/**\n * A left-associative infix operator: `a - b - c` is `(a - b) - c`.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const infixLeft = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (left: A, op: O, right: A) => A\n): Infix<A, E, R, I> => ({ _tag: \"Infix\", associativity: \"left\", parser, precedence, fold })\n\n/**\n * A right-associative infix operator: `a ^ b ^ c` is `a ^ (b ^ c)`.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const infixRight = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (left: A, op: O, right: A) => A\n): Infix<A, E, R, I> => ({ _tag: \"Infix\", associativity: \"right\", parser, precedence, fold })\n\ntype OpError<Op> = Op extends OperatorBase<infer E, infer _R, infer _I> ? E : never\ntype OpServices<Op> = Op extends OperatorBase<infer _E, infer R, infer _I> ? R : never\ntype OpInput<Op> = [Op] extends [never] ? Input : [Op] extends [OperatorBase<infer _E, infer _R, infer I>] ? I : never\n\n/**\n * Builds an expression parser from an operand parser and an operator table.\n *\n * Precedences are compared numerically: higher binds tighter. A prefix\n * operator applies to everything that binds tighter than it, so with `-`\n * at precedence 4 and `^` at 3, `-2 ^ 2` parses as `(-2) ^ 2`.\n *\n * When several operators of the same kind match, the first one in the\n * table wins, so list longer operators (`<=`) before their prefixes (`<`).\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const make = <A, E, R, I extends Input, const Ops extends ReadonlyArray<Operator<A, any, any, any>>>(options: {\n readonly operand: Parser<A, E, R, I>\n readonly operators: Ops\n}): Parser<A, E | OpError<Ops[number]>, R | OpServices<Ops[number]>, I & OpInput<Ops[number]>> => {\n const operand = options.operand.step\n const prefixes = options.operators.filter((o): o is Prefix<A, any, any, any> => o._tag === \"Prefix\")\n const postfixes = options.operators.filter((o): o is Postfix<A, any, any, any> => o._tag === \"Postfix\")\n const infixes = options.operators.filter((o): o is Infix<A, any, any, any> => o._tag === \"Infix\")\n\n // Attempts each operator in turn. Returns the index of the first match\n // (with its output in `matched`), -1 if none matched, or -2 on a fatal\n // failure. Expectations of failed attempts are accumulated in `failure`.\n let matched: unknown\n let failure: Failure | undefined\n const tryOps = (state: State<any>, ops: ReadonlyArray<OperatorBase<any, any, any>>, mustConsume: boolean): number => {\n const start = state.pos\n for (let i = 0; i < ops.length; i++) {\n const out = ops[i].parser.step(state)\n if (out !== Fail) {\n // A prefix or postfix operator matching nothing would apply forever\n if (mustConsume && state.pos === start) {\n state.failMessage(\"Cut\", \"Assert\", \"prefix and postfix operators must always consume\")\n return -2\n }\n matched = out\n return i\n }\n if (state.mode !== \"Backtrack\") return -2\n failure = failure === undefined ? state.failure : mergeFailures(failure, state.failure)\n state.pos = start\n }\n return -1\n }\n\n const expr = (state: State<any>, minPower: number): A | Fail => {\n let lhs: A | Fail\n failure = undefined\n const p = tryOps(state, prefixes, true)\n if (p === -2) return Fail\n if (p >= 0) {\n const op = prefixes[p]\n const opOut = matched\n const operandValue = expr(state, op.precedence * 2)\n if (operandValue === Fail) return Fail\n lhs = op.fold(opOut, operandValue)\n } else {\n const prefixFailure = failure\n lhs = operand(state)\n if (lhs === Fail) {\n if (prefixFailure !== undefined && state.mode === \"Backtrack\") {\n state.failWith(\"Backtrack\", mergeFailures(prefixFailure, state.failure))\n }\n return Fail\n }\n }\n\n while (true) {\n const start = state.pos\n failure = undefined\n const q = tryOps(state, postfixes, true)\n if (q === -2) return Fail\n if (q >= 0) {\n const op = postfixes[q]\n if (op.precedence * 2 < minPower) {\n state.pos = start\n break\n }\n lhs = op.fold(lhs, matched)\n continue\n }\n const postfixFailure = failure\n const i = tryOps(state, infixes, false)\n if (i === -2) return Fail\n if (i < 0) {\n const f =\n postfixFailure === undefined\n ? failure\n : failure === undefined\n ? postfixFailure\n : mergeFailures(postfixFailure, failure)\n if (f !== undefined && f.offset === state.pos) state.setHint(f.offset, f.expected)\n break\n }\n const op = infixes[i]\n const leftPower = op.precedence * 2\n if (leftPower < minPower) {\n state.pos = start\n break\n }\n const opOut = matched\n const rhs = expr(state, op.associativity === \"left\" ? leftPower + 1 : leftPower)\n if (rhs === Fail) return Fail\n lhs = op.fold(lhs, opOut, rhs)\n }\n return lhs\n }\n\n return makeParser((state: State<any>) => expr(state, 0), \"expression\")\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Expression.js","names":["makeParser"],"sources":["../src/Expression.ts"],"sourcesContent":["/**\n * Operator-precedence expression parsing with a Pratt parser (from\n * `nom-language`'s `precedence`, and winnow's expression parser).\n *\n * Describe the operand parser and a table of prefix, postfix and infix\n * operators with their precedence (higher binds tighter) and associativity.\n * Each operator carries a function that folds its operands into a result.\n *\n * @example\n * ```ts\n * import { Ascii, Expression, Parser, Token } from \"@thomasfosterau/effect-parse\"\n *\n * const op = (s: string) => Parser.delimited(Ascii.space0, Token.literal(s), Ascii.space0)\n *\n * const arithmetic: Parser.Parser<number, string> = Expression.make({\n * operand: Parser.alt(\n * Ascii.decInt,\n * Parser.delimited(op(\"(\"), Parser.suspend(() => arithmetic), op(\")\"))\n * ),\n * operators: [\n * Expression.prefix(op(\"-\"), 4, (_, x) => -x),\n * Expression.postfix(op(\"!\"), 5, (x) => factorial(x)),\n * Expression.infixRight(op(\"^\"), 3, (l, _, r) => l ** r),\n * Expression.infixLeft(op(\"*\"), 2, (l, _, r) => l * r),\n * Expression.infixLeft(op(\"/\"), 2, (l, _, r) => l / r),\n * Expression.infixLeft(op(\"+\"), 1, (l, _, r) => l + r),\n * Expression.infixLeft(op(\"-\"), 1, (l, _, r) => l - r)\n * ]\n * })\n *\n * declare const factorial: (n: number) => number\n * Parser.parse(arithmetic, \"1 + 2 * 3 ^ 2\") // Result.succeed(19)\n * ```\n *\n * @since 0.1.0\n */\nimport * as Predicate from \"effect/Predicate\"\nimport type { Input, State } from \"./Input.js\"\nimport { Fail } from \"./Input.js\"\nimport type { Parser } from \"./Parser.js\"\nimport { make as makeParser } from \"./Parser.js\"\nimport { TiedFailures } from \"./internal/ties.js\"\n\n/**\n * An operator in an expression grammar.\n *\n * @since 0.1.0\n * @category models\n */\nexport type Operator<A, E = never, R = never, I extends Input = string> =\n | Prefix<A, E, R, I>\n | Postfix<A, E, R, I>\n | Infix<A, E, R, I>\n\ninterface OperatorBase<E, R, I extends Input> {\n readonly parser: Parser<unknown, E, R, I>\n readonly precedence: number\n}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport interface Prefix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {\n readonly _tag: \"Prefix\"\n readonly fold: (op: any, operand: A) => A\n}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport interface Postfix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {\n readonly _tag: \"Postfix\"\n readonly fold: (operand: A, op: any) => A\n}\n\n/**\n * @since 0.1.0\n * @category models\n */\nexport interface Infix<A, E = never, R = never, I extends Input = string> extends OperatorBase<E, R, I> {\n readonly _tag: \"Infix\"\n readonly associativity: \"left\" | \"right\"\n readonly fold: (left: A, op: any, right: A) => A\n}\n\n/**\n * A prefix operator, e.g. unary minus.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const prefix = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (op: O, operand: A) => A\n): Prefix<A, E, R, I> => ({ _tag: \"Prefix\", parser, precedence, fold })\n\n/**\n * A postfix operator, e.g. factorial or a function call suffix.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const postfix = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (operand: A, op: O) => A\n): Postfix<A, E, R, I> => ({ _tag: \"Postfix\", parser, precedence, fold })\n\n/**\n * A left-associative infix operator: `a - b - c` is `(a - b) - c`.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const infixLeft = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (left: A, op: O, right: A) => A\n): Infix<A, E, R, I> => ({ _tag: \"Infix\", associativity: \"left\", parser, precedence, fold })\n\n/**\n * A right-associative infix operator: `a ^ b ^ c` is `a ^ (b ^ c)`.\n *\n * @since 0.1.0\n * @category operators\n */\nexport const infixRight = <O, E, R, I extends Input, A>(\n parser: Parser<O, E, R, I>,\n precedence: number,\n fold: (left: A, op: O, right: A) => A\n): Infix<A, E, R, I> => ({ _tag: \"Infix\", associativity: \"right\", parser, precedence, fold })\n\ntype OpError<Op> = Op extends OperatorBase<infer E, infer _R, infer _I> ? E : never\ntype OpServices<Op> = Op extends OperatorBase<infer _E, infer R, infer _I> ? R : never\ntype OpInput<Op> = [Op] extends [never] ? Input : [Op] extends [OperatorBase<infer _E, infer _R, infer I>] ? I : never\n\n/**\n * Builds an expression parser from an operand parser and an operator table.\n *\n * Precedences are compared numerically: higher binds tighter. A prefix\n * operator applies to everything that binds tighter than it, so with `-`\n * at precedence 4 and `^` at 3, `-2 ^ 2` parses as `(-2) ^ 2`.\n *\n * When several operators of the same kind match, the first one in the\n * table wins, so list longer operators (`<=`) before their prefixes (`<`).\n *\n * @since 0.1.0\n * @category constructors\n */\nexport const make = <A, E, R, I extends Input, const Ops extends ReadonlyArray<Operator<A, any, any, any>>>(options: {\n readonly operand: Parser<A, E, R, I>\n readonly operators: Ops\n}): Parser<A, E | OpError<Ops[number]>, R | OpServices<Ops[number]>, I & OpInput<Ops[number]>> => {\n const operand = options.operand.step\n const prefixes = options.operators.filter((o): o is Prefix<A, any, any, any> => Predicate.isTagged(o, \"Prefix\"))\n const postfixes = options.operators.filter((o): o is Postfix<A, any, any, any> => Predicate.isTagged(o, \"Postfix\"))\n const infixes = options.operators.filter((o): o is Infix<A, any, any, any> => Predicate.isTagged(o, \"Infix\"))\n\n // Attempts each operator in turn. Returns the index of the first match\n // (with its output in `matched`), -1 if none matched, or -2 on a fatal\n // failure. Failed attempts are folded into `failures`, merged only when\n // read (#44): a round in which an operator matches discards them unmerged.\n // Like `matched`, it is shared by nested expressions, which clear it.\n let matched: unknown\n const failures = new TiedFailures()\n const tryOps = (state: State<any>, ops: ReadonlyArray<OperatorBase<any, any, any>>, mustConsume: boolean): number => {\n const start = state.pos\n for (let i = 0; i < ops.length; i++) {\n const out = ops[i].parser.step(state)\n if (out !== Fail) {\n // A prefix or postfix operator matching nothing would apply forever\n if (mustConsume && state.pos === start) {\n state.failMessage(\"Cut\", \"Assert\", \"prefix and postfix operators must always consume\")\n return -2\n }\n matched = out\n return i\n }\n if (state.mode !== \"Backtrack\") return -2\n state.rewind(start)\n failures.add(state)\n }\n return -1\n }\n\n const expr = (state: State<any>, minPower: number): A | Fail => {\n let lhs: A | Fail\n failures.clear()\n const p = tryOps(state, prefixes, true)\n if (p === -2) return Fail\n if (p >= 0) {\n const op = prefixes[p]\n const opOut = matched\n const operandValue = expr(state, op.precedence * 2)\n if (operandValue === Fail) return Fail\n lhs = op.fold(opOut, operandValue)\n } else {\n const prefixFailure = failures.failure\n const prefixTie1 = failures.tie1\n const prefixTie2 = failures.tie2\n lhs = operand(state)\n if (lhs === Fail) {\n if (prefixFailure !== undefined && state.mode === \"Backtrack\") {\n // The operand may have parsed nested expressions, which reuse `failures`\n failures.failure = prefixFailure\n failures.tie1 = prefixTie1\n failures.tie2 = prefixTie2\n failures.add(state)\n failures.fail(state)\n }\n return Fail\n }\n }\n\n while (true) {\n const start = state.pos\n failures.clear()\n const q = tryOps(state, postfixes, true)\n if (q === -2) return Fail\n if (q >= 0) {\n const op = postfixes[q]\n if (op.precedence * 2 < minPower) {\n state.rewind(start)\n break\n }\n lhs = op.fold(lhs, matched)\n continue\n }\n // The infix attempts are folded on top of the postfix ones\n const i = tryOps(state, infixes, false)\n if (i === -2) return Fail\n if (i < 0) {\n failures.hint(state)\n break\n }\n const op = infixes[i]\n const leftPower = op.precedence * 2\n if (leftPower < minPower) {\n state.rewind(start)\n break\n }\n const opOut = matched\n const rhs = expr(state, op.associativity === \"left\" ? leftPower + 1 : leftPower)\n if (rhs === Fail) return Fail\n lhs = op.fold(lhs, opOut, rhs)\n }\n return lhs\n }\n\n return makeParser((state: State<any>) => expr(state, 0), \"expression\")\n}\n"],"mappings":"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"}
package/dist/Input.d.ts CHANGED
@@ -2,7 +2,6 @@ import * as Context from "effect/Context";
2
2
  import { ErrorKind } from "./ParseError.js";
3
3
  import { Builder } from "./internal/syntax.js";
4
4
  import { Encoding } from "./Encoding.js";
5
- //#region src/Input.d.ts
6
5
  /**
7
6
  * The kinds of input a parser can consume.
8
7
  *
@@ -161,7 +160,10 @@ export interface TraceEvent {
161
160
  export declare class State<I extends Input = Input> {
162
161
  /** The full input being parsed. */
163
162
  readonly input: I;
164
- /** The current offset. Advanced by parsers, rewound on backtracking. */
163
+ /**
164
+ * The current offset. Parsers advance it by assigning it; moving it back
165
+ * (backtracking, or after a lookahead) goes through {@link rewind}.
166
+ */
165
167
  pos: number;
166
168
  /** The end offset (the input's length; in bits inside `Bits.bits`). */
167
169
  end: number;
@@ -173,8 +175,31 @@ export declare class State<I extends Input = Input> {
173
175
  context: Context.Context<never>;
174
176
  /** Mode of the latest failure. Only meaningful after a step returned {@link Fail}. */
175
177
  mode: ErrMode;
176
- /** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
177
- failure: Failure;
178
+ /**
179
+ * The latest failure, kept unmerged: the failure is `failureHead` merged
180
+ * (as `mergeFailures` does) with `failureTie1` and then `failureTie2`, all
181
+ * at the same offset. `Parser.alt` records its tied branch failures here
182
+ * with {@link failTies} instead of merging them, so that an enclosing `alt`
183
+ * whose next branch succeeds never pays for the merge. Reading
184
+ * {@link failure} performs the merge, once.
185
+ *
186
+ * @internal
187
+ */
188
+ failureHead: Failure;
189
+ /** @internal */
190
+ failureTie1: Failure | undefined;
191
+ /** @internal */
192
+ failureTie2: Failure | undefined;
193
+ /** Backs {@link deepest}. @internal */
194
+ deepestFailure: Failure | undefined;
195
+ /**
196
+ * Whether the furthest failure is the latest failure, still unmerged, so
197
+ * that {@link deepest} is that failure once merged. Only set while
198
+ * `failureTie1` is.
199
+ *
200
+ * @internal
201
+ */
202
+ deepestIsLatest: boolean;
178
203
  /**
179
204
  * The offset of a recoverable failure that occurred without consuming
180
205
  * input (for example the last attempt of a `repeat`). Its
@@ -193,7 +218,8 @@ export declare class State<I extends Input = Input> {
193
218
  * recorded inside it when it commits, and `Parser.peek` and `Parser.not`
194
219
  * forget the failures of a lookahead that succeeded.
195
220
  */
196
- deepest: Failure | undefined;
221
+ get deepest(): Failure | undefined;
222
+ set deepest(failure: Failure | undefined);
197
223
  /** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
198
224
  recovered: Array<Failure> | undefined;
199
225
  /** When defined, `Parser.trace` reports events here. */
@@ -213,7 +239,8 @@ export declare class State<I extends Input = Input> {
213
239
  */
214
240
  resume: Map<object, Map<number, unknown>> | undefined;
215
241
  /**
216
- * The tree builder of a `Syntax` parse; `undefined` otherwise.
242
+ * The tree builder of a `Syntax` parse; `undefined` otherwise. A builder
243
+ * rather than a subclass of `State`, so that every state has one shape.
217
244
  *
218
245
  * @internal
219
246
  */
@@ -223,6 +250,11 @@ export declare class State<I extends Input = Input> {
223
250
  readonly context?: Context.Context<never> | undefined;
224
251
  readonly offset?: number | undefined;
225
252
  });
253
+ /** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
254
+ get failure(): Failure;
255
+ set failure(failure: Failure);
256
+ /** Merges the latest failure's ties into it (see {@link failureHead}). */
257
+ private settleFailure;
226
258
  /**
227
259
  * Prepares the state for another run over the same input, starting at
228
260
  * `offset`: afterwards it behaves exactly like
@@ -255,6 +287,32 @@ export declare class State<I extends Input = Input> {
255
287
  readonly partial?: boolean | undefined;
256
288
  readonly context?: Context.Context<never> | undefined;
257
289
  }): this;
290
+ /**
291
+ * Moves the cursor back to `offset`, abandoning what was parsed after it:
292
+ * how a parser backtracks, or returns to where a lookahead started. A step
293
+ * that moves the cursor backwards must use this rather than assigning
294
+ * {@link pos}, so that a `Syntax` parse drops the nodes the abandoned
295
+ * attempt built. Moving forward is an assignment.
296
+ *
297
+ * @example
298
+ * ```ts
299
+ * import { Input, Parser } from "@thomasfosterau/effect-parse"
300
+ *
301
+ * // Like `Parser.opt`, for one parser
302
+ * const optional = <A>(p: Parser.Parser<A>) =>
303
+ * Parser.make<A | undefined>((state) => {
304
+ * const start = state.pos
305
+ * const a = p.step(state)
306
+ * if (a !== Input.Fail) return a
307
+ * if (state.mode !== "Backtrack") return Input.Fail
308
+ * state.rewind(start)
309
+ * return undefined
310
+ * })
311
+ * ```
312
+ *
313
+ * @since 0.4.0
314
+ */
315
+ rewind(offset: number): void;
258
316
  /**
259
317
  * The number of input units (code units, bytes or elements) remaining.
260
318
  */
@@ -297,6 +355,14 @@ export declare class State<I extends Input = Input> {
297
355
  * Records the given failure and returns {@link Fail}.
298
356
  */
299
357
  failWith(mode: ErrMode, failure: Failure): Fail;
358
+ /**
359
+ * Records a recoverable failure made of tied failures, merged only when
360
+ * read: `failWith("Backtrack", f)` where `f` merges `head` with `tie1`
361
+ * and then `tie2` (all at the same offset), as `mergeFailures` would.
362
+ *
363
+ * @internal
364
+ */
365
+ failTies(head: Failure, tie1: Failure, tie2: Failure | undefined): Fail;
300
366
  /**
301
367
  * Replaces the details of the current failure (for example to add context),
302
368
  * keeping the record of the furthest failure consistent.
@@ -326,5 +392,12 @@ export declare const mergeExpected: (a: ReadonlyArray<string>, b: ReadonlyArray<
326
392
  * @internal
327
393
  */
328
394
  export declare const mergeFailures: (a: Failure, b: Failure) => Failure;
329
- //#endregion
395
+ /**
396
+ * {@link mergeFailures} for two failures at the same offset, given their
397
+ * merged expectations (`mergeExpected(a.expected, b.expected)`, possibly
398
+ * memoised by the caller).
399
+ *
400
+ * @internal
401
+ */
402
+ export declare const mergeTiedFailures: (a: Failure, b: Failure, expected: ReadonlyArray<string>) => Failure;
330
403
  //# sourceMappingURL=Input.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Input.d.ts","names":["SyntaxBuilder"],"sources":["../src/Input.ts"],"mappings":";;;;;;;;;;;;;;;;YAgCY,iBAAiB,aAAa;;;;;;;;;;;;;YAc9B,cAAc,kBAAkB;;;;;;;;YAShC,OAAO,KAAK,MAAM,YAAY,cAAc;;;;;;;;;;;;;;;qBAgB3C;GACV,QAAQ,cAAc;GACtB,UAAU,OAAO,QAAQ,OAAO,KAAK;;;;;;;;YAkB5B,MAAM,KAAK,4BAEnB,UAAU,sBAER,UAAU,oBAAoB,KAC5B;;;;;;;YASI,MAAM,KAAK,4BAEnB,UAAU,aACR,aACA,UAAU,oBAAoB,KAC5B,cAAc;;;;;;;;qBAUT;;;;;YAMD,cAAc;;;;;;;;;;;YAYd;;;;;;;qBAQC;;WAGA;;WAEA,MAAM;;WAEN,UAAU;;WAEV,SAAS;;WAET;;WAEA;;WAEA;;WAEA;;;EAdA;;EAEA,MAAM;;EAEN,UAAU;;EAEV,SAAS;;EAET;;EAEA;;EAEA;;EAEA;;;qBAUA;;;;;;;iBAQI;WACN;WACA;WACA;WACA;;WAEA,gBAAgB;;WAEhB;WACA,OAAO;;;;;;;;qBASL,MAAM,UAAU,QAAQ;;WAE1B,OAAO;;EAGR;;EAER;;WAES;;EAET;;EAEA,SAAS,QAAQ;;EAEjB,MAAM;;EAEN,SAAS;;;;;;;EAOT;;EAEA,cAAc;;;;;;;;;;EAUd,SAAS;;EAET,WAAW,MAAM;;EAEjB,UAAU,OAAO;;EAEjB;;;;;EAKA,UAAU;;;;;;;EAOV,QAAQ,YAAY;;;;;;EAMpB,QAAQA;EAGN,YAAA,QAAQ,IAAI,OAAO,IACnB;aACW;aACA,UAAU,QAAQ;aAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyCb,MACE,iBACA;aACW;aACA,UAAU,QAAQ;;;;;EAuB/B;;;;EAOA;;;;EAOA,MAAM,eAAe,cAAc,MAAM;;;;EAezC,UAAU,MAAM,WAAW,WAAU,uBAA+B,kBAA4B;;;;EAOhG,IAAI,MAAM,WAAW,WAAU,uBAA+B,kBAA4B;;;;;EAQ1F,WAAW,gBAAgB,WAAU,wBAAgC;;;;;EAWrE,IAAI,MAAM,WAAW,UAAU,uBAAuB,iBAAiB;;;;EAOvE,UAAU,MAAM,SAAS,MAAM,WAAW,gBAAgB,kBAA4B;;;;EAOtF,YAAY,MAAM,SAAS,MAAM,WAAW,iBAAiB,kBAA4B;;;;EAOzF,SAAS,MAAM,SAAS,SAAS,UAAU;;;;;EAa3C,eAAe,SAAS;;;;;;EAUxB,mBAAmB;;;;;;EAuBnB,QAAQ,gBAAgB,UAAU;;EAUlC,YAAY,gBAAgB,MAAM,WAAW,UAAU,wBAAwB;;;qBASpE,gBAAiB,GAAG,uBAAuB,GAAG,0BAAwB;;;;;;;qBAgBtE,gBAAiB,GAAG,SAAS,GAAG,YAAU"}
1
+ {"version":3,"file":"Input.d.ts","names":["SyntaxBuilder"],"sources":["../src/Input.ts"],"mappings":";;;;;;;;;;;;;;;YAiCY,iBAAiB,aAAa;;;;;;;;;;;;;YAc9B,cAAc,kBAAkB;;;;;;;;YAShC,OAAO,KAAK,MAAM,YAAY,cAAc;;;;;;;;;;;;;;;qBAgB3C;GACV,QAAQ,cAAc;GACtB,UAAU,OAAO,QAAQ,OAAO,KAAK;;;;;;;;YAkB5B,MAAM,KAAK,4BAEnB,UAAU,sBAER,UAAU,oBAAoB,KAC5B;;;;;;;YASI,MAAM,KAAK,4BAEnB,UAAU,aACR,aACA,UAAU,oBAAoB,KAC5B,cAAc;;;;;;;;qBAUT;;;;;YAMD,cAAc;;;;;;;;;;;YAYd;;;;;;;qBAQC;;WAGA;;WAEA,MAAM;;WAEN,UAAU;;WAEV,SAAS;;WAET;;WAEA;;WAEA;;WAEA;;;EAdA;;EAEA,MAAM;;EAEN,UAAU;;EAEV,SAAS;;EAET;;EAEA;;EAEA;;EAEA;;;qBAUA;;;;;;;iBAQI;WACN;WACA;WACA;WACA;;WAEA,gBAAgB;;WAEhB;WACA,OAAO;;;;;;;;qBASL,MAAM,UAAU,QAAQ;;WAE1B,OAAO;;;;;EAKhB;;EAEA;;WAES;;EAET;;EAEA,SAAS,QAAQ;;EAEjB,MAAM;;;;;;;;;;;EAWN,aAAa;;EAEb,aAAa;;EAEb,aAAa;;EAEb,gBAAgB;;;;;;;;EAQhB;;;;;;;EAOA;;EAEA,cAAc;;;;;;;;;;MAUV,WAAW;MAIX,QAAQ,SAAS;;EAKrB,WAAW,MAAM;;EAEjB,UAAU,OAAO;;EAEjB;;;;;EAKA,UAAU;;;;;;;EAOV,QAAQ,YAAY;;;;;;;EAOpB,QAAQA;EAGN,YAAA,QAAQ,IAAI,OAAO,IACnB;aACW;aACA,UAAU,QAAQ;aAClB;;;MAcT,WAAW;MAIX,QAAQ,SAAS;;UAQb;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CR,MACE,iBACA;aACW;aACA,UAAU,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgD/B,OAAO;;;;EASP;;;;EAOA;;;;EAOA,MAAM,eAAe,cAAc,MAAM;;;;EAezC,UAAU,MAAM,WAAW,WAAU,uBAA+B,kBAA4B;;;;EAOhG,IAAI,MAAM,WAAW,WAAU,uBAA+B,kBAA4B;;;;;EAQ1F,WAAW,gBAAgB,WAAU,wBAAgC;;;;;EAWrE,IAAI,MAAM,WAAW,UAAU,uBAAuB,iBAAiB;;;;EAOvE,UAAU,MAAM,SAAS,MAAM,WAAW,gBAAgB,kBAA4B;;;;EAOtF,YAAY,MAAM,SAAS,MAAM,WAAW,iBAAiB,kBAA4B;;;;EAOzF,SAAS,MAAM,SAAS,SAAS,UAAU;;;;;;;;EAwB3C,SAAS,MAAM,SAAS,MAAM,SAAS,MAAM,sBAAsB;;;;;EAenE,eAAe,SAAS;;;;;;EAUxB,mBAAmB;;;;;;EAwBnB,QAAQ,gBAAgB,UAAU;;EAWlC,YAAY,gBAAgB,MAAM,WAAW,UAAU,wBAAwB;;;qBASpE,gBAAiB,GAAG,uBAAuB,GAAG,0BAAwB;;;;;;;qBAgBtE,gBAAiB,GAAG,SAAS,GAAG,YAAU;;;;;;;;qBAa1C,oBAAqB,GAAG,SAAS,GAAG,SAAS,UAAU,0BAAwB"}
package/dist/Input.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as Predicate from "effect/Predicate";
2
+ import * as Arr from "effect/Array";
2
3
  import * as Context from "effect/Context";
3
- //#region src/Input.ts
4
+ // src/Input.ts
4
5
  /**
5
6
  * Input types and the mutable parse {@link State} cursor.
6
7
  *
@@ -85,6 +86,11 @@ const emptyStrings = empty;
85
86
  var State = class {
86
87
  /** The full input being parsed. */
87
88
  input;
89
+ /**
90
+ * The current offset. Parsers advance it by assigning it; moving it back
91
+ * (backtracking, or after a lookahead) goes through {@link rewind}.
92
+ */
93
+ pos;
88
94
  /** The end offset (the input's length; in bits inside `Bits.bits`). */
89
95
  end;
90
96
  /** 0 = string, 1 = Uint8Array, 2 = array. */
@@ -95,8 +101,31 @@ var State = class {
95
101
  context;
96
102
  /** Mode of the latest failure. Only meaningful after a step returned {@link Fail}. */
97
103
  mode = "Backtrack";
98
- /** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
99
- failure;
104
+ /**
105
+ * The latest failure, kept unmerged: the failure is `failureHead` merged
106
+ * (as `mergeFailures` does) with `failureTie1` and then `failureTie2`, all
107
+ * at the same offset. `Parser.alt` records its tied branch failures here
108
+ * with {@link failTies} instead of merging them, so that an enclosing `alt`
109
+ * whose next branch succeeds never pays for the merge. Reading
110
+ * {@link failure} performs the merge, once.
111
+ *
112
+ * @internal
113
+ */
114
+ failureHead;
115
+ /** @internal */
116
+ failureTie1 = void 0;
117
+ /** @internal */
118
+ failureTie2 = void 0;
119
+ /** Backs {@link deepest}. @internal */
120
+ deepestFailure = void 0;
121
+ /**
122
+ * Whether the furthest failure is the latest failure, still unmerged, so
123
+ * that {@link deepest} is that failure once merged. Only set while
124
+ * `failureTie1` is.
125
+ *
126
+ * @internal
127
+ */
128
+ deepestIsLatest = false;
100
129
  /**
101
130
  * The offset of a recoverable failure that occurred without consuming
102
131
  * input (for example the last attempt of a `repeat`). Its
@@ -115,7 +144,14 @@ var State = class {
115
144
  * recorded inside it when it commits, and `Parser.peek` and `Parser.not`
116
145
  * forget the failures of a lookahead that succeeded.
117
146
  */
118
- deepest = void 0;
147
+ get deepest() {
148
+ if (this.deepestIsLatest) this.settleFailure();
149
+ return this.deepestFailure;
150
+ }
151
+ set deepest(failure) {
152
+ this.deepestIsLatest = false;
153
+ this.deepestFailure = failure;
154
+ }
119
155
  /** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
120
156
  recovered = void 0;
121
157
  /** When defined, `Parser.trace` reports events here. */
@@ -135,7 +171,8 @@ var State = class {
135
171
  */
136
172
  resume = void 0;
137
173
  /**
138
- * The tree builder of a `Syntax` parse; `undefined` otherwise.
174
+ * The tree builder of a `Syntax` parse; `undefined` otherwise. A builder
175
+ * rather than a subclass of `State`, so that every state has one shape.
139
176
  *
140
177
  * @internal
141
178
  */
@@ -148,7 +185,35 @@ var State = class {
148
185
  this.partial = options?.partial ?? false;
149
186
  this.context = options?.context ?? Context.empty();
150
187
  this.inputKind = Predicate.isString(input) ? 0 : input instanceof Uint8Array ? 1 : 2;
151
- this.failure = initialFailure;
188
+ this.failureHead = initialFailure;
189
+ }
190
+ /** Details of the latest failure. Only meaningful after a step returned {@link Fail}. */
191
+ get failure() {
192
+ if (this.failureTie1 !== void 0) this.settleFailure();
193
+ return this.failureHead;
194
+ }
195
+ set failure(failure) {
196
+ if (this.deepestIsLatest) this.settleFailure();
197
+ this.failureHead = failure;
198
+ this.failureTie1 = void 0;
199
+ this.failureTie2 = void 0;
200
+ }
201
+ /** Merges the latest failure's ties into it (see {@link failureHead}). */
202
+ settleFailure() {
203
+ let failure = this.failureHead;
204
+ const tie1 = this.failureTie1;
205
+ if (tie1 !== void 0) {
206
+ failure = mergeTiedFailures(failure, tie1, mergeExpected(failure.expected, tie1.expected));
207
+ const tie2 = this.failureTie2;
208
+ if (tie2 !== void 0) failure = mergeTiedFailures(failure, tie2, mergeExpected(failure.expected, tie2.expected));
209
+ this.failureHead = failure;
210
+ this.failureTie1 = void 0;
211
+ this.failureTie2 = void 0;
212
+ }
213
+ if (this.deepestIsLatest) {
214
+ this.deepestFailure = failure;
215
+ this.deepestIsLatest = false;
216
+ }
152
217
  }
153
218
  /**
154
219
  * Prepares the state for another run over the same input, starting at
@@ -184,10 +249,13 @@ var State = class {
184
249
  this.partial = options?.partial ?? false;
185
250
  this.context = options?.context ?? Context.empty();
186
251
  this.mode = "Backtrack";
187
- this.failure = initialFailure;
252
+ this.failureHead = initialFailure;
253
+ this.failureTie1 = void 0;
254
+ this.failureTie2 = void 0;
188
255
  this.hintOffset = -1;
189
256
  this.hintExpected = empty;
190
- this.deepest = void 0;
257
+ this.deepestFailure = void 0;
258
+ this.deepestIsLatest = false;
191
259
  this.recovered = void 0;
192
260
  this.tracer = void 0;
193
261
  this.depth = 0;
@@ -196,6 +264,36 @@ var State = class {
196
264
  return this;
197
265
  }
198
266
  /**
267
+ * Moves the cursor back to `offset`, abandoning what was parsed after it:
268
+ * how a parser backtracks, or returns to where a lookahead started. A step
269
+ * that moves the cursor backwards must use this rather than assigning
270
+ * {@link pos}, so that a `Syntax` parse drops the nodes the abandoned
271
+ * attempt built. Moving forward is an assignment.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * import { Input, Parser } from "@thomasfosterau/effect-parse"
276
+ *
277
+ * // Like `Parser.opt`, for one parser
278
+ * const optional = <A>(p: Parser.Parser<A>) =>
279
+ * Parser.make<A | undefined>((state) => {
280
+ * const start = state.pos
281
+ * const a = p.step(state)
282
+ * if (a !== Input.Fail) return a
283
+ * if (state.mode !== "Backtrack") return Input.Fail
284
+ * state.rewind(start)
285
+ * return undefined
286
+ * })
287
+ * ```
288
+ *
289
+ * @since 0.4.0
290
+ */
291
+ rewind(offset) {
292
+ const syntax = this.syntax;
293
+ if (syntax !== void 0 && offset < this.pos) syntax.rewound(this.pos, offset);
294
+ this.pos = offset;
295
+ }
296
+ /**
199
297
  * The number of input units (code units, bytes or elements) remaining.
200
298
  */
201
299
  remaining() {
@@ -261,8 +359,32 @@ var State = class {
261
359
  */
262
360
  failWith(mode, failure) {
263
361
  this.mode = mode;
264
- this.failure = failure;
265
- if (mode === "Backtrack" && (this.deepest === void 0 || failure.offset >= this.deepest.offset)) this.deepest = failure;
362
+ if (this.deepestIsLatest) {
363
+ if (mode === "Backtrack" && failure.offset >= this.failureHead.offset) this.deepestIsLatest = false;
364
+ else this.settleFailure();
365
+ }
366
+ this.failureHead = failure;
367
+ this.failureTie1 = void 0;
368
+ this.failureTie2 = void 0;
369
+ if (mode === "Backtrack" && (this.deepestFailure === void 0 || failure.offset >= this.deepestFailure.offset)) this.deepestFailure = failure;
370
+ if (this.syntax !== void 0) this.syntax.reach(failure.offset);
371
+ return Fail;
372
+ }
373
+ /**
374
+ * Records a recoverable failure made of tied failures, merged only when
375
+ * read: `failWith("Backtrack", f)` where `f` merges `head` with `tie1`
376
+ * and then `tie2` (all at the same offset), as `mergeFailures` would.
377
+ *
378
+ * @internal
379
+ */
380
+ failTies(head, tie1, tie2) {
381
+ this.mode = "Backtrack";
382
+ if (this.deepestIsLatest && head.offset < this.failureHead.offset) this.settleFailure();
383
+ this.failureHead = head;
384
+ this.failureTie1 = tie1;
385
+ this.failureTie2 = tie2;
386
+ this.deepestIsLatest = this.deepestFailure === void 0 || head.offset >= this.deepestFailure.offset;
387
+ if (this.syntax !== void 0) this.syntax.reach(head.offset);
266
388
  return Fail;
267
389
  }
268
390
  /**
@@ -282,7 +404,7 @@ var State = class {
282
404
  const f = this.failure;
283
405
  const d = this.deepest;
284
406
  if (d === void 0 || d === f || d.offset <= f.offset || this.mode === "Incomplete" || f.hasCause || f.message !== void 0) return f;
285
- const context = d.context.length === 0 ? f.context : [...d.context, ...f.context.filter((l) => !d.context.includes(l))];
407
+ const context = Arr.isReadonlyArrayEmpty(d.context) ? f.context : [...d.context, ...f.context.filter((l) => !d.context.includes(l))];
286
408
  return new Failure(d.offset, d.kind, d.expected, context, d.hasCause, d.cause, d.message, d.needed);
287
409
  }
288
410
  /**
@@ -291,6 +413,7 @@ var State = class {
291
413
  * `repeat` and `Ascii.digit1`.
292
414
  */
293
415
  setHint(offset, expected) {
416
+ if (this.syntax !== void 0) this.syntax.reach(offset);
294
417
  if (this.hintOffset === offset) this.hintExpected = mergeExpected(this.hintExpected, expected);
295
418
  else {
296
419
  this.hintOffset = offset;
@@ -299,14 +422,14 @@ var State = class {
299
422
  }
300
423
  /** @internal */
301
424
  makeFailure(offset, kind, expected) {
302
- if (this.hintOffset === offset && this.hintExpected.length > 0) expected = mergeExpected(this.hintExpected, expected);
425
+ if (this.hintOffset === offset && Arr.isReadonlyArrayNonEmpty(this.hintExpected)) expected = mergeExpected(this.hintExpected, expected);
303
426
  return new Failure(offset, kind, expected, empty, false, void 0, void 0, 0);
304
427
  }
305
428
  };
306
429
  /** @internal */
307
430
  const mergeExpected = (a, b) => {
308
- if (a.length === 0) return b;
309
- if (b.length === 0) return a;
431
+ if (Arr.isReadonlyArrayEmpty(a)) return b;
432
+ if (Arr.isReadonlyArrayEmpty(b)) return a;
310
433
  const out = a.slice();
311
434
  for (const e of b) if (!out.includes(e)) out.push(e);
312
435
  return out;
@@ -320,11 +443,19 @@ const mergeExpected = (a, b) => {
320
443
  const mergeFailures = (a, b) => {
321
444
  if (a.offset > b.offset) return a;
322
445
  if (b.offset > a.offset) return b;
323
- const expected = mergeExpected(a.expected, b.expected);
446
+ return mergeTiedFailures(a, b, mergeExpected(a.expected, b.expected));
447
+ };
448
+ /**
449
+ * {@link mergeFailures} for two failures at the same offset, given their
450
+ * merged expectations (`mergeExpected(a.expected, b.expected)`, possibly
451
+ * memoised by the caller).
452
+ *
453
+ * @internal
454
+ */
455
+ const mergeTiedFailures = (a, b, expected) => {
324
456
  if (expected === b.expected) return b;
325
- return new Failure(b.offset, b.kind, expected, b.context.length > 0 ? b.context : a.context, b.hasCause || a.hasCause, b.hasCause ? b.cause : a.cause, b.message ?? a.message, b.needed);
457
+ return new Failure(b.offset, b.kind, expected, Arr.isReadonlyArrayNonEmpty(b.context) ? b.context : a.context, b.hasCause || a.hasCause, b.hasCause ? b.cause : a.cause, b.message ?? a.message, b.needed);
326
458
  };
327
- //#endregion
328
- export { Fail, Failure, State, emptyStrings, fromSource, mergeExpected, mergeFailures };
459
+ export { Fail, Failure, State, emptyStrings, fromSource, mergeExpected, mergeFailures, mergeTiedFailures };
329
460
 
330
461
  //# sourceMappingURL=Input.js.map