Skip to content

Make the ShEx vocabulary and ShExR's renderings derived, and keep them in sync - #73

Open
ericprud wants to merge 6 commits into
mainfrom
vocab-source
Open

Make the ShEx vocabulary and ShExR's renderings derived, and keep them in sync#73
ericprud wants to merge 6 commits into
mainfrom
vocab-source

Conversation

@ericprud

@ericprud ericprud commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Six commits. The theme is that several files here were copies of each other maintained by hand, and they had drifted; this makes each one derived from a single source and adds checks to npm test so they cannot drift again.

vocab/ — the source of http://www.w3.org/ns/shex

shex.ttl, shex.jsonld and shex.html at w3.org/ns are generated from a vocab.csv by a script Gregg Kellogg (RIP) wrote for the purpose. That source had been sitting in the long-dormant shexspec.github.io repo. It now lives in vocab/, ported from Ruby to dependency-free Node and verified to reproduce the Ruby's output byte-for-byte before any change was made.

It lands in this repo because this repo already carried two hand-maintained copies of that vocabulary, and both had drifted:

  • doc/ShExJ-context.jsonld was the @context of shex.jsonld, copied. All 70 keys matched exactly; it was missing only shex:extends. It is now generated (npm run vocab).
  • doc/ShExR.shex ran ahead of the published vocabulary, using sx:ShapeDecl, sx:abstract, sx:imports — none of which had ever been published — and sx:negated.

npm run vocab-check reports terms ShExR uses that the vocabulary does not define, and is part of npm test.

The vocabulary side of this is w3c/ns#38, which adds those three terms plus the shape-map terms.

sx:negated removed

negated was the ShEx 2.0 draft's ! operator on TripleConstraint. It was removed from the language in November 2016 — 224c610 here, d3f882d in spec — over the semantics problem in shexSpec/shex#11, and replaced by {0,0} cardinality and ShapeNot. Seven weeks later fe51e15 ("~ aligned with ShExJ.jsg") copied it back into doc/ShExR.shex from the grammar, which had not been cleaned up yet. e01f012 ("TripleConstraint -= negated:BOOL") dropped it from doc/ShExJ.jsg in 2020 but left ShExR alone, so the RDF rendering kept a term the JSON rendering had deleted.

It was never in the published namespace, is not in the spec, and the ShExC parser has no ! rule — nothing could produce it. Gone from ShExR.{shex,ttl,json,ntriples}, plus doc/ShExV.jsg and the schemas/meta.ttl sameAbstractSyntaxAs line referencing two fixtures deleted in 2016. shexSpec/spec#(companion PR) and shex.js 489a1a75 cover the copies elsewhere.

bin/mkShExR.js — ShExR's RDF renderings are now derived

doc/ShExR.shex is the hand-maintained source; the other three files in doc/ are the same schema in other syntaxes, hand-synced on the honour system. doc/ShExR.ntriples was last regenerated in 2017 (7a7da0d) — 800 triples against ShExR.shex's 1341, predating ShapeDecl, abstract, imports and extends entirely.

bin/mkShExR.js parses ShExR.shex to ShExJ and expands it through doc/ShExJ-context.jsonld, since ShExJ + that context is ShExR. So this also exercises the context, which is generated from vocab/vocab.csv — the vocabulary and the schema-of-schemas are now chained together.

  • ShExR.ttl and .ntriples are regenerated and byte-compared by CI (npm run shexr-check).
  • ShExR.json is compared as an AST rather than regenerated, so its hand formatting survives. It was already exactly what ShExR.shex parses to.
  • No content changed in ShExR.ttl — canonicalising the previously committed file yields the regenerated one exactly. That diff is formatting; the .ntriples diff is nine years of catch-up.

The Turtle is pretty-printed rather than handed to N3.Writer, which labels every blank node and spells out all 265 rdf:first/rdf:rest triples. The ShExR graph is a tree, so blank nodes nest as [ ] and lists collapse to ( ). Subject order comes from the rendered name, never a blank node label, so output does not depend on which parser produced the quads; the printer is idempotent.

doc/syntax-deltas.html

Records where ShExC, ShExJ and ShExR disagree, found while doing the above, with links into the ShExC productions and both grammars here. The substantive one: ShExR's sx:start @<#shapeDeclOrExpr> admits a ShapeDecl at start, which ShExJ.jsg rejects and no ShExC production denotes — verified against all three. ShExR has no choice, since RDF cannot distinguish reference from containment, and it cannot even be narrowed to IRIs because shapeDeclLabel admits BNODE.

Checks

npm test passes. test-ts remains broken independently of this PR — it shells out to bin/makeTsTests.sh and calls a bare tsc with no typescript devDependency; it is excluded from CI for that reason and unchanged here.

🤖 Generated with Claude Code

Eric Prud'hommeaux and others added 6 commits August 6, 2026 09:49
Ports Gregg Kellogg's mk_vocab.rb (shexspec.github.io/ns, dormant since 2018)
to dependency-free Node, verified to reproduce its output byte-for-byte, and
makes vocab.csv the source of truth for http://www.w3.org/ns/shex#.

It lands here because this repo already carried two hand-maintained copies of
that vocabulary and both had drifted: doc/ShExJ-context.jsonld is the @context
of w3c/ns shex.jsonld, missing shex:extends; doc/ShExR.shex runs ahead of the
published vocabulary, using sx:ShapeDecl, sx:abstract, sx:imports and
sx:negated, which it never defined.

doc/ShExJ-context.jsonld is now generated (npm run vocab). ShExR stays
hand-maintained -- the CSV cannot express CLOSED, type arcs or the list shapes
-- and npm run vocab-check reports the four missing terms instead. That check
fails today, so it is deliberately not wired into npm test; adding the terms
changes a published W3C namespace and is Eric's call.

Also adds the shape-map terms (QueryMap, ShapeMap, node, shape, status, FOCUS,
_) per https://shexspec.github.io/shape-map/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No definitions change: the generator reproduced all 70 existing keys exactly.
Adds the 8 it was missing -- shex:extends, which drifted when EXTENDS was
added to w3c/ns, and the seven shape-map terms.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
shexTest publishes Pages from main, but shex.html is only generated into
w3c/ns, so vocab/shex.html never exists.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the drift between doc/ShExR.shex and http://www.w3.org/ns/shex#, in
both directions, and turns npm run vocab-check green so it can join npm test.

Adds to vocab.csv the three terms ShExR had run ahead with -- sx:ShapeDecl,
sx:abstract and sx:imports -- taken from doc/ShExJ.jsg. Following
ShapeDeclList1Plus, sx:shapes now has range ShapeDecl rather than
ShapeExpression, and sx:shapeExpr's domain widens to ShapeNot | ShapeDecl.

Removes sx:negated, which pointed the other way. It was the ShEx 2.0 draft's
`!` operator, removed from the language in 224c610 / spec d3f882d (Nov 2016)
over shexSpec/shex#11 and replaced by {0,0} and ShapeNot. fe51e15 copied it
back from ShExJ.jsg seven weeks later; e01f012 ("TripleConstraint -=
negated:BOOL") cleaned the grammar in 2020 but not ShExR. It was never
published, is not in the spec, and the ShExC parser has no `!` rule, so
nothing could ever produce it.

Gone from ShExR.{shex,ttl,json,ntriples}; in .ntriples the list cell was
relinked and the orphaned subtree swept (13 triples). Also drops the same
vintage of leftover from ShExV.jsg and the meta.ttl sameAbstractSyntaxAs
line referencing two fixtures deleted in 2016.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…in sync

doc/ShExR.shex is the hand-maintained source. The other three files in doc/ are
the same schema in other syntaxes, hand-synced on the honour system, and they
drifted. doc/ShExR.ntriples was the worst: last regenerated in 2017 (7a7da0d),
800 triples against ShExR.shex's 1341, predating ShapeDecl, abstract, imports
and extends entirely.

mkShExR.js parses ShExR.shex to ShExJ and expands that through
doc/ShExJ-context.jsonld, since ShExJ + that context *is* ShExR. So this also
exercises the context, which is generated from vocab/vocab.csv -- one edit to
the vocabulary now has to stay consistent with the schema-of-schemas.

ShExR.ttl and .ntriples are regenerated and byte-compared by CI. ShExR.json is
compared as an AST instead, so its hand formatting survives -- it was already
exactly what ShExR.shex parses to.

The Turtle is pretty-printed rather than handed to N3.Writer, which labels
every blank node and spells out all 265 rdf:first/rdf:rest triples. The ShExR
graph is a tree, so blank nodes nest as [ ] and lists collapse to ( ), close to
how the file was written by hand. Subject order comes from the rendered name,
never a blank node label, so output does not depend on which parser produced
the quads; the printer is idempotent.

No content changed in ShExR.ttl: canonicalising the previously committed file
yields the regenerated one exactly. The diff is formatting, and the nine years
of catch-up in .ntriples.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Records the deltas found while deriving the ShExR renderings, with the evidence
for each and links into the ShExC productions and the two grammars here.

The substantive one is `start`. ShExC has `start ::= "start" '='
inlineShapeExpression` and ShExJ has `start:shapeExprOrRef?`, where shapeExpr
cannot be a ShapeDecl since 2.1 moved id onto ShapeDecl. ShExR has `sx:start
@<#shapeDeclOrExpr>`, which admits one. Verified against all three: an
anonymous ShapeDecl at start is rejected by ShExJ.jsg and accepted by
ShExR.shex, and no ShExC production denotes it.

ShExR has no choice -- RDF cannot distinguish reference from containment, so
`sx:start <S>` where <S> is a ShapeDecl is how `start = @<S>` must be encoded.
Nor can it be tightened to IRI-only, because shapeDeclLabel admits BNODE. The
same shapeDeclOrExpr sits under shapeExprs, shapeExpr, extends and valueExpr.

Two lesser ones: an inline abstract ShapeDecl at start slips past the
abstract-shape requirement, which is worded over shapeExprRefs; and the
inlineShapeExpression restriction (no annotations or semantic actions without
parentheses) exists only in ShExC, so a ShExJ->ShExC writer has to parenthesise
or emit invalid ShExC -- shex.js's parser accepts the bare form regardless.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant