eyeling 1.6.33 → 1.7.1
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/README.md +3 -1
- package/examples/alignment-demo.n3 +1 -1
- package/examples/bind.n3 +7 -0
- package/examples/bind.srl +12 -0
- package/examples/family.n3 +10 -0
- package/examples/family.srl +12 -0
- package/examples/filter.n3 +8 -0
- package/examples/filter.srl +9 -0
- package/examples/json-pointer.n3 +1 -1
- package/examples/json-reconcile-vat.n3 +1 -1
- package/examples/log-collect-all-in.n3 +1 -1
- package/examples/log-for-all-in.n3 +1 -1
- package/examples/log-not-includes.n3 +1 -1
- package/examples/log-skolem.n3 +1 -1
- package/examples/log-uri.n3 +1 -1
- package/examples/minimal-skos-alignment.n3 +1 -1
- package/examples/output/alignment-demo.n3 +1 -1
- package/examples/output/bind.n3 +3 -0
- package/examples/output/family.n3 +13 -0
- package/examples/output/filter.n3 +3 -0
- package/examples/output/json-pointer.n3 +1 -1
- package/examples/output/json-reconcile-vat.n3 +1 -1
- package/examples/output/log-collect-all-in.n3 +1 -1
- package/examples/output/log-for-all-in.n3 +1 -1
- package/examples/output/log-not-includes.n3 +1 -1
- package/examples/output/log-skolem.n3 +2 -2
- package/examples/output/log-uri.n3 +1 -1
- package/examples/output/minimal-skos-alignment.n3 +1 -1
- package/examples/output/snaf.n3 +1 -1
- package/examples/output/traffic-skos-aggregate.n3 +16 -16
- package/examples/snaf.n3 +1 -1
- package/examples/snaf.srl +6 -0
- package/examples/traffic-skos-aggregate.n3 +1 -1
- package/eyeling.js +146 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,13 +99,15 @@ npm run test:packlist
|
|
|
99
99
|
### Usage
|
|
100
100
|
|
|
101
101
|
```
|
|
102
|
-
Usage: eyeling
|
|
102
|
+
Usage: eyeling [options] <file.n3>
|
|
103
103
|
|
|
104
104
|
Options:
|
|
105
105
|
-h, --help Show this help and exit.
|
|
106
106
|
-v, --version Print version and exit.
|
|
107
107
|
-p, --proof-comments Enable proof explanations.
|
|
108
108
|
-n, --no-proof-comments Disable proof explanations (default).
|
|
109
|
+
-s, --super-restricted Disable all builtins except => and <=.
|
|
110
|
+
-a, --ast Print parsed AST as JSON and exit.
|
|
109
111
|
```
|
|
110
112
|
|
|
111
113
|
By default, `eyeling`:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# alignment-demo.n3
|
|
2
2
|
# Minimal alignment example (SKOS mappings + inferred roll-up to a reference concept)
|
|
3
3
|
|
|
4
|
-
@prefix ex: <http://example.org
|
|
4
|
+
@prefix ex: <http://example.org/#> .
|
|
5
5
|
@prefix ref: <http://example.org/taxonomy/ref/> .
|
|
6
6
|
@prefix tel: <http://example.org/taxonomy/telraam/> .
|
|
7
7
|
@prefix anpr: <http://example.org/taxonomy/anpr/> .
|
package/examples/bind.n3
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
3
|
+
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
4
|
+
|
|
5
|
+
:phayes a :Person ; :givenName "Pat"; :familyName "Hayes" .
|
|
6
|
+
|
|
7
|
+
{ ?x a :Person . ?SCOPE log:notIncludes { ?x :name ?someName . } . ?x :givenName ?name1 . ?x :familyName ?name2 . (?name1 " " ?name2) string:concatenation ?FN . } => { ?x :name ?FN } .
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA { :phayes a :Person ; :givenName "Pat"; :familyName "Hayes" . }
|
|
4
|
+
|
|
5
|
+
# Default value - calculate a name
|
|
6
|
+
RULE { ?x :name ?FN } WHERE {
|
|
7
|
+
?x a :Person
|
|
8
|
+
NOT { ?x :name ?someName }
|
|
9
|
+
?x :givenName ?name1 ;
|
|
10
|
+
:familyName ?name2 .
|
|
11
|
+
BIND(concat(?name1, " ", ?name2) AS ?FN)
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@prefix : <http://example.org/#> .
|
|
2
|
+
|
|
3
|
+
:A :fatherOf :X . :B :motherOf :X . :C :motherOf :A .
|
|
4
|
+
|
|
5
|
+
{ ?y :fatherOf ?x . } => { ?x :childOf ?y } .
|
|
6
|
+
{ ?y :motherOf ?x . } => { ?x :childOf ?y } .
|
|
7
|
+
{ ?x :childOf ?y . } => { ?x :descendedFrom ?y } .
|
|
8
|
+
{ ?x :childOf ?z . ?z :childOf ?y . } => { ?x :descendedFrom ?y } .
|
|
9
|
+
{ ?y :descendedFrom ?x . } => { ?x :ancestorOf ?y } .
|
|
10
|
+
{ ?a :ancestorOf ?c . ?c :ancestorOf ?b . } => { ?a :ancestorOf ?b } .
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
PREFIX : <http://example.org/#>
|
|
2
|
+
|
|
3
|
+
DATA { :A :fatherOf :X . :B :motherOf :X . :C :motherOf :A . }
|
|
4
|
+
|
|
5
|
+
RULE { ?x :childOf ?y } WHERE { ?y :fatherOf ?x }
|
|
6
|
+
RULE { ?x :childOf ?y } WHERE { ?y :motherOf ?x }
|
|
7
|
+
|
|
8
|
+
RULE { ?x :descendedFrom ?y } WHERE { ?x :childOf ?y }
|
|
9
|
+
RULE { ?x :descendedFrom ?y } WHERE { ?x :childOf ?z . ?z :childOf ?y }
|
|
10
|
+
|
|
11
|
+
RULE { ?x :ancestorOf ?y } WHERE { ?y :descendedFrom ?x }
|
|
12
|
+
RULE { ?a :ancestorOf ?b } WHERE { ?a :ancestorOf ?c . ?c :ancestorOf ?b }
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@prefix : <http://example/#> .
|
|
2
|
+
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
3
|
+
|
|
4
|
+
:x :p 1 ; :q 2 .
|
|
5
|
+
|
|
6
|
+
{ ?x :p ?v1 . ?x :q ?v2 . ?v1 math:greaterThan 0 . ?v2 math:greaterThan 0 . } => { ?x :bothPositive true . } .
|
|
7
|
+
{ ?x :p ?v1 . ?x :q ?v2 . ?v1 math:equalTo 0 . } => { ?x :oneIsZero true . } .
|
|
8
|
+
{ ?x :p ?v1 . ?x :q ?v2 . ?v2 math:equalTo 0 . } => { ?x :oneIsZero true . } .
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
PREFIX : <http://example/#>
|
|
2
|
+
|
|
3
|
+
DATA { :x :p 1 ; :q 2 . }
|
|
4
|
+
|
|
5
|
+
RULE { ?x :bothPositive true . }
|
|
6
|
+
WHERE { ?x :p ?v1 FILTER ( ?v1 > 0 ) ?x :q ?v2 FILTER ( ?v2 > 0 ) }
|
|
7
|
+
|
|
8
|
+
RULE { ?x :oneIsZero true . }
|
|
9
|
+
WHERE { ?x :p ?v1 ; :q ?v2 FILTER ( ( ?v1 = 0 ) || ( ?v2 = 0 ) ) }
|
package/examples/json-pointer.n3
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
@prefix string: <http://www.w3.org/2000/10/swap/string#> .
|
|
8
8
|
@prefix list: <http://www.w3.org/2000/10/swap/list#> .
|
|
9
9
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
10
|
-
@prefix ex: <http://example.org
|
|
10
|
+
@prefix ex: <http://example.org/#> .
|
|
11
11
|
|
|
12
12
|
ex:doc ex:json """{
|
|
13
13
|
"users": [
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
|
10
10
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
11
11
|
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
12
|
-
@prefix ex: <http://example.org
|
|
12
|
+
@prefix ex: <http://example.org/#> .
|
|
13
13
|
|
|
14
14
|
# ------------------------------------------------------------------
|
|
15
15
|
# Master data (canonical N3 graph)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# log:notIncludes example
|
|
3
3
|
# =======================
|
|
4
4
|
|
|
5
|
-
@prefix : <http://example.org
|
|
5
|
+
@prefix : <http://example.org/#> .
|
|
6
6
|
@prefix log: <http://www.w3.org/2000/10/swap/log#> .
|
|
7
7
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
8
8
|
@prefix math: <http://www.w3.org/2000/10/swap/math#> .
|
package/examples/log-skolem.n3
CHANGED
package/examples/log-uri.n3
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# eyeling minimal-skos-alignment.n3
|
|
15
15
|
# ------------------------------------------------------------
|
|
16
16
|
|
|
17
|
-
@prefix ex: <http://example.org
|
|
17
|
+
@prefix ex: <http://example.org/#> .
|
|
18
18
|
@prefix ref: <http://example.org/ref/> .
|
|
19
19
|
@prefix anpr: <http://example.org/anpr/> .
|
|
20
20
|
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@prefix anpr: <http://example.org/taxonomy/anpr/> .
|
|
2
|
-
@prefix ex: <http://example.org
|
|
2
|
+
@prefix ex: <http://example.org/#> .
|
|
3
3
|
@prefix ref: <http://example.org/taxonomy/ref/> .
|
|
4
4
|
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
5
5
|
@prefix tel: <http://example.org/taxonomy/telraam/> .
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@prefix : <http://example.org/#> .
|
|
2
|
+
|
|
3
|
+
:X :childOf :A .
|
|
4
|
+
:X :childOf :B .
|
|
5
|
+
:A :childOf :C .
|
|
6
|
+
:X :descendedFrom :A .
|
|
7
|
+
:X :descendedFrom :B .
|
|
8
|
+
:A :descendedFrom :C .
|
|
9
|
+
:X :descendedFrom :C .
|
|
10
|
+
:A :ancestorOf :X .
|
|
11
|
+
:B :ancestorOf :X .
|
|
12
|
+
:C :ancestorOf :A .
|
|
13
|
+
:C :ancestorOf :X .
|
package/examples/output/snaf.n3
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
@prefix anpr: <http://example.org/taxonomy/anpr/> .
|
|
2
|
-
@prefix ex: <http://example.org
|
|
2
|
+
@prefix ex: <http://example.org/#> .
|
|
3
3
|
@prefix genid: <https://eyereasoner.github.io/.well-known/genid/> .
|
|
4
4
|
@prefix prov: <http://www.w3.org/ns/prov#> .
|
|
5
5
|
@prefix ref: <http://example.org/taxonomy/ref/> .
|
|
@@ -130,18 +130,18 @@ anpr:PassengerCar ex:narrowerOrEqualOf ref:RoadUser .
|
|
|
130
130
|
anpr:Van ex:narrowerOrEqualOf ref:RoadUser .
|
|
131
131
|
anpr:Truck ex:narrowerOrEqualOf ref:RoadUser .
|
|
132
132
|
anpr:Bus ex:narrowerOrEqualOf ref:RoadUser .
|
|
133
|
-
genid:
|
|
134
|
-
genid:
|
|
135
|
-
genid:
|
|
136
|
-
genid:
|
|
137
|
-
genid:
|
|
138
|
-
genid:
|
|
139
|
-
genid:
|
|
140
|
-
genid:
|
|
141
|
-
genid:
|
|
142
|
-
genid:
|
|
143
|
-
genid:
|
|
144
|
-
genid:
|
|
145
|
-
genid:
|
|
146
|
-
genid:
|
|
147
|
-
genid:
|
|
133
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 a sosa:Observation .
|
|
134
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 a ex:AggregatedObservation .
|
|
135
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 sosa:observedProperty ref:Car .
|
|
136
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 sosa:hasFeatureOfInterest ex:segment1 .
|
|
137
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 sosa:resultTime "2025-12-23T08:00:00Z"^^xsd:dateTime .
|
|
138
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 sosa:hasSimpleResult 22 .
|
|
139
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 ex:basedOnRequest ex:request_cars_at_t1 .
|
|
140
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 ex:contributingValues (10 2 8 1 1 0) .
|
|
141
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 ex:contributingObservations (ex:obs_tel_car ex:obs_tel_heavy ex:obs_anpr_passenger ex:obs_anpr_van ex:obs_anpr_truck ex:obs_anpr_bus) .
|
|
142
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 prov:wasDerivedFrom ex:obs_tel_car .
|
|
143
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 prov:wasDerivedFrom ex:obs_tel_heavy .
|
|
144
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 prov:wasDerivedFrom ex:obs_anpr_passenger .
|
|
145
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 prov:wasDerivedFrom ex:obs_anpr_van .
|
|
146
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 prov:wasDerivedFrom ex:obs_anpr_truck .
|
|
147
|
+
genid:baddfb7c-3e49-3ca8-bc4f-6de82f22dc60 prov:wasDerivedFrom ex:obs_anpr_bus .
|
package/examples/snaf.n3
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# or:
|
|
11
11
|
# node eyeling.js traffic-skos-aggregate.n3
|
|
12
12
|
|
|
13
|
-
@prefix ex: <http://example.org
|
|
13
|
+
@prefix ex: <http://example.org/#> .
|
|
14
14
|
@prefix ref: <http://example.org/taxonomy/ref/> .
|
|
15
15
|
@prefix tel: <http://example.org/taxonomy/telraam/> .
|
|
16
16
|
@prefix anpr: <http://example.org/taxonomy/anpr/> .
|
package/eyeling.js
CHANGED
|
@@ -84,6 +84,8 @@ const jsonPointerCache = new Map();
|
|
|
84
84
|
|
|
85
85
|
// Controls whether human-readable proof comments are printed.
|
|
86
86
|
let proofCommentsEnabled = false;
|
|
87
|
+
// Super restricted mode: disable *all* builtins except => / <= (log:implies / log:impliedBy)
|
|
88
|
+
let superRestrictedMode = false;
|
|
87
89
|
|
|
88
90
|
// ----------------------------------------------------------------------------
|
|
89
91
|
// Deterministic time support
|
|
@@ -313,13 +315,24 @@ class DerivedFact {
|
|
|
313
315
|
// ===========================================================================
|
|
314
316
|
|
|
315
317
|
class Token {
|
|
316
|
-
constructor(typ, value = null) {
|
|
318
|
+
constructor(typ, value = null, offset = null) {
|
|
317
319
|
this.typ = typ;
|
|
318
320
|
this.value = value;
|
|
321
|
+
// Codepoint offset in the original source (Array.from(text) index).
|
|
322
|
+
this.offset = offset;
|
|
319
323
|
}
|
|
320
324
|
toString() {
|
|
321
|
-
|
|
322
|
-
return `Token(${this.typ}
|
|
325
|
+
const loc = typeof this.offset === 'number' ? `@${this.offset}` : '';
|
|
326
|
+
if (this.value == null) return `Token(${this.typ}${loc})`;
|
|
327
|
+
return `Token(${this.typ}${loc}, ${JSON.stringify(this.value)})`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
class N3SyntaxError extends SyntaxError {
|
|
332
|
+
constructor(message, offset = null) {
|
|
333
|
+
super(message);
|
|
334
|
+
this.name = 'N3SyntaxError';
|
|
335
|
+
this.offset = offset;
|
|
323
336
|
}
|
|
324
337
|
}
|
|
325
338
|
|
|
@@ -432,12 +445,12 @@ function lex(inputText) {
|
|
|
432
445
|
// 3) Two-character operators: => and <=
|
|
433
446
|
if (c === '=') {
|
|
434
447
|
if (peek(1) === '>') {
|
|
435
|
-
tokens.push(new Token('OpImplies'));
|
|
448
|
+
tokens.push(new Token('OpImplies', null, i));
|
|
436
449
|
i += 2;
|
|
437
450
|
continue;
|
|
438
451
|
} else {
|
|
439
452
|
// N3 syntactic sugar: '=' means owl:sameAs
|
|
440
|
-
tokens.push(new Token('Equals'));
|
|
453
|
+
tokens.push(new Token('Equals', null, i));
|
|
441
454
|
i += 1;
|
|
442
455
|
continue;
|
|
443
456
|
}
|
|
@@ -445,17 +458,18 @@ function lex(inputText) {
|
|
|
445
458
|
|
|
446
459
|
if (c === '<') {
|
|
447
460
|
if (peek(1) === '=') {
|
|
448
|
-
tokens.push(new Token('OpImpliedBy'));
|
|
461
|
+
tokens.push(new Token('OpImpliedBy', null, i));
|
|
449
462
|
i += 2;
|
|
450
463
|
continue;
|
|
451
464
|
}
|
|
452
465
|
// N3 predicate inversion: "<-" (swap subject/object for this predicate)
|
|
453
466
|
if (peek(1) === '-') {
|
|
454
|
-
tokens.push(new Token('OpPredInvert'));
|
|
467
|
+
tokens.push(new Token('OpPredInvert', null, i));
|
|
455
468
|
i += 2;
|
|
456
469
|
continue;
|
|
457
470
|
}
|
|
458
471
|
// Otherwise IRIREF <...>
|
|
472
|
+
const start = i;
|
|
459
473
|
i++; // skip '<'
|
|
460
474
|
const iriChars = [];
|
|
461
475
|
while (i < n && chars[i] !== '>') {
|
|
@@ -463,27 +477,27 @@ function lex(inputText) {
|
|
|
463
477
|
i++;
|
|
464
478
|
}
|
|
465
479
|
if (i >= n || chars[i] !== '>') {
|
|
466
|
-
throw new
|
|
480
|
+
throw new N3SyntaxError('Unterminated IRI <...>', start);
|
|
467
481
|
}
|
|
468
482
|
i++; // skip '>'
|
|
469
483
|
const iri = iriChars.join('');
|
|
470
|
-
tokens.push(new Token('IriRef', iri));
|
|
484
|
+
tokens.push(new Token('IriRef', iri, start));
|
|
471
485
|
continue;
|
|
472
486
|
}
|
|
473
487
|
|
|
474
488
|
// 4) Path + datatype operators: !, ^, ^^
|
|
475
489
|
if (c === '!') {
|
|
476
|
-
tokens.push(new Token('OpPathFwd'));
|
|
490
|
+
tokens.push(new Token('OpPathFwd', null, i));
|
|
477
491
|
i += 1;
|
|
478
492
|
continue;
|
|
479
493
|
}
|
|
480
494
|
if (c === '^') {
|
|
481
495
|
if (peek(1) === '^') {
|
|
482
|
-
tokens.push(new Token('HatHat'));
|
|
496
|
+
tokens.push(new Token('HatHat', null, i));
|
|
483
497
|
i += 2;
|
|
484
498
|
continue;
|
|
485
499
|
}
|
|
486
|
-
tokens.push(new Token('OpPathRev'));
|
|
500
|
+
tokens.push(new Token('OpPathRev', null, i));
|
|
487
501
|
i += 1;
|
|
488
502
|
continue;
|
|
489
503
|
}
|
|
@@ -501,13 +515,15 @@ function lex(inputText) {
|
|
|
501
515
|
',': 'Comma',
|
|
502
516
|
'.': 'Dot',
|
|
503
517
|
};
|
|
504
|
-
tokens.push(new Token(mapping[c]));
|
|
518
|
+
tokens.push(new Token(mapping[c], null, i));
|
|
505
519
|
i++;
|
|
506
520
|
continue;
|
|
507
521
|
}
|
|
508
522
|
|
|
509
523
|
// String literal: short "..." or long """..."""
|
|
510
524
|
if (c === '"') {
|
|
525
|
+
const start = i;
|
|
526
|
+
|
|
511
527
|
// Long string literal """ ... """
|
|
512
528
|
if (peek(1) === '"' && peek(2) === '"') {
|
|
513
529
|
i += 3; // consume opening """
|
|
@@ -551,11 +567,11 @@ function lex(inputText) {
|
|
|
551
567
|
sChars.push(cc);
|
|
552
568
|
i++;
|
|
553
569
|
}
|
|
554
|
-
if (!closed) throw new
|
|
570
|
+
if (!closed) throw new N3SyntaxError('Unterminated long string literal """..."""', start);
|
|
555
571
|
const raw = '"""' + sChars.join('') + '"""';
|
|
556
572
|
const decoded = decodeN3StringEscapes(stripQuotes(raw));
|
|
557
573
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
558
|
-
tokens.push(new Token('Literal', s));
|
|
574
|
+
tokens.push(new Token('Literal', s, start));
|
|
559
575
|
continue;
|
|
560
576
|
}
|
|
561
577
|
|
|
@@ -580,12 +596,14 @@ function lex(inputText) {
|
|
|
580
596
|
const raw = '"' + sChars.join('') + '"';
|
|
581
597
|
const decoded = decodeN3StringEscapes(stripQuotes(raw));
|
|
582
598
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
583
|
-
tokens.push(new Token('Literal', s));
|
|
599
|
+
tokens.push(new Token('Literal', s, start));
|
|
584
600
|
continue;
|
|
585
601
|
}
|
|
586
602
|
|
|
587
603
|
// String literal: short '...' or long '''...'''
|
|
588
604
|
if (c === "'") {
|
|
605
|
+
const start = i;
|
|
606
|
+
|
|
589
607
|
// Long string literal ''' ... '''
|
|
590
608
|
if (peek(1) === "'" && peek(2) === "'") {
|
|
591
609
|
i += 3; // consume opening '''
|
|
@@ -629,11 +647,11 @@ function lex(inputText) {
|
|
|
629
647
|
sChars.push(cc);
|
|
630
648
|
i++;
|
|
631
649
|
}
|
|
632
|
-
if (!closed) throw new
|
|
650
|
+
if (!closed) throw new N3SyntaxError("Unterminated long string literal '''...'''", start);
|
|
633
651
|
const raw = "'''" + sChars.join('') + "'''";
|
|
634
652
|
const decoded = decodeN3StringEscapes(stripQuotes(raw));
|
|
635
653
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
636
|
-
tokens.push(new Token('Literal', s));
|
|
654
|
+
tokens.push(new Token('Literal', s, start));
|
|
637
655
|
continue;
|
|
638
656
|
}
|
|
639
657
|
|
|
@@ -658,12 +676,13 @@ function lex(inputText) {
|
|
|
658
676
|
const raw = "'" + sChars.join('') + "'";
|
|
659
677
|
const decoded = decodeN3StringEscapes(stripQuotes(raw));
|
|
660
678
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
661
|
-
tokens.push(new Token('Literal', s));
|
|
679
|
+
tokens.push(new Token('Literal', s, start));
|
|
662
680
|
continue;
|
|
663
681
|
}
|
|
664
682
|
|
|
665
683
|
// Variable ?name
|
|
666
684
|
if (c === '?') {
|
|
685
|
+
const start = i;
|
|
667
686
|
i++;
|
|
668
687
|
const nameChars = [];
|
|
669
688
|
let cc;
|
|
@@ -672,12 +691,13 @@ function lex(inputText) {
|
|
|
672
691
|
i++;
|
|
673
692
|
}
|
|
674
693
|
const name = nameChars.join('');
|
|
675
|
-
tokens.push(new Token('Var', name));
|
|
694
|
+
tokens.push(new Token('Var', name, start));
|
|
676
695
|
continue;
|
|
677
696
|
}
|
|
678
697
|
|
|
679
698
|
// Directives: @prefix, @base (and language tags after string literals)
|
|
680
699
|
if (c === '@') {
|
|
700
|
+
const start = i;
|
|
681
701
|
const prevTok = tokens.length ? tokens[tokens.length - 1] : null;
|
|
682
702
|
const prevWasQuotedLiteral =
|
|
683
703
|
prevTok && prevTok.typ === 'Literal' && typeof prevTok.value === 'string' && prevTok.value.startsWith('"');
|
|
@@ -690,7 +710,7 @@ function lex(inputText) {
|
|
|
690
710
|
const tagChars = [];
|
|
691
711
|
let cc = peek();
|
|
692
712
|
if (cc === null || !/[A-Za-z]/.test(cc)) {
|
|
693
|
-
throw new
|
|
713
|
+
throw new N3SyntaxError("Invalid language tag (expected [A-Za-z] after '@')", start);
|
|
694
714
|
}
|
|
695
715
|
while ((cc = peek()) !== null && /[A-Za-z]/.test(cc)) {
|
|
696
716
|
tagChars.push(cc);
|
|
@@ -705,11 +725,11 @@ function lex(inputText) {
|
|
|
705
725
|
i++;
|
|
706
726
|
}
|
|
707
727
|
if (!segChars.length) {
|
|
708
|
-
throw new
|
|
728
|
+
throw new N3SyntaxError("Invalid language tag (expected [A-Za-z0-9]+ after '-')", start);
|
|
709
729
|
}
|
|
710
730
|
tagChars.push(...segChars);
|
|
711
731
|
}
|
|
712
|
-
tokens.push(new Token('LangTag', tagChars.join('')));
|
|
732
|
+
tokens.push(new Token('LangTag', tagChars.join(''), start));
|
|
713
733
|
continue;
|
|
714
734
|
}
|
|
715
735
|
|
|
@@ -721,14 +741,15 @@ function lex(inputText) {
|
|
|
721
741
|
i++;
|
|
722
742
|
}
|
|
723
743
|
const word = wordChars.join('');
|
|
724
|
-
if (word === 'prefix') tokens.push(new Token('AtPrefix'));
|
|
725
|
-
else if (word === 'base') tokens.push(new Token('AtBase'));
|
|
726
|
-
else throw new
|
|
744
|
+
if (word === 'prefix') tokens.push(new Token('AtPrefix', null, start));
|
|
745
|
+
else if (word === 'base') tokens.push(new Token('AtBase', null, start));
|
|
746
|
+
else throw new N3SyntaxError(`Unknown directive @${word}`, start);
|
|
727
747
|
continue;
|
|
728
748
|
}
|
|
729
749
|
|
|
730
750
|
// 6) Numeric literal (integer or float)
|
|
731
751
|
if (/[0-9]/.test(c) || (c === '-' && peek(1) !== null && /[0-9]/.test(peek(1)))) {
|
|
752
|
+
const start = i;
|
|
732
753
|
const numChars = [c];
|
|
733
754
|
i++;
|
|
734
755
|
while (i < n) {
|
|
@@ -768,11 +789,12 @@ function lex(inputText) {
|
|
|
768
789
|
}
|
|
769
790
|
}
|
|
770
791
|
|
|
771
|
-
tokens.push(new Token('Literal', numChars.join('')));
|
|
792
|
+
tokens.push(new Token('Literal', numChars.join(''), start));
|
|
772
793
|
continue;
|
|
773
794
|
}
|
|
774
795
|
|
|
775
796
|
// 7) Identifiers / keywords / QNames
|
|
797
|
+
const start = i;
|
|
776
798
|
const wordChars = [];
|
|
777
799
|
let cc;
|
|
778
800
|
while ((cc = peek()) !== null && isNameChar(cc)) {
|
|
@@ -780,19 +802,19 @@ function lex(inputText) {
|
|
|
780
802
|
i++;
|
|
781
803
|
}
|
|
782
804
|
if (!wordChars.length) {
|
|
783
|
-
throw new
|
|
805
|
+
throw new N3SyntaxError(`Unexpected char: ${JSON.stringify(c)}`, i);
|
|
784
806
|
}
|
|
785
807
|
const word = wordChars.join('');
|
|
786
808
|
if (word === 'true' || word === 'false') {
|
|
787
|
-
tokens.push(new Token('Literal', word));
|
|
809
|
+
tokens.push(new Token('Literal', word, start));
|
|
788
810
|
} else if ([...word].every((ch) => /[0-9.\-]/.test(ch))) {
|
|
789
|
-
tokens.push(new Token('Literal', word));
|
|
811
|
+
tokens.push(new Token('Literal', word, start));
|
|
790
812
|
} else {
|
|
791
|
-
tokens.push(new Token('Ident', word));
|
|
813
|
+
tokens.push(new Token('Ident', word, start));
|
|
792
814
|
}
|
|
793
815
|
}
|
|
794
816
|
|
|
795
|
-
tokens.push(new Token('EOF'));
|
|
817
|
+
tokens.push(new Token('EOF', null, n));
|
|
796
818
|
return tokens;
|
|
797
819
|
}
|
|
798
820
|
|
|
@@ -982,10 +1004,15 @@ class Parser {
|
|
|
982
1004
|
return tok;
|
|
983
1005
|
}
|
|
984
1006
|
|
|
1007
|
+
fail(message, tok = this.peek()) {
|
|
1008
|
+
const off = tok && typeof tok.offset === 'number' ? tok.offset : null;
|
|
1009
|
+
throw new N3SyntaxError(message, off);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
985
1012
|
expectDot() {
|
|
986
1013
|
const tok = this.next();
|
|
987
1014
|
if (tok.typ !== 'Dot') {
|
|
988
|
-
|
|
1015
|
+
this.fail(`Expected '.', got ${tok.toString()}`, tok);
|
|
989
1016
|
}
|
|
990
1017
|
}
|
|
991
1018
|
|
|
@@ -1077,7 +1104,7 @@ class Parser {
|
|
|
1077
1104
|
parsePrefixDirective() {
|
|
1078
1105
|
const tok = this.next();
|
|
1079
1106
|
if (tok.typ !== 'Ident') {
|
|
1080
|
-
|
|
1107
|
+
this.fail(`Expected prefix name, got ${tok.toString()}`, tok);
|
|
1081
1108
|
}
|
|
1082
1109
|
const pref = tok.value || '';
|
|
1083
1110
|
const prefName = pref.endsWith(':') ? pref.slice(0, -1) : pref;
|
|
@@ -1097,7 +1124,7 @@ class Parser {
|
|
|
1097
1124
|
} else if (tok2.typ === 'Ident') {
|
|
1098
1125
|
iri = this.prefixes.expandQName(tok2.value || '');
|
|
1099
1126
|
} else {
|
|
1100
|
-
|
|
1127
|
+
this.fail(`Expected IRI after @prefix, got ${tok2.toString()}`, tok2);
|
|
1101
1128
|
}
|
|
1102
1129
|
this.expectDot();
|
|
1103
1130
|
this.prefixes.set(prefName, iri);
|
|
@@ -1111,7 +1138,7 @@ class Parser {
|
|
|
1111
1138
|
} else if (tok.typ === 'Ident') {
|
|
1112
1139
|
iri = tok.value || '';
|
|
1113
1140
|
} else {
|
|
1114
|
-
|
|
1141
|
+
this.fail(`Expected IRI after @base, got ${tok.toString()}`, tok);
|
|
1115
1142
|
}
|
|
1116
1143
|
this.expectDot();
|
|
1117
1144
|
this.prefixes.setBase(iri);
|
|
@@ -1121,7 +1148,7 @@ class Parser {
|
|
|
1121
1148
|
// SPARQL/Turtle-style PREFIX directive: PREFIX pfx: <iri> (no trailing '.')
|
|
1122
1149
|
const tok = this.next();
|
|
1123
1150
|
if (tok.typ !== 'Ident') {
|
|
1124
|
-
|
|
1151
|
+
this.fail(`Expected prefix name after PREFIX, got ${tok.toString()}`, tok);
|
|
1125
1152
|
}
|
|
1126
1153
|
const pref = tok.value || '';
|
|
1127
1154
|
const prefName = pref.endsWith(':') ? pref.slice(0, -1) : pref;
|
|
@@ -1133,7 +1160,7 @@ class Parser {
|
|
|
1133
1160
|
} else if (tok2.typ === 'Ident') {
|
|
1134
1161
|
iri = this.prefixes.expandQName(tok2.value || '');
|
|
1135
1162
|
} else {
|
|
1136
|
-
|
|
1163
|
+
this.fail(`Expected IRI after PREFIX, got ${tok2.toString()}`, tok2);
|
|
1137
1164
|
}
|
|
1138
1165
|
|
|
1139
1166
|
// N3/Turtle: PREFIX directives do not have a trailing '.', but accept it permissively.
|
|
@@ -1151,7 +1178,7 @@ class Parser {
|
|
|
1151
1178
|
} else if (tok.typ === 'Ident') {
|
|
1152
1179
|
iri = tok.value || '';
|
|
1153
1180
|
} else {
|
|
1154
|
-
|
|
1181
|
+
this.fail(`Expected IRI after BASE, got ${tok.toString()}`, tok);
|
|
1155
1182
|
}
|
|
1156
1183
|
|
|
1157
1184
|
// N3/Turtle: BASE directives do not have a trailing '.', but accept it permissively.
|
|
@@ -1211,7 +1238,7 @@ class Parser {
|
|
|
1211
1238
|
if (this.peek().typ === 'LangTag') {
|
|
1212
1239
|
// Only quoted string literals can carry a language tag.
|
|
1213
1240
|
if (!(s.startsWith('"') && s.endsWith('"'))) {
|
|
1214
|
-
|
|
1241
|
+
this.fail('Language tag is only allowed on quoted string literals', this.peek());
|
|
1215
1242
|
}
|
|
1216
1243
|
const langTok = this.next();
|
|
1217
1244
|
const lang = langTok.value || '';
|
|
@@ -1219,7 +1246,7 @@ class Parser {
|
|
|
1219
1246
|
|
|
1220
1247
|
// N3/Turtle: language tags and datatypes are mutually exclusive.
|
|
1221
1248
|
if (this.peek().typ === 'HatHat') {
|
|
1222
|
-
|
|
1249
|
+
this.fail('A literal cannot have both a language tag (@...) and a datatype (^^...)', this.peek());
|
|
1223
1250
|
}
|
|
1224
1251
|
}
|
|
1225
1252
|
|
|
@@ -1234,7 +1261,7 @@ class Parser {
|
|
|
1234
1261
|
if (qn.includes(':')) dtIri = this.prefixes.expandQName(qn);
|
|
1235
1262
|
else dtIri = qn;
|
|
1236
1263
|
} else {
|
|
1237
|
-
|
|
1264
|
+
this.fail(`Expected datatype after ^^, got ${dtTok.toString()}`, dtTok);
|
|
1238
1265
|
}
|
|
1239
1266
|
s = `${s}^^<${dtIri}>`;
|
|
1240
1267
|
}
|
|
@@ -1246,7 +1273,7 @@ class Parser {
|
|
|
1246
1273
|
if (typ === 'LBracket') return this.parseBlank();
|
|
1247
1274
|
if (typ === 'LBrace') return this.parseGraph();
|
|
1248
1275
|
|
|
1249
|
-
|
|
1276
|
+
this.fail(`Unexpected term token: ${tok.toString()}`, tok);
|
|
1250
1277
|
}
|
|
1251
1278
|
|
|
1252
1279
|
parseList() {
|
|
@@ -1269,12 +1296,12 @@ class Parser {
|
|
|
1269
1296
|
// IRI property list: [ id <IRI> predicateObjectList? ]
|
|
1270
1297
|
// Lets you embed descriptions of an IRI directly in object position.
|
|
1271
1298
|
if (this.peek().typ === 'Ident' && (this.peek().value || '') === 'id') {
|
|
1272
|
-
this.next(); // consume 'id'
|
|
1299
|
+
const iriTok = this.next(); // consume 'id'
|
|
1273
1300
|
const iriTerm = this.parseTerm();
|
|
1274
1301
|
|
|
1275
1302
|
// N3 note: 'id' form is not meant to be used with blank node identifiers.
|
|
1276
1303
|
if (iriTerm instanceof Blank && iriTerm.label.startsWith('_:')) {
|
|
1277
|
-
|
|
1304
|
+
this.fail("Cannot use 'id' keyword with a blank node identifier inside [...]", iriTok);
|
|
1278
1305
|
}
|
|
1279
1306
|
|
|
1280
1307
|
// Optional ';' right after the id IRI (tolerated).
|
|
@@ -1320,7 +1347,7 @@ class Parser {
|
|
|
1320
1347
|
}
|
|
1321
1348
|
|
|
1322
1349
|
if (this.peek().typ !== 'RBracket') {
|
|
1323
|
-
|
|
1350
|
+
this.fail(`Expected ']' at end of IRI property list, got ${this.peek().toString()}`);
|
|
1324
1351
|
}
|
|
1325
1352
|
this.next();
|
|
1326
1353
|
return iriTerm;
|
|
@@ -1368,7 +1395,7 @@ class Parser {
|
|
|
1368
1395
|
if (this.peek().typ === 'RBracket') {
|
|
1369
1396
|
this.next();
|
|
1370
1397
|
} else {
|
|
1371
|
-
|
|
1398
|
+
this.fail(`Expected ']' at end of blank node property list, got ${this.peek().toString()}`);
|
|
1372
1399
|
}
|
|
1373
1400
|
|
|
1374
1401
|
return new Blank(id);
|
|
@@ -1387,7 +1414,7 @@ class Parser {
|
|
|
1387
1414
|
else if (this.peek().typ === 'RBrace') {
|
|
1388
1415
|
// ok
|
|
1389
1416
|
} else {
|
|
1390
|
-
|
|
1417
|
+
this.fail(`Expected '.' or '}', got ${this.peek().toString()}`);
|
|
1391
1418
|
}
|
|
1392
1419
|
} else if (this.peek().typ === 'OpImpliedBy') {
|
|
1393
1420
|
this.next();
|
|
@@ -1398,7 +1425,7 @@ class Parser {
|
|
|
1398
1425
|
else if (this.peek().typ === 'RBrace') {
|
|
1399
1426
|
// ok
|
|
1400
1427
|
} else {
|
|
1401
|
-
|
|
1428
|
+
this.fail(`Expected '.' or '}', got ${this.peek().toString()}`);
|
|
1402
1429
|
}
|
|
1403
1430
|
} else {
|
|
1404
1431
|
// N3 grammar allows: triples ::= subject predicateObjectList?
|
|
@@ -1417,7 +1444,7 @@ class Parser {
|
|
|
1417
1444
|
else if (this.peek().typ === 'RBrace') {
|
|
1418
1445
|
// ok
|
|
1419
1446
|
} else {
|
|
1420
|
-
|
|
1447
|
+
this.fail(`Expected '.' or '}', got ${this.peek().toString()}`);
|
|
1421
1448
|
}
|
|
1422
1449
|
}
|
|
1423
1450
|
}
|
|
@@ -1450,7 +1477,7 @@ class Parser {
|
|
|
1450
1477
|
this.next(); // consume "is"
|
|
1451
1478
|
verb = this.parseTerm();
|
|
1452
1479
|
if (!(this.peek().typ === 'Ident' && (this.peek().value || '') === 'of')) {
|
|
1453
|
-
|
|
1480
|
+
this.fail(`Expected 'of' after 'is <expr>', got ${this.peek().toString()}`);
|
|
1454
1481
|
}
|
|
1455
1482
|
this.next(); // consume "of"
|
|
1456
1483
|
invert = true;
|
|
@@ -3699,6 +3726,13 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3699
3726
|
const pv = iriValue(g.p);
|
|
3700
3727
|
if (pv === null) return null;
|
|
3701
3728
|
|
|
3729
|
+
// Super restricted mode: disable *all* builtins except => / <= (log:implies / log:impliedBy)
|
|
3730
|
+
if (superRestrictedMode) {
|
|
3731
|
+
const allow1 = LOG_NS + 'implies';
|
|
3732
|
+
const allow2 = LOG_NS + 'impliedBy';
|
|
3733
|
+
if (pv !== allow1 && pv !== allow2) return [];
|
|
3734
|
+
}
|
|
3735
|
+
|
|
3702
3736
|
// -----------------------------------------------------------------
|
|
3703
3737
|
// 4.1 crypto: builtins
|
|
3704
3738
|
// -----------------------------------------------------------------
|
|
@@ -5118,6 +5152,13 @@ function isBuiltinPred(p) {
|
|
|
5118
5152
|
if (!(p instanceof Iri)) return false;
|
|
5119
5153
|
const v = p.value;
|
|
5120
5154
|
|
|
5155
|
+
// Super restricted mode: only treat => / <= as builtins.
|
|
5156
|
+
// Everything else should be handled as ordinary predicates (and thus must be
|
|
5157
|
+
// provided explicitly as facts/rules, without builtin evaluation).
|
|
5158
|
+
if (superRestrictedMode) {
|
|
5159
|
+
return v === LOG_NS + 'implies' || v === LOG_NS + 'impliedBy';
|
|
5160
|
+
}
|
|
5161
|
+
|
|
5121
5162
|
// Treat RDF Collections as list-term builtins too.
|
|
5122
5163
|
if (v === RDF_NS + 'first' || v === RDF_NS + 'rest') {
|
|
5123
5164
|
return true;
|
|
@@ -5778,6 +5819,41 @@ function printExplanation(df, prefixes) {
|
|
|
5778
5819
|
console.log('# ----------------------------------------------------------------------\n');
|
|
5779
5820
|
}
|
|
5780
5821
|
|
|
5822
|
+
|
|
5823
|
+
function offsetToLineCol(text, offset) {
|
|
5824
|
+
const chars = Array.from(text);
|
|
5825
|
+
const n = Math.max(0, Math.min(typeof offset === 'number' ? offset : 0, chars.length));
|
|
5826
|
+
let line = 1;
|
|
5827
|
+
let col = 1;
|
|
5828
|
+
for (let i = 0; i < n; i++) {
|
|
5829
|
+
const c = chars[i];
|
|
5830
|
+
if (c === '\n') {
|
|
5831
|
+
line++;
|
|
5832
|
+
col = 1;
|
|
5833
|
+
} else if (c === '\r') {
|
|
5834
|
+
line++;
|
|
5835
|
+
col = 1;
|
|
5836
|
+
if (i + 1 < n && chars[i + 1] === '\n') i++; // swallow \n in CRLF
|
|
5837
|
+
} else {
|
|
5838
|
+
col++;
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
return { line, col };
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
function formatN3SyntaxError(err, text, path) {
|
|
5845
|
+
const off = err && typeof err.offset === 'number' ? err.offset : null;
|
|
5846
|
+
const label = path ? String(path) : '<input>';
|
|
5847
|
+
if (off === null) {
|
|
5848
|
+
return `Syntax error in ${label}: ${err && err.message ? err.message : String(err)}`;
|
|
5849
|
+
}
|
|
5850
|
+
const { line, col } = offsetToLineCol(text, off);
|
|
5851
|
+
const lines = String(text).split(/\r\n|\n|\r/);
|
|
5852
|
+
const lineText = lines[line - 1] ?? '';
|
|
5853
|
+
const caret = ' '.repeat(Math.max(0, col - 1)) + '^';
|
|
5854
|
+
return `Syntax error in ${label}:${line}:${col}: ${err.message}\n${lineText}\n${caret}`;
|
|
5855
|
+
}
|
|
5856
|
+
|
|
5781
5857
|
// ===========================================================================
|
|
5782
5858
|
// CLI entry point
|
|
5783
5859
|
// ===========================================================================
|
|
@@ -5796,6 +5872,7 @@ function main() {
|
|
|
5796
5872
|
` -v, --version Print version and exit.\n` +
|
|
5797
5873
|
` -p, --proof-comments Enable proof explanations.\n` +
|
|
5798
5874
|
` -n, --no-proof-comments Disable proof explanations (default).\n` +
|
|
5875
|
+
` -s, --super-restricted Disable all builtins except => and <=.\n` +
|
|
5799
5876
|
` -a, --ast Print parsed AST as JSON and exit.\n`;
|
|
5800
5877
|
(toStderr ? console.error : console.log)(msg);
|
|
5801
5878
|
}
|
|
@@ -5828,6 +5905,11 @@ function main() {
|
|
|
5828
5905
|
proofCommentsEnabled = false;
|
|
5829
5906
|
}
|
|
5830
5907
|
|
|
5908
|
+
// --super-restricted / -s: disable all builtins except => / <=
|
|
5909
|
+
if (argv.includes('--super-restricted') || argv.includes('-s')) {
|
|
5910
|
+
superRestrictedMode = true;
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5831
5913
|
// --------------------------------------------------------------------------
|
|
5832
5914
|
// Positional args (the N3 file)
|
|
5833
5915
|
// --------------------------------------------------------------------------
|
|
@@ -5853,9 +5935,19 @@ function main() {
|
|
|
5853
5935
|
process.exit(1);
|
|
5854
5936
|
}
|
|
5855
5937
|
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5938
|
+
let toks;
|
|
5939
|
+
let prefixes, triples, frules, brules;
|
|
5940
|
+
try {
|
|
5941
|
+
toks = lex(text);
|
|
5942
|
+
const parser = new Parser(toks);
|
|
5943
|
+
[prefixes, triples, frules, brules] = parser.parseDocument();
|
|
5944
|
+
} catch (e) {
|
|
5945
|
+
if (e && e.name === 'N3SyntaxError') {
|
|
5946
|
+
console.error(formatN3SyntaxError(e, text, path));
|
|
5947
|
+
process.exit(1);
|
|
5948
|
+
}
|
|
5949
|
+
throw e;
|
|
5950
|
+
}
|
|
5859
5951
|
if (showAst) {
|
|
5860
5952
|
function astReplacer(_key, value) {
|
|
5861
5953
|
if (value instanceof Set) return Array.from(value);
|