eyeling 1.27.3 → 1.27.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 CHANGED
@@ -3851,7 +3851,9 @@ The playground also exposes configuration toggles that are especially useful for
3851
3851
  - **RDF/TriG compatibility**, which mirrors command-line `-r, --rdf` for RDF surface syntax and message-log replay,
3852
3852
  - **stream RDF Messages**, which mirrors `--stream-messages` under RDF mode and runs the editor rules over one RDF Message at a time.
3853
3853
 
3854
- For streamed RDF Messages in the playground, put the extraction rules and any small background knowledge in the editor, then provide the large RDF Message Log through the **RDF Message Log URL** field. The URL is preserved in compact share links. A pasted message log in the editor is still useful for small teaching examples, but the URL field is the intended path for large logs because it lets the worker fetch and parse the stream incrementally rather than asking CodeMirror to hold the log text.
3854
+ For streamed RDF Messages in the playground, put the extraction rules and any small background knowledge in the editor, then provide the large RDF Message Log through the **RDF Message Log URL** field. The URL is preserved in compact share links. A pasted message log in the editor is still useful for small teaching examples, but the URL field is the intended path for large logs because it lets the worker fetch and parse the stream incrementally rather than asking CodeMirror to hold the log text. URL fetches follow HTTP redirects, so short, stable, or repository URLs can redirect to the actual raw resource while the final URL is still used for base handling where needed.
3855
+
3856
+ The layout is responsive: on small screens the URL fields and action buttons stack vertically, controls remain tap-friendly, and the editor/output panes scroll internally when their height is capped. That keeps the playground useful on phones for opening shared links, changing toggles, and running small or URL-backed streaming examples.
3855
3857
 
3856
3858
  Together these choices make the playground better suited to live explanation, teaching, and debugging than a minimal browser wrapper would be.
3857
3859
 
@@ -10900,10 +10900,10 @@ function normalizeRdfCompatibility(inputText) {
10900
10900
  // unless they actually contain RDF 1.2 triple terms, VERSION directives, or a
10901
10901
  // plausible top-level TriG named graph block.
10902
10902
  const hasTripleTerms = text.includes('<<');
10903
- const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
10903
+ const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)\1\s*\.?\s*(?:#.*)?$/im.test(text);
10904
10904
  const hasMessageVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im.test(text);
10905
- 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);
10906
- const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
10905
+ 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{};,.()[\]]+)\s*\{/m.test(text);
10906
+ const hasAnnotationSyntax = /(?:^|\s)~|\{\|/.test(text);
10907
10907
 
10908
10908
  if (!hasTripleTerms && !hasVersionDirective && !hasMessageVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
10909
10909
 
@@ -11190,8 +11190,10 @@ function normalizeRdfCompatibility(inputText) {
11190
11190
  }
11191
11191
  if (s.startsWith('{|', j)) {
11192
11192
  const block = readAnnotationBlockAt(s, j);
11193
+ const inner = block.inner.trim();
11194
+ if (!inner) throw new N3SyntaxError('Empty RDF annotation block is not allowed');
11193
11195
  if (!reifier) reifier = `_:rdfAnnotation${++generatedBlank}`;
11194
- annotationBlocks.push(block.inner.trim());
11196
+ annotationBlocks.push(inner);
11195
11197
  j = block.end;
11196
11198
  continue;
11197
11199
  }
@@ -11199,7 +11201,7 @@ function normalizeRdfCompatibility(inputText) {
11199
11201
  }
11200
11202
 
11201
11203
  const after = skipWsAndComments(s, j);
11202
- if (s[after] !== '.') return null;
11204
+ if (!['.', ';', ',', '}'].includes(s[after])) return null;
11203
11205
  if (!reifier && annotationBlocks.length === 0) return null;
11204
11206
 
11205
11207
  const baseTriple = `${subj.text} ${pred.text} ${obj.text}`;
@@ -11209,10 +11211,15 @@ function normalizeRdfCompatibility(inputText) {
11209
11211
  for (const inner of annotationBlocks) {
11210
11212
  if (inner) extra.push(`${reifier} ${inner} .`);
11211
11213
  }
11214
+
11215
+ let continuation = '';
11216
+ if (s[after] === ';') continuation = `\n${subj.text} `;
11217
+ else if (s[after] === ',') continuation = `\n${subj.text} ${pred.text} `;
11218
+
11212
11219
  return {
11213
11220
  start,
11214
- end: after + 1,
11215
- text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}`,
11221
+ end: s[after] === '}' ? after : after + 1,
11222
+ text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}${continuation}`,
11216
11223
  };
11217
11224
  }
11218
11225
 
@@ -11306,6 +11313,29 @@ function normalizeRdfCompatibility(inputText) {
11306
11313
  while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
11307
11314
  continue;
11308
11315
  }
11316
+ if (s.startsWith('{|', i)) {
11317
+ i += 2;
11318
+ while (i < s.length) {
11319
+ if (s[i] === '"' || s[i] === "'") {
11320
+ i = readStringAt(s, i).end;
11321
+ continue;
11322
+ }
11323
+ if (s[i] === '<' && !s.startsWith('<<', i)) {
11324
+ i = readIriAt(s, i).end;
11325
+ continue;
11326
+ }
11327
+ if (s[i] === '#') {
11328
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
11329
+ continue;
11330
+ }
11331
+ if (s.startsWith('|}', i)) {
11332
+ i += 2;
11333
+ break;
11334
+ }
11335
+ i += 1;
11336
+ }
11337
+ continue;
11338
+ }
11309
11339
  if (ch === '{') depth += 1;
11310
11340
  if (ch === '}') {
11311
11341
  depth -= 1;
@@ -11330,6 +11360,28 @@ function normalizeRdfCompatibility(inputText) {
11330
11360
  out += s.slice(i, termStart);
11331
11361
  i = termStart;
11332
11362
 
11363
+ // Preserve directives and keep the scanner at statement-start for
11364
+ // following TriG graph blocks. SPARQL-style PREFIX/BASE/VERSION lines
11365
+ // have no trailing '.', while @prefix/@base/@version directives do.
11366
+ if (startsWordAt(s, 'PREFIX', i) || startsWordAt(s, 'BASE', i) || startsWordAt(s, 'VERSION', i)) {
11367
+ let end = i;
11368
+ while (end < s.length && s[end] !== '\n' && s[end] !== '\r') end += 1;
11369
+ out += s.slice(i, end);
11370
+ i = end;
11371
+ statementStart = true;
11372
+ continue;
11373
+ }
11374
+ if (s[i] === '@') {
11375
+ const lower = s.slice(i, i + 9).toLowerCase();
11376
+ if (lower.startsWith('@prefix') || lower.startsWith('@base') || lower.startsWith('@version')) {
11377
+ const end = skipOldStyleDirective(s, i);
11378
+ out += s.slice(i, end);
11379
+ i = end;
11380
+ statementStart = true;
11381
+ continue;
11382
+ }
11383
+ }
11384
+
11333
11385
  // Top-level TriG default graph block: { ... } .
11334
11386
  if (s[i] === '{') {
11335
11387
  const block = readBalancedBlock(s, i);
@@ -11618,7 +11670,7 @@ function normalizeRdfCompatibility(inputText) {
11618
11670
  function normalizeMessageChunk(chunk, messageIndex) {
11619
11671
  let body = String(chunk || '');
11620
11672
  if (hasTripleTerms || body.includes('<<')) body = convertTripleTerms(body);
11621
- if (hasAnnotationSyntax || /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(body)) {
11673
+ if (hasAnnotationSyntax || /(?:^|\s)~|\{\|/.test(body)) {
11622
11674
  body = convertAnnotations(body);
11623
11675
  }
11624
11676
  body = normalizeNamedGraphs(body);
@@ -12151,6 +12203,17 @@ function lex(inputText, opts = {}) {
12151
12203
  i++;
12152
12204
  }
12153
12205
  while (peek() === '-') {
12206
+ if (peek(1) === '-') {
12207
+ const dir = sliceChars(i + 2, i + 5);
12208
+ const afterDir = peek(5);
12209
+ if ((dir === 'ltr' || dir === 'rtl') && (afterDir === null || !/[A-Za-z0-9-]/.test(afterDir))) {
12210
+ tagChars.push('--', dir);
12211
+ i += 5;
12212
+ break;
12213
+ }
12214
+ throw new N3SyntaxError('Invalid language direction (expected --ltr or --rtl)', start);
12215
+ }
12216
+
12154
12217
  tagChars.push('-');
12155
12218
  i++; // consume '-'
12156
12219
  const segChars = [];
@@ -14901,7 +14964,7 @@ function splitLiteralLexAndLang(value) {
14901
14964
  return { lexical: value, language: '' };
14902
14965
  }
14903
14966
  const language = value.slice(lastQuote + 2);
14904
- if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(language)) {
14967
+ if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--(?:ltr|rtl))?$/.test(language)) {
14905
14968
  return { lexical: value, language: '' };
14906
14969
  }
14907
14970
  return { lexical: value.slice(0, lastQuote + 1), language };
@@ -14938,6 +15001,51 @@ function internalTermToRdfJs(term, factory, position) {
14938
15001
  return unsupportedRdfJsTerm(term, position);
14939
15002
  }
14940
15003
 
15004
+ function internalRdf12TermToRdfJs(term, factory, position) {
15005
+ const rdfFactory = getDataFactory(factory);
15006
+ if (term instanceof GraphTerm) {
15007
+ if (position === 'predicate' || position === 'graph') {
15008
+ return unsupportedRdfJsTerm(term, position);
15009
+ }
15010
+ if (!Array.isArray(term.triples) || term.triples.length !== 1) {
15011
+ return unsupportedRdfJsTerm(term, position);
15012
+ }
15013
+ const triple = term.triples[0];
15014
+ return rdfFactory.quad(
15015
+ internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject'),
15016
+ internalTermToRdfJs(triple.p, rdfFactory, 'predicate'),
15017
+ internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object'),
15018
+ rdfFactory.defaultGraph(),
15019
+ );
15020
+ }
15021
+ return internalTermToRdfJs(term, rdfFactory, position);
15022
+ }
15023
+
15024
+ function assertRdfJsQuadShape(subject, predicate, object, graph) {
15025
+ if (!['NamedNode', 'BlankNode', 'Quad'].includes(subject.termType)) {
15026
+ throw new TypeError(`Invalid RDF subject termType ${subject.termType}`);
15027
+ }
15028
+ if (predicate.termType !== 'NamedNode') {
15029
+ throw new TypeError(`Invalid RDF predicate termType ${predicate.termType}`);
15030
+ }
15031
+ if (!['NamedNode', 'BlankNode', 'Literal', 'Quad'].includes(object.termType)) {
15032
+ throw new TypeError(`Invalid RDF object termType ${object.termType}`);
15033
+ }
15034
+ if (!['DefaultGraph', 'NamedNode', 'BlankNode'].includes(graph.termType)) {
15035
+ throw new TypeError(`Invalid RDF graph termType ${graph.termType}`);
15036
+ }
15037
+ }
15038
+
15039
+ function internalTripleToRdfJsQuadInGraph(triple, graph, factory) {
15040
+ const rdfFactory = getDataFactory(factory);
15041
+ const subject = internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject');
15042
+ const predicate = internalTermToRdfJs(triple.p, rdfFactory, 'predicate');
15043
+ const object = internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object');
15044
+ const graphTerm = graph || rdfFactory.defaultGraph();
15045
+ assertRdfJsQuadShape(subject, predicate, object, graphTerm);
15046
+ return rdfFactory.quad(subject, predicate, object, graphTerm);
15047
+ }
15048
+
14941
15049
  function internalTripleToRdfJsQuad(triple, factory) {
14942
15050
  const rdfFactory = getDataFactory(factory);
14943
15051
  return rdfFactory.quad(
@@ -15424,6 +15532,7 @@ module.exports = {
15424
15532
  rdfJsQuadToInternalTriple,
15425
15533
  internalTermToRdfJs,
15426
15534
  internalTripleToRdfJsQuad,
15535
+ internalTripleToRdfJsQuadInGraph,
15427
15536
  normalizeParsedReasonerInputSync,
15428
15537
  normalizeReasonerInputSync,
15429
15538
  normalizeReasonerInputAsync,
package/eyeling.js CHANGED
@@ -10900,10 +10900,10 @@ function normalizeRdfCompatibility(inputText) {
10900
10900
  // unless they actually contain RDF 1.2 triple terms, VERSION directives, or a
10901
10901
  // plausible top-level TriG named graph block.
10902
10902
  const hasTripleTerms = text.includes('<<');
10903
- const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
10903
+ const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)\1\s*\.?\s*(?:#.*)?$/im.test(text);
10904
10904
  const hasMessageVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im.test(text);
10905
- 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);
10906
- const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
10905
+ 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{};,.()[\]]+)\s*\{/m.test(text);
10906
+ const hasAnnotationSyntax = /(?:^|\s)~|\{\|/.test(text);
10907
10907
 
10908
10908
  if (!hasTripleTerms && !hasVersionDirective && !hasMessageVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
10909
10909
 
@@ -11190,8 +11190,10 @@ function normalizeRdfCompatibility(inputText) {
11190
11190
  }
11191
11191
  if (s.startsWith('{|', j)) {
11192
11192
  const block = readAnnotationBlockAt(s, j);
11193
+ const inner = block.inner.trim();
11194
+ if (!inner) throw new N3SyntaxError('Empty RDF annotation block is not allowed');
11193
11195
  if (!reifier) reifier = `_:rdfAnnotation${++generatedBlank}`;
11194
- annotationBlocks.push(block.inner.trim());
11196
+ annotationBlocks.push(inner);
11195
11197
  j = block.end;
11196
11198
  continue;
11197
11199
  }
@@ -11199,7 +11201,7 @@ function normalizeRdfCompatibility(inputText) {
11199
11201
  }
11200
11202
 
11201
11203
  const after = skipWsAndComments(s, j);
11202
- if (s[after] !== '.') return null;
11204
+ if (!['.', ';', ',', '}'].includes(s[after])) return null;
11203
11205
  if (!reifier && annotationBlocks.length === 0) return null;
11204
11206
 
11205
11207
  const baseTriple = `${subj.text} ${pred.text} ${obj.text}`;
@@ -11209,10 +11211,15 @@ function normalizeRdfCompatibility(inputText) {
11209
11211
  for (const inner of annotationBlocks) {
11210
11212
  if (inner) extra.push(`${reifier} ${inner} .`);
11211
11213
  }
11214
+
11215
+ let continuation = '';
11216
+ if (s[after] === ';') continuation = `\n${subj.text} `;
11217
+ else if (s[after] === ',') continuation = `\n${subj.text} ${pred.text} `;
11218
+
11212
11219
  return {
11213
11220
  start,
11214
- end: after + 1,
11215
- text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}`,
11221
+ end: s[after] === '}' ? after : after + 1,
11222
+ text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}${continuation}`,
11216
11223
  };
11217
11224
  }
11218
11225
 
@@ -11306,6 +11313,29 @@ function normalizeRdfCompatibility(inputText) {
11306
11313
  while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
11307
11314
  continue;
11308
11315
  }
11316
+ if (s.startsWith('{|', i)) {
11317
+ i += 2;
11318
+ while (i < s.length) {
11319
+ if (s[i] === '"' || s[i] === "'") {
11320
+ i = readStringAt(s, i).end;
11321
+ continue;
11322
+ }
11323
+ if (s[i] === '<' && !s.startsWith('<<', i)) {
11324
+ i = readIriAt(s, i).end;
11325
+ continue;
11326
+ }
11327
+ if (s[i] === '#') {
11328
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
11329
+ continue;
11330
+ }
11331
+ if (s.startsWith('|}', i)) {
11332
+ i += 2;
11333
+ break;
11334
+ }
11335
+ i += 1;
11336
+ }
11337
+ continue;
11338
+ }
11309
11339
  if (ch === '{') depth += 1;
11310
11340
  if (ch === '}') {
11311
11341
  depth -= 1;
@@ -11330,6 +11360,28 @@ function normalizeRdfCompatibility(inputText) {
11330
11360
  out += s.slice(i, termStart);
11331
11361
  i = termStart;
11332
11362
 
11363
+ // Preserve directives and keep the scanner at statement-start for
11364
+ // following TriG graph blocks. SPARQL-style PREFIX/BASE/VERSION lines
11365
+ // have no trailing '.', while @prefix/@base/@version directives do.
11366
+ if (startsWordAt(s, 'PREFIX', i) || startsWordAt(s, 'BASE', i) || startsWordAt(s, 'VERSION', i)) {
11367
+ let end = i;
11368
+ while (end < s.length && s[end] !== '\n' && s[end] !== '\r') end += 1;
11369
+ out += s.slice(i, end);
11370
+ i = end;
11371
+ statementStart = true;
11372
+ continue;
11373
+ }
11374
+ if (s[i] === '@') {
11375
+ const lower = s.slice(i, i + 9).toLowerCase();
11376
+ if (lower.startsWith('@prefix') || lower.startsWith('@base') || lower.startsWith('@version')) {
11377
+ const end = skipOldStyleDirective(s, i);
11378
+ out += s.slice(i, end);
11379
+ i = end;
11380
+ statementStart = true;
11381
+ continue;
11382
+ }
11383
+ }
11384
+
11333
11385
  // Top-level TriG default graph block: { ... } .
11334
11386
  if (s[i] === '{') {
11335
11387
  const block = readBalancedBlock(s, i);
@@ -11618,7 +11670,7 @@ function normalizeRdfCompatibility(inputText) {
11618
11670
  function normalizeMessageChunk(chunk, messageIndex) {
11619
11671
  let body = String(chunk || '');
11620
11672
  if (hasTripleTerms || body.includes('<<')) body = convertTripleTerms(body);
11621
- if (hasAnnotationSyntax || /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(body)) {
11673
+ if (hasAnnotationSyntax || /(?:^|\s)~|\{\|/.test(body)) {
11622
11674
  body = convertAnnotations(body);
11623
11675
  }
11624
11676
  body = normalizeNamedGraphs(body);
@@ -12151,6 +12203,17 @@ function lex(inputText, opts = {}) {
12151
12203
  i++;
12152
12204
  }
12153
12205
  while (peek() === '-') {
12206
+ if (peek(1) === '-') {
12207
+ const dir = sliceChars(i + 2, i + 5);
12208
+ const afterDir = peek(5);
12209
+ if ((dir === 'ltr' || dir === 'rtl') && (afterDir === null || !/[A-Za-z0-9-]/.test(afterDir))) {
12210
+ tagChars.push('--', dir);
12211
+ i += 5;
12212
+ break;
12213
+ }
12214
+ throw new N3SyntaxError('Invalid language direction (expected --ltr or --rtl)', start);
12215
+ }
12216
+
12154
12217
  tagChars.push('-');
12155
12218
  i++; // consume '-'
12156
12219
  const segChars = [];
@@ -14901,7 +14964,7 @@ function splitLiteralLexAndLang(value) {
14901
14964
  return { lexical: value, language: '' };
14902
14965
  }
14903
14966
  const language = value.slice(lastQuote + 2);
14904
- if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(language)) {
14967
+ if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--(?:ltr|rtl))?$/.test(language)) {
14905
14968
  return { lexical: value, language: '' };
14906
14969
  }
14907
14970
  return { lexical: value.slice(0, lastQuote + 1), language };
@@ -14938,6 +15001,51 @@ function internalTermToRdfJs(term, factory, position) {
14938
15001
  return unsupportedRdfJsTerm(term, position);
14939
15002
  }
14940
15003
 
15004
+ function internalRdf12TermToRdfJs(term, factory, position) {
15005
+ const rdfFactory = getDataFactory(factory);
15006
+ if (term instanceof GraphTerm) {
15007
+ if (position === 'predicate' || position === 'graph') {
15008
+ return unsupportedRdfJsTerm(term, position);
15009
+ }
15010
+ if (!Array.isArray(term.triples) || term.triples.length !== 1) {
15011
+ return unsupportedRdfJsTerm(term, position);
15012
+ }
15013
+ const triple = term.triples[0];
15014
+ return rdfFactory.quad(
15015
+ internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject'),
15016
+ internalTermToRdfJs(triple.p, rdfFactory, 'predicate'),
15017
+ internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object'),
15018
+ rdfFactory.defaultGraph(),
15019
+ );
15020
+ }
15021
+ return internalTermToRdfJs(term, rdfFactory, position);
15022
+ }
15023
+
15024
+ function assertRdfJsQuadShape(subject, predicate, object, graph) {
15025
+ if (!['NamedNode', 'BlankNode', 'Quad'].includes(subject.termType)) {
15026
+ throw new TypeError(`Invalid RDF subject termType ${subject.termType}`);
15027
+ }
15028
+ if (predicate.termType !== 'NamedNode') {
15029
+ throw new TypeError(`Invalid RDF predicate termType ${predicate.termType}`);
15030
+ }
15031
+ if (!['NamedNode', 'BlankNode', 'Literal', 'Quad'].includes(object.termType)) {
15032
+ throw new TypeError(`Invalid RDF object termType ${object.termType}`);
15033
+ }
15034
+ if (!['DefaultGraph', 'NamedNode', 'BlankNode'].includes(graph.termType)) {
15035
+ throw new TypeError(`Invalid RDF graph termType ${graph.termType}`);
15036
+ }
15037
+ }
15038
+
15039
+ function internalTripleToRdfJsQuadInGraph(triple, graph, factory) {
15040
+ const rdfFactory = getDataFactory(factory);
15041
+ const subject = internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject');
15042
+ const predicate = internalTermToRdfJs(triple.p, rdfFactory, 'predicate');
15043
+ const object = internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object');
15044
+ const graphTerm = graph || rdfFactory.defaultGraph();
15045
+ assertRdfJsQuadShape(subject, predicate, object, graphTerm);
15046
+ return rdfFactory.quad(subject, predicate, object, graphTerm);
15047
+ }
15048
+
14941
15049
  function internalTripleToRdfJsQuad(triple, factory) {
14942
15050
  const rdfFactory = getDataFactory(factory);
14943
15051
  return rdfFactory.quad(
@@ -15424,6 +15532,7 @@ module.exports = {
15424
15532
  rdfJsQuadToInternalTriple,
15425
15533
  internalTermToRdfJs,
15426
15534
  internalTripleToRdfJsQuad,
15535
+ internalTripleToRdfJsQuadInGraph,
15427
15536
  normalizeParsedReasonerInputSync,
15428
15537
  normalizeReasonerInputSync,
15429
15538
  normalizeReasonerInputAsync,
package/lib/lexer.js CHANGED
@@ -400,10 +400,10 @@ function normalizeRdfCompatibility(inputText) {
400
400
  // unless they actually contain RDF 1.2 triple terms, VERSION directives, or a
401
401
  // plausible top-level TriG named graph block.
402
402
  const hasTripleTerms = text.includes('<<');
403
- const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])1\.2\1\s*\.?\s*(?:#.*)?$/im.test(text);
403
+ const hasVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)\1\s*\.?\s*(?:#.*)?$/im.test(text);
404
404
  const hasMessageVersionDirective = /^\s*(?:@version|VERSION)\s+(["'])(?:1\.1|1\.2|1\.2-basic)-messages\1\s*\.?\s*(?:#.*)?$/im.test(text);
405
- 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);
406
- const hasAnnotationSyntax = /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(text);
405
+ 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{};,.()[\]]+)\s*\{/m.test(text);
406
+ const hasAnnotationSyntax = /(?:^|\s)~|\{\|/.test(text);
407
407
 
408
408
  if (!hasTripleTerms && !hasVersionDirective && !hasMessageVersionDirective && !hasNamedGraphCandidate && !hasAnnotationSyntax) return text;
409
409
 
@@ -690,8 +690,10 @@ function normalizeRdfCompatibility(inputText) {
690
690
  }
691
691
  if (s.startsWith('{|', j)) {
692
692
  const block = readAnnotationBlockAt(s, j);
693
+ const inner = block.inner.trim();
694
+ if (!inner) throw new N3SyntaxError('Empty RDF annotation block is not allowed');
693
695
  if (!reifier) reifier = `_:rdfAnnotation${++generatedBlank}`;
694
- annotationBlocks.push(block.inner.trim());
696
+ annotationBlocks.push(inner);
695
697
  j = block.end;
696
698
  continue;
697
699
  }
@@ -699,7 +701,7 @@ function normalizeRdfCompatibility(inputText) {
699
701
  }
700
702
 
701
703
  const after = skipWsAndComments(s, j);
702
- if (s[after] !== '.') return null;
704
+ if (!['.', ';', ',', '}'].includes(s[after])) return null;
703
705
  if (!reifier && annotationBlocks.length === 0) return null;
704
706
 
705
707
  const baseTriple = `${subj.text} ${pred.text} ${obj.text}`;
@@ -709,10 +711,15 @@ function normalizeRdfCompatibility(inputText) {
709
711
  for (const inner of annotationBlocks) {
710
712
  if (inner) extra.push(`${reifier} ${inner} .`);
711
713
  }
714
+
715
+ let continuation = '';
716
+ if (s[after] === ';') continuation = `\n${subj.text} `;
717
+ else if (s[after] === ',') continuation = `\n${subj.text} ${pred.text} `;
718
+
712
719
  return {
713
720
  start,
714
- end: after + 1,
715
- text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}`,
721
+ end: s[after] === '}' ? after : after + 1,
722
+ text: `${baseTriple} .${extra.length ? '\n' + extra.join('\n') : ''}${continuation}`,
716
723
  };
717
724
  }
718
725
 
@@ -806,6 +813,29 @@ function normalizeRdfCompatibility(inputText) {
806
813
  while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
807
814
  continue;
808
815
  }
816
+ if (s.startsWith('{|', i)) {
817
+ i += 2;
818
+ while (i < s.length) {
819
+ if (s[i] === '"' || s[i] === "'") {
820
+ i = readStringAt(s, i).end;
821
+ continue;
822
+ }
823
+ if (s[i] === '<' && !s.startsWith('<<', i)) {
824
+ i = readIriAt(s, i).end;
825
+ continue;
826
+ }
827
+ if (s[i] === '#') {
828
+ while (i < s.length && s[i] !== '\n' && s[i] !== '\r') i += 1;
829
+ continue;
830
+ }
831
+ if (s.startsWith('|}', i)) {
832
+ i += 2;
833
+ break;
834
+ }
835
+ i += 1;
836
+ }
837
+ continue;
838
+ }
809
839
  if (ch === '{') depth += 1;
810
840
  if (ch === '}') {
811
841
  depth -= 1;
@@ -830,6 +860,28 @@ function normalizeRdfCompatibility(inputText) {
830
860
  out += s.slice(i, termStart);
831
861
  i = termStart;
832
862
 
863
+ // Preserve directives and keep the scanner at statement-start for
864
+ // following TriG graph blocks. SPARQL-style PREFIX/BASE/VERSION lines
865
+ // have no trailing '.', while @prefix/@base/@version directives do.
866
+ if (startsWordAt(s, 'PREFIX', i) || startsWordAt(s, 'BASE', i) || startsWordAt(s, 'VERSION', i)) {
867
+ let end = i;
868
+ while (end < s.length && s[end] !== '\n' && s[end] !== '\r') end += 1;
869
+ out += s.slice(i, end);
870
+ i = end;
871
+ statementStart = true;
872
+ continue;
873
+ }
874
+ if (s[i] === '@') {
875
+ const lower = s.slice(i, i + 9).toLowerCase();
876
+ if (lower.startsWith('@prefix') || lower.startsWith('@base') || lower.startsWith('@version')) {
877
+ const end = skipOldStyleDirective(s, i);
878
+ out += s.slice(i, end);
879
+ i = end;
880
+ statementStart = true;
881
+ continue;
882
+ }
883
+ }
884
+
833
885
  // Top-level TriG default graph block: { ... } .
834
886
  if (s[i] === '{') {
835
887
  const block = readBalancedBlock(s, i);
@@ -1118,7 +1170,7 @@ function normalizeRdfCompatibility(inputText) {
1118
1170
  function normalizeMessageChunk(chunk, messageIndex) {
1119
1171
  let body = String(chunk || '');
1120
1172
  if (hasTripleTerms || body.includes('<<')) body = convertTripleTerms(body);
1121
- if (hasAnnotationSyntax || /(?:^|\s)~\s*(?:<|_:[A-Za-z]|[A-Za-z][A-Za-z0-9_-]*:|\{\|)|\{\|/.test(body)) {
1173
+ if (hasAnnotationSyntax || /(?:^|\s)~|\{\|/.test(body)) {
1122
1174
  body = convertAnnotations(body);
1123
1175
  }
1124
1176
  body = normalizeNamedGraphs(body);
@@ -1651,6 +1703,17 @@ function lex(inputText, opts = {}) {
1651
1703
  i++;
1652
1704
  }
1653
1705
  while (peek() === '-') {
1706
+ if (peek(1) === '-') {
1707
+ const dir = sliceChars(i + 2, i + 5);
1708
+ const afterDir = peek(5);
1709
+ if ((dir === 'ltr' || dir === 'rtl') && (afterDir === null || !/[A-Za-z0-9-]/.test(afterDir))) {
1710
+ tagChars.push('--', dir);
1711
+ i += 5;
1712
+ break;
1713
+ }
1714
+ throw new N3SyntaxError('Invalid language direction (expected --ltr or --rtl)', start);
1715
+ }
1716
+
1654
1717
  tagChars.push('-');
1655
1718
  i++; // consume '-'
1656
1719
  const segChars = [];
package/lib/rdfjs.js CHANGED
@@ -265,7 +265,7 @@ function splitLiteralLexAndLang(value) {
265
265
  return { lexical: value, language: '' };
266
266
  }
267
267
  const language = value.slice(lastQuote + 2);
268
- if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(language)) {
268
+ if (!/^[A-Za-z]+(?:-[A-Za-z0-9]+)*(?:--(?:ltr|rtl))?$/.test(language)) {
269
269
  return { lexical: value, language: '' };
270
270
  }
271
271
  return { lexical: value.slice(0, lastQuote + 1), language };
@@ -302,6 +302,51 @@ function internalTermToRdfJs(term, factory, position) {
302
302
  return unsupportedRdfJsTerm(term, position);
303
303
  }
304
304
 
305
+ function internalRdf12TermToRdfJs(term, factory, position) {
306
+ const rdfFactory = getDataFactory(factory);
307
+ if (term instanceof GraphTerm) {
308
+ if (position === 'predicate' || position === 'graph') {
309
+ return unsupportedRdfJsTerm(term, position);
310
+ }
311
+ if (!Array.isArray(term.triples) || term.triples.length !== 1) {
312
+ return unsupportedRdfJsTerm(term, position);
313
+ }
314
+ const triple = term.triples[0];
315
+ return rdfFactory.quad(
316
+ internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject'),
317
+ internalTermToRdfJs(triple.p, rdfFactory, 'predicate'),
318
+ internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object'),
319
+ rdfFactory.defaultGraph(),
320
+ );
321
+ }
322
+ return internalTermToRdfJs(term, rdfFactory, position);
323
+ }
324
+
325
+ function assertRdfJsQuadShape(subject, predicate, object, graph) {
326
+ if (!['NamedNode', 'BlankNode', 'Quad'].includes(subject.termType)) {
327
+ throw new TypeError(`Invalid RDF subject termType ${subject.termType}`);
328
+ }
329
+ if (predicate.termType !== 'NamedNode') {
330
+ throw new TypeError(`Invalid RDF predicate termType ${predicate.termType}`);
331
+ }
332
+ if (!['NamedNode', 'BlankNode', 'Literal', 'Quad'].includes(object.termType)) {
333
+ throw new TypeError(`Invalid RDF object termType ${object.termType}`);
334
+ }
335
+ if (!['DefaultGraph', 'NamedNode', 'BlankNode'].includes(graph.termType)) {
336
+ throw new TypeError(`Invalid RDF graph termType ${graph.termType}`);
337
+ }
338
+ }
339
+
340
+ function internalTripleToRdfJsQuadInGraph(triple, graph, factory) {
341
+ const rdfFactory = getDataFactory(factory);
342
+ const subject = internalRdf12TermToRdfJs(triple.s, rdfFactory, 'subject');
343
+ const predicate = internalTermToRdfJs(triple.p, rdfFactory, 'predicate');
344
+ const object = internalRdf12TermToRdfJs(triple.o, rdfFactory, 'object');
345
+ const graphTerm = graph || rdfFactory.defaultGraph();
346
+ assertRdfJsQuadShape(subject, predicate, object, graphTerm);
347
+ return rdfFactory.quad(subject, predicate, object, graphTerm);
348
+ }
349
+
305
350
  function internalTripleToRdfJsQuad(triple, factory) {
306
351
  const rdfFactory = getDataFactory(factory);
307
352
  return rdfFactory.quad(
@@ -788,6 +833,7 @@ module.exports = {
788
833
  rdfJsQuadToInternalTriple,
789
834
  internalTermToRdfJs,
790
835
  internalTripleToRdfJsQuad,
836
+ internalTripleToRdfJsQuadInGraph,
791
837
  normalizeParsedReasonerInputSync,
792
838
  normalizeReasonerInputSync,
793
839
  normalizeReasonerInputAsync,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.27.3",
3
+ "version": "1.27.5",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
@@ -52,7 +52,15 @@
52
52
  "posttest": "npm run test:package",
53
53
  "preversion": "npm test",
54
54
  "postversion": "git push origin HEAD --follow-tags",
55
- "test:stream-messages": "node test/stream_messages.test.js"
55
+ "test:stream-messages": "node test/stream_messages.test.js",
56
+ "rdf12": "npm run rdf-12-turtle && npm run rdf-12-ntriples && npm run rdf-12-nquads && npm run rdf-12-trig",
57
+ "rdf-12-ntriples": "rdf-test-suite spec/rdf12-parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-triples/syntax/manifest.ttl -i '{ \"format\": \"n-triples\" }' -c .rdf-test-suite-cache/",
58
+ "rdf-12-nquads": "rdf-test-suite spec/rdf12-parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-n-quads/syntax/manifest.ttl -i '{ \"format\": \"n-quads\" }' -c .rdf-test-suite-cache/",
59
+ "rdf-12-turtle": "rdf-test-suite spec/rdf12-parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-turtle/syntax/manifest.ttl -i '{ \"format\": \"turtle\" }' -c .rdf-test-suite-cache/",
60
+ "rdf-12-trig": "rdf-test-suite spec/rdf12-parser.js https://w3c.github.io/rdf-tests/rdf/rdf12/rdf-trig/syntax/manifest.ttl -i '{ \"format\": \"trig\" }' -c .rdf-test-suite-cache/"
61
+ },
62
+ "devDependencies": {
63
+ "rdf-test-suite": "^2.1.4"
56
64
  },
57
65
  "exports": {
58
66
  ".": {
@@ -96,6 +96,15 @@ function startStaticServer(rootDir) {
96
96
  return;
97
97
  }
98
98
 
99
+ if (pathname === '/redirect/playground-stream-messages.txt') {
100
+ res.writeHead(302, {
101
+ Location: '/test/fixtures/playground-stream-messages.txt',
102
+ 'Cache-Control': 'no-store',
103
+ });
104
+ res.end();
105
+ return;
106
+ }
107
+
99
108
  if (pathname === '/' || pathname === '') pathname = '/playground.html';
100
109
  // Prevent directory traversal.
101
110
  let fsPath = path.resolve(rootDir, '.' + pathname);
@@ -1132,7 +1141,7 @@ ${JSON.stringify(last, null, 2)}`);
1132
1141
  assert.equal(logQueryTurtle.sourceHidden, false, 'Expected Turtle log:query output to show source directly');
1133
1142
  endTest();
1134
1143
 
1135
- beginTest('playground streams RDF Messages from a URL while keeping rules in the editor');
1144
+ beginTest('playground follows redirects while streaming RDF Messages from a URL');
1136
1145
  const streamMessageRules = `@prefix : <urn:test#> .
1137
1146
  @prefix eymsg: <https://eyereasoner.github.io/eyeling/vocab/message#> .
1138
1147
  @prefix log: <http://www.w3.org/2000/10/swap/log#> .
@@ -1146,7 +1155,7 @@ ${JSON.stringify(last, null, 2)}`);
1146
1155
  }.
1147
1156
  `;
1148
1157
  await setProgram(streamMessageRules);
1149
- await setStreamMessageUrlMode(started.baseUrl + '/test/fixtures/playground-stream-messages.txt');
1158
+ await setStreamMessageUrlMode(started.baseUrl + '/redirect/playground-stream-messages.txt');
1150
1159
  await clickRun();
1151
1160
  const streamedMessages = await waitForState(
1152
1161
  'URL-backed RDF Message stream completion',