eyeling 1.24.4 → 1.24.5
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/HANDBOOK.md +2 -2
- package/dist/browser/eyeling.browser.js +250 -4
- package/eyeling.js +250 -4
- package/lib/lexer.js +250 -4
- package/package.json +1 -1
- package/see/README.md +1 -1
- package/test/api.test.js +40 -0
- package/test/package.test.js +1 -1
- package/examples/annotation.n3 +0 -8
- package/examples/arcling/README.md +0 -11
- package/examples/collection.n3 +0 -4
- package/examples/context-association.n3 +0 -33
- package/examples/input/annotation.ttl +0 -6
- package/examples/input/collection.ttl +0 -13
- package/examples/input/context-association.trig +0 -35
- package/examples/input/reifies.ttl +0 -10
- package/examples/input/triple-term.ttl +0 -8
- package/examples/output/annotation.n3 +0 -0
- package/examples/output/collection.n3 +0 -0
- package/examples/output/context-association.n3 +0 -0
- package/examples/output/reifies.n3 +0 -0
- package/examples/output/triple-term.n3 +0 -0
- package/examples/reifies.n3 +0 -8
- package/examples/triple-term.n3 +0 -7
package/HANDBOOK.md
CHANGED
|
@@ -239,7 +239,7 @@ Parsing becomes dramatically simpler because tokenization already decided where
|
|
|
239
239
|
|
|
240
240
|
By default, Eyeling parses ordinary N3. Selected RDF/TriG surface syntax is accepted only when RDF compatibility is explicitly enabled with `eyeling -r file.trig`, `eyeling --rdf file.trig`, or API option `{ rdf: true }`. In that mode, the lexer normalizes RDF/TriG input syntax to ordinary N3 graph terms before normal parsing, and the printer emits RDF/TriG-compatible output where feasible. Eyeling remains an N3 reasoner; this is syntax compatibility, not a separate RDF dataset reasoning model.
|
|
241
241
|
|
|
242
|
-
In RDF compatibility mode, RDF 1.2 triple terms written as `<<( s p o )
|
|
242
|
+
In RDF compatibility mode, RDF 1.2 triple terms written as `<<( s p o )>>`, plus the reified triple form `<<s p o ~ r>>`, are normalized to Eyeling's existing singleton quoted-formula term `{ s p o }`. A reifier `r` is preserved as `r rdf:reifies { s p o }`. A leading `VERSION "1.2"` or `@version "1.2"` directive is ignored for the same reason. On output, `--rdf` converts a singleton graph term back to `<<( ... )>>` only when its inner triple is valid as an RDF triple term; otherwise it stays in N3 graph-term form. It also prints `log:nameOf` graph-term triples back as TriG named graph blocks. For example:
|
|
243
243
|
|
|
244
244
|
```n3
|
|
245
245
|
:observation rdf:reifies <<( :sensor :reports :overheating )>> .
|
|
@@ -2594,7 +2594,7 @@ Quoted graphs/formulas use `{ ... }`. Inside a quoted formula, directive scope m
|
|
|
2594
2594
|
|
|
2595
2595
|
- `@prefix/@base` and `PREFIX/BASE` directives may appear at top level **or inside `{ ... }`**, and apply to the formula they occur in (formula-local scoping).
|
|
2596
2596
|
|
|
2597
|
-
With `-r, --rdf` / `{ rdf: true }`, Eyeling also accepts
|
|
2597
|
+
With `-r, --rdf` / `{ rdf: true }`, Eyeling also accepts RDF 1.2 triple-term surface forms such as `<<( s p o )>>` and `<<s p o ~ r>>` as compatibility spellings for a singleton quoted formula `{ s p o }`. In the same mode, feasible singleton graph terms are printed back as RDF 1.2 triple terms, while invalid cases such as a literal subject remain ordinary N3 graph terms. This is useful for inputs that use `rdf:reifies` or other predicates whose objects are RDF 1.2 triple terms, while keeping the default language and the rest of Eyeling on its N3 formula-term model.
|
|
2598
2598
|
|
|
2599
2599
|
For the formal grammar, see the N3 spec grammar:
|
|
2600
2600
|
|
|
@@ -9590,6 +9590,7 @@ function stripQuotes(lex) {
|
|
|
9590
9590
|
// - A top-level default graph block { ... } is unwrapped into ordinary triples.
|
|
9591
9591
|
// This keeps all downstream parsing/reasoning N3-only.
|
|
9592
9592
|
const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
|
|
9593
|
+
const RDF_REIFIES_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies>';
|
|
9593
9594
|
|
|
9594
9595
|
function normalizeRdfCompatibility(inputText) {
|
|
9595
9596
|
let text = String(inputText ?? '');
|
|
@@ -9598,11 +9599,12 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9598
9599
|
// surface-syntax normalization. Avoid scanning large files character-by-character
|
|
9599
9600
|
// unless they actually contain RDF 1.2 triple terms, VERSION directives, or a
|
|
9600
9601
|
// plausible top-level TriG named graph block.
|
|
9601
|
-
const hasTripleTerms = text.includes('<<
|
|
9602
|
+
const hasTripleTerms = text.includes('<<');
|
|
9602
9603
|
const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
|
|
9603
9604
|
const hasNamedGraphCandidate = /(?:^|[.\r\n])\s*(?:GRAPH\s+)?(?:<[^>\r\n]*>|_:[A-Za-z][A-Za-z0-9_-]*|[A-Za-z][A-Za-z0-9_-]*:[^\s{};,.()[\]]*)\s*\{/m.test(text);
|
|
9605
|
+
const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
|
|
9604
9606
|
|
|
9605
|
-
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate) return text;
|
|
9607
|
+
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
|
|
9606
9608
|
|
|
9607
9609
|
function isWordChar(ch) {
|
|
9608
9610
|
return ch != null && /[A-Za-z0-9_:-]/.test(ch);
|
|
@@ -9664,11 +9666,67 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9664
9666
|
|
|
9665
9667
|
function convertTripleTerms(s) {
|
|
9666
9668
|
let i = 0;
|
|
9669
|
+
const reifierTriples = [];
|
|
9667
9670
|
|
|
9668
9671
|
function startsAt(needle, at = i) {
|
|
9669
9672
|
return s.startsWith(needle, at);
|
|
9670
9673
|
}
|
|
9671
9674
|
|
|
9675
|
+
function splitTopLevelReifier(body) {
|
|
9676
|
+
let depthBrace = 0;
|
|
9677
|
+
let depthBracket = 0;
|
|
9678
|
+
let depthParen = 0;
|
|
9679
|
+
for (let j = 0; j < body.length; j++) {
|
|
9680
|
+
const ch = body[j];
|
|
9681
|
+
if (ch === '"' || ch === "'") {
|
|
9682
|
+
const str = readStringAt(body, j);
|
|
9683
|
+
j = str.end - 1;
|
|
9684
|
+
continue;
|
|
9685
|
+
}
|
|
9686
|
+
if (ch === '<') {
|
|
9687
|
+
const iri = readIriAt(body, j);
|
|
9688
|
+
j = iri.end - 1;
|
|
9689
|
+
continue;
|
|
9690
|
+
}
|
|
9691
|
+
if (ch === '#') {
|
|
9692
|
+
while (j < body.length && body[j] !== '\n' && body[j] !== '\r') j += 1;
|
|
9693
|
+
continue;
|
|
9694
|
+
}
|
|
9695
|
+
if (ch === '{') depthBrace += 1;
|
|
9696
|
+
else if (ch === '}' && depthBrace > 0) depthBrace -= 1;
|
|
9697
|
+
else if (ch === '[') depthBracket += 1;
|
|
9698
|
+
else if (ch === ']' && depthBracket > 0) depthBracket -= 1;
|
|
9699
|
+
else if (ch === '(') depthParen += 1;
|
|
9700
|
+
else if (ch === ')' && depthParen > 0) depthParen -= 1;
|
|
9701
|
+
else if (ch === '~' && depthBrace === 0 && depthBracket === 0 && depthParen === 0) {
|
|
9702
|
+
return { triple: body.slice(0, j).trim(), reifier: body.slice(j + 1).trim() };
|
|
9703
|
+
}
|
|
9704
|
+
}
|
|
9705
|
+
return { triple: body.trim(), reifier: '' };
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9708
|
+
function firstTerm(text) {
|
|
9709
|
+
const at = skipWsAndComments(text, 0);
|
|
9710
|
+
if (at >= text.length) return '';
|
|
9711
|
+
if (text[at] === '<') return readIriAt(text, at).text;
|
|
9712
|
+
let j = at;
|
|
9713
|
+
while (j < text.length && !/\s/.test(text[j]) && !'{}[](),;.'.includes(text[j])) j += 1;
|
|
9714
|
+
return text.slice(at, j);
|
|
9715
|
+
}
|
|
9716
|
+
|
|
9717
|
+
function graphTermFromTripleBody(rawBody, parenthesized) {
|
|
9718
|
+
let body = rawBody.trim();
|
|
9719
|
+
if (parenthesized && body.startsWith('(') && body.endsWith(')')) body = body.slice(1, -1).trim();
|
|
9720
|
+
const split = splitTopLevelReifier(body);
|
|
9721
|
+
const triple = split.triple;
|
|
9722
|
+
const graph = '{ ' + triple + ' }';
|
|
9723
|
+
if (split.reifier) {
|
|
9724
|
+
const reifier = firstTerm(split.reifier);
|
|
9725
|
+
if (reifier) reifierTriples.push(`${reifier} ${RDF_REIFIES_IRI} ${graph} .`);
|
|
9726
|
+
}
|
|
9727
|
+
return graph;
|
|
9728
|
+
}
|
|
9729
|
+
|
|
9672
9730
|
function convertUntil(stopToken) {
|
|
9673
9731
|
let out = '';
|
|
9674
9732
|
while (i < s.length) {
|
|
@@ -9678,7 +9736,12 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9678
9736
|
}
|
|
9679
9737
|
if (startsAt('<<(')) {
|
|
9680
9738
|
i += 3;
|
|
9681
|
-
out +=
|
|
9739
|
+
out += graphTermFromTripleBody(convertUntil(')>>'), false);
|
|
9740
|
+
continue;
|
|
9741
|
+
}
|
|
9742
|
+
if (startsAt('<<')) {
|
|
9743
|
+
i += 2;
|
|
9744
|
+
out += graphTermFromTripleBody(convertUntil('>>'), false);
|
|
9682
9745
|
continue;
|
|
9683
9746
|
}
|
|
9684
9747
|
const ch = s[i];
|
|
@@ -9709,7 +9772,189 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9709
9772
|
return out;
|
|
9710
9773
|
}
|
|
9711
9774
|
|
|
9712
|
-
|
|
9775
|
+
const converted = convertUntil(null);
|
|
9776
|
+
if (reifierTriples.length === 0) return converted;
|
|
9777
|
+
return converted + (converted.endsWith('\n') ? '' : '\n') + reifierTriples.join('\n') + '\n';
|
|
9778
|
+
}
|
|
9779
|
+
|
|
9780
|
+
|
|
9781
|
+
function convertAnnotations(s) {
|
|
9782
|
+
let out = '';
|
|
9783
|
+
let i = 0;
|
|
9784
|
+
let statementStart = true;
|
|
9785
|
+
let generatedBlank = 0;
|
|
9786
|
+
|
|
9787
|
+
function readBalancedDelimited(s, at, open, close) {
|
|
9788
|
+
if (!s.startsWith(open, at)) return null;
|
|
9789
|
+
let j = at + open.length;
|
|
9790
|
+
let depth = 1;
|
|
9791
|
+
while (j < s.length) {
|
|
9792
|
+
const ch = s[j];
|
|
9793
|
+
if (ch === '"' || ch === "'") {
|
|
9794
|
+
j = readStringAt(s, j).end;
|
|
9795
|
+
continue;
|
|
9796
|
+
}
|
|
9797
|
+
if (ch === '<' && !s.startsWith('<<', j)) {
|
|
9798
|
+
j = readIriAt(s, j).end;
|
|
9799
|
+
continue;
|
|
9800
|
+
}
|
|
9801
|
+
if (ch === '#') {
|
|
9802
|
+
while (j < s.length && s[j] !== '\n' && s[j] !== '\r') j += 1;
|
|
9803
|
+
continue;
|
|
9804
|
+
}
|
|
9805
|
+
if (s.startsWith(open, j)) {
|
|
9806
|
+
depth += 1;
|
|
9807
|
+
j += open.length;
|
|
9808
|
+
continue;
|
|
9809
|
+
}
|
|
9810
|
+
if (s.startsWith(close, j)) {
|
|
9811
|
+
depth -= 1;
|
|
9812
|
+
j += close.length;
|
|
9813
|
+
if (depth === 0) return { text: s.slice(at, j), inner: s.slice(at + open.length, j - close.length), end: j };
|
|
9814
|
+
continue;
|
|
9815
|
+
}
|
|
9816
|
+
j += 1;
|
|
9817
|
+
}
|
|
9818
|
+
throw new N3SyntaxError(`Unterminated RDF annotation block, expected ${close}`);
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9821
|
+
function readTermLikeAt(s, at) {
|
|
9822
|
+
const j = skipWsAndComments(s, at);
|
|
9823
|
+
if (j >= s.length) return null;
|
|
9824
|
+
if (s[j] === '<') return readIriAt(s, j);
|
|
9825
|
+
if (s[j] === '"' || s[j] === "'") {
|
|
9826
|
+
const str = readStringAt(s, j);
|
|
9827
|
+
let end = str.end;
|
|
9828
|
+
let text = str.text;
|
|
9829
|
+
if (s.startsWith('^^', end)) {
|
|
9830
|
+
const dt = readTermAt(s, end + 2);
|
|
9831
|
+
if (dt) {
|
|
9832
|
+
text += '^^' + dt.text;
|
|
9833
|
+
end = dt.end;
|
|
9834
|
+
}
|
|
9835
|
+
} else if (s[end] === '@') {
|
|
9836
|
+
let k = end + 1;
|
|
9837
|
+
if (/[A-Za-z]/.test(s[k] || '')) {
|
|
9838
|
+
while (k < s.length && /[A-Za-z0-9-]/.test(s[k])) k += 1;
|
|
9839
|
+
text += s.slice(end, k);
|
|
9840
|
+
end = k;
|
|
9841
|
+
}
|
|
9842
|
+
}
|
|
9843
|
+
return { text, end };
|
|
9844
|
+
}
|
|
9845
|
+
if (s[j] === '{') return readBalancedBlock(s, j);
|
|
9846
|
+
if (s[j] === '[') return readBalancedDelimited(s, j, '[', ']');
|
|
9847
|
+
if (s[j] === '(') return readBalancedDelimited(s, j, '(', ')');
|
|
9848
|
+
return readTermAt(s, j);
|
|
9849
|
+
}
|
|
9850
|
+
|
|
9851
|
+
function readAnnotationBlockAt(s, at) {
|
|
9852
|
+
if (!s.startsWith('{|', at)) return null;
|
|
9853
|
+
return readBalancedDelimited(s, at, '{|', '|}');
|
|
9854
|
+
}
|
|
9855
|
+
|
|
9856
|
+
function tryReadAnnotatedTriple(at) {
|
|
9857
|
+
const start = skipWsAndComments(s, at);
|
|
9858
|
+
if (start >= s.length) return null;
|
|
9859
|
+
if (s[start] === '@') return null;
|
|
9860
|
+
if (startsWordAt(s, 'PREFIX', start) || startsWordAt(s, 'BASE', start) || startsWordAt(s, 'VERSION', start)) return null;
|
|
9861
|
+
if (startsWordAt(s, 'GRAPH', start)) return null;
|
|
9862
|
+
|
|
9863
|
+
const subj = readTermLikeAt(s, start);
|
|
9864
|
+
if (!subj) return null;
|
|
9865
|
+
let j = skipWsAndComments(s, subj.end);
|
|
9866
|
+
const pred = readTermLikeAt(s, j);
|
|
9867
|
+
if (!pred) return null;
|
|
9868
|
+
j = skipWsAndComments(s, pred.end);
|
|
9869
|
+
const obj = readTermLikeAt(s, j);
|
|
9870
|
+
if (!obj) return null;
|
|
9871
|
+
j = skipWsAndComments(s, obj.end);
|
|
9872
|
+
if (s[j] !== '~' && !s.startsWith('{|', j)) return null;
|
|
9873
|
+
|
|
9874
|
+
let reifier = '';
|
|
9875
|
+
const annotationBlocks = [];
|
|
9876
|
+
while (j < s.length) {
|
|
9877
|
+
j = skipWsAndComments(s, j);
|
|
9878
|
+
if (s[j] === '~') {
|
|
9879
|
+
j += 1;
|
|
9880
|
+
j = skipWsAndComments(s, j);
|
|
9881
|
+
const term = readTermAt(s, j);
|
|
9882
|
+
if (term) {
|
|
9883
|
+
reifier = term.text;
|
|
9884
|
+
j = term.end;
|
|
9885
|
+
} else if (!reifier) {
|
|
9886
|
+
reifier = `_:rdfAnnotation${++generatedBlank}`;
|
|
9887
|
+
}
|
|
9888
|
+
continue;
|
|
9889
|
+
}
|
|
9890
|
+
if (s.startsWith('{|', j)) {
|
|
9891
|
+
const block = readAnnotationBlockAt(s, j);
|
|
9892
|
+
if (!reifier) reifier = `_:rdfAnnotation${++generatedBlank}`;
|
|
9893
|
+
annotationBlocks.push(block.inner.trim());
|
|
9894
|
+
j = block.end;
|
|
9895
|
+
continue;
|
|
9896
|
+
}
|
|
9897
|
+
break;
|
|
9898
|
+
}
|
|
9899
|
+
|
|
9900
|
+
const after = skipWsAndComments(s, j);
|
|
9901
|
+
if (s[after] !== '.') return null;
|
|
9902
|
+
if (!reifier && annotationBlocks.length === 0) return null;
|
|
9903
|
+
|
|
9904
|
+
const baseTriple = `${subj.text} ${pred.text} ${obj.text}`;
|
|
9905
|
+
const graph = `{ ${baseTriple} }`;
|
|
9906
|
+
const extra = [];
|
|
9907
|
+
if (reifier) extra.push(`${reifier} ${RDF_REIFIES_IRI} ${graph} .`);
|
|
9908
|
+
for (const inner of annotationBlocks) {
|
|
9909
|
+
if (inner) extra.push(`${reifier} ${inner} .`);
|
|
9910
|
+
}
|
|
9911
|
+
return {
|
|
9912
|
+
start,
|
|
9913
|
+
end: after + 1,
|
|
9914
|
+
text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}`,
|
|
9915
|
+
};
|
|
9916
|
+
}
|
|
9917
|
+
|
|
9918
|
+
while (i < s.length) {
|
|
9919
|
+
if (statementStart) {
|
|
9920
|
+
const converted = tryReadAnnotatedTriple(i);
|
|
9921
|
+
if (converted) {
|
|
9922
|
+
out += s.slice(i, converted.start) + converted.text;
|
|
9923
|
+
i = converted.end;
|
|
9924
|
+
statementStart = true;
|
|
9925
|
+
continue;
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
|
|
9929
|
+
const ch = s[i];
|
|
9930
|
+
if (ch === '"' || ch === "'") {
|
|
9931
|
+
const str = readStringAt(s, i);
|
|
9932
|
+
out += str.text;
|
|
9933
|
+
i = str.end;
|
|
9934
|
+
continue;
|
|
9935
|
+
}
|
|
9936
|
+
if (ch === '<' && !s.startsWith('<<', i)) {
|
|
9937
|
+
const iri = readIriAt(s, i);
|
|
9938
|
+
out += iri.text;
|
|
9939
|
+
i = iri.end;
|
|
9940
|
+
continue;
|
|
9941
|
+
}
|
|
9942
|
+
if (ch === '#') {
|
|
9943
|
+
while (i < s.length) {
|
|
9944
|
+
const c = s[i++];
|
|
9945
|
+
out += c;
|
|
9946
|
+
if (c === '\n' || c === '\r') break;
|
|
9947
|
+
}
|
|
9948
|
+
statementStart = true;
|
|
9949
|
+
continue;
|
|
9950
|
+
}
|
|
9951
|
+
out += ch;
|
|
9952
|
+
if (ch === '.' || ch === '{' || ch === '}' || ch === '\n' || ch === '\r') statementStart = true;
|
|
9953
|
+
else if (!/\s/.test(ch)) statementStart = false;
|
|
9954
|
+
i += 1;
|
|
9955
|
+
}
|
|
9956
|
+
|
|
9957
|
+
return out;
|
|
9713
9958
|
}
|
|
9714
9959
|
|
|
9715
9960
|
function stripVersionDirectives(s) {
|
|
@@ -9868,6 +10113,7 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9868
10113
|
}
|
|
9869
10114
|
|
|
9870
10115
|
if (hasTripleTerms) text = convertTripleTerms(text);
|
|
10116
|
+
if (hasAnnotationSyntax) text = convertAnnotations(text);
|
|
9871
10117
|
if (hasVersionDirective) text = stripVersionDirectives(text);
|
|
9872
10118
|
if (hasVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
|
|
9873
10119
|
return text;
|
package/eyeling.js
CHANGED
|
@@ -9590,6 +9590,7 @@ function stripQuotes(lex) {
|
|
|
9590
9590
|
// - A top-level default graph block { ... } is unwrapped into ordinary triples.
|
|
9591
9591
|
// This keeps all downstream parsing/reasoning N3-only.
|
|
9592
9592
|
const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
|
|
9593
|
+
const RDF_REIFIES_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies>';
|
|
9593
9594
|
|
|
9594
9595
|
function normalizeRdfCompatibility(inputText) {
|
|
9595
9596
|
let text = String(inputText ?? '');
|
|
@@ -9598,11 +9599,12 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9598
9599
|
// surface-syntax normalization. Avoid scanning large files character-by-character
|
|
9599
9600
|
// unless they actually contain RDF 1.2 triple terms, VERSION directives, or a
|
|
9600
9601
|
// plausible top-level TriG named graph block.
|
|
9601
|
-
const hasTripleTerms = text.includes('<<
|
|
9602
|
+
const hasTripleTerms = text.includes('<<');
|
|
9602
9603
|
const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
|
|
9603
9604
|
const hasNamedGraphCandidate = /(?:^|[.\r\n])\s*(?:GRAPH\s+)?(?:<[^>\r\n]*>|_:[A-Za-z][A-Za-z0-9_-]*|[A-Za-z][A-Za-z0-9_-]*:[^\s{};,.()[\]]*)\s*\{/m.test(text);
|
|
9605
|
+
const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
|
|
9604
9606
|
|
|
9605
|
-
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate) return text;
|
|
9607
|
+
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
|
|
9606
9608
|
|
|
9607
9609
|
function isWordChar(ch) {
|
|
9608
9610
|
return ch != null && /[A-Za-z0-9_:-]/.test(ch);
|
|
@@ -9664,11 +9666,67 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9664
9666
|
|
|
9665
9667
|
function convertTripleTerms(s) {
|
|
9666
9668
|
let i = 0;
|
|
9669
|
+
const reifierTriples = [];
|
|
9667
9670
|
|
|
9668
9671
|
function startsAt(needle, at = i) {
|
|
9669
9672
|
return s.startsWith(needle, at);
|
|
9670
9673
|
}
|
|
9671
9674
|
|
|
9675
|
+
function splitTopLevelReifier(body) {
|
|
9676
|
+
let depthBrace = 0;
|
|
9677
|
+
let depthBracket = 0;
|
|
9678
|
+
let depthParen = 0;
|
|
9679
|
+
for (let j = 0; j < body.length; j++) {
|
|
9680
|
+
const ch = body[j];
|
|
9681
|
+
if (ch === '"' || ch === "'") {
|
|
9682
|
+
const str = readStringAt(body, j);
|
|
9683
|
+
j = str.end - 1;
|
|
9684
|
+
continue;
|
|
9685
|
+
}
|
|
9686
|
+
if (ch === '<') {
|
|
9687
|
+
const iri = readIriAt(body, j);
|
|
9688
|
+
j = iri.end - 1;
|
|
9689
|
+
continue;
|
|
9690
|
+
}
|
|
9691
|
+
if (ch === '#') {
|
|
9692
|
+
while (j < body.length && body[j] !== '\n' && body[j] !== '\r') j += 1;
|
|
9693
|
+
continue;
|
|
9694
|
+
}
|
|
9695
|
+
if (ch === '{') depthBrace += 1;
|
|
9696
|
+
else if (ch === '}' && depthBrace > 0) depthBrace -= 1;
|
|
9697
|
+
else if (ch === '[') depthBracket += 1;
|
|
9698
|
+
else if (ch === ']' && depthBracket > 0) depthBracket -= 1;
|
|
9699
|
+
else if (ch === '(') depthParen += 1;
|
|
9700
|
+
else if (ch === ')' && depthParen > 0) depthParen -= 1;
|
|
9701
|
+
else if (ch === '~' && depthBrace === 0 && depthBracket === 0 && depthParen === 0) {
|
|
9702
|
+
return { triple: body.slice(0, j).trim(), reifier: body.slice(j + 1).trim() };
|
|
9703
|
+
}
|
|
9704
|
+
}
|
|
9705
|
+
return { triple: body.trim(), reifier: '' };
|
|
9706
|
+
}
|
|
9707
|
+
|
|
9708
|
+
function firstTerm(text) {
|
|
9709
|
+
const at = skipWsAndComments(text, 0);
|
|
9710
|
+
if (at >= text.length) return '';
|
|
9711
|
+
if (text[at] === '<') return readIriAt(text, at).text;
|
|
9712
|
+
let j = at;
|
|
9713
|
+
while (j < text.length && !/\s/.test(text[j]) && !'{}[](),;.'.includes(text[j])) j += 1;
|
|
9714
|
+
return text.slice(at, j);
|
|
9715
|
+
}
|
|
9716
|
+
|
|
9717
|
+
function graphTermFromTripleBody(rawBody, parenthesized) {
|
|
9718
|
+
let body = rawBody.trim();
|
|
9719
|
+
if (parenthesized && body.startsWith('(') && body.endsWith(')')) body = body.slice(1, -1).trim();
|
|
9720
|
+
const split = splitTopLevelReifier(body);
|
|
9721
|
+
const triple = split.triple;
|
|
9722
|
+
const graph = '{ ' + triple + ' }';
|
|
9723
|
+
if (split.reifier) {
|
|
9724
|
+
const reifier = firstTerm(split.reifier);
|
|
9725
|
+
if (reifier) reifierTriples.push(`${reifier} ${RDF_REIFIES_IRI} ${graph} .`);
|
|
9726
|
+
}
|
|
9727
|
+
return graph;
|
|
9728
|
+
}
|
|
9729
|
+
|
|
9672
9730
|
function convertUntil(stopToken) {
|
|
9673
9731
|
let out = '';
|
|
9674
9732
|
while (i < s.length) {
|
|
@@ -9678,7 +9736,12 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9678
9736
|
}
|
|
9679
9737
|
if (startsAt('<<(')) {
|
|
9680
9738
|
i += 3;
|
|
9681
|
-
out +=
|
|
9739
|
+
out += graphTermFromTripleBody(convertUntil(')>>'), false);
|
|
9740
|
+
continue;
|
|
9741
|
+
}
|
|
9742
|
+
if (startsAt('<<')) {
|
|
9743
|
+
i += 2;
|
|
9744
|
+
out += graphTermFromTripleBody(convertUntil('>>'), false);
|
|
9682
9745
|
continue;
|
|
9683
9746
|
}
|
|
9684
9747
|
const ch = s[i];
|
|
@@ -9709,7 +9772,189 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9709
9772
|
return out;
|
|
9710
9773
|
}
|
|
9711
9774
|
|
|
9712
|
-
|
|
9775
|
+
const converted = convertUntil(null);
|
|
9776
|
+
if (reifierTriples.length === 0) return converted;
|
|
9777
|
+
return converted + (converted.endsWith('\n') ? '' : '\n') + reifierTriples.join('\n') + '\n';
|
|
9778
|
+
}
|
|
9779
|
+
|
|
9780
|
+
|
|
9781
|
+
function convertAnnotations(s) {
|
|
9782
|
+
let out = '';
|
|
9783
|
+
let i = 0;
|
|
9784
|
+
let statementStart = true;
|
|
9785
|
+
let generatedBlank = 0;
|
|
9786
|
+
|
|
9787
|
+
function readBalancedDelimited(s, at, open, close) {
|
|
9788
|
+
if (!s.startsWith(open, at)) return null;
|
|
9789
|
+
let j = at + open.length;
|
|
9790
|
+
let depth = 1;
|
|
9791
|
+
while (j < s.length) {
|
|
9792
|
+
const ch = s[j];
|
|
9793
|
+
if (ch === '"' || ch === "'") {
|
|
9794
|
+
j = readStringAt(s, j).end;
|
|
9795
|
+
continue;
|
|
9796
|
+
}
|
|
9797
|
+
if (ch === '<' && !s.startsWith('<<', j)) {
|
|
9798
|
+
j = readIriAt(s, j).end;
|
|
9799
|
+
continue;
|
|
9800
|
+
}
|
|
9801
|
+
if (ch === '#') {
|
|
9802
|
+
while (j < s.length && s[j] !== '\n' && s[j] !== '\r') j += 1;
|
|
9803
|
+
continue;
|
|
9804
|
+
}
|
|
9805
|
+
if (s.startsWith(open, j)) {
|
|
9806
|
+
depth += 1;
|
|
9807
|
+
j += open.length;
|
|
9808
|
+
continue;
|
|
9809
|
+
}
|
|
9810
|
+
if (s.startsWith(close, j)) {
|
|
9811
|
+
depth -= 1;
|
|
9812
|
+
j += close.length;
|
|
9813
|
+
if (depth === 0) return { text: s.slice(at, j), inner: s.slice(at + open.length, j - close.length), end: j };
|
|
9814
|
+
continue;
|
|
9815
|
+
}
|
|
9816
|
+
j += 1;
|
|
9817
|
+
}
|
|
9818
|
+
throw new N3SyntaxError(`Unterminated RDF annotation block, expected ${close}`);
|
|
9819
|
+
}
|
|
9820
|
+
|
|
9821
|
+
function readTermLikeAt(s, at) {
|
|
9822
|
+
const j = skipWsAndComments(s, at);
|
|
9823
|
+
if (j >= s.length) return null;
|
|
9824
|
+
if (s[j] === '<') return readIriAt(s, j);
|
|
9825
|
+
if (s[j] === '"' || s[j] === "'") {
|
|
9826
|
+
const str = readStringAt(s, j);
|
|
9827
|
+
let end = str.end;
|
|
9828
|
+
let text = str.text;
|
|
9829
|
+
if (s.startsWith('^^', end)) {
|
|
9830
|
+
const dt = readTermAt(s, end + 2);
|
|
9831
|
+
if (dt) {
|
|
9832
|
+
text += '^^' + dt.text;
|
|
9833
|
+
end = dt.end;
|
|
9834
|
+
}
|
|
9835
|
+
} else if (s[end] === '@') {
|
|
9836
|
+
let k = end + 1;
|
|
9837
|
+
if (/[A-Za-z]/.test(s[k] || '')) {
|
|
9838
|
+
while (k < s.length && /[A-Za-z0-9-]/.test(s[k])) k += 1;
|
|
9839
|
+
text += s.slice(end, k);
|
|
9840
|
+
end = k;
|
|
9841
|
+
}
|
|
9842
|
+
}
|
|
9843
|
+
return { text, end };
|
|
9844
|
+
}
|
|
9845
|
+
if (s[j] === '{') return readBalancedBlock(s, j);
|
|
9846
|
+
if (s[j] === '[') return readBalancedDelimited(s, j, '[', ']');
|
|
9847
|
+
if (s[j] === '(') return readBalancedDelimited(s, j, '(', ')');
|
|
9848
|
+
return readTermAt(s, j);
|
|
9849
|
+
}
|
|
9850
|
+
|
|
9851
|
+
function readAnnotationBlockAt(s, at) {
|
|
9852
|
+
if (!s.startsWith('{|', at)) return null;
|
|
9853
|
+
return readBalancedDelimited(s, at, '{|', '|}');
|
|
9854
|
+
}
|
|
9855
|
+
|
|
9856
|
+
function tryReadAnnotatedTriple(at) {
|
|
9857
|
+
const start = skipWsAndComments(s, at);
|
|
9858
|
+
if (start >= s.length) return null;
|
|
9859
|
+
if (s[start] === '@') return null;
|
|
9860
|
+
if (startsWordAt(s, 'PREFIX', start) || startsWordAt(s, 'BASE', start) || startsWordAt(s, 'VERSION', start)) return null;
|
|
9861
|
+
if (startsWordAt(s, 'GRAPH', start)) return null;
|
|
9862
|
+
|
|
9863
|
+
const subj = readTermLikeAt(s, start);
|
|
9864
|
+
if (!subj) return null;
|
|
9865
|
+
let j = skipWsAndComments(s, subj.end);
|
|
9866
|
+
const pred = readTermLikeAt(s, j);
|
|
9867
|
+
if (!pred) return null;
|
|
9868
|
+
j = skipWsAndComments(s, pred.end);
|
|
9869
|
+
const obj = readTermLikeAt(s, j);
|
|
9870
|
+
if (!obj) return null;
|
|
9871
|
+
j = skipWsAndComments(s, obj.end);
|
|
9872
|
+
if (s[j] !== '~' && !s.startsWith('{|', j)) return null;
|
|
9873
|
+
|
|
9874
|
+
let reifier = '';
|
|
9875
|
+
const annotationBlocks = [];
|
|
9876
|
+
while (j < s.length) {
|
|
9877
|
+
j = skipWsAndComments(s, j);
|
|
9878
|
+
if (s[j] === '~') {
|
|
9879
|
+
j += 1;
|
|
9880
|
+
j = skipWsAndComments(s, j);
|
|
9881
|
+
const term = readTermAt(s, j);
|
|
9882
|
+
if (term) {
|
|
9883
|
+
reifier = term.text;
|
|
9884
|
+
j = term.end;
|
|
9885
|
+
} else if (!reifier) {
|
|
9886
|
+
reifier = `_:rdfAnnotation${++generatedBlank}`;
|
|
9887
|
+
}
|
|
9888
|
+
continue;
|
|
9889
|
+
}
|
|
9890
|
+
if (s.startsWith('{|', j)) {
|
|
9891
|
+
const block = readAnnotationBlockAt(s, j);
|
|
9892
|
+
if (!reifier) reifier = `_:rdfAnnotation${++generatedBlank}`;
|
|
9893
|
+
annotationBlocks.push(block.inner.trim());
|
|
9894
|
+
j = block.end;
|
|
9895
|
+
continue;
|
|
9896
|
+
}
|
|
9897
|
+
break;
|
|
9898
|
+
}
|
|
9899
|
+
|
|
9900
|
+
const after = skipWsAndComments(s, j);
|
|
9901
|
+
if (s[after] !== '.') return null;
|
|
9902
|
+
if (!reifier && annotationBlocks.length === 0) return null;
|
|
9903
|
+
|
|
9904
|
+
const baseTriple = `${subj.text} ${pred.text} ${obj.text}`;
|
|
9905
|
+
const graph = `{ ${baseTriple} }`;
|
|
9906
|
+
const extra = [];
|
|
9907
|
+
if (reifier) extra.push(`${reifier} ${RDF_REIFIES_IRI} ${graph} .`);
|
|
9908
|
+
for (const inner of annotationBlocks) {
|
|
9909
|
+
if (inner) extra.push(`${reifier} ${inner} .`);
|
|
9910
|
+
}
|
|
9911
|
+
return {
|
|
9912
|
+
start,
|
|
9913
|
+
end: after + 1,
|
|
9914
|
+
text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}`,
|
|
9915
|
+
};
|
|
9916
|
+
}
|
|
9917
|
+
|
|
9918
|
+
while (i < s.length) {
|
|
9919
|
+
if (statementStart) {
|
|
9920
|
+
const converted = tryReadAnnotatedTriple(i);
|
|
9921
|
+
if (converted) {
|
|
9922
|
+
out += s.slice(i, converted.start) + converted.text;
|
|
9923
|
+
i = converted.end;
|
|
9924
|
+
statementStart = true;
|
|
9925
|
+
continue;
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
|
|
9929
|
+
const ch = s[i];
|
|
9930
|
+
if (ch === '"' || ch === "'") {
|
|
9931
|
+
const str = readStringAt(s, i);
|
|
9932
|
+
out += str.text;
|
|
9933
|
+
i = str.end;
|
|
9934
|
+
continue;
|
|
9935
|
+
}
|
|
9936
|
+
if (ch === '<' && !s.startsWith('<<', i)) {
|
|
9937
|
+
const iri = readIriAt(s, i);
|
|
9938
|
+
out += iri.text;
|
|
9939
|
+
i = iri.end;
|
|
9940
|
+
continue;
|
|
9941
|
+
}
|
|
9942
|
+
if (ch === '#') {
|
|
9943
|
+
while (i < s.length) {
|
|
9944
|
+
const c = s[i++];
|
|
9945
|
+
out += c;
|
|
9946
|
+
if (c === '\n' || c === '\r') break;
|
|
9947
|
+
}
|
|
9948
|
+
statementStart = true;
|
|
9949
|
+
continue;
|
|
9950
|
+
}
|
|
9951
|
+
out += ch;
|
|
9952
|
+
if (ch === '.' || ch === '{' || ch === '}' || ch === '\n' || ch === '\r') statementStart = true;
|
|
9953
|
+
else if (!/\s/.test(ch)) statementStart = false;
|
|
9954
|
+
i += 1;
|
|
9955
|
+
}
|
|
9956
|
+
|
|
9957
|
+
return out;
|
|
9713
9958
|
}
|
|
9714
9959
|
|
|
9715
9960
|
function stripVersionDirectives(s) {
|
|
@@ -9868,6 +10113,7 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
9868
10113
|
}
|
|
9869
10114
|
|
|
9870
10115
|
if (hasTripleTerms) text = convertTripleTerms(text);
|
|
10116
|
+
if (hasAnnotationSyntax) text = convertAnnotations(text);
|
|
9871
10117
|
if (hasVersionDirective) text = stripVersionDirectives(text);
|
|
9872
10118
|
if (hasVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
|
|
9873
10119
|
return text;
|
package/lib/lexer.js
CHANGED
|
@@ -355,6 +355,7 @@ function stripQuotes(lex) {
|
|
|
355
355
|
// - A top-level default graph block { ... } is unwrapped into ordinary triples.
|
|
356
356
|
// This keeps all downstream parsing/reasoning N3-only.
|
|
357
357
|
const LOG_NAME_OF_IRI = '<http://www.w3.org/2000/10/swap/log#nameOf>';
|
|
358
|
+
const RDF_REIFIES_IRI = '<http://www.w3.org/1999/02/22-rdf-syntax-ns#reifies>';
|
|
358
359
|
|
|
359
360
|
function normalizeRdfCompatibility(inputText) {
|
|
360
361
|
let text = String(inputText ?? '');
|
|
@@ -363,11 +364,12 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
363
364
|
// surface-syntax normalization. Avoid scanning large files character-by-character
|
|
364
365
|
// unless they actually contain RDF 1.2 triple terms, VERSION directives, or a
|
|
365
366
|
// plausible top-level TriG named graph block.
|
|
366
|
-
const hasTripleTerms = text.includes('<<
|
|
367
|
+
const hasTripleTerms = text.includes('<<');
|
|
367
368
|
const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
|
|
368
369
|
const hasNamedGraphCandidate = /(?:^|[.\r\n])\s*(?:GRAPH\s+)?(?:<[^>\r\n]*>|_:[A-Za-z][A-Za-z0-9_-]*|[A-Za-z][A-Za-z0-9_-]*:[^\s{};,.()[\]]*)\s*\{/m.test(text);
|
|
370
|
+
const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
|
|
369
371
|
|
|
370
|
-
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate) return text;
|
|
372
|
+
if (!hasTripleTerms && !hasVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
|
|
371
373
|
|
|
372
374
|
function isWordChar(ch) {
|
|
373
375
|
return ch != null && /[A-Za-z0-9_:-]/.test(ch);
|
|
@@ -429,11 +431,67 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
429
431
|
|
|
430
432
|
function convertTripleTerms(s) {
|
|
431
433
|
let i = 0;
|
|
434
|
+
const reifierTriples = [];
|
|
432
435
|
|
|
433
436
|
function startsAt(needle, at = i) {
|
|
434
437
|
return s.startsWith(needle, at);
|
|
435
438
|
}
|
|
436
439
|
|
|
440
|
+
function splitTopLevelReifier(body) {
|
|
441
|
+
let depthBrace = 0;
|
|
442
|
+
let depthBracket = 0;
|
|
443
|
+
let depthParen = 0;
|
|
444
|
+
for (let j = 0; j < body.length; j++) {
|
|
445
|
+
const ch = body[j];
|
|
446
|
+
if (ch === '"' || ch === "'") {
|
|
447
|
+
const str = readStringAt(body, j);
|
|
448
|
+
j = str.end - 1;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (ch === '<') {
|
|
452
|
+
const iri = readIriAt(body, j);
|
|
453
|
+
j = iri.end - 1;
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (ch === '#') {
|
|
457
|
+
while (j < body.length && body[j] !== '\n' && body[j] !== '\r') j += 1;
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (ch === '{') depthBrace += 1;
|
|
461
|
+
else if (ch === '}' && depthBrace > 0) depthBrace -= 1;
|
|
462
|
+
else if (ch === '[') depthBracket += 1;
|
|
463
|
+
else if (ch === ']' && depthBracket > 0) depthBracket -= 1;
|
|
464
|
+
else if (ch === '(') depthParen += 1;
|
|
465
|
+
else if (ch === ')' && depthParen > 0) depthParen -= 1;
|
|
466
|
+
else if (ch === '~' && depthBrace === 0 && depthBracket === 0 && depthParen === 0) {
|
|
467
|
+
return { triple: body.slice(0, j).trim(), reifier: body.slice(j + 1).trim() };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return { triple: body.trim(), reifier: '' };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function firstTerm(text) {
|
|
474
|
+
const at = skipWsAndComments(text, 0);
|
|
475
|
+
if (at >= text.length) return '';
|
|
476
|
+
if (text[at] === '<') return readIriAt(text, at).text;
|
|
477
|
+
let j = at;
|
|
478
|
+
while (j < text.length && !/\s/.test(text[j]) && !'{}[](),;.'.includes(text[j])) j += 1;
|
|
479
|
+
return text.slice(at, j);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function graphTermFromTripleBody(rawBody, parenthesized) {
|
|
483
|
+
let body = rawBody.trim();
|
|
484
|
+
if (parenthesized && body.startsWith('(') && body.endsWith(')')) body = body.slice(1, -1).trim();
|
|
485
|
+
const split = splitTopLevelReifier(body);
|
|
486
|
+
const triple = split.triple;
|
|
487
|
+
const graph = '{ ' + triple + ' }';
|
|
488
|
+
if (split.reifier) {
|
|
489
|
+
const reifier = firstTerm(split.reifier);
|
|
490
|
+
if (reifier) reifierTriples.push(`${reifier} ${RDF_REIFIES_IRI} ${graph} .`);
|
|
491
|
+
}
|
|
492
|
+
return graph;
|
|
493
|
+
}
|
|
494
|
+
|
|
437
495
|
function convertUntil(stopToken) {
|
|
438
496
|
let out = '';
|
|
439
497
|
while (i < s.length) {
|
|
@@ -443,7 +501,12 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
443
501
|
}
|
|
444
502
|
if (startsAt('<<(')) {
|
|
445
503
|
i += 3;
|
|
446
|
-
out +=
|
|
504
|
+
out += graphTermFromTripleBody(convertUntil(')>>'), false);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (startsAt('<<')) {
|
|
508
|
+
i += 2;
|
|
509
|
+
out += graphTermFromTripleBody(convertUntil('>>'), false);
|
|
447
510
|
continue;
|
|
448
511
|
}
|
|
449
512
|
const ch = s[i];
|
|
@@ -474,7 +537,189 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
474
537
|
return out;
|
|
475
538
|
}
|
|
476
539
|
|
|
477
|
-
|
|
540
|
+
const converted = convertUntil(null);
|
|
541
|
+
if (reifierTriples.length === 0) return converted;
|
|
542
|
+
return converted + (converted.endsWith('\n') ? '' : '\n') + reifierTriples.join('\n') + '\n';
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
function convertAnnotations(s) {
|
|
547
|
+
let out = '';
|
|
548
|
+
let i = 0;
|
|
549
|
+
let statementStart = true;
|
|
550
|
+
let generatedBlank = 0;
|
|
551
|
+
|
|
552
|
+
function readBalancedDelimited(s, at, open, close) {
|
|
553
|
+
if (!s.startsWith(open, at)) return null;
|
|
554
|
+
let j = at + open.length;
|
|
555
|
+
let depth = 1;
|
|
556
|
+
while (j < s.length) {
|
|
557
|
+
const ch = s[j];
|
|
558
|
+
if (ch === '"' || ch === "'") {
|
|
559
|
+
j = readStringAt(s, j).end;
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (ch === '<' && !s.startsWith('<<', j)) {
|
|
563
|
+
j = readIriAt(s, j).end;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (ch === '#') {
|
|
567
|
+
while (j < s.length && s[j] !== '\n' && s[j] !== '\r') j += 1;
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (s.startsWith(open, j)) {
|
|
571
|
+
depth += 1;
|
|
572
|
+
j += open.length;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (s.startsWith(close, j)) {
|
|
576
|
+
depth -= 1;
|
|
577
|
+
j += close.length;
|
|
578
|
+
if (depth === 0) return { text: s.slice(at, j), inner: s.slice(at + open.length, j - close.length), end: j };
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
j += 1;
|
|
582
|
+
}
|
|
583
|
+
throw new N3SyntaxError(`Unterminated RDF annotation block, expected ${close}`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function readTermLikeAt(s, at) {
|
|
587
|
+
const j = skipWsAndComments(s, at);
|
|
588
|
+
if (j >= s.length) return null;
|
|
589
|
+
if (s[j] === '<') return readIriAt(s, j);
|
|
590
|
+
if (s[j] === '"' || s[j] === "'") {
|
|
591
|
+
const str = readStringAt(s, j);
|
|
592
|
+
let end = str.end;
|
|
593
|
+
let text = str.text;
|
|
594
|
+
if (s.startsWith('^^', end)) {
|
|
595
|
+
const dt = readTermAt(s, end + 2);
|
|
596
|
+
if (dt) {
|
|
597
|
+
text += '^^' + dt.text;
|
|
598
|
+
end = dt.end;
|
|
599
|
+
}
|
|
600
|
+
} else if (s[end] === '@') {
|
|
601
|
+
let k = end + 1;
|
|
602
|
+
if (/[A-Za-z]/.test(s[k] || '')) {
|
|
603
|
+
while (k < s.length && /[A-Za-z0-9-]/.test(s[k])) k += 1;
|
|
604
|
+
text += s.slice(end, k);
|
|
605
|
+
end = k;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return { text, end };
|
|
609
|
+
}
|
|
610
|
+
if (s[j] === '{') return readBalancedBlock(s, j);
|
|
611
|
+
if (s[j] === '[') return readBalancedDelimited(s, j, '[', ']');
|
|
612
|
+
if (s[j] === '(') return readBalancedDelimited(s, j, '(', ')');
|
|
613
|
+
return readTermAt(s, j);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function readAnnotationBlockAt(s, at) {
|
|
617
|
+
if (!s.startsWith('{|', at)) return null;
|
|
618
|
+
return readBalancedDelimited(s, at, '{|', '|}');
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function tryReadAnnotatedTriple(at) {
|
|
622
|
+
const start = skipWsAndComments(s, at);
|
|
623
|
+
if (start >= s.length) return null;
|
|
624
|
+
if (s[start] === '@') return null;
|
|
625
|
+
if (startsWordAt(s, 'PREFIX', start) || startsWordAt(s, 'BASE', start) || startsWordAt(s, 'VERSION', start)) return null;
|
|
626
|
+
if (startsWordAt(s, 'GRAPH', start)) return null;
|
|
627
|
+
|
|
628
|
+
const subj = readTermLikeAt(s, start);
|
|
629
|
+
if (!subj) return null;
|
|
630
|
+
let j = skipWsAndComments(s, subj.end);
|
|
631
|
+
const pred = readTermLikeAt(s, j);
|
|
632
|
+
if (!pred) return null;
|
|
633
|
+
j = skipWsAndComments(s, pred.end);
|
|
634
|
+
const obj = readTermLikeAt(s, j);
|
|
635
|
+
if (!obj) return null;
|
|
636
|
+
j = skipWsAndComments(s, obj.end);
|
|
637
|
+
if (s[j] !== '~' && !s.startsWith('{|', j)) return null;
|
|
638
|
+
|
|
639
|
+
let reifier = '';
|
|
640
|
+
const annotationBlocks = [];
|
|
641
|
+
while (j < s.length) {
|
|
642
|
+
j = skipWsAndComments(s, j);
|
|
643
|
+
if (s[j] === '~') {
|
|
644
|
+
j += 1;
|
|
645
|
+
j = skipWsAndComments(s, j);
|
|
646
|
+
const term = readTermAt(s, j);
|
|
647
|
+
if (term) {
|
|
648
|
+
reifier = term.text;
|
|
649
|
+
j = term.end;
|
|
650
|
+
} else if (!reifier) {
|
|
651
|
+
reifier = `_:rdfAnnotation${++generatedBlank}`;
|
|
652
|
+
}
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
if (s.startsWith('{|', j)) {
|
|
656
|
+
const block = readAnnotationBlockAt(s, j);
|
|
657
|
+
if (!reifier) reifier = `_:rdfAnnotation${++generatedBlank}`;
|
|
658
|
+
annotationBlocks.push(block.inner.trim());
|
|
659
|
+
j = block.end;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
break;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const after = skipWsAndComments(s, j);
|
|
666
|
+
if (s[after] !== '.') return null;
|
|
667
|
+
if (!reifier && annotationBlocks.length === 0) return null;
|
|
668
|
+
|
|
669
|
+
const baseTriple = `${subj.text} ${pred.text} ${obj.text}`;
|
|
670
|
+
const graph = `{ ${baseTriple} }`;
|
|
671
|
+
const extra = [];
|
|
672
|
+
if (reifier) extra.push(`${reifier} ${RDF_REIFIES_IRI} ${graph} .`);
|
|
673
|
+
for (const inner of annotationBlocks) {
|
|
674
|
+
if (inner) extra.push(`${reifier} ${inner} .`);
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
start,
|
|
678
|
+
end: after + 1,
|
|
679
|
+
text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}`,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
while (i < s.length) {
|
|
684
|
+
if (statementStart) {
|
|
685
|
+
const converted = tryReadAnnotatedTriple(i);
|
|
686
|
+
if (converted) {
|
|
687
|
+
out += s.slice(i, converted.start) + converted.text;
|
|
688
|
+
i = converted.end;
|
|
689
|
+
statementStart = true;
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const ch = s[i];
|
|
695
|
+
if (ch === '"' || ch === "'") {
|
|
696
|
+
const str = readStringAt(s, i);
|
|
697
|
+
out += str.text;
|
|
698
|
+
i = str.end;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (ch === '<' && !s.startsWith('<<', i)) {
|
|
702
|
+
const iri = readIriAt(s, i);
|
|
703
|
+
out += iri.text;
|
|
704
|
+
i = iri.end;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
if (ch === '#') {
|
|
708
|
+
while (i < s.length) {
|
|
709
|
+
const c = s[i++];
|
|
710
|
+
out += c;
|
|
711
|
+
if (c === '\n' || c === '\r') break;
|
|
712
|
+
}
|
|
713
|
+
statementStart = true;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
out += ch;
|
|
717
|
+
if (ch === '.' || ch === '{' || ch === '}' || ch === '\n' || ch === '\r') statementStart = true;
|
|
718
|
+
else if (!/\s/.test(ch)) statementStart = false;
|
|
719
|
+
i += 1;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
return out;
|
|
478
723
|
}
|
|
479
724
|
|
|
480
725
|
function stripVersionDirectives(s) {
|
|
@@ -633,6 +878,7 @@ function normalizeRdfCompatibility(inputText) {
|
|
|
633
878
|
}
|
|
634
879
|
|
|
635
880
|
if (hasTripleTerms) text = convertTripleTerms(text);
|
|
881
|
+
if (hasAnnotationSyntax) text = convertAnnotations(text);
|
|
636
882
|
if (hasVersionDirective) text = stripVersionDirectives(text);
|
|
637
883
|
if (hasVersionDirective || hasNamedGraphCandidate) text = normalizeNamedGraphs(text);
|
|
638
884
|
return text;
|
package/package.json
CHANGED
package/see/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Each example starts from a Notation3 source and is compiled by `see.js` into a s
|
|
|
18
18
|
|
|
19
19
|
The trust gate is executable verification. If a required fact is missing, the program fails instead of emitting an unsupported entailment.
|
|
20
20
|
|
|
21
|
-
The `triple_terms` and `rdf_dataset` examples use RDF 1.2 `<<( ... )>>`
|
|
21
|
+
The `triple_terms` and `rdf_dataset` examples use RDF 1.2 triple-term syntax, including `<<( ... )>>` and the reified `<<s p o ~ r>>` form. Eyeling accepts that syntax only with `-r, --rdf`, where it normalizes triple terms to existing N3 singleton graph terms `{ ... }` and prints feasible output graph terms back as RDF 1.2 triple terms. The `rdf_dataset` example also uses an RDF/TriG named graph block, which RDF compatibility mode normalizes to the N3 `log:nameOf { ... }` graph-term shape and prints back as a TriG named graph block where feasible; SEE keeps the committed `.trig` input and formal output in RDF 1.2/TriG form.
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
## Run
|
package/test/api.test.js
CHANGED
|
@@ -2572,6 +2572,46 @@ _:b a ex:Person ; ex:name "B" .
|
|
|
2572
2572
|
expect: [/:observation\s+:entails\s+<<\(\s+:sensor\s+:needs\s+:inspection\s*\)>>\s*\./m],
|
|
2573
2573
|
},
|
|
2574
2574
|
|
|
2575
|
+
{
|
|
2576
|
+
name: 'RDF mode accepts PREFIX and reified triple terms with reifiers',
|
|
2577
|
+
opt: { proofComments: false, rdf: true },
|
|
2578
|
+
input: `VERSION "1.2"
|
|
2579
|
+
|
|
2580
|
+
PREFIX : <http://www.example.org/>
|
|
2581
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
2582
|
+
|
|
2583
|
+
<<:employee38 :jobTitle "Assistant Designer" ~ _:id>> :accordingTo :employee22 .
|
|
2584
|
+
|
|
2585
|
+
{ ?S ?P ?O } log:query { ?S ?P ?O } .
|
|
2586
|
+
`,
|
|
2587
|
+
expect: [
|
|
2588
|
+
/<<\(\s*:employee38\s+:jobTitle\s+"Assistant Designer"\s*\)>>\s+:accordingTo\s+:employee22\s*\./m,
|
|
2589
|
+
/_:id\s+(?:rdf:reifies|<http:\/\/www\.w3\.org\/1999\/02\/22-rdf-syntax-ns#reifies>)\s+<<\(\s*:employee38\s+:jobTitle\s+"Assistant Designer"\s*\)>>\s*\./m,
|
|
2590
|
+
],
|
|
2591
|
+
},
|
|
2592
|
+
|
|
2593
|
+
{
|
|
2594
|
+
name: 'RDF mode accepts RDF 1.2 annotation syntax after objects',
|
|
2595
|
+
opt: { proofComments: false, rdf: true },
|
|
2596
|
+
input: `VERSION "1.2"
|
|
2597
|
+
|
|
2598
|
+
PREFIX : <http://example.com/>
|
|
2599
|
+
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
2600
|
+
|
|
2601
|
+
:a :name "Alice" ~ :t {| :statedBy :bob ; :recorded "2021-07-07"^^xsd:date |} .
|
|
2602
|
+
|
|
2603
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#>.
|
|
2604
|
+
|
|
2605
|
+
{ ?S ?P ?O } log:query { ?S ?P ?O }.
|
|
2606
|
+
`,
|
|
2607
|
+
expect: [
|
|
2608
|
+
/:a\s+:name\s+"Alice"\s*\./m,
|
|
2609
|
+
/:t\s+(?:rdf:reifies|<http:\/\/www\.w3\.org\/1999\/02\/22-rdf-syntax-ns#reifies>)\s+<<\(\s*:a\s+:name\s+"Alice"\s*\)>>\s*\./m,
|
|
2610
|
+
/:t\s+:statedBy\s+:bob\s*\./m,
|
|
2611
|
+
/:t\s+:recorded\s+"2021-07-07"\^\^xsd:date\s*\./m,
|
|
2612
|
+
],
|
|
2613
|
+
},
|
|
2614
|
+
|
|
2575
2615
|
{
|
|
2576
2616
|
name: 'RDF mode serializes only valid triple terms as RDF 1.2 triple terms',
|
|
2577
2617
|
opt: { proofComments: false, rdf: true },
|
package/test/package.test.js
CHANGED
|
@@ -174,7 +174,7 @@ function main() {
|
|
|
174
174
|
if (!fs.existsSync(eyelingJsPath)) throw new Error(`Missing eyeling.js in installed package: ${eyelingJsPath}`);
|
|
175
175
|
|
|
176
176
|
// Keep this fast: package.test.js is a smoke test. The full matrix is covered by test/examples.test.js in-repo.
|
|
177
|
-
const SMOKE_EXAMPLES = ['age.n3', 'basic-monadic.n3', '
|
|
177
|
+
const SMOKE_EXAMPLES = ['age.n3', 'basic-monadic.n3', 'family-cousins.n3', 'backward.n3'];
|
|
178
178
|
|
|
179
179
|
const tmpExamplesOut = fs.mkdtempSync(path.join(os.tmpdir(), 'eyeling-pkg-examples-'));
|
|
180
180
|
let smokeIdx = 1;
|
package/examples/annotation.n3
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
@prefix : <http://example.com/> .
|
|
2
|
-
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
4
|
-
|
|
5
|
-
:a :name "Alice" .
|
|
6
|
-
:t log:nameOf { :a :name "Alice" . } .
|
|
7
|
-
:t :statedBy :bob .
|
|
8
|
-
:t :recorded "2021-07-07"^^xsd:date .
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# ARC-style examples in Eyeling
|
|
2
|
-
|
|
3
|
-
This content is now integrated into the handbook:
|
|
4
|
-
|
|
5
|
-
- [Appendix F — The ARC approach: Answer • Reason Why • Check](../../HANDBOOK.md#app-f)
|
|
6
|
-
|
|
7
|
-
That appendix now includes:
|
|
8
|
-
|
|
9
|
-
- a brief explanation of ARC,
|
|
10
|
-
- a brief explanation of the Insight Economy,
|
|
11
|
-
- and links to the ARC-style examples in `examples/` together with their outputs in `examples/output/`.
|
package/examples/collection.n3
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
@prefix : <http://example.org/#> .
|
|
2
|
-
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
|
|
3
|
-
@prefix sec: <https://w3id.org/security#> .
|
|
4
|
-
@prefix skolem: <https://eyereasoner.github.io/.well-known/genid/5649fff4-464d-5969-88ed-956a6b5f0d90#> .
|
|
5
|
-
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
6
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
7
|
-
|
|
8
|
-
skolem:g0 log:nameOf {
|
|
9
|
-
:Bob foaf:name "Bob" .
|
|
10
|
-
} .
|
|
11
|
-
|
|
12
|
-
skolem:g1 log:nameOf {
|
|
13
|
-
skolem:g0 sec:proof _:dataSignature .
|
|
14
|
-
_:signature1 a sec:DataIntegrityProof .
|
|
15
|
-
_:signature1 sec:cryptosuite "ecdsa-rdfc-2019" .
|
|
16
|
-
_:signature1 sec:created "2021-11-13T18:19:39Z" .
|
|
17
|
-
_:signature1 sec:verificationMethod "https://university.example/issuers/14#key-1" .
|
|
18
|
-
_:signature1 sec:proofPurpose "assertionMethod" .
|
|
19
|
-
_:signature1 sec:proofValue "z58DAdFfa9SkqZMVPxAQp...jQCrfFPP2oumHKtz" .
|
|
20
|
-
_:signature1 sec:issuer <https://university.example/issuers/14> .
|
|
21
|
-
_:signature1 sec:validFrom "2024-04-03T00:00:00.000Z"^^xsd:dateTime .
|
|
22
|
-
_:signature1 sec:validUntil "2025-04-03T00:00:00.000Z"^^xsd:dateTime .
|
|
23
|
-
} .
|
|
24
|
-
|
|
25
|
-
_:g3 log:nameOf {
|
|
26
|
-
skolem:g1 sec:proof _:signature2 .
|
|
27
|
-
_:signature2 a sec:DataIntegrityProof .
|
|
28
|
-
_:signature2 sec:cryptosuite "ecdsa-rdfc-2019" .
|
|
29
|
-
_:signature2 sec:created "2021-11-13T18:19:39Z" .
|
|
30
|
-
_:signature2 sec:verificationMethod "https://university.example/issuers/14#key-1" .
|
|
31
|
-
_:signature2 sec:proofPurpose "assertionMethod" .
|
|
32
|
-
_:signature2 sec:proofValue "adad123efv434r5200...dqed2t44v43das" .
|
|
33
|
-
} .
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
2
|
-
PREFIX : <http://example.org/stuff/1.0/>
|
|
3
|
-
|
|
4
|
-
_:b0 :p2 :q2 .
|
|
5
|
-
_:b0 rdf:first 1 ;
|
|
6
|
-
rdf:rest _:b1 .
|
|
7
|
-
_:b1 rdf:first _:b2 .
|
|
8
|
-
_:b2 :p :q .
|
|
9
|
-
_:b1 rdf:rest _:b3 .
|
|
10
|
-
_:b3 rdf:first _:b4 .
|
|
11
|
-
_:b4 rdf:first 2 ;
|
|
12
|
-
rdf:rest rdf:nil .
|
|
13
|
-
_:b3 rdf:rest rdf:nil .
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
|
|
2
|
-
@prefix sec: <https://w3id.org/security#>.
|
|
3
|
-
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
|
|
4
|
-
@prefix : <http://example.org/#>.
|
|
5
|
-
|
|
6
|
-
_:g0 {
|
|
7
|
-
:Bob foaf:name "Bob".
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
# sign data graph
|
|
11
|
-
_:g1 {
|
|
12
|
-
_:g0 sec:proof _:dataSignature.
|
|
13
|
-
_:signature1
|
|
14
|
-
a sec:DataIntegrityProof;
|
|
15
|
-
sec:cryptosuite "ecdsa-rdfc-2019";
|
|
16
|
-
sec:created "2021-11-13T18:19:39Z";
|
|
17
|
-
sec:verificationMethod "https://university.example/issuers/14#key-1";
|
|
18
|
-
sec:proofPurpose "assertionMethod";
|
|
19
|
-
sec:proofValue "z58DAdFfa9SkqZMVPxAQp...jQCrfFPP2oumHKtz";
|
|
20
|
-
sec:issuer <https://university.example/issuers/14>;
|
|
21
|
-
sec:validFrom "2024-04-03T00:00:00.000Z"^^xsd:dateTime;
|
|
22
|
-
sec:validUntil "2025-04-03T00:00:00.000Z"^^xsd:dateTime.
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
# sign signature graph for verifiable metadata if needed
|
|
26
|
-
_:g3 {
|
|
27
|
-
_:g1 sec:proof _:signature2.
|
|
28
|
-
_:signature2
|
|
29
|
-
a sec:DataIntegrityProof;
|
|
30
|
-
sec:cryptosuite "ecdsa-rdfc-2019";
|
|
31
|
-
sec:created "2021-11-13T18:19:39Z";
|
|
32
|
-
sec:verificationMethod "https://university.example/issuers/14#key-1";
|
|
33
|
-
sec:proofPurpose "assertionMethod";
|
|
34
|
-
sec:proofValue "adad123efv434r5200...dqed2t44v43das".
|
|
35
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
VERSION "1.2"
|
|
2
|
-
|
|
3
|
-
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
4
|
-
PREFIX : <http://www.example.org/>
|
|
5
|
-
|
|
6
|
-
_:b rdf:reifies <<(:Socrates a :Human)>> .
|
|
7
|
-
_:b :is true .
|
|
8
|
-
|
|
9
|
-
:employee38 :familyName "Smith" .
|
|
10
|
-
<<:employee38 :jobTitle "Assistant Designer" ~ _:id>> :accordingTo :employee22 .
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/examples/reifies.n3
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
@prefix : <http://www.example.org/> .
|
|
2
|
-
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
-
|
|
4
|
-
_:b log:nameOf { :Socrates a :Human . } .
|
|
5
|
-
_:b :is true .
|
|
6
|
-
:employee38 :familyName "Smith" .
|
|
7
|
-
_:id log:nameOf { :employee38 :jobTitle "Assistant Designer" . } .
|
|
8
|
-
_:id :accordingTo :employee22 .
|
package/examples/triple-term.n3
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
@prefix : <http://www.example.org/> .
|
|
2
|
-
@prefix skolem: <https://eyereasoner.github.io/.well-known/genid/3ce038d5-ea5f-5c1e-8635-dcdabec19d5f#> .
|
|
3
|
-
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
4
|
-
|
|
5
|
-
skolem:e38 :familyName "Smith" .
|
|
6
|
-
_:anno log:nameOf { skolem:e38 :jobTitle "Designer" . } .
|
|
7
|
-
_:anno :accordingTo _:e22 .
|