Skip to content

perf(provenance): prototype packed location events - #18

Closed
jeswr wants to merge 11 commits into
mainfrom
codex/provenance-packed-events
Closed

perf(provenance): prototype packed location events#18
jeswr wants to merge 11 commits into
mainfrom
codex/provenance-packed-events

Conversation

@jeswr

@jeswr jeswr commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Purpose

Prototype the packed location-event architecture discussed while investigating rdfjs#672, as an independent reviewable diff.

Architecture

  • expose a positional onLocation(quad, subjectToken, predicateToken, objectToken, graphToken) boundary
  • transport lexer tokens temporarily on parsed terms
  • consume each event into one packed Uint32Array
  • clear temporary term slots after synchronous parsing
  • index value-equal quad occurrences and materialize public Range[] objects lazily

This tests the cost of a compact event consumer without yet paying the larger parser-state diff needed for true occurrence IDs.

Performance

100,000 N-Triples (6,677,780 bytes):

The fresh host was noisy, so the ratio is included as a smoke measurement rather than a precise cross-branch ranking.

Validation

  • node and browser bundles build
  • 22 test suites / 6,887 tests pass with 100% statements, branches, functions, and lines
  • touched files pass ESLint
  • value-equal lookup and duplicate utterances remain supported

Known limitations

The event boundary is clean, but this prototype transports tokens through temporary term symbols. It therefore still requires extensible terms and remains ambiguous with an interning DataFactory. The RDF4J-style PR removes that shortcut by carrying occurrence IDs in parser state.

Eric Prud'hommeaux and others added 11 commits August 3, 2026 19:27
Lexer: an absolute-offset counter and, under the new trackOffsets
option, offsetStart/offsetEnd on each token (default token shape and
hot path unchanged).

Parser: under the new onQuadSpans option, remember each term's source
token span (WeakMap, populated in _readEntity, literal completion and
synthetic-blank-node creation) and report per-position spans for every
emitted quad. Zero cost when the option is absent.

N3ProvenanceParser: wraps Parser to maintain a multiset of quad
*utterances* on the side - Map from a canonical quad key (value-based,
never object identity, so store-reconstructed quads still resolve) to
{quad, subject/predicate/object/graph: Range[]} with absolute character
offsets. Stores stay plain sets of quads at full speed; the multiset
lives in the wrapper.

All existing tests pass unchanged and coverage stays at 100%; new tests
cover utterance multiset semantics, value-keyed lookup, TriG graph
labels, RDF 1.2 annotations and span-less synthetic terms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
apply-provenance.mjs applies the term-span transforms (a payload of
context-anchored replacements generated by diffing this branch against
main, plus the N3ProvenanceParser source) to a pristine N3.js source
tree. Anchors must match exactly once, so upstream drift fails loudly.
By construction, term-provenance == main + this script, byte for byte -
so the instrumentation can also be maintained entirely out-of-tree
against upstream releases if it isn't wanted in-tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Ted Thibodeau Jr <tthibodeau@openlinksw.com>
rdfjs#613 fixed literal subjects/predicates in N3 mode. Two
consequences for the provenance branch:

- The subject-literal test document `"s" <p> <o> <g> .` only parsed
  before because the old `_completeSubjectLiteral` swallowed the token
  after the literal, shifting `<o>`/`<g>` into predicate/object. With
  the token no longer dropped, the document is an N3-invalid quad. Use
  `"s" <p> <o> .`, which is what the test meant to exercise.

- `_readPredicate`'s new `case 'literal'` is a fourth site that stashes
  `_literalValue` for later completion, so it needs the same
  `_literalSpan` stash as the subject, object, and list-item sites, or
  predicate literals come back span-less. Instrument it and add the
  corresponding payload transform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Numbers and booleans reach the parser as a single `literal` token whose
`prefix` already carries the datatype, so they skip the
`_literalValue`/`_literalSpan` handshake that the quoted-literal paths
use and were constructed with no span at all: the object of
`<s> <p> 42 .` came back span-less. Note the span directly from the
token at all four such sites (subject, predicate, object, list item);
the token covers exactly the numeric or boolean lexeme, so no offset
arithmetic is needed.

apply-provenance.mjs itself is unchanged -- this is four more
context-anchored replacements in the payload, and the derivation still
reproduces src/ byte-identically from pristine upstream sources.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The anchor spanned the whole function body, including the _emit line
that carries the reifies quad's graph.  That line is unrelated to the
instrumentation -- the transform only wraps the blankNode() and quad()
calls -- but including it made the anchor drift the moment upstream
touched the graph argument (as the fix for rdfjs#676 does).

Trim it to the three lines actually rewritten.  Output on main is
byte-identical, and the payload now applies cleanly on top of the
reifies-graph, lone-reifier and blank-node-annotation fixes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/N3Lexer.js
#	src/N3Parser.js
@jeswr jeswr closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants