parseman 0.46.0 → 0.47.1
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/README.md +8 -3
- package/dist/analysis/choice-cost-render.d.ts +12 -4
- package/dist/analysis/choice-cost-render.d.ts.map +1 -1
- package/dist/analysis/choice-cost.d.ts +2 -2
- package/dist/analysis/choice-cost.d.ts.map +1 -1
- package/dist/analysis/commitment.d.ts +131 -0
- package/dist/analysis/commitment.d.ts.map +1 -0
- package/dist/analysis/diagnostics.cjs +10339 -7787
- package/dist/analysis/diagnostics.cjs.map +3 -4
- package/dist/analysis/diagnostics.d.ts +2 -2
- package/dist/analysis/diagnostics.js +10339 -7787
- package/dist/analysis/diagnostics.js.map +3 -4
- package/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/fix.d.ts +3 -3
- package/dist/analysis/fix.d.ts.map +1 -1
- package/dist/analysis/format-number.d.ts +23 -0
- package/dist/analysis/format-number.d.ts.map +1 -0
- package/dist/analysis/gating.d.ts +39 -2
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/analysis/rebuild.d.ts.map +1 -1
- package/dist/analysis/terminal.d.ts +5 -2
- package/dist/analysis/terminal.d.ts.map +1 -1
- package/dist/cli/index.cjs +10924 -8506
- package/dist/cli/index.cjs.map +3 -4
- package/dist/cli/index.js +10924 -8506
- package/dist/cli/index.js.map +3 -4
- package/dist/combinators/adjacency.d.ts +100 -0
- package/dist/combinators/adjacency.d.ts.map +1 -0
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/completions.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +8 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/gate.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts +22 -0
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts +8 -0
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts +0 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +13 -3
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/parser.d.ts +1 -1
- package/dist/combinators/parser.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts +1 -16
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts +30 -2
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scanTo.d.ts +71 -1
- package/dist/combinators/scanTo.d.ts.map +1 -1
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +26 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -0
- package/dist/combinators/trivia-skip.d.ts +45 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/combinators/withCtx.d.ts.map +1 -1
- package/dist/compiler/compile-linkable-table.d.ts +104 -0
- package/dist/compiler/compile-linkable-table.d.ts.map +1 -0
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts +13 -0
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +5 -5
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +47 -53
- package/dist/compiler/linker.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +81 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -0
- package/dist/compiler/token-dispatch.d.ts +95 -0
- package/dist/compiler/token-dispatch.d.ts.map +1 -0
- package/dist/compiler/token-scanner.d.ts +53 -0
- package/dist/compiler/token-scanner.d.ts.map +1 -0
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/capture-buffer.d.ts +35 -0
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/cst/reflection-attach.d.ts +8 -0
- package/dist/cst/reflection-attach.d.ts.map +1 -0
- package/dist/cst/reflection-symbols.d.ts +10 -0
- package/dist/cst/reflection-symbols.d.ts.map +1 -0
- package/dist/cst/reflection.d.ts +2 -4
- package/dist/cst/reflection.d.ts.map +1 -1
- package/dist/cst/root-trivia-scope.d.ts +26 -0
- package/dist/cst/root-trivia-scope.d.ts.map +1 -0
- package/dist/cst/trivia-charscan.d.ts +18 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts +19 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/functional/doc.d.ts.map +1 -1
- package/dist/functional/run-tabled.d.ts +12 -0
- package/dist/functional/run-tabled.d.ts.map +1 -0
- package/dist/functional/run.d.ts +25 -42
- package/dist/functional/run.d.ts.map +1 -1
- package/dist/grammar-metadata.d.ts +3 -0
- package/dist/grammar-metadata.d.ts.map +1 -0
- package/dist/index.cjs +9290 -7181
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +18 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9300 -7191
- package/dist/index.js.map +3 -4
- package/dist/language-service/index.cjs +313 -121
- package/dist/language-service/index.cjs.map +3 -4
- package/dist/language-service/index.js +313 -121
- package/dist/language-service/index.js.map +3 -4
- package/dist/oracle/index.cjs.map +0 -1
- package/dist/oracle/index.js.map +0 -1
- package/dist/parse-context.d.ts +49 -0
- package/dist/parse-context.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts +40 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +11175 -9534
- package/dist/plugin/index.cjs.map +3 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +11175 -9534
- package/dist/plugin/index.js.map +3 -4
- package/dist/regex/classes.d.ts +28 -0
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +14 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.cjs +136 -51
- package/dist/run/index.cjs.map +2 -3
- package/dist/run/index.js +136 -51
- package/dist/run/index.js.map +2 -3
- package/dist/spec/index.cjs +2934 -1611
- package/dist/spec/index.cjs.map +3 -4
- package/dist/spec/index.js +2934 -1611
- package/dist/spec/index.js.map +3 -4
- package/dist/spec/model.d.ts +8 -0
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +307 -0
- package/dist/table/assemble.d.ts.map +1 -0
- package/dist/table/cell.d.ts +42 -0
- package/dist/table/cell.d.ts.map +1 -0
- package/dist/table/child-slots.d.ts +30 -0
- package/dist/table/child-slots.d.ts.map +1 -0
- package/dist/table/compile-rule-map.d.ts +136 -0
- package/dist/table/compile-rule-map.d.ts.map +1 -0
- package/dist/table/compile.d.ts +56 -0
- package/dist/table/compile.d.ts.map +1 -0
- package/dist/table/duplication-hook.d.ts +32 -0
- package/dist/table/duplication-hook.d.ts.map +1 -0
- package/dist/table/emit-assembly.d.ts +139 -0
- package/dist/table/emit-assembly.d.ts.map +1 -0
- package/dist/table/emit.d.ts +113 -0
- package/dist/table/emit.d.ts.map +1 -0
- package/dist/table/encode-baseline.d.ts +16 -0
- package/dist/table/encode-baseline.d.ts.map +1 -0
- package/dist/table/encode.d.ts +71 -0
- package/dist/table/encode.d.ts.map +1 -0
- package/dist/table/exec-baseline.d.ts +9 -0
- package/dist/table/exec-baseline.d.ts.map +1 -0
- package/dist/table/exec.d.ts +72 -0
- package/dist/table/exec.d.ts.map +1 -0
- package/dist/table/fold.d.ts +25 -0
- package/dist/table/fold.d.ts.map +1 -0
- package/dist/table/index.cjs +11463 -0
- package/dist/table/index.cjs.map +6 -0
- package/dist/table/index.d.ts +100 -0
- package/dist/table/index.d.ts.map +1 -0
- package/dist/table/index.js +11440 -0
- package/dist/table/index.js.map +6 -0
- package/dist/table/inspect.d.ts +17 -0
- package/dist/table/inspect.d.ts.map +1 -0
- package/dist/table/ops.d.ts +453 -0
- package/dist/table/ops.d.ts.map +1 -0
- package/dist/table/program.d.ts +496 -0
- package/dist/table/program.d.ts.map +1 -0
- package/dist/table/run-support.d.ts +38 -0
- package/dist/table/run-support.d.ts.map +1 -0
- package/dist/{compiler/scannable-run.d.ts → table/scan-shapes.d.ts} +14 -13
- package/dist/table/scan-shapes.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts +58 -0
- package/dist/table/site-labels.d.ts.map +1 -0
- package/dist/table/stamp.d.ts +70 -0
- package/dist/table/stamp.d.ts.map +1 -0
- package/dist/types.d.ts +80 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +26 -2
- package/src/analysis/choice-cost-gate.ts +359 -0
- package/src/analysis/choice-cost-render.ts +293 -0
- package/src/analysis/choice-cost.ts +1008 -0
- package/src/analysis/commitment.ts +557 -0
- package/src/analysis/corpus.ts +130 -0
- package/src/analysis/diagnose-render.ts +625 -0
- package/src/analysis/diagnose.ts +317 -0
- package/src/analysis/diagnostics.ts +33 -0
- package/src/analysis/duplication.ts +1955 -0
- package/src/analysis/fix-render.ts +217 -0
- package/src/analysis/fix.ts +669 -0
- package/src/analysis/format-number.ts +31 -0
- package/src/analysis/gating.ts +792 -0
- package/src/analysis/grammar.ts +72 -0
- package/src/analysis/rebuild.ts +287 -0
- package/src/analysis/terminal.ts +336 -0
- package/src/cli/index.ts +413 -0
- package/src/combinators/adjacency.ts +171 -0
- package/src/combinators/attempt.ts +41 -0
- package/src/combinators/case-fold.ts +90 -0
- package/src/combinators/choice.ts +466 -0
- package/src/combinators/completions.ts +64 -0
- package/src/combinators/dispatch.ts +358 -0
- package/src/combinators/expect.ts +159 -0
- package/src/combinators/first-set.ts +348 -0
- package/src/combinators/gate.ts +50 -0
- package/src/combinators/grammar.ts +272 -0
- package/src/combinators/keywords.ts +145 -0
- package/src/combinators/literal.ts +102 -0
- package/src/combinators/map.ts +106 -0
- package/src/combinators/node.ts +345 -0
- package/src/combinators/not.ts +55 -0
- package/src/combinators/parser.ts +266 -0
- package/src/combinators/peek.ts +63 -0
- package/src/combinators/probe.ts +25 -0
- package/src/combinators/ref.ts +109 -0
- package/src/combinators/regex.ts +170 -0
- package/src/combinators/repeat.ts +561 -0
- package/src/combinators/scanTo.ts +393 -0
- package/src/combinators/sequence.ts +237 -0
- package/src/combinators/token.ts +120 -0
- package/src/combinators/trivia-boundary.ts +48 -0
- package/src/combinators/trivia-skip.ts +852 -0
- package/src/combinators/withCtx.ts +45 -0
- package/src/compiler/build-arity.ts +330 -0
- package/src/compiler/compile-linkable-table.ts +209 -0
- package/src/compiler/degradation.ts +266 -0
- package/src/compiler/fields.ts +191 -0
- package/src/compiler/grammar-coverage-ids.ts +129 -0
- package/src/compiler/ir-serialize.ts +615 -0
- package/src/compiler/linker.ts +954 -0
- package/src/compiler/token-alphabet.ts +209 -0
- package/src/compiler/token-dispatch.ts +353 -0
- package/src/compiler/token-scanner.ts +318 -0
- package/src/compiler/value-usage.ts +147 -0
- package/src/coverage.ts +383 -0
- package/src/cst/capture-buffer.ts +297 -0
- package/src/cst/host-mode.ts +69 -0
- package/src/cst/reflection-attach.ts +11 -0
- package/src/cst/reflection-symbols.ts +9 -0
- package/src/cst/reflection.ts +149 -0
- package/src/cst/relative-spans.ts +207 -0
- package/src/cst/root-trivia-scope.ts +30 -0
- package/src/cst/trivia-charscan.ts +312 -0
- package/src/cst/trivia-entries.ts +285 -0
- package/src/cst/trivia-index.ts +131 -0
- package/src/cst/trivia-kinds.ts +358 -0
- package/src/cst/types.ts +76 -0
- package/src/cst/walk.ts +101 -0
- package/src/functional/doc.ts +959 -0
- package/src/functional/run-tabled.ts +153 -0
- package/src/functional/run.ts +558 -0
- package/src/grammar-metadata.ts +2 -0
- package/src/index.ts +189 -0
- package/src/language-service/index.ts +221 -0
- package/src/line-index.ts +131 -0
- package/src/oracle/digest.ts +620 -0
- package/src/oracle/index.ts +55 -0
- package/src/parse-context.ts +91 -0
- package/src/plugin/direct-builder-static.ts +102 -0
- package/src/plugin/evaluator.ts +1505 -0
- package/src/plugin/free-identifiers.ts +338 -0
- package/src/plugin/index.ts +2281 -0
- package/src/plugin/reducer-resolver.ts +606 -0
- package/src/recovery/scan.ts +155 -0
- package/src/regex/classes.ts +183 -0
- package/src/regex/first-set.ts +316 -0
- package/src/run/index.ts +31 -0
- package/src/spec/ebnf.ts +128 -0
- package/src/spec/index.ts +54 -0
- package/src/spec/model.ts +636 -0
- package/src/spec/railroad-lib.ts +559 -0
- package/src/spec/railroad.ts +256 -0
- package/src/table/assemble.ts +3006 -0
- package/src/table/cell.ts +42 -0
- package/src/table/child-slots.ts +113 -0
- package/src/table/compile-rule-map.ts +336 -0
- package/src/table/compile.ts +244 -0
- package/src/table/duplication-hook.ts +89 -0
- package/src/table/emit-assembly.ts +1777 -0
- package/src/table/emit.ts +429 -0
- package/src/table/encode-baseline.ts +340 -0
- package/src/table/encode.ts +1569 -0
- package/src/table/exec-baseline.ts +537 -0
- package/src/table/exec.ts +1680 -0
- package/src/table/fold.ts +38 -0
- package/src/table/index.ts +108 -0
- package/src/table/inspect.ts +52 -0
- package/src/table/ops.ts +467 -0
- package/src/table/program.ts +876 -0
- package/src/table/run-support.ts +66 -0
- package/src/table/scan-shapes.ts +1578 -0
- package/src/table/site-labels.ts +228 -0
- package/src/table/stamp.ts +229 -0
- package/src/types.ts +563 -0
- package/src/version.ts +16 -0
- package/dist/compiler/codegen.d.ts +0 -334
- package/dist/compiler/codegen.d.ts.map +0 -1
- package/dist/compiler/inline-build.d.ts +0 -11
- package/dist/compiler/inline-build.d.ts.map +0 -1
- package/dist/compiler/inline-callback.d.ts +0 -21
- package/dist/compiler/inline-callback.d.ts.map +0 -1
- package/dist/compiler/line-index.d.ts +0 -2
- package/dist/compiler/line-index.d.ts.map +0 -1
- package/dist/compiler/module-hoist.d.ts +0 -106
- package/dist/compiler/module-hoist.d.ts.map +0 -1
- package/dist/compiler/scannable-run.d.ts.map +0 -1
- package/dist/compiler/scannable-terminal.d.ts +0 -23
- package/dist/compiler/scannable-terminal.d.ts.map +0 -1
- package/dist/compiler/trivia-fast-path.d.ts +0 -38
- package/dist/compiler/trivia-fast-path.d.ts.map +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/oracle/index.ts", "../../src/oracle/digest.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * `parseman/oracle` \u2014 deterministic serialization of a parse result.\n *\n * Turns \"is this rule collapse safe?\" from a judgement call into an accept/reject.\n * Digest your parse output before and after the change and compare the two\n * digests: equal means the refactor is output-neutral and can land on its own\n * merits; different means it is not a refactor, it is a semantics change, and\n * needs the decision that goes with one.\n *\n * import { digestInto } from 'parseman/oracle'\n * import { createHash } from 'node:crypto'\n *\n * const sha = createHash('sha256')\n * digestInto(sha, parse(source))\n * const digest = sha.digest('hex')\n *\n * It is a differential, not a correctness check. It says the output did not move;\n * it says nothing about whether the output was right to begin with.\n *\n * ## Why the projection is here and the harness is not\n *\n * Deciding what the canonical byte stream IS depends on parseman's node shapes\n * and on nothing else \u2014 which distinctions a refactor can move, and which are\n * bookkeeping no consumer observes. No consumer can get that right, so it lives\n * here, and every grammar author needs it whatever they are building.\n *\n * Walking a corpus, folding per-entry digests into an aggregate, three-way\n * verdicts and report formatting used to live here too, as `loadCorpus`,\n * `digestCorpus`, `compareReports` and `formatComparison`. Those do not help\n * anyone build or diagnose a grammar: they are one consumer's regression-suite\n * plumbing, and they only mean anything with that consumer's corpus roots and\n * committed baseline in hand. They now live with the suite that needs them \u2014\n * jess's is `packages/syntax/less/less-parser/test/identity-oracle/`, a\n * reasonable model to copy. Two things are worth carrying over: keep an\n * `OK:`/`ERR:` prefix so a successful parse and a thrown error stay in disjoint\n * hash spaces, and keep \"the grammar rejected this input\" and \"the digest could\n * not be computed\" on separate channels \u2014 the second is a fact about the TOOL,\n * and reporting it as the first is how a gate lies.\n *\n * Node-only: it hashes with `node:crypto`. It is a development gate, so it is a\n * separate entry point and nothing in it reaches the browser-capable `parseman`\n * bundle.\n *\n * See `docs/guide/identity-oracle.md` for the operating discipline \u2014 in\n * particular why you must digest the BUILT artifact and rebuild between edits.\n */\nexport {\n canonicalize,\n digestInto,\n digestValue,\n CanonicalBudgetError,\n DEFAULT_MAX_VISITS,\n DIGEST_FORMAT,\n} from './digest.ts'\nexport type { CanonicalOptions, DigestTarget } from './digest.ts'\n", "/**\n * The canonical projection and the hashing the identity oracle is built on.\n *\n * Everything here exists to make ONE claim trustworthy: \"this grammar change did\n * not move the tree.\" That claim is only worth as much as the projection behind\n * it, so the rules below are stated rather than left to `JSON.stringify`, which\n * silently collapses several distinctions a grammar refactor can actually move:\n *\n * | value | `JSON.stringify` | here |\n * |------------------------|------------------|-----------------|\n * | `{ a: undefined }` | `{}` | distinct from `{}` |\n * | `NaN` / `\u00B1Infinity` | `null` | each distinct |\n * | `-0` | `0` | distinct from `0` |\n * | `new Map([[1, 2]])` | `{}` | entries, in order |\n * | `new Set([1])` | `{}` | members, in order |\n * | `new Foo({ x: 1 })` | `{\"x\":1}` | tagged `Foo` |\n *\n * A refactor that starts emitting `undefined` where it used to omit a key, or\n * that swaps one node class for another with the same fields, is a tree move.\n * An oracle that cannot see it is worse than no oracle, because it certifies.\n *\n * ## Key order does not matter; array and Map/Set order does\n *\n * Object keys are sorted, so property INSERTION order \u2014 which a refactor churns\n * constantly and which no consumer observes \u2014 never moves a digest. Array\n * elements and Map/Set entries keep their order, because those orders are the\n * output.\n *\n * ## Sharing is not a cycle\n *\n * A node reachable by two paths is written out twice, in full. Only a genuine\n * back-edge into the CURRENT path is abbreviated, and it records the DISTANCE to\n * the ancestor it points at, so a self-reference and a reference to the root are\n * different digests. (Marking every repeat as a cycle \u2014 the easy version \u2014 makes\n * the digest depend on traversal order for any DAG, which parse trees with\n * interned or shared subtrees routinely are.)\n *\n * The cost of that choice is stated plainly, because it has now bitten a\n * consumer: writing a shared subtree once per PATH is EXPONENTIAL in sharing\n * depth. A node referenced from two places at each of `d` levels is written\n * `2^d` times. See {@link DEFAULT_MAX_VISITS} for the backstop and for why the\n * obvious fix is a format change rather than a patch.\n *\n * ## Own ENUMERABLE keys only\n *\n * `Object.keys`, deliberately, not `getOwnPropertyNames`. The oracle answers \"did\n * the OUTPUT move\" and non-enumerable properties are engine bookkeeping a\n * consumer never sees \u2014 parseman's own carried CST pieces among them. Hashing\n * them would make a consumer's grammar digest move when parseman's internals\n * change, which is precisely the wrong sensitivity for a grammar-refactor gate.\n *\n * ## Tokens, not concatenation\n *\n * Every value writes one or more self-contained tokens joined by NUL. Writing\n * `#1` then `#2` into a single buffer would be indistinguishable from `#12`; a\n * projection with an ambiguity in it is a projection that can be made to lie.\n * Strings are JSON-escaped, so no token can contain a raw NUL.\n *\n * ## The projection is streamed, never materialised\n *\n * {@link digestInto} walks a value and pushes the token stream at a caller-owned\n * hash. Nothing accumulates, so there is no maximum-string-length ceiling and no\n * corpus size at which the digest stops being takeable. {@link canonicalize}\n * still builds the string, because reading it is its entire purpose \u2014 but it is\n * a debugging aid, not the path a gate runs on.\n */\nimport { createHash } from 'node:crypto'\n\n/**\n * Bump when the token grammar below changes in a way that alters output for any\n * input. It is a coarse, human-readable companion to the behavioural harness\n * digest \u2014 the harness digest is what actually enforces this, but a reader\n * comparing two reports should not have to diff hex to see that the format moved.\n */\nexport const DIGEST_FORMAT = 1\n\n/**\n * The token separator, in one place.\n *\n * {@link canonicalize} joins with it and {@link StreamSink} interleaves it, and\n * they MUST agree \u2014 the equality of those two byte streams is what lets a\n * streamed digest reproduce one taken from the string form. Sharing the constant\n * is how that stops being a thing anyone has to remember.\n */\nconst SEPARATOR = String.fromCharCode(0)\n\nconst str = (s: string): string => JSON.stringify(s)\n\nconst tagOf = (v: object): string => {\n const ctor = (v as { constructor?: unknown }).constructor\n return typeof ctor === 'function' && typeof ctor.name === 'string' ? ctor.name : ''\n}\n\n/**\n * Where canonical tokens go.\n *\n * Two implementations describing the SAME byte stream: {@link ArraySink}\n * materialises it, {@link StreamSink} pushes it at a hash and forgets it.\n */\ntype CanonicalSink = {\n push(token: string): void\n}\n\n/** Collects tokens so {@link canonicalize} can join them. */\nclass ArraySink implements CanonicalSink {\n readonly tokens: string[] = []\n\n push(token: string): void {\n this.tokens.push(token)\n }\n}\n\n/**\n * Anything that absorbs a stream of text. Node's `crypto.Hash` satisfies it, and\n * so does a three-line test double.\n *\n * The CALLER supplies this, and therefore owns the algorithm and the result.\n * Deciding what the canonical byte stream IS depends on parseman's node shapes\n * and on nothing else; deciding that you wanted sha256-hex does not.\n */\nexport type DigestTarget = {\n update(chunk: string): void\n}\n\n/**\n * Pushes the canonical token stream at a {@link DigestTarget} without ever\n * materialising it.\n *\n * The bytes written are exactly what {@link canonicalize} would have returned \u2014\n * the separator goes BETWEEN tokens, never after \u2014 so hashing either gives the\n * same digest. That equality is not an implementation detail, it is the entire\n * safety argument for streaming: every digest ever recorded stays reproducible.\n * `test/unit/oracle-digest.test.ts` asserts it directly over the canary shapes.\n *\n * Tokens are buffered and flushed in blocks rather than written one at a time:\n * one `update()` per token is a call into native code per AST node. Flushing\n * only ever happens at a token boundary and tokens are whole JS strings, so a\n * surrogate pair can never be split across two `update()` calls \u2014 which would\n * encode to different UTF-8 bytes than the joined string does.\n */\nclass StreamSink implements CanonicalSink {\n private readonly target: DigestTarget\n private buffer: string[] = []\n private buffered = 0\n private started = false\n\n constructor(target: DigestTarget) {\n this.target = target\n }\n\n /**\n * Write text that is NOT a canonical token and takes no separator \u2014 the\n * `OK:` / `ERR:` discriminator, which the string form concatenates onto the\n * front rather than joining.\n */\n prefix(text: string): void {\n if (text === '') return\n this.buffer.push(text)\n this.buffered += text.length\n }\n\n push(token: string): void {\n if (this.started) {\n this.buffer.push(SEPARATOR)\n this.buffered += 1\n } else {\n this.started = true\n }\n this.buffer.push(token)\n this.buffered += token.length\n if (this.buffered >= FLUSH_CHARS) this.flush()\n }\n\n flush(): void {\n if (this.buffer.length === 0) return\n this.target.update(this.buffer.join(''))\n this.buffer = []\n this.buffered = 0\n }\n}\n\n/**\n * How much token text to accumulate before handing a block to the target. Large\n * enough that per-call overhead disappears, small enough that the extra live\n * memory is a rounding error next to the tree being walked.\n */\nconst FLUSH_CHARS = 1 << 16\n\n/**\n * Raised when a walk exceeds its visit budget.\n *\n * A distinct class, and deliberately NOT an ordinary `Error` a caller might\n * lump in with a parse failure: this says the TOOL gave up, which is the\n * opposite of a fact about the grammar.\n */\nexport class CanonicalBudgetError extends Error {\n override readonly name = 'CanonicalBudgetError'\n\n constructor(budget: number, projected?: number) {\n super(\n `canonicalize: exceeded ${budget} visits. The value is not merely large \u2014 it is almost certainly a DAG being `\n + 'unrolled. This projection writes a shared subtree once per PATH that reaches it (see \"Sharing is not a '\n + 'cycle\"), so a node referenced from two places at each of d levels costs 2^d. Deduplicate the shared '\n + 'structure, or raise `maxVisits` if the value really is that big.'\n + (projected === undefined\n ? ''\n : ` Refused EARLY: the walk was projected to visit ${projected} objects, so it could not have finished `\n + 'under this budget however long it ran. See {@link projectVisits}.'),\n )\n }\n}\n\n/**\n * Default visit budget.\n *\n * A backstop against unbounded work, not a correctness feature. Any walk that\n * finishes under budget writes exactly the bytes it always did, so this cannot\n * move a recorded digest \u2014 which is the only reason it can be added to a shipped\n * format at all.\n *\n * It is NOT a fix for the underlying asymmetry. The fix is to dedupe by node\n * IDENTITY instead of by ancestor path, and that rewrites the byte stream for\n * every value with any sharing in it: `{ left: shared, right: shared }` stops\n * being two full writes and becomes a back-reference. That invalidates every\n * digest, every committed baseline, and {@link DIGEST_FORMAT} itself. It is a\n * format decision for the owner, not something to slip in behind a bug fix.\n *\n * ## The tempting middle road does not exist\n *\n * Stated because it looks obviously right and is obviously right about the wrong\n * thing: memoise the canonical BYTES of a subtree by node identity, splice the\n * cached bytes in at each of the two places, and you get the identical output\n * with the work done once. It is sound \u2014 a subtree's bytes depend only on the\n * subtree, except for a back-edge leaving it, which is exactly the case\n * {@link projectVisits} declines \u2014 and it does not help.\n *\n * It does not help because the walk is ALREADY linear in its own output, and it\n * is the OUTPUT that is exponential: a depth-`d` two-way unroll writes 33\u00B72^d\n * chars, measured, and every one of them has to reach the hash. Caching removes\n * re-derivation, of which there is a constant factor's worth; it cannot remove\n * bytes the format requires. At depth 40 that is 36 TB of canonical text \u2014 7\n * hours of sha256 at the 1.37 Gchar/s this machine manages, with traversal,\n * allocation and splicing all free. Depth 40 has no answer to compute quickly.\n * There is only refusing quickly, which is what the probe does.\n */\nexport const DEFAULT_MAX_VISITS = 100_000_000\n\n/**\n * Object visits to spend before the walk stops to ask whether it can possibly\n * finish. Zero cost below it, so nothing a gate actually digests ever pays.\n *\n * A million visits is ~0.4s of walking on the machine this was measured on \u2014\n * far past any real parse tree, far short of the 49 SECONDS an exponential\n * unroll used to spend before {@link DEFAULT_MAX_VISITS} noticed.\n */\nconst EXPANSION_PROBE_VISITS = 1_000_000\n\n/**\n * Distinct objects the probe will look at before giving up on answering.\n *\n * A value with more distinct nodes than this is genuinely enormous rather than\n * DAG-shaped, and the probe has nothing useful to say about it \u2014 an unrolling\n * DAG is tiny in DISTINCT nodes (depth 40 is 41 of them) and huge only in paths.\n * Capping here is what keeps the probe from becoming a second full walk: the cap\n * is what the probe costs, once, in the worst case, and giving up early costs\n * only the old behaviour.\n */\nconst EXPANSION_PROBE_NODES = 200_000\n\n/**\n * How far past the budget a projection must land before it is acted on.\n *\n * The projection re-reads the value \u2014 including any getters \u2014 so it is a\n * measurement of the value, not of the walk, and a value that answers\n * differently the second time can make it wrong. A margin this wide means only\n * an order-of-magnitude disagreement can refuse a walk that would have\n * finished; anything nearer the line falls through to the walk itself, which is\n * the authority. Erring costs time, never bytes.\n */\nconst EXPANSION_PROBE_MARGIN = 4\n\ntype WriteState = {\n readonly path: Map<object, number>\n readonly sink: CanonicalSink\n readonly budget: number\n readonly root: unknown\n visits: number\n /**\n * The value of {@link visits} at which to run the probe, or `-1` to never run\n * it. Exact equality, so it fires once and is then unreachable.\n */\n probeAt: number\n}\n\n/**\n * The number of objects {@link write} will visit for `root`, or `undefined` if\n * that cannot be answered cheaply and exactly.\n *\n * This counts what the walk counts \u2014 one per object visit, zero for a scalar,\n * one for a back-edge \u2014 but MEMOISED by node identity, so a shared subtree is\n * counted once instead of once per path. That is the whole trick: counting the\n * work is linear in DISTINCT nodes even when doing the work is exponential in\n * paths.\n *\n * It answers `undefined` rather than guessing whenever the count could be wrong\n * in the direction that matters:\n *\n * - **A back-edge.** Whether a node abbreviates to `^n` depends on the ancestor\n * set at the moment it is reached, so for a cyclic value a memoised count is\n * not context-free and can come out HIGH. Any back-edge at all abandons the\n * answer.\n * - **Too many distinct nodes.** {@link EXPANSION_PROBE_NODES}, above.\n * - **A throw.** A getter, a Proxy trap, or the added stack depth of running a\n * recursive count on top of an in-progress recursive walk. None of these are\n * the probe's business to diagnose; not answering is always available.\n */\nfunction projectVisits(root: unknown): number | undefined {\n const memo = new Map<object, number>()\n const path = new Set<object>()\n let nodes = 0\n let usable = true\n\n const count = (v: unknown): number => {\n if (typeof v !== 'object' || v === null) return 0\n if (path.has(v)) {\n usable = false\n return 1\n }\n const done = memo.get(v)\n if (done !== undefined) return done\n if (++nodes > EXPANSION_PROBE_NODES) {\n usable = false\n return 1\n }\n\n path.add(v)\n // Mirrors `write`'s child enumeration exactly. Key ORDER is irrelevant to a\n // count, so this does not sort; nothing else here may drift from `write`.\n let total = 1\n if (Array.isArray(v)) {\n for (const item of v) total += count(item)\n } else if (v instanceof Map) {\n for (const [k, val] of v) total += count(k) + count(val)\n } else if (v instanceof Set) {\n for (const item of v) total += count(item)\n } else if (!(v instanceof Date) && !(v instanceof RegExp)) {\n for (const k of Object.keys(v)) total += count((v as Record<string, unknown>)[k])\n }\n path.delete(v)\n\n memo.set(v, total)\n return total\n }\n\n try {\n const total = count(root)\n return usable ? total : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Stop a walk that provably cannot finish, at the point it has proved expensive.\n *\n * The budget is a backstop that only notices after the work is done, and for an\n * exponential unroll \"after\" is minutes. This asks the cheap question instead \u2014\n * how many visits would the whole walk take \u2014 and refuses immediately when the\n * answer is far past the budget the walk is already spending.\n *\n * The OUTCOME is unchanged: a walk projected past its budget is a walk that\n * raises {@link CanonicalBudgetError}, so this moves nothing but the clock. It\n * cannot move a digest, because no walk it stops was ever going to produce one.\n */\nfunction probeExpansion(state: WriteState): void {\n state.probeAt = -1\n const projected = projectVisits(state.root)\n if (projected !== undefined && projected / EXPANSION_PROBE_MARGIN > state.budget) {\n throw new CanonicalBudgetError(state.budget, projected)\n }\n}\n\nfunction writeScalar(v: unknown, out: CanonicalSink): void {\n switch (typeof v) {\n case 'undefined':\n out.push('u')\n return\n case 'boolean':\n out.push(v ? 'T' : 'F')\n return\n case 'bigint':\n out.push(`n${v}`)\n return\n case 'string':\n out.push(`s${str(v)}`)\n return\n // TODO(oracle-callable-identity): a callable is projected by NAME and a symbol by\n // DESCRIPTION, so two distinct same-named functions \u2014 or two same-description\n // symbols \u2014 digest identically, and a change between them reads as \"identical\".\n // This is a real hole in a tool whose whole claim is paranoia, and it is left open\n // deliberately: closing it means either hashing `Function.prototype.toString()`,\n // which flips the digest on a comment edit inside a builder, or REFUSING to digest\n // any value carrying a callable, which rejects legitimate ASTs that park a callback\n // on a node. Both are worse than the hole for some caller, and picking between them\n // is a product decision, not a bug fix. Documented under \"What it is not\" in\n // `docs/guide/identity-oracle.md` so no reader infers a guarantee that is not here.\n case 'symbol':\n out.push(`y${str(v.description ?? '')}`)\n return\n case 'function':\n out.push(`f${str(v.name)}`)\n return\n case 'number':\n if (Number.isNaN(v)) out.push('#nan')\n else if (v === Number.POSITIVE_INFINITY) out.push('#inf')\n else if (v === Number.NEGATIVE_INFINITY) out.push('#-inf')\n else if (Object.is(v, -0)) out.push('#-0')\n // Number\u2192string is exact and round-trips in JS, so this loses nothing.\n else out.push(`#${v}`)\n return\n default:\n // `typeof null`. Distinct from `undefined`, which is `'u'`.\n out.push('z')\n }\n}\n\nfunction write(v: unknown, state: WriteState, depth: number): void {\n const out = state.sink\n // Everything that is not a live object reference is written here, so the rest\n // of the function has `v: object` and needs no assertion to index or key on it.\n if (typeof v !== 'object' || v === null) {\n writeScalar(v, out)\n return\n }\n\n // Counted per OBJECT visit, not per token: the runaway this guards against is\n // re-walking structure, and scalars cannot re-walk anything.\n if (--state.visits < 0) throw new CanonicalBudgetError(state.budget)\n // One equality against a number already in a register, on a path that already\n // does three Map operations. Fires at most once per walk; `-1` is unreachable.\n if (state.visits === state.probeAt) probeExpansion(state)\n\n const path = state.path\n const seenAt = path.get(v)\n if (seenAt !== undefined) {\n // Distance to the ancestor, not its identity: a self-reference and a\n // reference to the root must not hash the same.\n out.push(`^${depth - seenAt}`)\n return\n }\n path.set(v, depth)\n\n if (Array.isArray(v)) {\n out.push('[')\n // `for..of` rather than indexing so a sparse hole reads as the `undefined`\n // it is, consistently with a present `undefined` \u2014 the distinction between\n // a hole and an explicit undefined is not observable output.\n for (const item of v) write(item, state, depth + 1)\n out.push(']')\n } else if (v instanceof Map) {\n out.push('M')\n for (const [k, val] of v) {\n write(k, state, depth + 1)\n write(val, state, depth + 1)\n }\n out.push('}')\n } else if (v instanceof Set) {\n out.push('S')\n for (const item of v) write(item, state, depth + 1)\n out.push('}')\n } else if (v instanceof Date) {\n out.push(`D${v.getTime()}`)\n } else if (v instanceof RegExp) {\n out.push(`R${str(v.source)}${str(v.flags)}`)\n } else {\n const tag = tagOf(v)\n out.push(`{${str(tag === 'Object' ? '' : tag)}`)\n for (const k of Object.keys(v).sort()) {\n out.push(`k${str(k)}`)\n write((v as Record<string, unknown>)[k], state, depth + 1)\n }\n out.push('}')\n }\n\n path.delete(v)\n}\n\n/** Options shared by every entry point that walks a value. */\nexport type CanonicalOptions = {\n /**\n * Maximum object visits before {@link CanonicalBudgetError}. Defaults to\n * {@link DEFAULT_MAX_VISITS}. A walk that finishes under budget is byte-for-byte\n * unaffected, so this can never change a digest.\n *\n * MUST be a non-negative safe integer. `NaN`, `Infinity`, a negative and a\n * fractional value are all REJECTED rather than clamped \u2014 see\n * {@link newState} for why a bad budget is a thrown error and not a shrug.\n */\n maxVisits?: number | undefined\n}\n\n/**\n * Build the walk state, validating the budget before anything is written.\n *\n * The budget is spent with `--visits < 0`, and that comparison is silently\n * FALSE for `NaN` \u2014 so `maxVisits: NaN` did not raise the ceiling, it removed\n * it, and the walk it was supposed to bound ran to the OOM the budget exists to\n * prevent. `Infinity` is the same wish spelled honestly and gets the same\n * refusal, because an unbounded walk is not an option this offers. A fraction\n * never reaches `-1` cleanly either.\n *\n * So the budget is checked once, here, where the option is read: a bad number is\n * a caller mistake and says so, rather than turning into a hang under load.\n */\nfunction newState(sink: CanonicalSink, options: CanonicalOptions | undefined, root: unknown): WriteState {\n const budget = options?.maxVisits ?? DEFAULT_MAX_VISITS\n if (!Number.isSafeInteger(budget) || budget < 0) {\n throw new RangeError(\n `canonicalize: maxVisits must be a non-negative safe integer, received ${String(budget)}. `\n + 'A non-finite or fractional budget does not raise the ceiling, it REMOVES it \u2014 the budget is spent with '\n + '`--visits < 0`, which is false forever for NaN and never reached for Infinity \u2014 so the walk this bounds '\n + 'would run unbounded into the OOM the budget exists to prevent.',\n )\n }\n // A budget too small to reach the probe never reaches it: the walk raises\n // CanonicalBudgetError first, exactly as it always did.\n const probeAt = budget >= EXPANSION_PROBE_VISITS ? budget - EXPANSION_PROBE_VISITS : -1\n return { path: new Map(), sink, budget, root, visits: budget, probeAt }\n}\n\n/**\n * The canonical token string for a value. Exported because a moved digest is\n * only actionable if you can see WHAT moved: diff two canonical strings and the\n * answer is a line, not a hex mismatch.\n *\n * This MATERIALISES the projection, so it is bounded by the maximum JS string\n * length and by available memory. Use it to explain a move you already know\n * about; take the digest itself with {@link digestInto}, which is bounded by\n * neither.\n */\nexport function canonicalize(value: unknown, options?: CanonicalOptions): string {\n const out = new ArraySink()\n write(value, newState(out, options, value), 0)\n return out.tokens.join(SEPARATOR)\n}\n\n/**\n * Stream a value's canonical projection into a caller-owned hash.\n *\n * This is the primitive: deterministic serialization of ONE parse result, which\n * is the part only parseman can do \u2014 it is parseman's node shapes that decide\n * which distinctions are semantically meaningful. The caller brings the hash and\n * keeps the result, so the algorithm, the encoding and the digest width are all\n * theirs.\n *\n * ```ts\n * const sha = createHash('sha256')\n * digestInto(sha, tree)\n * const digest = sha.digest('hex')\n * ```\n *\n * `prefix` is written ahead of the first token with NO separator, for callers\n * that discriminate one digest space from another (parseman's own oracle writes\n * `OK:` or `ERR:`). Pass `''` when there is nothing to discriminate.\n *\n * Equivalent to `hash(prefix + canonicalize(value))`, and preferable in every\n * case where you do not need to READ the projection.\n *\n * ## Two sharp edges, stated because the target is YOURS\n *\n * **A throw leaves the target written-to.** The token stream is pushed at\n * `target` as the walk proceeds, so a {@link CanonicalBudgetError}, a throwing\n * getter, or any other failure mid-walk leaves an ARBITRARY PREFIX of the\n * projection already absorbed. The hash object is then polluted: its digest is\n * neither the value's nor anything else's, and it is not recoverable \u2014 a\n * `crypto.Hash` cannot be rewound. On any throw, DISCARD the target and start a\n * fresh one. {@link digestValue} does exactly that by owning its hash for a\n * single call.\n *\n * **Two calls against one target concatenate with NO delimiter.** There is no\n * record separator between calls: `digestInto(t, a); digestInto(t, b)` writes\n * exactly `canonicalize(a) + canonicalize(b)`, with nothing in between. That is\n * ambiguous across the call boundary \u2014 the byte stream does not record where one\n * value ended, so a differently-split sequence can produce the identical stream\n * and therefore the identical digest. Concretely, `digestInto(t, 1);\n * digestInto(t, 2)` and the single call `digestInto(t, 2, '#1')` both write\n * `#1#2`. (Within ONE call the projection is unambiguous \u2014 every token is\n * self-contained and NUL-joined; it is only the seam between calls that is not.)\n *\n * This is DOCUMENTED rather than fixed. Any delimiter at the seam \u2014 written\n * before, after, or between \u2014 changes the bytes some existing caller already\n * hashes, and moving a recorded digest is a {@link DIGEST_FORMAT} decision for\n * the owner, not a patch. Callers hashing a SEQUENCE should digest a wrapper\n * value (`digestInto(t, [a, b])`), or write their own unambiguous separator\n * between calls, or use one target per value.\n */\nexport function digestInto(\n target: DigestTarget,\n value: unknown,\n prefix = '',\n options?: CanonicalOptions,\n): void {\n const sink = new StreamSink(target)\n sink.prefix(prefix)\n write(value, newState(sink, options, value), 0)\n sink.flush()\n}\n\n/**\n * Full 64-hex sha256 of a value's canonical projection, streamed.\n *\n * The convenience wrapper over {@link digestInto} for the common case.\n * Identical to `sha256(prefix + canonicalize(value))` for every value the\n * latter can survive, and unbounded where it is not.\n */\nexport function digestValue(value: unknown, prefix = '', options?: CanonicalOptions): string {\n const sha = createHash('sha256')\n digestInto(sha, value, prefix, options)\n return sha.digest('hex')\n}\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
package/dist/oracle/index.js.map
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/oracle/digest.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * The canonical projection and the hashing the identity oracle is built on.\n *\n * Everything here exists to make ONE claim trustworthy: \"this grammar change did\n * not move the tree.\" That claim is only worth as much as the projection behind\n * it, so the rules below are stated rather than left to `JSON.stringify`, which\n * silently collapses several distinctions a grammar refactor can actually move:\n *\n * | value | `JSON.stringify` | here |\n * |------------------------|------------------|-----------------|\n * | `{ a: undefined }` | `{}` | distinct from `{}` |\n * | `NaN` / `\u00B1Infinity` | `null` | each distinct |\n * | `-0` | `0` | distinct from `0` |\n * | `new Map([[1, 2]])` | `{}` | entries, in order |\n * | `new Set([1])` | `{}` | members, in order |\n * | `new Foo({ x: 1 })` | `{\"x\":1}` | tagged `Foo` |\n *\n * A refactor that starts emitting `undefined` where it used to omit a key, or\n * that swaps one node class for another with the same fields, is a tree move.\n * An oracle that cannot see it is worse than no oracle, because it certifies.\n *\n * ## Key order does not matter; array and Map/Set order does\n *\n * Object keys are sorted, so property INSERTION order \u2014 which a refactor churns\n * constantly and which no consumer observes \u2014 never moves a digest. Array\n * elements and Map/Set entries keep their order, because those orders are the\n * output.\n *\n * ## Sharing is not a cycle\n *\n * A node reachable by two paths is written out twice, in full. Only a genuine\n * back-edge into the CURRENT path is abbreviated, and it records the DISTANCE to\n * the ancestor it points at, so a self-reference and a reference to the root are\n * different digests. (Marking every repeat as a cycle \u2014 the easy version \u2014 makes\n * the digest depend on traversal order for any DAG, which parse trees with\n * interned or shared subtrees routinely are.)\n *\n * The cost of that choice is stated plainly, because it has now bitten a\n * consumer: writing a shared subtree once per PATH is EXPONENTIAL in sharing\n * depth. A node referenced from two places at each of `d` levels is written\n * `2^d` times. See {@link DEFAULT_MAX_VISITS} for the backstop and for why the\n * obvious fix is a format change rather than a patch.\n *\n * ## Own ENUMERABLE keys only\n *\n * `Object.keys`, deliberately, not `getOwnPropertyNames`. The oracle answers \"did\n * the OUTPUT move\" and non-enumerable properties are engine bookkeeping a\n * consumer never sees \u2014 parseman's own carried CST pieces among them. Hashing\n * them would make a consumer's grammar digest move when parseman's internals\n * change, which is precisely the wrong sensitivity for a grammar-refactor gate.\n *\n * ## Tokens, not concatenation\n *\n * Every value writes one or more self-contained tokens joined by NUL. Writing\n * `#1` then `#2` into a single buffer would be indistinguishable from `#12`; a\n * projection with an ambiguity in it is a projection that can be made to lie.\n * Strings are JSON-escaped, so no token can contain a raw NUL.\n *\n * ## The projection is streamed, never materialised\n *\n * {@link digestInto} walks a value and pushes the token stream at a caller-owned\n * hash. Nothing accumulates, so there is no maximum-string-length ceiling and no\n * corpus size at which the digest stops being takeable. {@link canonicalize}\n * still builds the string, because reading it is its entire purpose \u2014 but it is\n * a debugging aid, not the path a gate runs on.\n */\nimport { createHash } from 'node:crypto'\n\n/**\n * Bump when the token grammar below changes in a way that alters output for any\n * input. It is a coarse, human-readable companion to the behavioural harness\n * digest \u2014 the harness digest is what actually enforces this, but a reader\n * comparing two reports should not have to diff hex to see that the format moved.\n */\nexport const DIGEST_FORMAT = 1\n\n/**\n * The token separator, in one place.\n *\n * {@link canonicalize} joins with it and {@link StreamSink} interleaves it, and\n * they MUST agree \u2014 the equality of those two byte streams is what lets a\n * streamed digest reproduce one taken from the string form. Sharing the constant\n * is how that stops being a thing anyone has to remember.\n */\nconst SEPARATOR = String.fromCharCode(0)\n\nconst str = (s: string): string => JSON.stringify(s)\n\nconst tagOf = (v: object): string => {\n const ctor = (v as { constructor?: unknown }).constructor\n return typeof ctor === 'function' && typeof ctor.name === 'string' ? ctor.name : ''\n}\n\n/**\n * Where canonical tokens go.\n *\n * Two implementations describing the SAME byte stream: {@link ArraySink}\n * materialises it, {@link StreamSink} pushes it at a hash and forgets it.\n */\ntype CanonicalSink = {\n push(token: string): void\n}\n\n/** Collects tokens so {@link canonicalize} can join them. */\nclass ArraySink implements CanonicalSink {\n readonly tokens: string[] = []\n\n push(token: string): void {\n this.tokens.push(token)\n }\n}\n\n/**\n * Anything that absorbs a stream of text. Node's `crypto.Hash` satisfies it, and\n * so does a three-line test double.\n *\n * The CALLER supplies this, and therefore owns the algorithm and the result.\n * Deciding what the canonical byte stream IS depends on parseman's node shapes\n * and on nothing else; deciding that you wanted sha256-hex does not.\n */\nexport type DigestTarget = {\n update(chunk: string): void\n}\n\n/**\n * Pushes the canonical token stream at a {@link DigestTarget} without ever\n * materialising it.\n *\n * The bytes written are exactly what {@link canonicalize} would have returned \u2014\n * the separator goes BETWEEN tokens, never after \u2014 so hashing either gives the\n * same digest. That equality is not an implementation detail, it is the entire\n * safety argument for streaming: every digest ever recorded stays reproducible.\n * `test/unit/oracle-digest.test.ts` asserts it directly over the canary shapes.\n *\n * Tokens are buffered and flushed in blocks rather than written one at a time:\n * one `update()` per token is a call into native code per AST node. Flushing\n * only ever happens at a token boundary and tokens are whole JS strings, so a\n * surrogate pair can never be split across two `update()` calls \u2014 which would\n * encode to different UTF-8 bytes than the joined string does.\n */\nclass StreamSink implements CanonicalSink {\n private readonly target: DigestTarget\n private buffer: string[] = []\n private buffered = 0\n private started = false\n\n constructor(target: DigestTarget) {\n this.target = target\n }\n\n /**\n * Write text that is NOT a canonical token and takes no separator \u2014 the\n * `OK:` / `ERR:` discriminator, which the string form concatenates onto the\n * front rather than joining.\n */\n prefix(text: string): void {\n if (text === '') return\n this.buffer.push(text)\n this.buffered += text.length\n }\n\n push(token: string): void {\n if (this.started) {\n this.buffer.push(SEPARATOR)\n this.buffered += 1\n } else {\n this.started = true\n }\n this.buffer.push(token)\n this.buffered += token.length\n if (this.buffered >= FLUSH_CHARS) this.flush()\n }\n\n flush(): void {\n if (this.buffer.length === 0) return\n this.target.update(this.buffer.join(''))\n this.buffer = []\n this.buffered = 0\n }\n}\n\n/**\n * How much token text to accumulate before handing a block to the target. Large\n * enough that per-call overhead disappears, small enough that the extra live\n * memory is a rounding error next to the tree being walked.\n */\nconst FLUSH_CHARS = 1 << 16\n\n/**\n * Raised when a walk exceeds its visit budget.\n *\n * A distinct class, and deliberately NOT an ordinary `Error` a caller might\n * lump in with a parse failure: this says the TOOL gave up, which is the\n * opposite of a fact about the grammar.\n */\nexport class CanonicalBudgetError extends Error {\n override readonly name = 'CanonicalBudgetError'\n\n constructor(budget: number, projected?: number) {\n super(\n `canonicalize: exceeded ${budget} visits. The value is not merely large \u2014 it is almost certainly a DAG being `\n + 'unrolled. This projection writes a shared subtree once per PATH that reaches it (see \"Sharing is not a '\n + 'cycle\"), so a node referenced from two places at each of d levels costs 2^d. Deduplicate the shared '\n + 'structure, or raise `maxVisits` if the value really is that big.'\n + (projected === undefined\n ? ''\n : ` Refused EARLY: the walk was projected to visit ${projected} objects, so it could not have finished `\n + 'under this budget however long it ran. See {@link projectVisits}.'),\n )\n }\n}\n\n/**\n * Default visit budget.\n *\n * A backstop against unbounded work, not a correctness feature. Any walk that\n * finishes under budget writes exactly the bytes it always did, so this cannot\n * move a recorded digest \u2014 which is the only reason it can be added to a shipped\n * format at all.\n *\n * It is NOT a fix for the underlying asymmetry. The fix is to dedupe by node\n * IDENTITY instead of by ancestor path, and that rewrites the byte stream for\n * every value with any sharing in it: `{ left: shared, right: shared }` stops\n * being two full writes and becomes a back-reference. That invalidates every\n * digest, every committed baseline, and {@link DIGEST_FORMAT} itself. It is a\n * format decision for the owner, not something to slip in behind a bug fix.\n *\n * ## The tempting middle road does not exist\n *\n * Stated because it looks obviously right and is obviously right about the wrong\n * thing: memoise the canonical BYTES of a subtree by node identity, splice the\n * cached bytes in at each of the two places, and you get the identical output\n * with the work done once. It is sound \u2014 a subtree's bytes depend only on the\n * subtree, except for a back-edge leaving it, which is exactly the case\n * {@link projectVisits} declines \u2014 and it does not help.\n *\n * It does not help because the walk is ALREADY linear in its own output, and it\n * is the OUTPUT that is exponential: a depth-`d` two-way unroll writes 33\u00B72^d\n * chars, measured, and every one of them has to reach the hash. Caching removes\n * re-derivation, of which there is a constant factor's worth; it cannot remove\n * bytes the format requires. At depth 40 that is 36 TB of canonical text \u2014 7\n * hours of sha256 at the 1.37 Gchar/s this machine manages, with traversal,\n * allocation and splicing all free. Depth 40 has no answer to compute quickly.\n * There is only refusing quickly, which is what the probe does.\n */\nexport const DEFAULT_MAX_VISITS = 100_000_000\n\n/**\n * Object visits to spend before the walk stops to ask whether it can possibly\n * finish. Zero cost below it, so nothing a gate actually digests ever pays.\n *\n * A million visits is ~0.4s of walking on the machine this was measured on \u2014\n * far past any real parse tree, far short of the 49 SECONDS an exponential\n * unroll used to spend before {@link DEFAULT_MAX_VISITS} noticed.\n */\nconst EXPANSION_PROBE_VISITS = 1_000_000\n\n/**\n * Distinct objects the probe will look at before giving up on answering.\n *\n * A value with more distinct nodes than this is genuinely enormous rather than\n * DAG-shaped, and the probe has nothing useful to say about it \u2014 an unrolling\n * DAG is tiny in DISTINCT nodes (depth 40 is 41 of them) and huge only in paths.\n * Capping here is what keeps the probe from becoming a second full walk: the cap\n * is what the probe costs, once, in the worst case, and giving up early costs\n * only the old behaviour.\n */\nconst EXPANSION_PROBE_NODES = 200_000\n\n/**\n * How far past the budget a projection must land before it is acted on.\n *\n * The projection re-reads the value \u2014 including any getters \u2014 so it is a\n * measurement of the value, not of the walk, and a value that answers\n * differently the second time can make it wrong. A margin this wide means only\n * an order-of-magnitude disagreement can refuse a walk that would have\n * finished; anything nearer the line falls through to the walk itself, which is\n * the authority. Erring costs time, never bytes.\n */\nconst EXPANSION_PROBE_MARGIN = 4\n\ntype WriteState = {\n readonly path: Map<object, number>\n readonly sink: CanonicalSink\n readonly budget: number\n readonly root: unknown\n visits: number\n /**\n * The value of {@link visits} at which to run the probe, or `-1` to never run\n * it. Exact equality, so it fires once and is then unreachable.\n */\n probeAt: number\n}\n\n/**\n * The number of objects {@link write} will visit for `root`, or `undefined` if\n * that cannot be answered cheaply and exactly.\n *\n * This counts what the walk counts \u2014 one per object visit, zero for a scalar,\n * one for a back-edge \u2014 but MEMOISED by node identity, so a shared subtree is\n * counted once instead of once per path. That is the whole trick: counting the\n * work is linear in DISTINCT nodes even when doing the work is exponential in\n * paths.\n *\n * It answers `undefined` rather than guessing whenever the count could be wrong\n * in the direction that matters:\n *\n * - **A back-edge.** Whether a node abbreviates to `^n` depends on the ancestor\n * set at the moment it is reached, so for a cyclic value a memoised count is\n * not context-free and can come out HIGH. Any back-edge at all abandons the\n * answer.\n * - **Too many distinct nodes.** {@link EXPANSION_PROBE_NODES}, above.\n * - **A throw.** A getter, a Proxy trap, or the added stack depth of running a\n * recursive count on top of an in-progress recursive walk. None of these are\n * the probe's business to diagnose; not answering is always available.\n */\nfunction projectVisits(root: unknown): number | undefined {\n const memo = new Map<object, number>()\n const path = new Set<object>()\n let nodes = 0\n let usable = true\n\n const count = (v: unknown): number => {\n if (typeof v !== 'object' || v === null) return 0\n if (path.has(v)) {\n usable = false\n return 1\n }\n const done = memo.get(v)\n if (done !== undefined) return done\n if (++nodes > EXPANSION_PROBE_NODES) {\n usable = false\n return 1\n }\n\n path.add(v)\n // Mirrors `write`'s child enumeration exactly. Key ORDER is irrelevant to a\n // count, so this does not sort; nothing else here may drift from `write`.\n let total = 1\n if (Array.isArray(v)) {\n for (const item of v) total += count(item)\n } else if (v instanceof Map) {\n for (const [k, val] of v) total += count(k) + count(val)\n } else if (v instanceof Set) {\n for (const item of v) total += count(item)\n } else if (!(v instanceof Date) && !(v instanceof RegExp)) {\n for (const k of Object.keys(v)) total += count((v as Record<string, unknown>)[k])\n }\n path.delete(v)\n\n memo.set(v, total)\n return total\n }\n\n try {\n const total = count(root)\n return usable ? total : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * Stop a walk that provably cannot finish, at the point it has proved expensive.\n *\n * The budget is a backstop that only notices after the work is done, and for an\n * exponential unroll \"after\" is minutes. This asks the cheap question instead \u2014\n * how many visits would the whole walk take \u2014 and refuses immediately when the\n * answer is far past the budget the walk is already spending.\n *\n * The OUTCOME is unchanged: a walk projected past its budget is a walk that\n * raises {@link CanonicalBudgetError}, so this moves nothing but the clock. It\n * cannot move a digest, because no walk it stops was ever going to produce one.\n */\nfunction probeExpansion(state: WriteState): void {\n state.probeAt = -1\n const projected = projectVisits(state.root)\n if (projected !== undefined && projected / EXPANSION_PROBE_MARGIN > state.budget) {\n throw new CanonicalBudgetError(state.budget, projected)\n }\n}\n\nfunction writeScalar(v: unknown, out: CanonicalSink): void {\n switch (typeof v) {\n case 'undefined':\n out.push('u')\n return\n case 'boolean':\n out.push(v ? 'T' : 'F')\n return\n case 'bigint':\n out.push(`n${v}`)\n return\n case 'string':\n out.push(`s${str(v)}`)\n return\n // TODO(oracle-callable-identity): a callable is projected by NAME and a symbol by\n // DESCRIPTION, so two distinct same-named functions \u2014 or two same-description\n // symbols \u2014 digest identically, and a change between them reads as \"identical\".\n // This is a real hole in a tool whose whole claim is paranoia, and it is left open\n // deliberately: closing it means either hashing `Function.prototype.toString()`,\n // which flips the digest on a comment edit inside a builder, or REFUSING to digest\n // any value carrying a callable, which rejects legitimate ASTs that park a callback\n // on a node. Both are worse than the hole for some caller, and picking between them\n // is a product decision, not a bug fix. Documented under \"What it is not\" in\n // `docs/guide/identity-oracle.md` so no reader infers a guarantee that is not here.\n case 'symbol':\n out.push(`y${str(v.description ?? '')}`)\n return\n case 'function':\n out.push(`f${str(v.name)}`)\n return\n case 'number':\n if (Number.isNaN(v)) out.push('#nan')\n else if (v === Number.POSITIVE_INFINITY) out.push('#inf')\n else if (v === Number.NEGATIVE_INFINITY) out.push('#-inf')\n else if (Object.is(v, -0)) out.push('#-0')\n // Number\u2192string is exact and round-trips in JS, so this loses nothing.\n else out.push(`#${v}`)\n return\n default:\n // `typeof null`. Distinct from `undefined`, which is `'u'`.\n out.push('z')\n }\n}\n\nfunction write(v: unknown, state: WriteState, depth: number): void {\n const out = state.sink\n // Everything that is not a live object reference is written here, so the rest\n // of the function has `v: object` and needs no assertion to index or key on it.\n if (typeof v !== 'object' || v === null) {\n writeScalar(v, out)\n return\n }\n\n // Counted per OBJECT visit, not per token: the runaway this guards against is\n // re-walking structure, and scalars cannot re-walk anything.\n if (--state.visits < 0) throw new CanonicalBudgetError(state.budget)\n // One equality against a number already in a register, on a path that already\n // does three Map operations. Fires at most once per walk; `-1` is unreachable.\n if (state.visits === state.probeAt) probeExpansion(state)\n\n const path = state.path\n const seenAt = path.get(v)\n if (seenAt !== undefined) {\n // Distance to the ancestor, not its identity: a self-reference and a\n // reference to the root must not hash the same.\n out.push(`^${depth - seenAt}`)\n return\n }\n path.set(v, depth)\n\n if (Array.isArray(v)) {\n out.push('[')\n // `for..of` rather than indexing so a sparse hole reads as the `undefined`\n // it is, consistently with a present `undefined` \u2014 the distinction between\n // a hole and an explicit undefined is not observable output.\n for (const item of v) write(item, state, depth + 1)\n out.push(']')\n } else if (v instanceof Map) {\n out.push('M')\n for (const [k, val] of v) {\n write(k, state, depth + 1)\n write(val, state, depth + 1)\n }\n out.push('}')\n } else if (v instanceof Set) {\n out.push('S')\n for (const item of v) write(item, state, depth + 1)\n out.push('}')\n } else if (v instanceof Date) {\n out.push(`D${v.getTime()}`)\n } else if (v instanceof RegExp) {\n out.push(`R${str(v.source)}${str(v.flags)}`)\n } else {\n const tag = tagOf(v)\n out.push(`{${str(tag === 'Object' ? '' : tag)}`)\n for (const k of Object.keys(v).sort()) {\n out.push(`k${str(k)}`)\n write((v as Record<string, unknown>)[k], state, depth + 1)\n }\n out.push('}')\n }\n\n path.delete(v)\n}\n\n/** Options shared by every entry point that walks a value. */\nexport type CanonicalOptions = {\n /**\n * Maximum object visits before {@link CanonicalBudgetError}. Defaults to\n * {@link DEFAULT_MAX_VISITS}. A walk that finishes under budget is byte-for-byte\n * unaffected, so this can never change a digest.\n *\n * MUST be a non-negative safe integer. `NaN`, `Infinity`, a negative and a\n * fractional value are all REJECTED rather than clamped \u2014 see\n * {@link newState} for why a bad budget is a thrown error and not a shrug.\n */\n maxVisits?: number | undefined\n}\n\n/**\n * Build the walk state, validating the budget before anything is written.\n *\n * The budget is spent with `--visits < 0`, and that comparison is silently\n * FALSE for `NaN` \u2014 so `maxVisits: NaN` did not raise the ceiling, it removed\n * it, and the walk it was supposed to bound ran to the OOM the budget exists to\n * prevent. `Infinity` is the same wish spelled honestly and gets the same\n * refusal, because an unbounded walk is not an option this offers. A fraction\n * never reaches `-1` cleanly either.\n *\n * So the budget is checked once, here, where the option is read: a bad number is\n * a caller mistake and says so, rather than turning into a hang under load.\n */\nfunction newState(sink: CanonicalSink, options: CanonicalOptions | undefined, root: unknown): WriteState {\n const budget = options?.maxVisits ?? DEFAULT_MAX_VISITS\n if (!Number.isSafeInteger(budget) || budget < 0) {\n throw new RangeError(\n `canonicalize: maxVisits must be a non-negative safe integer, received ${String(budget)}. `\n + 'A non-finite or fractional budget does not raise the ceiling, it REMOVES it \u2014 the budget is spent with '\n + '`--visits < 0`, which is false forever for NaN and never reached for Infinity \u2014 so the walk this bounds '\n + 'would run unbounded into the OOM the budget exists to prevent.',\n )\n }\n // A budget too small to reach the probe never reaches it: the walk raises\n // CanonicalBudgetError first, exactly as it always did.\n const probeAt = budget >= EXPANSION_PROBE_VISITS ? budget - EXPANSION_PROBE_VISITS : -1\n return { path: new Map(), sink, budget, root, visits: budget, probeAt }\n}\n\n/**\n * The canonical token string for a value. Exported because a moved digest is\n * only actionable if you can see WHAT moved: diff two canonical strings and the\n * answer is a line, not a hex mismatch.\n *\n * This MATERIALISES the projection, so it is bounded by the maximum JS string\n * length and by available memory. Use it to explain a move you already know\n * about; take the digest itself with {@link digestInto}, which is bounded by\n * neither.\n */\nexport function canonicalize(value: unknown, options?: CanonicalOptions): string {\n const out = new ArraySink()\n write(value, newState(out, options, value), 0)\n return out.tokens.join(SEPARATOR)\n}\n\n/**\n * Stream a value's canonical projection into a caller-owned hash.\n *\n * This is the primitive: deterministic serialization of ONE parse result, which\n * is the part only parseman can do \u2014 it is parseman's node shapes that decide\n * which distinctions are semantically meaningful. The caller brings the hash and\n * keeps the result, so the algorithm, the encoding and the digest width are all\n * theirs.\n *\n * ```ts\n * const sha = createHash('sha256')\n * digestInto(sha, tree)\n * const digest = sha.digest('hex')\n * ```\n *\n * `prefix` is written ahead of the first token with NO separator, for callers\n * that discriminate one digest space from another (parseman's own oracle writes\n * `OK:` or `ERR:`). Pass `''` when there is nothing to discriminate.\n *\n * Equivalent to `hash(prefix + canonicalize(value))`, and preferable in every\n * case where you do not need to READ the projection.\n *\n * ## Two sharp edges, stated because the target is YOURS\n *\n * **A throw leaves the target written-to.** The token stream is pushed at\n * `target` as the walk proceeds, so a {@link CanonicalBudgetError}, a throwing\n * getter, or any other failure mid-walk leaves an ARBITRARY PREFIX of the\n * projection already absorbed. The hash object is then polluted: its digest is\n * neither the value's nor anything else's, and it is not recoverable \u2014 a\n * `crypto.Hash` cannot be rewound. On any throw, DISCARD the target and start a\n * fresh one. {@link digestValue} does exactly that by owning its hash for a\n * single call.\n *\n * **Two calls against one target concatenate with NO delimiter.** There is no\n * record separator between calls: `digestInto(t, a); digestInto(t, b)` writes\n * exactly `canonicalize(a) + canonicalize(b)`, with nothing in between. That is\n * ambiguous across the call boundary \u2014 the byte stream does not record where one\n * value ended, so a differently-split sequence can produce the identical stream\n * and therefore the identical digest. Concretely, `digestInto(t, 1);\n * digestInto(t, 2)` and the single call `digestInto(t, 2, '#1')` both write\n * `#1#2`. (Within ONE call the projection is unambiguous \u2014 every token is\n * self-contained and NUL-joined; it is only the seam between calls that is not.)\n *\n * This is DOCUMENTED rather than fixed. Any delimiter at the seam \u2014 written\n * before, after, or between \u2014 changes the bytes some existing caller already\n * hashes, and moving a recorded digest is a {@link DIGEST_FORMAT} decision for\n * the owner, not a patch. Callers hashing a SEQUENCE should digest a wrapper\n * value (`digestInto(t, [a, b])`), or write their own unambiguous separator\n * between calls, or use one target per value.\n */\nexport function digestInto(\n target: DigestTarget,\n value: unknown,\n prefix = '',\n options?: CanonicalOptions,\n): void {\n const sink = new StreamSink(target)\n sink.prefix(prefix)\n write(value, newState(sink, options, value), 0)\n sink.flush()\n}\n\n/**\n * Full 64-hex sha256 of a value's canonical projection, streamed.\n *\n * The convenience wrapper over {@link digestInto} for the common case.\n * Identical to `sha256(prefix + canonicalize(value))` for every value the\n * latter can survive, and unbounded where it is not.\n */\nexport function digestValue(value: unknown, prefix = '', options?: CanonicalOptions): string {\n const sha = createHash('sha256')\n digestInto(sha, value, prefix, options)\n return sha.digest('hex')\n}\n"],
|
|
5
4
|
"mappings": "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",
|
|
6
5
|
"names": []
|
|
7
6
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ParseContext } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* THE canonical `ParseContext` literal — the only place in the runtime where one
|
|
4
|
+
* is constructed from scratch.
|
|
5
|
+
*
|
|
6
|
+
* `ctx` is threaded through every combinator call, so it is the single most
|
|
7
|
+
* megamorphism-sensitive object in the system: every `ctx.<field>` load in
|
|
8
|
+
* `choice`/`sequence`/`regex`/`node`/… is an inline cache keyed on its hidden
|
|
9
|
+
* class. Historically the entry points built `ctx` with CONDITIONAL SPREADS
|
|
10
|
+
* (`...(cond ? { x } : {})`), which is a shape hazard, not a cost-free idiom:
|
|
11
|
+
*
|
|
12
|
+
* - `run()` had SIX of them (root trivia, grammar trivia, scan-skip, capture
|
|
13
|
+
* mask, tolerant, instrumentation) — up to 2^6 distinct hidden classes for
|
|
14
|
+
* `ctx` across configurations, all landing on the same ICs.
|
|
15
|
+
* - Fields that appear only inside a spread are ABSENT from the map on the
|
|
16
|
+
* paths that don't take the branch. That is why `ctx._triviaLog = undefined`
|
|
17
|
+
* is not free there: it is a property ADD (a map transition), not a store.
|
|
18
|
+
* And it is why `delete ctx._triviaLog` is expensive on the paths where the
|
|
19
|
+
* field IS present: a transition to dictionary mode.
|
|
20
|
+
*
|
|
21
|
+
* Declaring every field unconditionally — `undefined` when unset — collapses
|
|
22
|
+
* that to ONE realised map. Clearing a field is then a plain in-object store to
|
|
23
|
+
* an existing slot on every path, so `delete` is never needed and neither
|
|
24
|
+
* operation can transition the map.
|
|
25
|
+
*
|
|
26
|
+
* This is safe because absence and `undefined` are already interchangeable
|
|
27
|
+
* throughout the runtime: every reader tests `!== undefined` or truthiness, and
|
|
28
|
+
* none uses `in`, `hasOwnProperty`, or `Object.keys` on a `ParseContext`.
|
|
29
|
+
* `recovery/scan.ts` already restores these exact fields by assigning the saved
|
|
30
|
+
* value, which is `undefined` when the field was absent.
|
|
31
|
+
*
|
|
32
|
+
* Callers assign what they need AFTER construction; because every slot already
|
|
33
|
+
* exists those are in-object stores, and the shape is preserved. Do NOT
|
|
34
|
+
* reintroduce a conditional spread, and do NOT add a field here without also
|
|
35
|
+
* declaring it on `ParseContext`.
|
|
36
|
+
*
|
|
37
|
+
* Key ORDER matters as much as the key set — two literals with the same keys in
|
|
38
|
+
* a different order are two different maps — which is why there is one literal
|
|
39
|
+
* and not one per call site. It follows the declaration order of `ParseContext`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createParseContext(): ParseContext;
|
|
42
|
+
/**
|
|
43
|
+
* A `ctx` for a THROWAWAY sub-parse that must not inherit or pollute any sink —
|
|
44
|
+
* the trailing-trivia probe in `run()`/`doc()`, the trivia-kind classifier, and
|
|
45
|
+
* the no-capture trivia parses in `trivia-skip`. Identical shape to
|
|
46
|
+
* `createParseContext()`; only `trackLines`/`state` are seeded.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createDetachedParseContext(trackLines: boolean, state: unknown): ParseContext;
|
|
49
|
+
//# sourceMappingURL=parse-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-context.d.ts","sourceRoot":"","sources":["../src/parse-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,kBAAkB,IAAI,YAAY,CAoCjD;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,CAK5F"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Expression, Node } from '@oxc-project/types';
|
|
9
9
|
import type { Combinator } from '../types.ts';
|
|
10
|
+
import { type RulesOptions } from '../combinators/parser.ts';
|
|
10
11
|
import type { ReducerResolver } from './reducer-resolver.ts';
|
|
11
12
|
/** Install (or clear, with `null`) the resolver for the module being transformed. */
|
|
12
13
|
export declare function setReducerResolver(r: ReducerResolver | null, entrySource?: string | null): void;
|
|
@@ -24,6 +25,27 @@ type WhenFactoryEntry = {
|
|
|
24
25
|
tag: 'whenFactory';
|
|
25
26
|
caseInsensitive: boolean;
|
|
26
27
|
};
|
|
28
|
+
/** THE reader for an object-literal property key, for every consumer in the plugin.
|
|
29
|
+
*
|
|
30
|
+
* Returns the key a JavaScript engine would use, or null when the property does not
|
|
31
|
+
* name a static key at all — a spread, a rest, or a COMPUTED key whose value is not
|
|
32
|
+
* known until runtime.
|
|
33
|
+
*
|
|
34
|
+
* Both halves are load-bearing and each was got wrong somewhere:
|
|
35
|
+
* - a quoted key is a `Literal`, not an `Identifier`. Reading only `key.name` sees
|
|
36
|
+
* `{ 'hostMode': 'cst' }` as having no hostMode, and drops the option SILENTLY.
|
|
37
|
+
* - `key.name` is also populated for a COMPUTED key `{ [hostMode]: … }`, where the
|
|
38
|
+
* identifier is a variable and the actual key is its value. Reading only
|
|
39
|
+
* `key.name` there invents an option the source never set.
|
|
40
|
+
*
|
|
41
|
+
* So an Identifier-only reader both misses keys that are present and matches keys that
|
|
42
|
+
* are not. Three option readers in plugin/index.ts each re-derived one wrong half; the
|
|
43
|
+
* fix is this one function, imported. */
|
|
44
|
+
export declare function propName(p: {
|
|
45
|
+
type?: string;
|
|
46
|
+
computed?: boolean;
|
|
47
|
+
key?: unknown;
|
|
48
|
+
}): string | null;
|
|
27
49
|
/** Evaluate makeWord(boundary?, opts?) to a factory entry (not a combinator). */
|
|
28
50
|
export declare function evaluateWordFactory(node: Expression, scope: Scope, code?: string): WordFactoryEntry | null;
|
|
29
51
|
/** Evaluate makeWhen(opts?) to a dispatch-arm factory entry (not a combinator). */
|
|
@@ -54,7 +76,24 @@ export declare function evaluateStaticValue(node: Expression, scope: Scope, code
|
|
|
54
76
|
* The body-phase entries are stored as `mfSrcs` on localScope entries and
|
|
55
77
|
* replayed when those entries are referenced during return evaluation.
|
|
56
78
|
*/
|
|
57
|
-
export declare function evaluateParserFactory(factoryNode: Expression, scope: Scope, code: string, mapFnSources: string[]
|
|
79
|
+
export declare function evaluateParserFactory(factoryNode: Expression, scope: Scope, code: string, mapFnSources: string[], // receives ONLY the return-expression mfSrcs
|
|
80
|
+
out?: {
|
|
81
|
+
reason?: string;
|
|
82
|
+
}, // receives a SPECIFIC failure reason (see evalBodyStatements)
|
|
83
|
+
/**
|
|
84
|
+
* The `rules({ … }, factory)` options this call site declared, THREADED THROUGH
|
|
85
|
+
* rather than reapplied afterwards.
|
|
86
|
+
*
|
|
87
|
+
* `plugin/index.ts` used to stamp `grammarScanSkip`, `grammarHostMode` and
|
|
88
|
+
* `grammarTrackLines` onto the evaluated rules in three loops of its own,
|
|
89
|
+
* carrying a comment that it had to "because the macro evaluates the FACTORY
|
|
90
|
+
* directly and never calls `rules()`". It calls `rules()` now, so the options
|
|
91
|
+
* belong where every other caller puts them: in the argument. That also gets
|
|
92
|
+
* the `trackLines` half right for the first time — `rules()` does not merely
|
|
93
|
+
* stamp it, it WRAPS each rule in a `grammarParser({ trackLines: true })` scope
|
|
94
|
+
* (`parser.ts:228-242`), which the macro's stamp-only copy never did.
|
|
95
|
+
*/
|
|
96
|
+
options?: RulesOptions): Map<string, Combinator<unknown>> | null;
|
|
58
97
|
/** A combinator slot created by ref() — has a callable `define`. */
|
|
59
98
|
type DefinableRef = Combinator<unknown> & {
|
|
60
99
|
define(p: Combinator<unknown>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/plugin/evaluator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,UAAU,EAAE,IAAI,EAMjB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../../src/plugin/evaluator.ts"],"names":[],"mappings":"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"}
|