eyeling 1.5.26 → 1.5.28

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/eyeling.js CHANGED
@@ -235,6 +235,12 @@ function lex(inputText) {
235
235
  i += 2;
236
236
  continue;
237
237
  }
238
+ // N3 predicate inversion: "<-" (swap subject/object for this predicate)
239
+ if (peek(1) === "-") {
240
+ tokens.push(new Token("OpPredInvert"));
241
+ i += 2;
242
+ continue;
243
+ }
238
244
  // Otherwise IRIREF <...>
239
245
  i++; // skip '<'
240
246
  const iriChars = [];
@@ -280,8 +286,41 @@ function lex(inputText) {
280
286
  continue;
281
287
  }
282
288
 
283
- // String literal
289
+ // String literal: short "..." or long """..."""
284
290
  if (c === '"') {
291
+ // Long string literal """ ... """
292
+ if (peek(1) === '"' && peek(2) === '"') {
293
+ i += 3; // consume opening """
294
+ const sChars = [];
295
+ let closed = false;
296
+ while (i < n) {
297
+ // closing delimiter?
298
+ if (peek() === '"' && peek(1) === '"' && peek(2) === '"') {
299
+ i += 3; // consume closing """
300
+ closed = true;
301
+ break;
302
+ }
303
+ let cc = chars[i];
304
+ i++;
305
+ if (cc === "\\") {
306
+ // Preserve escapes verbatim (same behavior as short strings)
307
+ if (i < n) {
308
+ const esc = chars[i];
309
+ i++;
310
+ sChars.push("\\");
311
+ sChars.push(esc);
312
+ }
313
+ continue;
314
+ }
315
+ sChars.push(cc);
316
+ }
317
+ if (!closed) throw new Error('Unterminated long string literal """..."""');
318
+ const s = '"""' + sChars.join("") + '"""';
319
+ tokens.push(new Token("Literal", s));
320
+ continue;
321
+ }
322
+
323
+ // Short string literal " ... "
285
324
  i++; // consume opening "
286
325
  const sChars = [];
287
326
  while (i < n) {
@@ -790,9 +829,14 @@ class Parser {
790
829
  while (true) {
791
830
  // Verb (can also be 'a')
792
831
  let pred;
832
+ let invert = false;
793
833
  if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
794
834
  this.next();
795
835
  pred = new Iri(RDF_NS + "type");
836
+ } else if (this.peek().typ === "OpPredInvert") {
837
+ this.next(); // consume "<-"
838
+ pred = this.parseTerm();
839
+ invert = true;
796
840
  } else {
797
841
  pred = this.parseTerm();
798
842
  }
@@ -805,7 +849,8 @@ class Parser {
805
849
  }
806
850
 
807
851
  for (const o of objs) {
808
- this.pendingTriples.push(new Triple(subj, pred, o));
852
+ this.pendingTriples.push(invert ? new Triple(o, pred, subj)
853
+ : new Triple(subj, pred, o));
809
854
  }
810
855
 
811
856
  if (this.peek().typ === "Semicolon") {
@@ -873,14 +918,21 @@ class Parser {
873
918
  const out = [];
874
919
  while (true) {
875
920
  let verb;
921
+ let invert = false;
876
922
  if (this.peek().typ === "Ident" && (this.peek().value || "") === "a") {
877
923
  this.next();
878
924
  verb = new Iri(RDF_NS + "type");
925
+ } else if (this.peek().typ === "OpPredInvert") {
926
+ this.next(); // consume "<-"
927
+ verb = this.parseTerm(); // predicate expression
928
+ invert = true;
879
929
  } else {
880
930
  verb = this.parseTerm();
881
931
  }
882
932
  const objects = this.parseObjectList();
883
- for (const o of objects) out.push(new Triple(subject, verb, o));
933
+ for (const o of objects) {
934
+ out.push(new Triple(invert ? o : subject, verb, invert ? subject : o));
935
+ }
884
936
 
885
937
  if (this.peek().typ === "Semicolon") {
886
938
  this.next();
@@ -1680,6 +1732,9 @@ function literalParts(lit) {
1680
1732
  }
1681
1733
 
1682
1734
  function stripQuotes(lex) {
1735
+ if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
1736
+ return lex.slice(3, -3);
1737
+ }
1683
1738
  if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
1684
1739
  return lex.slice(1, -1);
1685
1740
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.5.26",
3
+ "version": "1.5.28",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -621,6 +621,35 @@ ${U('s')} ${U('p')} ${U('o')}. # another trailing comment
621
621
  mustOccurExactly(out, reD, 1, 'diamond subclass should not duplicate x type D');
622
622
  },
623
623
  },
624
+
625
+ {
626
+ name: '42 literals: language tags are accepted and preserved',
627
+ opt: { proofComments: false },
628
+ input: ` { ?s ${U('p')} ?o } => { ?s ${U('q')} ?o }. ${U('s')} ${U('p')} "colour"@en-GB.`,
629
+ expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+"colour"@en-GB\\s*\\.`)],
630
+ },
631
+
632
+ {
633
+ name: '43 literals: long """...""" strings are accepted (with lang tag)',
634
+ opt: { proofComments: false },
635
+ input: ` { ?s ${U('p')} ?o } => { ?s ${U('q')} ?o }. ${U('s')} ${U('p')} """Hello
636
+ world"""@en.`,
637
+ expect: [new RegExp(`${EX}s>\\s+<${EX}q>\\s+(?:"""Hello[\\s\\S]*?world"""@en|"Hello\\\\nworld"@en)\\s*\\.`)],
638
+ },
639
+
640
+ { name: '44 syntax: "<-" in predicate position swaps subject and object',
641
+ opt: { proofComments: false },
642
+ input: ` { ?s ${U('p')} ?o } => { ?s ${U('q')} ?o }.
643
+ ${U('a')} <-${U('p')} ${U('b')}.`,
644
+ expect: [new RegExp(`${EX}b>\\s+<${EX}q>\\s+<${EX}a>\\s*\\.`)],
645
+ },
646
+
647
+ { name: '45 syntax: "<-" works inside blank node property lists ([ ... ])',
648
+ opt: { proofComments: false },
649
+ input: ` ${U('s')} ${U('p')} [ <-${U('r')} ${U('o')} ].
650
+ { ${U('o')} ${U('r')} ?x } => { ?x ${U('q')} ${U('k')} }.`,
651
+ expect: [new RegExp(`_:b1\\s+<${EX}q>\\s+<${EX}k>\\s*\\.`)],
652
+ },
624
653
  ];
625
654
 
626
655
  let passed = 0;