eyeling 1.24.24 → 1.24.26

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/lib/parser.js CHANGED
@@ -465,6 +465,20 @@ class Parser {
465
465
  if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'a') {
466
466
  this.next();
467
467
  pred = internIri(RDF_NS + 'type');
468
+ } else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'has') {
469
+ // N3 syntactic sugar is also valid in predicate-object lists,
470
+ // including blank node property lists: [ has :p :o ] means _:b :p :o.
471
+ this.next();
472
+ pred = this.parseTerm();
473
+ } else if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'is') {
474
+ // N3 syntactic sugar: [ is :p of :s ] means :s :p _:b.
475
+ this.next();
476
+ pred = this.parseTerm();
477
+ if (!(this.peek().typ === 'Ident' && (this.peek().value || '') === 'of')) {
478
+ this.fail(`Expected 'of' after 'is <expr>', got ${this.peek().toString()}`);
479
+ }
480
+ this.next();
481
+ invert = true;
468
482
  } else if (this.peek().typ === 'OpPredInvert') {
469
483
  this.next();
470
484
  pred = this.parseTerm();
package/lib/prelude.js CHANGED
@@ -24,14 +24,142 @@ const STRING_NS = 'http://www.w3.org/2000/10/swap/string#';
24
24
  const SKOLEM_NS = 'https://eyereasoner.github.io/.well-known/genid/';
25
25
  const RDF_JSON_DT = RDF_NS + 'JSON';
26
26
 
27
+ function parseUriReferenceForResolution(uri) {
28
+ // RFC 3986 Appendix B-style component parser, with the scheme tightened to
29
+ // the RFC scheme grammar. Capturing delimiter presence matters: `?` with an
30
+ // empty query is defined, while no `?` means undefined.
31
+ const m = /^(([A-Za-z][A-Za-z0-9+.-]*):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/u.exec(String(uri));
32
+ if (!m) return null;
33
+ return {
34
+ scheme: m[2] !== undefined ? m[2] : undefined,
35
+ authority: m[4] !== undefined ? m[4] : undefined,
36
+ path: m[5] || '',
37
+ query: m[6] !== undefined ? (m[7] || '') : undefined,
38
+ fragment: m[8] !== undefined ? (m[9] || '') : undefined,
39
+ };
40
+ }
41
+
42
+ function recomposeUriReference(parts) {
43
+ let out = '';
44
+ if (parts.scheme !== undefined) out += `${parts.scheme}:`;
45
+ if (parts.authority !== undefined) out += `//${parts.authority}`;
46
+ out += parts.path || '';
47
+ if (parts.query !== undefined) out += `?${parts.query}`;
48
+ if (parts.fragment !== undefined) out += `#${parts.fragment}`;
49
+ return out;
50
+ }
51
+
52
+ function removeLastPathSegment(path) {
53
+ if (!path) return '';
54
+ const i = path.lastIndexOf('/');
55
+ if (i < 0) return '';
56
+ if (i === 0) return '';
57
+ return path.slice(0, i);
58
+ }
59
+
60
+ function removeDotSegments(path) {
61
+ // RFC 3986 section 5.2.4. This deliberately avoids WHATWG URL parsing so
62
+ // Eyeling preserves IRI spelling (for example, it does not add a trailing
63
+ // slash to `http://example.org`) while still normalizing `.` and `..` path
64
+ // segments as required by section 5.2.2.
65
+ let input = String(path || '');
66
+ let output = '';
67
+
68
+ while (input.length > 0) {
69
+ if (input.startsWith('../')) {
70
+ input = input.slice(3);
71
+ } else if (input.startsWith('./')) {
72
+ input = input.slice(2);
73
+ } else if (input.startsWith('/./')) {
74
+ input = `/${input.slice(3)}`;
75
+ } else if (input === '/.') {
76
+ input = '/';
77
+ } else if (input.startsWith('/../')) {
78
+ input = `/${input.slice(4)}`;
79
+ output = removeLastPathSegment(output);
80
+ } else if (input === '/..') {
81
+ input = '/';
82
+ output = removeLastPathSegment(output);
83
+ } else if (input === '.' || input === '..') {
84
+ input = '';
85
+ } else {
86
+ let segmentEnd;
87
+ if (input[0] === '/') {
88
+ segmentEnd = input.indexOf('/', 1);
89
+ } else {
90
+ segmentEnd = input.indexOf('/');
91
+ }
92
+
93
+ if (segmentEnd < 0) {
94
+ output += input;
95
+ input = '';
96
+ } else {
97
+ output += input.slice(0, segmentEnd);
98
+ input = input.slice(segmentEnd);
99
+ }
100
+ }
101
+ }
102
+
103
+ return output;
104
+ }
105
+
106
+ function mergePaths(base, refPath) {
107
+ if (base.authority !== undefined && base.path === '') {
108
+ return `/${refPath}`;
109
+ }
110
+ const i = base.path.lastIndexOf('/');
111
+ if (i < 0) return refPath;
112
+ return `${base.path.slice(0, i + 1)}${refPath}`;
113
+ }
114
+
27
115
  function resolveIriRef(ref, base) {
28
- if (!base) return ref;
29
- if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(ref)) return ref; // already absolute
30
- try {
31
- return new URL(ref, base).toString();
32
- } catch {
33
- return ref;
116
+ const r = parseUriReferenceForResolution(ref);
117
+ if (!r) return ref;
118
+
119
+ const baseParts = base ? parseUriReferenceForResolution(base) : null;
120
+
121
+ // Absolute references do not need a base, but RFC 3986 section 5.2.2 still
122
+ // applies remove_dot_segments(R.path) when R.scheme is defined.
123
+ if (r.scheme !== undefined) {
124
+ return recomposeUriReference({
125
+ scheme: r.scheme,
126
+ authority: r.authority,
127
+ path: removeDotSegments(r.path),
128
+ query: r.query,
129
+ fragment: r.fragment,
130
+ });
34
131
  }
132
+
133
+ // Without a usable base, preserve relative references as written.
134
+ if (!baseParts || baseParts.scheme === undefined) return ref;
135
+
136
+ const t = {
137
+ scheme: baseParts.scheme,
138
+ authority: undefined,
139
+ path: '',
140
+ query: undefined,
141
+ fragment: r.fragment,
142
+ };
143
+
144
+ if (r.authority !== undefined) {
145
+ t.authority = r.authority;
146
+ t.path = removeDotSegments(r.path);
147
+ t.query = r.query;
148
+ } else if (r.path === '') {
149
+ t.authority = baseParts.authority;
150
+ t.path = baseParts.path;
151
+ t.query = r.query !== undefined ? r.query : baseParts.query;
152
+ } else {
153
+ t.authority = baseParts.authority;
154
+ if (r.path.startsWith('/')) {
155
+ t.path = removeDotSegments(r.path);
156
+ } else {
157
+ t.path = removeDotSegments(mergePaths(baseParts, r.path));
158
+ }
159
+ t.query = r.query;
160
+ }
161
+
162
+ return recomposeUriReference(t);
35
163
  }
36
164
 
37
165
  // -----------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.24.24",
3
+ "version": "1.24.26",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -1269,6 +1269,38 @@ _:l2 rdf:rest rdf:nil.
1269
1269
  // Newer eyeling.js features
1270
1270
  // -------------------------
1271
1271
 
1272
+ {
1273
+ name: '50b regression: rdf:first/rest with variable subject binds existing N3 list term',
1274
+ opt: { proofComments: false },
1275
+ input: `@prefix : <http://example.org/> .
1276
+
1277
+ (1) <http://a.example/p> <http://a.example/o> .
1278
+
1279
+ {
1280
+ _:el1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#first> 1 .
1281
+ _:el1 <http://www.w3.org/1999/02/22-rdf-syntax-ns#rest> <http://www.w3.org/1999/02/22-rdf-syntax-ns#nil> .
1282
+ _:el1 <http://a.example/p> <http://a.example/o> .
1283
+ }
1284
+ =>
1285
+ {
1286
+ :result :has :success-literal-25.
1287
+ }.
1288
+
1289
+ {} => {
1290
+ :test :contains :success-literal-25.
1291
+ }.
1292
+
1293
+ {
1294
+ :result :has :success-literal-25.
1295
+ }
1296
+ =>
1297
+ {
1298
+ :test :is true.
1299
+ }.
1300
+ `,
1301
+ expect: [/:result\s+:has\s+:success-literal-25\s*\./, /:test\s+:is\s+true\s*\./],
1302
+ },
1303
+
1272
1304
  {
1273
1305
  name: '51 automatic output rendering: prints log:outputString values ordered by key (subject)',
1274
1306
  opt: ['-n'],
@@ -1396,6 +1428,111 @@ res:CITY_Chañaral rdfs:label "Chañaral".
1396
1428
  expect: [/:result\s+:has\s+:success-literal-24\s*\./, /:test\s+:is\s+true\s*\./],
1397
1429
  },
1398
1430
 
1431
+ {
1432
+ name: '52d regression: @base and @prefix remapping resolve relative IRIs incrementally',
1433
+ opt: { proofComments: false },
1434
+ input: `# Reference: https://www.w3.org/TR/turtle/#sec-iri-references
1435
+ @base <http://foo/bar/> .
1436
+ <a1> <b1> <c1> .
1437
+ @base <http://example.org/ns/> .
1438
+ <a2> <http://example.org/ns/b2> <c2> .
1439
+ @base <foo/> .
1440
+ <a3> <b3> <c3> .
1441
+ @prefix : <bar#> .
1442
+ :a4 :b4 :c4 .
1443
+ @prefix : <http://example.org/ns2#> .
1444
+ :a5 :b5 :c5 .
1445
+
1446
+ {
1447
+ <http://foo/bar/a1> <http://foo/bar/b1> <http://foo/bar/c1> .
1448
+ <http://example.org/ns/a2> <http://example.org/ns/b2> <http://example.org/ns/c2> .
1449
+ <http://example.org/ns/foo/a3> <http://example.org/ns/foo/b3> <http://example.org/ns/foo/c3> .
1450
+ <http://example.org/ns/foo/bar#a4> <http://example.org/ns/foo/bar#b4> <http://example.org/ns/foo/bar#c4> .
1451
+ <http://example.org/ns2#a5> <http://example.org/ns2#b5> <http://example.org/ns2#c5> .
1452
+ }
1453
+ =>
1454
+ {
1455
+ :result :has :success-literal-28.
1456
+ }.
1457
+
1458
+ {} => {
1459
+ :test :contains :success-literal-28.
1460
+ }.
1461
+
1462
+ {
1463
+ :result :has :success-literal-28.
1464
+ }
1465
+ =>
1466
+ {
1467
+ :test :is true.
1468
+ }.
1469
+ `,
1470
+ expect: [/:result\s+:has\s+:success-literal-28\s*\./, /:test\s+:is\s+true\s*\./],
1471
+ },
1472
+
1473
+ {
1474
+ name: '52e regression: is/of works in blank node property lists with list values',
1475
+ opt: { proofComments: false },
1476
+ input: `@prefix : <http://example.org/> .
1477
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
1478
+
1479
+ :thing :prop (1 2 3).
1480
+
1481
+ {
1482
+ (1 2 3) is :prop of :thing.
1483
+ [ is :prop of :thing ].
1484
+ }
1485
+ =>
1486
+ {
1487
+ :result :has :success-literal-29.
1488
+ }.
1489
+
1490
+ {} => {
1491
+ :test :contains :success-literal-29.
1492
+ }.
1493
+
1494
+ {
1495
+ :result :has :success-literal-29.
1496
+ }
1497
+ =>
1498
+ {
1499
+ :test :is true.
1500
+ }.
1501
+ `,
1502
+ expect: [/:result\s+:has\s+:success-literal-29\s*\./, /:test\s+:is\s+true\s*\./],
1503
+ },
1504
+
1505
+ {
1506
+ name: '52f regression: has works with list values in statement predicate position',
1507
+ opt: { proofComments: false },
1508
+ input: `@prefix : <http://example.org/> .
1509
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
1510
+
1511
+ :thing :prop (1 2 3).
1512
+
1513
+ {
1514
+ :thing has :prop (1 2 3).
1515
+ }
1516
+ =>
1517
+ {
1518
+ :result :has :success-literal-30.
1519
+ }.
1520
+
1521
+ {} => {
1522
+ :test :contains :success-literal-30.
1523
+ }.
1524
+
1525
+ {
1526
+ :result :has :success-literal-30.
1527
+ }
1528
+ =>
1529
+ {
1530
+ :test :is true.
1531
+ }.
1532
+ `,
1533
+ expect: [/:result\s+:has\s+:success-literal-30\s*\./, /:test\s+:is\s+true\s*\./],
1534
+ },
1535
+
1399
1536
  {
1400
1537
  name: '53 --stream: prints prefixes used in input (not just derived output) before streaming triples',
1401
1538
  opt: ['--stream', '-n'],