feat(parser): support N3 has, is-of, and inverted predicates - #694
Merged
Conversation
9 tasks
jeswr
marked this pull request as ready for review
September 1, 2026 01:06
jeswr
enabled auto-merge
September 1, 2026 01:07
jeswr
disabled auto-merge
September 1, 2026 01:07
jeswr
enabled auto-merge
September 1, 2026 01:08
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for current N3 verb/inverse predicate surface syntax by extending the lexer with N3-specific tokens and updating the parser to interpret has, is … of, and <- forms (plus regression tests for parsing/tokenization and chunk-boundary handling).
Changes:
- Extend
src/N3Lexer.jsto tokenizehas/is/ofand the standalone<-inverted-predicate marker in N3 mode (including streaming chunk splits). - Extend
src/N3Parser.jsto parsehasandis … ofas verb forms and to reuse the existing subject/object swap emission for inverse predicates, while scoping pendingofvia parser contexts. - Add Jest coverage in
test/N3Lexer-test.jsandtest/N3Parser-test.jsfor the new syntax and rejection of historical@has/@is … @of.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| test/N3Parser-test.js | Adds parse-level tests for has, is … of, <- and error/rejection cases. |
| test/N3Lexer-test.js | Adds lexer tokenization tests for verb keywords, <-, chunk splits, and non-N3-mode strictness. |
| src/N3Parser.js | Implements verb/inverse predicate parsing and of expectation scoping via contexts. |
| src/N3Lexer.js | Implements N3-mode tokenization for verb keywords and the <- marker. |
Suppressed comments (1)
src/N3Parser.js:360
- Inversion is applied when quads are emitted via
_readPunctuation, but some context-tail emit paths bypass that swap. For example, when a blank node property list ends directly with],_readBlankNodeTailemits(_subject, _predicate, _object)without checking_inversePredicate, so[ is <p> of <o> ]/[ <- <p> <o> ]would emit in the non-inverted direction. Emission sites like_readBlankNodeTail(and any similar “tail” emitters) should respect_inversePredicatethe same way_readPunctuationdoes and clear it afterwards.
case 'inversePredicate':
this._inversePredicate = true;
return this._readPredicateAfterVerb;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| this._boolean = /^(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/; | ||
| this._atKeyword = /^@[a-z]+(?=[\s#<:])/i; | ||
| this._keyword = /^(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<])/i; | ||
| this._n3Verb = /^(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/; |
Comment on lines
+1080
to
+1087
| it( | ||
| 'should keep keyword-like prefixes as prefixed names', | ||
| shouldTokenize('has:p is:p of:p', | ||
| { type: 'prefixed', prefix: 'has', value: 'p', line: 1 }, | ||
| { type: 'prefixed', prefix: 'is', value: 'p', line: 1 }, | ||
| { type: 'prefixed', prefix: 'of', value: 'p', line: 1 }, | ||
| { type: 'eof', line: 1 }), | ||
| ); |
Comment on lines
+352
to
+360
| case 'has': | ||
| return this._readPredicateAfterVerb; | ||
| case 'is': | ||
| this._inversePredicate = true; | ||
| this._expectOf = true; | ||
| return this._readPredicateAfterVerb; | ||
| case 'inversePredicate': | ||
| this._inversePredicate = true; | ||
| return this._readPredicateAfterVerb; |
Comment on lines
+2897
to
+2901
| it( | ||
| 'should parse the is-of verb', | ||
| shouldParse(parser, '<s> is <p> of <o>.', ['o', 'p', 's']), | ||
| ); | ||
|
|
Contributor
|
🎉 This PR is included in version 2.7.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the current N3 grammar's bare
has expression,is expression of, and<- expressioninverse-predicate forms.The historical
@has/@is ... @ofspellings are deliberately out of scope. They are absent from the generated current grammar and collide with language-tag tokenization for literal subjects. Regression tests verify that the core forms work with literal subjects and that unambiguous uses of the historical spellings are rejected.The inverse forms reuse the parser's existing subject/object-swapping emission. Pending
ofstate is scoped with parser contexts, so compound predicates and nested property lists cannot leak or overwrite it. The lexer keeps keyword-like prefixes such asis:pas prefixed names, distinguishes standalone<-from IRIs such as<-s>, and waits correctly across stream chunk boundaries. Turtle and TriG remain strict.This also fixes the previously masked
inverted_propertiescase tracked in #698.Tests: full Jest suite (6,865 tests, 100% coverage); ESLint; Node and browser bundle builds.
Closes #688
Closes #698