eyeling 1.5.38 → 1.5.39
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/examples/oslo-steps-library-scholarly.n3 +144 -183
- package/examples/oslo-steps-workflow-composition.n3 +192 -217
- package/examples/output/oslo-steps-library-scholarly.n3 +672 -392
- package/examples/output/oslo-steps-workflow-composition.n3 +96 -56
- package/eyeling.js +614 -325
- package/package.json +1 -1
package/eyeling.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 5) Print only newly derived forward facts with explanations.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
const { version } = require(
|
|
19
|
+
const { version } = require("./package.json");
|
|
20
20
|
const nodeCrypto = require("crypto");
|
|
21
21
|
|
|
22
22
|
// ============================================================================
|
|
@@ -52,9 +52,9 @@ function termToJsonText(t) {
|
|
|
52
52
|
function makeRdfJsonLiteral(jsonText) {
|
|
53
53
|
// Prefer a readable long literal when safe; fall back to short if needed.
|
|
54
54
|
if (!jsonText.includes('"""')) {
|
|
55
|
-
return new Literal('"""' + jsonText + '"""^^<' + RDF_JSON_DT +
|
|
55
|
+
return new Literal('"""' + jsonText + '"""^^<' + RDF_JSON_DT + ">");
|
|
56
56
|
}
|
|
57
|
-
return new Literal(JSON.stringify(jsonText) +
|
|
57
|
+
return new Literal(JSON.stringify(jsonText) + "^^<" + RDF_JSON_DT + ">");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// For a single reasoning run, this maps a canonical representation
|
|
@@ -90,7 +90,9 @@ function normalizeDateTimeLex(s) {
|
|
|
90
90
|
function utcIsoDateTimeStringFromEpochSeconds(sec) {
|
|
91
91
|
const ms = sec * 1000;
|
|
92
92
|
const d = new Date(ms);
|
|
93
|
-
function pad(n, w = 2) {
|
|
93
|
+
function pad(n, w = 2) {
|
|
94
|
+
return String(n).padStart(w, "0");
|
|
95
|
+
}
|
|
94
96
|
const year = d.getUTCFullYear();
|
|
95
97
|
const month = d.getUTCMonth() + 1;
|
|
96
98
|
const day = d.getUTCDate();
|
|
@@ -100,8 +102,18 @@ function utcIsoDateTimeStringFromEpochSeconds(sec) {
|
|
|
100
102
|
const ms2 = d.getUTCMilliseconds();
|
|
101
103
|
const msPart = ms2 ? "." + String(ms2).padStart(3, "0") : "";
|
|
102
104
|
return (
|
|
103
|
-
pad(year, 4) +
|
|
104
|
-
|
|
105
|
+
pad(year, 4) +
|
|
106
|
+
"-" +
|
|
107
|
+
pad(month) +
|
|
108
|
+
"-" +
|
|
109
|
+
pad(day) +
|
|
110
|
+
"T" +
|
|
111
|
+
pad(hour) +
|
|
112
|
+
":" +
|
|
113
|
+
pad(min) +
|
|
114
|
+
":" +
|
|
115
|
+
pad(s2) +
|
|
116
|
+
msPart +
|
|
105
117
|
"+00:00"
|
|
106
118
|
);
|
|
107
119
|
}
|
|
@@ -139,15 +151,19 @@ function deterministicSkolemIdFromKey(key) {
|
|
|
139
151
|
}
|
|
140
152
|
|
|
141
153
|
const hex = [h1, h2, h3, h4]
|
|
142
|
-
.map(h => h.toString(16).padStart(8, "0"))
|
|
154
|
+
.map((h) => h.toString(16).padStart(8, "0"))
|
|
143
155
|
.join(""); // 32 hex chars
|
|
144
156
|
|
|
145
157
|
// Format like a UUID: 8-4-4-4-12
|
|
146
158
|
return (
|
|
147
|
-
hex.slice(0, 8) +
|
|
148
|
-
|
|
149
|
-
hex.slice(
|
|
150
|
-
|
|
159
|
+
hex.slice(0, 8) +
|
|
160
|
+
"-" +
|
|
161
|
+
hex.slice(8, 12) +
|
|
162
|
+
"-" +
|
|
163
|
+
hex.slice(12, 16) +
|
|
164
|
+
"-" +
|
|
165
|
+
hex.slice(16, 20) +
|
|
166
|
+
"-" +
|
|
151
167
|
hex.slice(20)
|
|
152
168
|
);
|
|
153
169
|
}
|
|
@@ -220,10 +236,10 @@ class Triple {
|
|
|
220
236
|
|
|
221
237
|
class Rule {
|
|
222
238
|
constructor(premise, conclusion, isForward, isFuse, headBlankLabels) {
|
|
223
|
-
this.premise = premise;
|
|
239
|
+
this.premise = premise; // Triple[]
|
|
224
240
|
this.conclusion = conclusion; // Triple[]
|
|
225
|
-
this.isForward = isForward;
|
|
226
|
-
this.isFuse = isFuse;
|
|
241
|
+
this.isForward = isForward; // boolean
|
|
242
|
+
this.isFuse = isFuse; // boolean
|
|
227
243
|
// Set<string> of blank-node labels that occur explicitly in the rule head
|
|
228
244
|
this.headBlankLabels = headBlankLabels || new Set();
|
|
229
245
|
}
|
|
@@ -231,10 +247,10 @@ class Rule {
|
|
|
231
247
|
|
|
232
248
|
class DerivedFact {
|
|
233
249
|
constructor(fact, rule, premises, subst) {
|
|
234
|
-
this.fact = fact;
|
|
235
|
-
this.rule = rule;
|
|
236
|
-
this.premises = premises;
|
|
237
|
-
this.subst = subst;
|
|
250
|
+
this.fact = fact; // Triple
|
|
251
|
+
this.rule = rule; // Rule
|
|
252
|
+
this.premises = premises; // Triple[]
|
|
253
|
+
this.subst = subst; // { varName: Term }
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
256
|
|
|
@@ -393,7 +409,8 @@ function lex(inputText) {
|
|
|
393
409
|
}
|
|
394
410
|
sChars.push(cc);
|
|
395
411
|
}
|
|
396
|
-
if (!closed)
|
|
412
|
+
if (!closed)
|
|
413
|
+
throw new Error('Unterminated long string literal """..."""');
|
|
397
414
|
const s = '"""' + sChars.join("") + '"""';
|
|
398
415
|
tokens.push(new Token("Literal", s));
|
|
399
416
|
continue;
|
|
@@ -468,7 +485,9 @@ function lex(inputText) {
|
|
|
468
485
|
i++;
|
|
469
486
|
}
|
|
470
487
|
if (!segChars.length) {
|
|
471
|
-
throw new Error(
|
|
488
|
+
throw new Error(
|
|
489
|
+
"Invalid language tag (expected [A-Za-z0-9]+ after '-')",
|
|
490
|
+
);
|
|
472
491
|
}
|
|
473
492
|
tagChars.push(...segChars);
|
|
474
493
|
}
|
|
@@ -491,7 +510,10 @@ function lex(inputText) {
|
|
|
491
510
|
}
|
|
492
511
|
|
|
493
512
|
// 6) Numeric literal (integer or float)
|
|
494
|
-
if (
|
|
513
|
+
if (
|
|
514
|
+
/[0-9]/.test(c) ||
|
|
515
|
+
(c === "-" && peek(1) !== null && /[0-9]/.test(peek(1)))
|
|
516
|
+
) {
|
|
495
517
|
const numChars = [c];
|
|
496
518
|
i++;
|
|
497
519
|
while (i < n) {
|
|
@@ -529,7 +551,7 @@ function lex(inputText) {
|
|
|
529
551
|
const word = wordChars.join("");
|
|
530
552
|
if (word === "true" || word === "false") {
|
|
531
553
|
tokens.push(new Token("Literal", word));
|
|
532
|
-
} else if ([...word].every(ch => /[0-9.\-]/.test(ch))) {
|
|
554
|
+
} else if ([...word].every((ch) => /[0-9.\-]/.test(ch))) {
|
|
533
555
|
tokens.push(new Token("Literal", word));
|
|
534
556
|
} else {
|
|
535
557
|
tokens.push(new Token("Ident", word));
|
|
@@ -758,12 +780,18 @@ class Parser {
|
|
|
758
780
|
if (this.peek().typ === "Dot") {
|
|
759
781
|
// Allow a bare blank-node property list statement, e.g. `[ a :Statement ].`
|
|
760
782
|
const lastTok = this.toks[this.pos - 1];
|
|
761
|
-
if (
|
|
783
|
+
if (
|
|
784
|
+
this.pendingTriples.length > 0 &&
|
|
785
|
+
lastTok &&
|
|
786
|
+
lastTok.typ === "RBracket"
|
|
787
|
+
) {
|
|
762
788
|
more = this.pendingTriples;
|
|
763
789
|
this.pendingTriples = [];
|
|
764
790
|
this.next(); // consume '.'
|
|
765
791
|
} else {
|
|
766
|
-
throw new Error(
|
|
792
|
+
throw new Error(
|
|
793
|
+
`Unexpected '.' after term; missing predicate/object list`,
|
|
794
|
+
);
|
|
767
795
|
}
|
|
768
796
|
} else {
|
|
769
797
|
more = this.parsePredicateObjectList(first);
|
|
@@ -772,9 +800,17 @@ class Parser {
|
|
|
772
800
|
|
|
773
801
|
// normalize explicit log:implies / log:impliedBy at top-level
|
|
774
802
|
for (const tr of more) {
|
|
775
|
-
if (
|
|
803
|
+
if (
|
|
804
|
+
isLogImplies(tr.p) &&
|
|
805
|
+
tr.s instanceof FormulaTerm &&
|
|
806
|
+
tr.o instanceof FormulaTerm
|
|
807
|
+
) {
|
|
776
808
|
forwardRules.push(this.makeRule(tr.s, tr.o, true));
|
|
777
|
-
} else if (
|
|
809
|
+
} else if (
|
|
810
|
+
isLogImpliedBy(tr.p) &&
|
|
811
|
+
tr.s instanceof FormulaTerm &&
|
|
812
|
+
tr.o instanceof FormulaTerm
|
|
813
|
+
) {
|
|
778
814
|
backwardRules.push(this.makeRule(tr.s, tr.o, false));
|
|
779
815
|
} else {
|
|
780
816
|
triples.push(tr);
|
|
@@ -834,16 +870,14 @@ class Parser {
|
|
|
834
870
|
let t = this.parsePathItem();
|
|
835
871
|
|
|
836
872
|
while (this.peek().typ === "OpPathFwd" || this.peek().typ === "OpPathRev") {
|
|
837
|
-
const dir = this.next().typ;
|
|
873
|
+
const dir = this.next().typ; // OpPathFwd | OpPathRev
|
|
838
874
|
const pred = this.parsePathItem();
|
|
839
875
|
|
|
840
876
|
this.blankCounter += 1;
|
|
841
877
|
const bn = new Blank(`_:b${this.blankCounter}`);
|
|
842
878
|
|
|
843
879
|
this.pendingTriples.push(
|
|
844
|
-
dir === "OpPathFwd"
|
|
845
|
-
? new Triple(t, pred, bn)
|
|
846
|
-
: new Triple(bn, pred, t)
|
|
880
|
+
dir === "OpPathFwd" ? new Triple(t, pred, bn) : new Triple(bn, pred, t),
|
|
847
881
|
);
|
|
848
882
|
|
|
849
883
|
t = bn;
|
|
@@ -885,7 +919,9 @@ class Parser {
|
|
|
885
919
|
if (this.peek().typ === "LangTag") {
|
|
886
920
|
// Only quoted string literals can carry a language tag.
|
|
887
921
|
if (!(s.startsWith('"') && s.endsWith('"'))) {
|
|
888
|
-
throw new Error(
|
|
922
|
+
throw new Error(
|
|
923
|
+
"Language tag is only allowed on quoted string literals",
|
|
924
|
+
);
|
|
889
925
|
}
|
|
890
926
|
const langTok = this.next();
|
|
891
927
|
const lang = langTok.value || "";
|
|
@@ -893,7 +929,9 @@ class Parser {
|
|
|
893
929
|
|
|
894
930
|
// N3/Turtle: language tags and datatypes are mutually exclusive.
|
|
895
931
|
if (this.peek().typ === "HatHat") {
|
|
896
|
-
throw new Error(
|
|
932
|
+
throw new Error(
|
|
933
|
+
"A literal cannot have both a language tag (@...) and a datatype (^^...)",
|
|
934
|
+
);
|
|
897
935
|
}
|
|
898
936
|
}
|
|
899
937
|
|
|
@@ -908,7 +946,9 @@ class Parser {
|
|
|
908
946
|
if (qn.includes(":")) dtIri = this.prefixes.expandQName(qn);
|
|
909
947
|
else dtIri = qn;
|
|
910
948
|
} else {
|
|
911
|
-
throw new Error(
|
|
949
|
+
throw new Error(
|
|
950
|
+
`Expected datatype after ^^, got ${dtTok.toString()}`,
|
|
951
|
+
);
|
|
912
952
|
}
|
|
913
953
|
s = `${s}^^<${dtIri}>`;
|
|
914
954
|
}
|
|
@@ -968,8 +1008,9 @@ class Parser {
|
|
|
968
1008
|
}
|
|
969
1009
|
|
|
970
1010
|
for (const o of objs) {
|
|
971
|
-
this.pendingTriples.push(
|
|
972
|
-
|
|
1011
|
+
this.pendingTriples.push(
|
|
1012
|
+
invert ? new Triple(o, pred, subj) : new Triple(subj, pred, o),
|
|
1013
|
+
);
|
|
973
1014
|
}
|
|
974
1015
|
|
|
975
1016
|
if (this.peek().typ === "Semicolon") {
|
|
@@ -985,8 +1026,8 @@ class Parser {
|
|
|
985
1026
|
} else {
|
|
986
1027
|
throw new Error(
|
|
987
1028
|
`Expected ']' at end of blank node property list, got ${JSON.stringify(
|
|
988
|
-
this.peek()
|
|
989
|
-
)}
|
|
1029
|
+
this.peek(),
|
|
1030
|
+
)}`,
|
|
990
1031
|
);
|
|
991
1032
|
}
|
|
992
1033
|
|
|
@@ -1023,7 +1064,11 @@ class Parser {
|
|
|
1023
1064
|
// Allow a bare blank-node property list statement inside a formula, e.g. `{ [ a :X ]. }`
|
|
1024
1065
|
if (this.peek().typ === "Dot" || this.peek().typ === "RBrace") {
|
|
1025
1066
|
const lastTok = this.toks[this.pos - 1];
|
|
1026
|
-
if (
|
|
1067
|
+
if (
|
|
1068
|
+
this.pendingTriples.length > 0 &&
|
|
1069
|
+
lastTok &&
|
|
1070
|
+
lastTok.typ === "RBracket"
|
|
1071
|
+
) {
|
|
1027
1072
|
triples.push(...this.pendingTriples);
|
|
1028
1073
|
this.pendingTriples = [];
|
|
1029
1074
|
if (this.peek().typ === "Dot") this.next();
|
|
@@ -1143,7 +1188,9 @@ class Parser {
|
|
|
1143
1188
|
const [premise0, conclusion] = liftBlankRuleVars(rawPremise, rawConclusion);
|
|
1144
1189
|
|
|
1145
1190
|
// Reorder constraints for *forward* rules.
|
|
1146
|
-
const premise = isForward
|
|
1191
|
+
const premise = isForward
|
|
1192
|
+
? reorderPremiseForConstraints(premise0)
|
|
1193
|
+
: premise0;
|
|
1147
1194
|
|
|
1148
1195
|
return new Rule(premise, conclusion, isForward, isFuse, headBlankLabels);
|
|
1149
1196
|
}
|
|
@@ -1164,21 +1211,22 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
1164
1211
|
return new Var(mapping[label]);
|
|
1165
1212
|
}
|
|
1166
1213
|
if (t instanceof ListTerm) {
|
|
1167
|
-
return new ListTerm(t.elems.map(e => convertTerm(e, mapping, counter)));
|
|
1214
|
+
return new ListTerm(t.elems.map((e) => convertTerm(e, mapping, counter)));
|
|
1168
1215
|
}
|
|
1169
1216
|
if (t instanceof OpenListTerm) {
|
|
1170
1217
|
return new OpenListTerm(
|
|
1171
|
-
t.prefix.map(e => convertTerm(e, mapping, counter)),
|
|
1172
|
-
t.tailVar
|
|
1218
|
+
t.prefix.map((e) => convertTerm(e, mapping, counter)),
|
|
1219
|
+
t.tailVar,
|
|
1173
1220
|
);
|
|
1174
1221
|
}
|
|
1175
1222
|
if (t instanceof FormulaTerm) {
|
|
1176
|
-
const triples = t.triples.map(
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1223
|
+
const triples = t.triples.map(
|
|
1224
|
+
(tr) =>
|
|
1225
|
+
new Triple(
|
|
1226
|
+
convertTerm(tr.s, mapping, counter),
|
|
1227
|
+
convertTerm(tr.p, mapping, counter),
|
|
1228
|
+
convertTerm(tr.o, mapping, counter),
|
|
1229
|
+
),
|
|
1182
1230
|
);
|
|
1183
1231
|
return new FormulaTerm(triples);
|
|
1184
1232
|
}
|
|
@@ -1189,13 +1237,13 @@ function liftBlankRuleVars(premise, conclusion) {
|
|
|
1189
1237
|
return new Triple(
|
|
1190
1238
|
convertTerm(tr.s, mapping, counter),
|
|
1191
1239
|
convertTerm(tr.p, mapping, counter),
|
|
1192
|
-
convertTerm(tr.o, mapping, counter)
|
|
1240
|
+
convertTerm(tr.o, mapping, counter),
|
|
1193
1241
|
);
|
|
1194
1242
|
}
|
|
1195
1243
|
|
|
1196
1244
|
const mapping = {};
|
|
1197
1245
|
const counter = [0];
|
|
1198
|
-
const newPremise = premise.map(tr => convertTriple(tr, mapping, counter));
|
|
1246
|
+
const newPremise = premise.map((tr) => convertTriple(tr, mapping, counter));
|
|
1199
1247
|
return [newPremise, conclusion];
|
|
1200
1248
|
}
|
|
1201
1249
|
|
|
@@ -1216,22 +1264,26 @@ function skolemizeTermForHeadBlanks(t, headBlankLabels, mapping, skCounter) {
|
|
|
1216
1264
|
|
|
1217
1265
|
if (t instanceof ListTerm) {
|
|
1218
1266
|
return new ListTerm(
|
|
1219
|
-
t.elems.map(e =>
|
|
1267
|
+
t.elems.map((e) =>
|
|
1268
|
+
skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter),
|
|
1269
|
+
),
|
|
1220
1270
|
);
|
|
1221
1271
|
}
|
|
1222
1272
|
|
|
1223
1273
|
if (t instanceof OpenListTerm) {
|
|
1224
1274
|
return new OpenListTerm(
|
|
1225
|
-
t.prefix.map(e =>
|
|
1226
|
-
|
|
1275
|
+
t.prefix.map((e) =>
|
|
1276
|
+
skolemizeTermForHeadBlanks(e, headBlankLabels, mapping, skCounter),
|
|
1277
|
+
),
|
|
1278
|
+
t.tailVar,
|
|
1227
1279
|
);
|
|
1228
1280
|
}
|
|
1229
1281
|
|
|
1230
1282
|
if (t instanceof FormulaTerm) {
|
|
1231
1283
|
return new FormulaTerm(
|
|
1232
|
-
t.triples.map(tr =>
|
|
1233
|
-
skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter)
|
|
1234
|
-
)
|
|
1284
|
+
t.triples.map((tr) =>
|
|
1285
|
+
skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter),
|
|
1286
|
+
),
|
|
1235
1287
|
);
|
|
1236
1288
|
}
|
|
1237
1289
|
|
|
@@ -1242,7 +1294,7 @@ function skolemizeTripleForHeadBlanks(tr, headBlankLabels, mapping, skCounter) {
|
|
|
1242
1294
|
return new Triple(
|
|
1243
1295
|
skolemizeTermForHeadBlanks(tr.s, headBlankLabels, mapping, skCounter),
|
|
1244
1296
|
skolemizeTermForHeadBlanks(tr.p, headBlankLabels, mapping, skCounter),
|
|
1245
|
-
skolemizeTermForHeadBlanks(tr.o, headBlankLabels, mapping, skCounter)
|
|
1297
|
+
skolemizeTermForHeadBlanks(tr.o, headBlankLabels, mapping, skCounter),
|
|
1246
1298
|
);
|
|
1247
1299
|
}
|
|
1248
1300
|
|
|
@@ -1280,9 +1332,7 @@ function termsEqual(a, b) {
|
|
|
1280
1332
|
}
|
|
1281
1333
|
|
|
1282
1334
|
function triplesEqual(a, b) {
|
|
1283
|
-
return (
|
|
1284
|
-
termsEqual(a.s, b.s) && termsEqual(a.p, b.p) && termsEqual(a.o, b.o)
|
|
1285
|
-
);
|
|
1335
|
+
return termsEqual(a.s, b.s) && termsEqual(a.p, b.p) && termsEqual(a.o, b.o);
|
|
1286
1336
|
}
|
|
1287
1337
|
|
|
1288
1338
|
function triplesListEqual(xs, ys) {
|
|
@@ -1322,7 +1372,8 @@ function alphaEqTermInFormula(a, b, vmap, bmap) {
|
|
|
1322
1372
|
if (a instanceof ListTerm && b instanceof ListTerm) {
|
|
1323
1373
|
if (a.elems.length !== b.elems.length) return false;
|
|
1324
1374
|
for (let i = 0; i < a.elems.length; i++) {
|
|
1325
|
-
if (!alphaEqTermInFormula(a.elems[i], b.elems[i], vmap, bmap))
|
|
1375
|
+
if (!alphaEqTermInFormula(a.elems[i], b.elems[i], vmap, bmap))
|
|
1376
|
+
return false;
|
|
1326
1377
|
}
|
|
1327
1378
|
return true;
|
|
1328
1379
|
}
|
|
@@ -1330,7 +1381,8 @@ function alphaEqTermInFormula(a, b, vmap, bmap) {
|
|
|
1330
1381
|
if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
|
|
1331
1382
|
if (a.prefix.length !== b.prefix.length) return false;
|
|
1332
1383
|
for (let i = 0; i < a.prefix.length; i++) {
|
|
1333
|
-
if (!alphaEqTermInFormula(a.prefix[i], b.prefix[i], vmap, bmap))
|
|
1384
|
+
if (!alphaEqTermInFormula(a.prefix[i], b.prefix[i], vmap, bmap))
|
|
1385
|
+
return false;
|
|
1334
1386
|
}
|
|
1335
1387
|
// tailVar is a var-name string, so treat it as renamable too
|
|
1336
1388
|
return alphaEqVarName(a.tailVar, b.tailVar, vmap);
|
|
@@ -1367,7 +1419,8 @@ function alphaEqFormulaTriples(xs, ys) {
|
|
|
1367
1419
|
if (used[j]) continue;
|
|
1368
1420
|
const y = ys[j];
|
|
1369
1421
|
// Cheap pruning when both predicates are IRIs.
|
|
1370
|
-
if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
|
|
1422
|
+
if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
|
|
1423
|
+
continue;
|
|
1371
1424
|
|
|
1372
1425
|
const v2 = { ...vmap };
|
|
1373
1426
|
const b2 = { ...bmap };
|
|
@@ -1429,7 +1482,7 @@ function alphaEqTriple(a, b) {
|
|
|
1429
1482
|
}
|
|
1430
1483
|
|
|
1431
1484
|
function hasAlphaEquiv(triples, tr) {
|
|
1432
|
-
return triples.some(t => alphaEqTriple(t, tr));
|
|
1485
|
+
return triples.some((t) => alphaEqTriple(t, tr));
|
|
1433
1486
|
}
|
|
1434
1487
|
|
|
1435
1488
|
// ============================================================================
|
|
@@ -1462,9 +1515,21 @@ function tripleFastKey(tr) {
|
|
|
1462
1515
|
function ensureFactIndexes(facts) {
|
|
1463
1516
|
if (facts.__byPred && facts.__byPO && facts.__keySet) return;
|
|
1464
1517
|
|
|
1465
|
-
Object.defineProperty(facts, "__byPred", {
|
|
1466
|
-
|
|
1467
|
-
|
|
1518
|
+
Object.defineProperty(facts, "__byPred", {
|
|
1519
|
+
value: new Map(),
|
|
1520
|
+
enumerable: false,
|
|
1521
|
+
writable: true,
|
|
1522
|
+
});
|
|
1523
|
+
Object.defineProperty(facts, "__byPO", {
|
|
1524
|
+
value: new Map(),
|
|
1525
|
+
enumerable: false,
|
|
1526
|
+
writable: true,
|
|
1527
|
+
});
|
|
1528
|
+
Object.defineProperty(facts, "__keySet", {
|
|
1529
|
+
value: new Set(),
|
|
1530
|
+
enumerable: false,
|
|
1531
|
+
writable: true,
|
|
1532
|
+
});
|
|
1468
1533
|
|
|
1469
1534
|
for (const f of facts) indexFact(facts, f);
|
|
1470
1535
|
}
|
|
@@ -1474,15 +1539,24 @@ function indexFact(facts, tr) {
|
|
|
1474
1539
|
const pk = tr.p.value;
|
|
1475
1540
|
|
|
1476
1541
|
let pb = facts.__byPred.get(pk);
|
|
1477
|
-
if (!pb) {
|
|
1542
|
+
if (!pb) {
|
|
1543
|
+
pb = [];
|
|
1544
|
+
facts.__byPred.set(pk, pb);
|
|
1545
|
+
}
|
|
1478
1546
|
pb.push(tr);
|
|
1479
1547
|
|
|
1480
1548
|
const ok = termFastKey(tr.o);
|
|
1481
1549
|
if (ok !== null) {
|
|
1482
1550
|
let po = facts.__byPO.get(pk);
|
|
1483
|
-
if (!po) {
|
|
1551
|
+
if (!po) {
|
|
1552
|
+
po = new Map();
|
|
1553
|
+
facts.__byPO.set(pk, po);
|
|
1554
|
+
}
|
|
1484
1555
|
let pob = po.get(ok);
|
|
1485
|
-
if (!pob) {
|
|
1556
|
+
if (!pob) {
|
|
1557
|
+
pob = [];
|
|
1558
|
+
po.set(ok, pob);
|
|
1559
|
+
}
|
|
1486
1560
|
pob.push(tr);
|
|
1487
1561
|
}
|
|
1488
1562
|
}
|
|
@@ -1526,12 +1600,12 @@ function hasFactIndexed(facts, tr) {
|
|
|
1526
1600
|
const po = facts.__byPO.get(pk);
|
|
1527
1601
|
if (po) {
|
|
1528
1602
|
const pob = po.get(ok) || [];
|
|
1529
|
-
return pob.some(t => alphaEqTriple(t, tr));
|
|
1603
|
+
return pob.some((t) => alphaEqTriple(t, tr));
|
|
1530
1604
|
}
|
|
1531
1605
|
}
|
|
1532
1606
|
|
|
1533
1607
|
const pb = facts.__byPred.get(pk) || [];
|
|
1534
|
-
return pb.some(t => alphaEqTriple(t, tr));
|
|
1608
|
+
return pb.some((t) => alphaEqTriple(t, tr));
|
|
1535
1609
|
}
|
|
1536
1610
|
|
|
1537
1611
|
return hasAlphaEquiv(facts, tr);
|
|
@@ -1546,8 +1620,16 @@ function pushFactIndexed(facts, tr) {
|
|
|
1546
1620
|
function ensureBackRuleIndexes(backRules) {
|
|
1547
1621
|
if (backRules.__byHeadPred && backRules.__wildHeadPred) return;
|
|
1548
1622
|
|
|
1549
|
-
Object.defineProperty(backRules, "__byHeadPred",
|
|
1550
|
-
|
|
1623
|
+
Object.defineProperty(backRules, "__byHeadPred", {
|
|
1624
|
+
value: new Map(),
|
|
1625
|
+
enumerable: false,
|
|
1626
|
+
writable: true,
|
|
1627
|
+
});
|
|
1628
|
+
Object.defineProperty(backRules, "__wildHeadPred", {
|
|
1629
|
+
value: [],
|
|
1630
|
+
enumerable: false,
|
|
1631
|
+
writable: true,
|
|
1632
|
+
});
|
|
1551
1633
|
|
|
1552
1634
|
for (const r of backRules) indexBackRule(backRules, r);
|
|
1553
1635
|
}
|
|
@@ -1558,7 +1640,10 @@ function indexBackRule(backRules, r) {
|
|
|
1558
1640
|
if (head && head.p instanceof Iri) {
|
|
1559
1641
|
const k = head.p.value;
|
|
1560
1642
|
let bucket = backRules.__byHeadPred.get(k);
|
|
1561
|
-
if (!bucket) {
|
|
1643
|
+
if (!bucket) {
|
|
1644
|
+
bucket = [];
|
|
1645
|
+
backRules.__byHeadPred.set(k, bucket);
|
|
1646
|
+
}
|
|
1562
1647
|
bucket.push(r);
|
|
1563
1648
|
} else {
|
|
1564
1649
|
backRules.__wildHeadPred.push(r);
|
|
@@ -1574,7 +1659,7 @@ function isRdfTypePred(p) {
|
|
|
1574
1659
|
}
|
|
1575
1660
|
|
|
1576
1661
|
function isOwlSameAsPred(t) {
|
|
1577
|
-
return t instanceof Iri && t.value ===
|
|
1662
|
+
return t instanceof Iri && t.value === OWL_NS + "sameAs";
|
|
1578
1663
|
}
|
|
1579
1664
|
|
|
1580
1665
|
function isLogImplies(p) {
|
|
@@ -1595,11 +1680,11 @@ function isConstraintBuiltin(tr) {
|
|
|
1595
1680
|
|
|
1596
1681
|
// math: numeric comparisons (no new bindings, just tests)
|
|
1597
1682
|
if (
|
|
1598
|
-
v === MATH_NS + "equalTo"
|
|
1599
|
-
v === MATH_NS + "greaterThan"
|
|
1600
|
-
v === MATH_NS + "lessThan"
|
|
1601
|
-
v === MATH_NS + "notEqualTo"
|
|
1602
|
-
v === MATH_NS + "notGreaterThan"
|
|
1683
|
+
v === MATH_NS + "equalTo" ||
|
|
1684
|
+
v === MATH_NS + "greaterThan" ||
|
|
1685
|
+
v === MATH_NS + "lessThan" ||
|
|
1686
|
+
v === MATH_NS + "notEqualTo" ||
|
|
1687
|
+
v === MATH_NS + "notGreaterThan" ||
|
|
1603
1688
|
v === MATH_NS + "notLessThan"
|
|
1604
1689
|
) {
|
|
1605
1690
|
return true;
|
|
@@ -1612,8 +1697,8 @@ function isConstraintBuiltin(tr) {
|
|
|
1612
1697
|
|
|
1613
1698
|
// log: tests that are purely constraints (no new bindings)
|
|
1614
1699
|
if (
|
|
1615
|
-
v === LOG_NS + "forAllIn"
|
|
1616
|
-
v === LOG_NS + "notEqualTo"
|
|
1700
|
+
v === LOG_NS + "forAllIn" ||
|
|
1701
|
+
v === LOG_NS + "notEqualTo" ||
|
|
1617
1702
|
v === LOG_NS + "notIncludes"
|
|
1618
1703
|
) {
|
|
1619
1704
|
return true;
|
|
@@ -1621,17 +1706,17 @@ function isConstraintBuiltin(tr) {
|
|
|
1621
1706
|
|
|
1622
1707
|
// string: relational / membership style tests (no bindings)
|
|
1623
1708
|
if (
|
|
1624
|
-
v === STRING_NS + "contains"
|
|
1709
|
+
v === STRING_NS + "contains" ||
|
|
1625
1710
|
v === STRING_NS + "containsIgnoringCase" ||
|
|
1626
|
-
v === STRING_NS + "endsWith"
|
|
1627
|
-
v === STRING_NS + "equalIgnoringCase"
|
|
1628
|
-
v === STRING_NS + "greaterThan"
|
|
1629
|
-
v === STRING_NS + "lessThan"
|
|
1630
|
-
v === STRING_NS + "matches"
|
|
1711
|
+
v === STRING_NS + "endsWith" ||
|
|
1712
|
+
v === STRING_NS + "equalIgnoringCase" ||
|
|
1713
|
+
v === STRING_NS + "greaterThan" ||
|
|
1714
|
+
v === STRING_NS + "lessThan" ||
|
|
1715
|
+
v === STRING_NS + "matches" ||
|
|
1631
1716
|
v === STRING_NS + "notEqualIgnoringCase" ||
|
|
1632
|
-
v === STRING_NS + "notGreaterThan"
|
|
1633
|
-
v === STRING_NS + "notLessThan"
|
|
1634
|
-
v === STRING_NS + "notMatches"
|
|
1717
|
+
v === STRING_NS + "notGreaterThan" ||
|
|
1718
|
+
v === STRING_NS + "notLessThan" ||
|
|
1719
|
+
v === STRING_NS + "notMatches" ||
|
|
1635
1720
|
v === STRING_NS + "startsWith"
|
|
1636
1721
|
) {
|
|
1637
1722
|
return true;
|
|
@@ -1665,17 +1750,15 @@ function reorderPremiseForConstraints(premise) {
|
|
|
1665
1750
|
|
|
1666
1751
|
function containsVarTerm(t, v) {
|
|
1667
1752
|
if (t instanceof Var) return t.name === v;
|
|
1668
|
-
if (t instanceof ListTerm) return t.elems.some(e => containsVarTerm(e, v));
|
|
1753
|
+
if (t instanceof ListTerm) return t.elems.some((e) => containsVarTerm(e, v));
|
|
1669
1754
|
if (t instanceof OpenListTerm)
|
|
1670
|
-
return (
|
|
1671
|
-
t.prefix.some(e => containsVarTerm(e, v)) || t.tailVar === v
|
|
1672
|
-
);
|
|
1755
|
+
return t.prefix.some((e) => containsVarTerm(e, v)) || t.tailVar === v;
|
|
1673
1756
|
if (t instanceof FormulaTerm)
|
|
1674
1757
|
return t.triples.some(
|
|
1675
|
-
tr =>
|
|
1758
|
+
(tr) =>
|
|
1676
1759
|
containsVarTerm(tr.s, v) ||
|
|
1677
1760
|
containsVarTerm(tr.p, v) ||
|
|
1678
|
-
containsVarTerm(tr.o, v)
|
|
1761
|
+
containsVarTerm(tr.o, v),
|
|
1679
1762
|
);
|
|
1680
1763
|
return false;
|
|
1681
1764
|
}
|
|
@@ -1684,8 +1767,10 @@ function isGroundTermInFormula(t) {
|
|
|
1684
1767
|
// EYE-style: variables inside formula terms are treated as local placeholders,
|
|
1685
1768
|
// so they don't make the *surrounding triple* non-ground.
|
|
1686
1769
|
if (t instanceof OpenListTerm) return false;
|
|
1687
|
-
if (t instanceof ListTerm)
|
|
1688
|
-
|
|
1770
|
+
if (t instanceof ListTerm)
|
|
1771
|
+
return t.elems.every((e) => isGroundTermInFormula(e));
|
|
1772
|
+
if (t instanceof FormulaTerm)
|
|
1773
|
+
return t.triples.every((tr) => isGroundTripleInFormula(tr));
|
|
1689
1774
|
// Iri/Literal/Blank/Var are all OK inside formulas
|
|
1690
1775
|
return true;
|
|
1691
1776
|
}
|
|
@@ -1700,9 +1785,10 @@ function isGroundTripleInFormula(tr) {
|
|
|
1700
1785
|
|
|
1701
1786
|
function isGroundTerm(t) {
|
|
1702
1787
|
if (t instanceof Var) return false;
|
|
1703
|
-
if (t instanceof ListTerm) return t.elems.every(e => isGroundTerm(e));
|
|
1788
|
+
if (t instanceof ListTerm) return t.elems.every((e) => isGroundTerm(e));
|
|
1704
1789
|
if (t instanceof OpenListTerm) return false;
|
|
1705
|
-
if (t instanceof FormulaTerm)
|
|
1790
|
+
if (t instanceof FormulaTerm)
|
|
1791
|
+
return t.triples.every((tr) => isGroundTripleInFormula(tr));
|
|
1706
1792
|
return true;
|
|
1707
1793
|
}
|
|
1708
1794
|
|
|
@@ -1715,21 +1801,17 @@ function isGroundTriple(tr) {
|
|
|
1715
1801
|
// robust to seeing vars/open lists anyway.
|
|
1716
1802
|
function skolemKeyFromTerm(t) {
|
|
1717
1803
|
function enc(u) {
|
|
1718
|
-
if (u instanceof Iri)
|
|
1719
|
-
if (u instanceof Literal)
|
|
1720
|
-
if (u instanceof Blank)
|
|
1721
|
-
if (u instanceof Var)
|
|
1804
|
+
if (u instanceof Iri) return ["I", u.value];
|
|
1805
|
+
if (u instanceof Literal) return ["L", u.value];
|
|
1806
|
+
if (u instanceof Blank) return ["B", u.label];
|
|
1807
|
+
if (u instanceof Var) return ["V", u.name];
|
|
1722
1808
|
if (u instanceof ListTerm) return ["List", u.elems.map(enc)];
|
|
1723
1809
|
if (u instanceof OpenListTerm)
|
|
1724
1810
|
return ["OpenList", u.prefix.map(enc), u.tailVar];
|
|
1725
1811
|
if (u instanceof FormulaTerm)
|
|
1726
1812
|
return [
|
|
1727
1813
|
"Formula",
|
|
1728
|
-
u.triples.map(tr => [
|
|
1729
|
-
enc(tr.s),
|
|
1730
|
-
enc(tr.p),
|
|
1731
|
-
enc(tr.o)
|
|
1732
|
-
])
|
|
1814
|
+
u.triples.map((tr) => [enc(tr.s), enc(tr.p), enc(tr.o)]),
|
|
1733
1815
|
];
|
|
1734
1816
|
return ["Other", String(u)];
|
|
1735
1817
|
}
|
|
@@ -1768,11 +1850,11 @@ function applySubstTerm(t, s) {
|
|
|
1768
1850
|
|
|
1769
1851
|
// Non-variable terms
|
|
1770
1852
|
if (t instanceof ListTerm) {
|
|
1771
|
-
return new ListTerm(t.elems.map(e => applySubstTerm(e, s)));
|
|
1853
|
+
return new ListTerm(t.elems.map((e) => applySubstTerm(e, s)));
|
|
1772
1854
|
}
|
|
1773
1855
|
|
|
1774
1856
|
if (t instanceof OpenListTerm) {
|
|
1775
|
-
const newPrefix = t.prefix.map(e => applySubstTerm(e, s));
|
|
1857
|
+
const newPrefix = t.prefix.map((e) => applySubstTerm(e, s));
|
|
1776
1858
|
const tailTerm = s[t.tailVar];
|
|
1777
1859
|
if (tailTerm !== undefined) {
|
|
1778
1860
|
const tailApplied = applySubstTerm(tailTerm, s);
|
|
@@ -1781,7 +1863,7 @@ function applySubstTerm(t, s) {
|
|
|
1781
1863
|
} else if (tailApplied instanceof OpenListTerm) {
|
|
1782
1864
|
return new OpenListTerm(
|
|
1783
1865
|
newPrefix.concat(tailApplied.prefix),
|
|
1784
|
-
tailApplied.tailVar
|
|
1866
|
+
tailApplied.tailVar,
|
|
1785
1867
|
);
|
|
1786
1868
|
} else {
|
|
1787
1869
|
return new OpenListTerm(newPrefix, t.tailVar);
|
|
@@ -1792,7 +1874,7 @@ function applySubstTerm(t, s) {
|
|
|
1792
1874
|
}
|
|
1793
1875
|
|
|
1794
1876
|
if (t instanceof FormulaTerm) {
|
|
1795
|
-
return new FormulaTerm(t.triples.map(tr => applySubstTriple(tr, s)));
|
|
1877
|
+
return new FormulaTerm(t.triples.map((tr) => applySubstTriple(tr, s)));
|
|
1796
1878
|
}
|
|
1797
1879
|
|
|
1798
1880
|
return t;
|
|
@@ -1802,7 +1884,7 @@ function applySubstTriple(tr, s) {
|
|
|
1802
1884
|
return new Triple(
|
|
1803
1885
|
applySubstTerm(tr.s, s),
|
|
1804
1886
|
applySubstTerm(tr.p, s),
|
|
1805
|
-
applySubstTerm(tr.o, s)
|
|
1887
|
+
applySubstTerm(tr.o, s),
|
|
1806
1888
|
);
|
|
1807
1889
|
}
|
|
1808
1890
|
|
|
@@ -1837,9 +1919,10 @@ function unifyFormulaTriples(xs, ys, subst) {
|
|
|
1837
1919
|
const y = ys[j];
|
|
1838
1920
|
|
|
1839
1921
|
// Cheap pruning when both predicates are IRIs.
|
|
1840
|
-
if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
|
|
1922
|
+
if (x.p instanceof Iri && y.p instanceof Iri && x.p.value !== y.p.value)
|
|
1923
|
+
continue;
|
|
1841
1924
|
|
|
1842
|
-
const s2 = unifyTriple(x, y, s);
|
|
1925
|
+
const s2 = unifyTriple(x, y, s); // IMPORTANT: use `s`, not {}
|
|
1843
1926
|
if (s2 === null) continue;
|
|
1844
1927
|
|
|
1845
1928
|
used[j] = true;
|
|
@@ -1972,7 +2055,11 @@ function literalParts(lit) {
|
|
|
1972
2055
|
// Strip LANGTAG from the lexical form when present.
|
|
1973
2056
|
if (lex.length >= 2 && lex[0] === '"') {
|
|
1974
2057
|
const lastQuote = lex.lastIndexOf('"');
|
|
1975
|
-
if (
|
|
2058
|
+
if (
|
|
2059
|
+
lastQuote > 0 &&
|
|
2060
|
+
lastQuote < lex.length - 1 &&
|
|
2061
|
+
lex[lastQuote + 1] === "@"
|
|
2062
|
+
) {
|
|
1976
2063
|
const lang = lex.slice(lastQuote + 2);
|
|
1977
2064
|
if (/^[A-Za-z]+(?:-[A-Za-z0-9]+)*$/.test(lang)) {
|
|
1978
2065
|
lex = lex.slice(0, lastQuote + 1);
|
|
@@ -2019,7 +2106,11 @@ function termToJsStringDecoded(t) {
|
|
|
2019
2106
|
|
|
2020
2107
|
// Short strings: try to decode escapes (this makes "{\"a\":1}" usable too).
|
|
2021
2108
|
if (lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') {
|
|
2022
|
-
try {
|
|
2109
|
+
try {
|
|
2110
|
+
return JSON.parse(lex);
|
|
2111
|
+
} catch (e) {
|
|
2112
|
+
/* fall through */
|
|
2113
|
+
}
|
|
2023
2114
|
return stripQuotes(lex);
|
|
2024
2115
|
}
|
|
2025
2116
|
|
|
@@ -2031,7 +2122,10 @@ function jsonPointerUnescape(seg) {
|
|
|
2031
2122
|
let out = "";
|
|
2032
2123
|
for (let i = 0; i < seg.length; i++) {
|
|
2033
2124
|
const c = seg[i];
|
|
2034
|
-
if (c !== "~") {
|
|
2125
|
+
if (c !== "~") {
|
|
2126
|
+
out += c;
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2035
2129
|
if (i + 1 >= seg.length) return null;
|
|
2036
2130
|
const n = seg[i + 1];
|
|
2037
2131
|
if (n === "0") out += "~";
|
|
@@ -2060,13 +2154,21 @@ function jsonPointerLookup(jsonText, pointer) {
|
|
|
2060
2154
|
|
|
2061
2155
|
// Support URI fragment form "#/a/b"
|
|
2062
2156
|
if (ptr.startsWith("#")) {
|
|
2063
|
-
try {
|
|
2157
|
+
try {
|
|
2158
|
+
ptr = decodeURIComponent(ptr.slice(1));
|
|
2159
|
+
} catch {
|
|
2160
|
+
return null;
|
|
2161
|
+
}
|
|
2064
2162
|
}
|
|
2065
2163
|
|
|
2066
2164
|
let entry = jsonPointerCache.get(jsonText);
|
|
2067
2165
|
if (!entry) {
|
|
2068
2166
|
let parsed = null;
|
|
2069
|
-
try {
|
|
2167
|
+
try {
|
|
2168
|
+
parsed = JSON.parse(jsonText);
|
|
2169
|
+
} catch {
|
|
2170
|
+
parsed = null;
|
|
2171
|
+
}
|
|
2070
2172
|
entry = { parsed, ptrCache: new Map() };
|
|
2071
2173
|
jsonPointerCache.set(jsonText, entry);
|
|
2072
2174
|
}
|
|
@@ -2081,20 +2183,35 @@ function jsonPointerLookup(jsonText, pointer) {
|
|
|
2081
2183
|
entry.ptrCache.set(ptr, t);
|
|
2082
2184
|
return t;
|
|
2083
2185
|
}
|
|
2084
|
-
if (!ptr.startsWith("/")) {
|
|
2186
|
+
if (!ptr.startsWith("/")) {
|
|
2187
|
+
entry.ptrCache.set(ptr, null);
|
|
2188
|
+
return null;
|
|
2189
|
+
}
|
|
2085
2190
|
|
|
2086
2191
|
const parts = ptr.split("/").slice(1);
|
|
2087
2192
|
for (const raw of parts) {
|
|
2088
2193
|
const seg = jsonPointerUnescape(raw);
|
|
2089
|
-
if (seg === null) {
|
|
2194
|
+
if (seg === null) {
|
|
2195
|
+
entry.ptrCache.set(ptr, null);
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2090
2198
|
|
|
2091
2199
|
if (Array.isArray(cur)) {
|
|
2092
|
-
if (!/^(0|[1-9]\d*)$/.test(seg)) {
|
|
2200
|
+
if (!/^(0|[1-9]\d*)$/.test(seg)) {
|
|
2201
|
+
entry.ptrCache.set(ptr, null);
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2093
2204
|
const idx = Number(seg);
|
|
2094
|
-
if (idx < 0 || idx >= cur.length) {
|
|
2205
|
+
if (idx < 0 || idx >= cur.length) {
|
|
2206
|
+
entry.ptrCache.set(ptr, null);
|
|
2207
|
+
return null;
|
|
2208
|
+
}
|
|
2095
2209
|
cur = cur[idx];
|
|
2096
2210
|
} else if (cur !== null && typeof cur === "object") {
|
|
2097
|
-
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
2211
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
2212
|
+
entry.ptrCache.set(ptr, null);
|
|
2213
|
+
return null;
|
|
2214
|
+
}
|
|
2098
2215
|
cur = cur[seg];
|
|
2099
2216
|
} else {
|
|
2100
2217
|
entry.ptrCache.set(ptr, null);
|
|
@@ -2140,33 +2257,124 @@ function simpleStringFormat(fmt, args) {
|
|
|
2140
2257
|
return out;
|
|
2141
2258
|
}
|
|
2142
2259
|
|
|
2260
|
+
// -----------------------------------------------------------------------------
|
|
2261
|
+
// Strict numeric literal parsing for math: builtins
|
|
2262
|
+
// -----------------------------------------------------------------------------
|
|
2263
|
+
const XSD_DECIMAL_DT = XSD_NS + "decimal";
|
|
2264
|
+
const XSD_DOUBLE_DT = XSD_NS + "double";
|
|
2265
|
+
const XSD_FLOAT_DT = XSD_NS + "float";
|
|
2266
|
+
const XSD_INTEGER_DT = XSD_NS + "integer";
|
|
2267
|
+
|
|
2268
|
+
// Integer-derived datatypes from XML Schema Part 2 (and commonly used ones).
|
|
2269
|
+
const XSD_INTEGER_DERIVED_DTS = new Set([
|
|
2270
|
+
XSD_INTEGER_DT,
|
|
2271
|
+
XSD_NS + "nonPositiveInteger",
|
|
2272
|
+
XSD_NS + "negativeInteger",
|
|
2273
|
+
XSD_NS + "long",
|
|
2274
|
+
XSD_NS + "int",
|
|
2275
|
+
XSD_NS + "short",
|
|
2276
|
+
XSD_NS + "byte",
|
|
2277
|
+
XSD_NS + "nonNegativeInteger",
|
|
2278
|
+
XSD_NS + "unsignedLong",
|
|
2279
|
+
XSD_NS + "unsignedInt",
|
|
2280
|
+
XSD_NS + "unsignedShort",
|
|
2281
|
+
XSD_NS + "unsignedByte",
|
|
2282
|
+
XSD_NS + "positiveInteger",
|
|
2283
|
+
]);
|
|
2284
|
+
|
|
2285
|
+
function isQuotedLexical(lex) {
|
|
2286
|
+
// Note: the lexer stores long strings with literal delimiters: """..."""
|
|
2287
|
+
return (
|
|
2288
|
+
(lex.length >= 2 && lex[0] === '"' && lex[lex.length - 1] === '"') ||
|
|
2289
|
+
(lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""'))
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
function isXsdNumericDatatype(dt) {
|
|
2294
|
+
if (dt === null) return false;
|
|
2295
|
+
return (
|
|
2296
|
+
dt === XSD_DECIMAL_DT ||
|
|
2297
|
+
dt === XSD_DOUBLE_DT ||
|
|
2298
|
+
dt === XSD_FLOAT_DT ||
|
|
2299
|
+
XSD_INTEGER_DERIVED_DTS.has(dt)
|
|
2300
|
+
);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
function isXsdIntegerDatatype(dt) {
|
|
2304
|
+
if (dt === null) return false;
|
|
2305
|
+
return XSD_INTEGER_DERIVED_DTS.has(dt);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function looksLikeUntypedNumericTokenLex(lex) {
|
|
2309
|
+
// We only treat *unquoted* tokens as "untyped numeric" (Turtle/N3 numeric literal).
|
|
2310
|
+
// Quoted literals without datatype are strings, never numbers.
|
|
2311
|
+
if (isQuotedLexical(lex)) return false;
|
|
2312
|
+
|
|
2313
|
+
// integer
|
|
2314
|
+
if (/^[+-]?\d+$/.test(lex)) return true;
|
|
2315
|
+
|
|
2316
|
+
// decimal (no exponent)
|
|
2317
|
+
if (/^[+-]?(?:\d+\.\d*|\.\d+)$/.test(lex)) return true;
|
|
2318
|
+
|
|
2319
|
+
// double (with exponent)
|
|
2320
|
+
if (/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)$/.test(lex)) return true;
|
|
2321
|
+
|
|
2322
|
+
return false;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2143
2325
|
function parseNum(t) {
|
|
2144
|
-
// Parse as JS Number
|
|
2326
|
+
// Parse as JS Number, but ONLY for xsd numeric datatypes or untyped numeric tokens.
|
|
2327
|
+
// Rejects values such as "1"^^<...non-numeric...> or "1" (a string literal).
|
|
2145
2328
|
if (!(t instanceof Literal)) return null;
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
if (
|
|
2151
|
-
|
|
2329
|
+
|
|
2330
|
+
const [lex, dt] = literalParts(t.value);
|
|
2331
|
+
|
|
2332
|
+
// Typed literals: must be xsd numeric.
|
|
2333
|
+
if (dt !== null) {
|
|
2334
|
+
if (!isXsdNumericDatatype(dt)) return null;
|
|
2335
|
+
const val = stripQuotes(lex);
|
|
2336
|
+
const n = Number(val);
|
|
2337
|
+
if (!Number.isFinite(n)) return null;
|
|
2338
|
+
return n;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// Untyped literals: accept only unquoted numeric tokens.
|
|
2342
|
+
if (!looksLikeUntypedNumericTokenLex(lex)) return null;
|
|
2343
|
+
const n = Number(lex);
|
|
2344
|
+
if (!Number.isFinite(n)) return null;
|
|
2345
|
+
return n;
|
|
2152
2346
|
}
|
|
2153
2347
|
|
|
2154
2348
|
function parseIntLiteral(t) {
|
|
2155
|
-
// Parse as BigInt if
|
|
2349
|
+
// Parse as BigInt if (and only if) it is an integer literal in an integer datatype,
|
|
2350
|
+
// or an untyped integer token.
|
|
2156
2351
|
if (!(t instanceof Literal)) return null;
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
if (
|
|
2352
|
+
|
|
2353
|
+
const [lex, dt] = literalParts(t.value);
|
|
2354
|
+
|
|
2355
|
+
if (dt !== null) {
|
|
2356
|
+
if (!isXsdIntegerDatatype(dt)) return null;
|
|
2357
|
+
const val = stripQuotes(lex);
|
|
2358
|
+
if (!/^[+-]?\d+$/.test(val)) return null;
|
|
2359
|
+
try {
|
|
2360
|
+
return BigInt(val);
|
|
2361
|
+
} catch {
|
|
2362
|
+
return null;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// Untyped: only accept unquoted integer tokens.
|
|
2367
|
+
if (isQuotedLexical(lex)) return null;
|
|
2368
|
+
if (!/^[+-]?\d+$/.test(lex)) return null;
|
|
2161
2369
|
try {
|
|
2162
|
-
return BigInt(
|
|
2163
|
-
} catch
|
|
2370
|
+
return BigInt(lex);
|
|
2371
|
+
} catch {
|
|
2164
2372
|
return null;
|
|
2165
2373
|
}
|
|
2166
2374
|
}
|
|
2167
2375
|
|
|
2168
2376
|
function parseNumberLiteral(t) {
|
|
2169
|
-
// Prefer BigInt for integers, fall back to Number for
|
|
2377
|
+
// Prefer BigInt for integers, fall back to Number for other numeric literals.
|
|
2170
2378
|
const bi = parseIntLiteral(t);
|
|
2171
2379
|
if (bi !== null) return bi;
|
|
2172
2380
|
const n = parseNum(t);
|
|
@@ -2180,28 +2388,22 @@ function formatNum(n) {
|
|
|
2180
2388
|
|
|
2181
2389
|
function parseXsdDateTerm(t) {
|
|
2182
2390
|
if (!(t instanceof Literal)) return null;
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2391
|
+
const [lex, dt] = literalParts(t.value);
|
|
2392
|
+
if (dt !== XSD_NS + "date") return null;
|
|
2185
2393
|
const val = stripQuotes(lex);
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
return d;
|
|
2190
|
-
}
|
|
2191
|
-
return null;
|
|
2394
|
+
const d = new Date(val + "T00:00:00Z");
|
|
2395
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
2396
|
+
return d;
|
|
2192
2397
|
}
|
|
2193
2398
|
|
|
2194
2399
|
function parseXsdDatetimeTerm(t) {
|
|
2195
2400
|
if (!(t instanceof Literal)) return null;
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2401
|
+
const [lex, dt] = literalParts(t.value);
|
|
2402
|
+
if (dt !== XSD_NS + "dateTime") return null;
|
|
2198
2403
|
const val = stripQuotes(lex);
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
return d; // Date in local/UTC, we only use timestamp
|
|
2203
|
-
}
|
|
2204
|
-
return null;
|
|
2404
|
+
const d = new Date(val);
|
|
2405
|
+
if (Number.isNaN(d.getTime())) return null;
|
|
2406
|
+
return d; // Date in local/UTC, we only use timestamp
|
|
2205
2407
|
}
|
|
2206
2408
|
|
|
2207
2409
|
function parseDatetimeLike(t) {
|
|
@@ -2260,24 +2462,13 @@ function parseIso8601DurationToSeconds(s) {
|
|
|
2260
2462
|
}
|
|
2261
2463
|
|
|
2262
2464
|
function parseNumericForCompareTerm(t) {
|
|
2263
|
-
//
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
} catch (e) {
|
|
2271
|
-
// fall through
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
// durations / dateTimes / floats -> Number (seconds or numeric)
|
|
2275
|
-
const nDur = parseNumOrDuration(t);
|
|
2276
|
-
if (nDur !== null) return { kind: "number", value: nDur };
|
|
2277
|
-
return null;
|
|
2278
|
-
}
|
|
2279
|
-
const n = parseNumOrDuration(t);
|
|
2280
|
-
if (n !== null) return { kind: "number", value: n };
|
|
2465
|
+
// Strict: only accept xsd numeric literals, xsd:duration, xsd:date, xsd:dateTime
|
|
2466
|
+
// (or untyped numeric tokens).
|
|
2467
|
+
const bi = parseIntLiteral(t);
|
|
2468
|
+
if (bi !== null) return { kind: "bigint", value: bi };
|
|
2469
|
+
|
|
2470
|
+
const nDur = parseNumOrDuration(t);
|
|
2471
|
+
if (nDur !== null) return { kind: "number", value: nDur };
|
|
2281
2472
|
return null;
|
|
2282
2473
|
}
|
|
2283
2474
|
|
|
@@ -2286,8 +2477,8 @@ function cmpNumericInfo(aInfo, bInfo, op) {
|
|
|
2286
2477
|
if (!aInfo || !bInfo) return false;
|
|
2287
2478
|
|
|
2288
2479
|
if (aInfo.kind === "bigint" && bInfo.kind === "bigint") {
|
|
2289
|
-
if (op === ">")
|
|
2290
|
-
if (op === "<")
|
|
2480
|
+
if (op === ">") return aInfo.value > bInfo.value;
|
|
2481
|
+
if (op === "<") return aInfo.value < bInfo.value;
|
|
2291
2482
|
if (op === ">=") return aInfo.value >= bInfo.value;
|
|
2292
2483
|
if (op === "<=") return aInfo.value <= bInfo.value;
|
|
2293
2484
|
if (op === "==") return aInfo.value == bInfo.value;
|
|
@@ -2298,8 +2489,8 @@ function cmpNumericInfo(aInfo, bInfo, op) {
|
|
|
2298
2489
|
const a = typeof aInfo.value === "bigint" ? Number(aInfo.value) : aInfo.value;
|
|
2299
2490
|
const b = typeof bInfo.value === "bigint" ? Number(bInfo.value) : bInfo.value;
|
|
2300
2491
|
|
|
2301
|
-
if (op === ">")
|
|
2302
|
-
if (op === "<")
|
|
2492
|
+
if (op === ">") return a > b;
|
|
2493
|
+
if (op === "<") return a < b;
|
|
2303
2494
|
if (op === ">=") return a >= b;
|
|
2304
2495
|
if (op === "<=") return a <= b;
|
|
2305
2496
|
if (op === "==") return a == b;
|
|
@@ -2310,22 +2501,22 @@ function cmpNumericInfo(aInfo, bInfo, op) {
|
|
|
2310
2501
|
function parseNumOrDuration(t) {
|
|
2311
2502
|
const n = parseNum(t);
|
|
2312
2503
|
if (n !== null) return n;
|
|
2504
|
+
|
|
2505
|
+
// xsd:duration
|
|
2313
2506
|
if (t instanceof Literal) {
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
if (
|
|
2318
|
-
dt === XSD_NS + "duration" ||
|
|
2319
|
-
val.startsWith("P") ||
|
|
2320
|
-
val.startsWith("-P")
|
|
2321
|
-
) {
|
|
2507
|
+
const [lex, dt] = literalParts(t.value);
|
|
2508
|
+
if (dt === XSD_NS + "duration") {
|
|
2509
|
+
const val = stripQuotes(lex);
|
|
2322
2510
|
const negative = val.startsWith("-");
|
|
2323
2511
|
const core = negative ? val.slice(1) : val;
|
|
2512
|
+
if (!core.startsWith("P")) return null;
|
|
2324
2513
|
const secs = parseIso8601DurationToSeconds(core);
|
|
2325
2514
|
if (secs === null) return null;
|
|
2326
2515
|
return negative ? -secs : secs;
|
|
2327
2516
|
}
|
|
2328
2517
|
}
|
|
2518
|
+
|
|
2519
|
+
// xsd:date / xsd:dateTime
|
|
2329
2520
|
const dtval = parseDatetimeLike(t);
|
|
2330
2521
|
if (dtval !== null) {
|
|
2331
2522
|
return dtval.getTime() / 1000.0;
|
|
@@ -2453,7 +2644,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2453
2644
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "greaterThan") {
|
|
2454
2645
|
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2455
2646
|
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2456
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">"))
|
|
2647
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">"))
|
|
2648
|
+
return [{ ...subst }];
|
|
2457
2649
|
|
|
2458
2650
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2459
2651
|
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
@@ -2467,7 +2659,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2467
2659
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "lessThan") {
|
|
2468
2660
|
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2469
2661
|
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2470
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<"))
|
|
2662
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<"))
|
|
2663
|
+
return [{ ...subst }];
|
|
2471
2664
|
|
|
2472
2665
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2473
2666
|
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
@@ -2481,7 +2674,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2481
2674
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "notLessThan") {
|
|
2482
2675
|
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2483
2676
|
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2484
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">="))
|
|
2677
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, ">="))
|
|
2678
|
+
return [{ ...subst }];
|
|
2485
2679
|
|
|
2486
2680
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2487
2681
|
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
@@ -2495,7 +2689,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2495
2689
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "notGreaterThan") {
|
|
2496
2690
|
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2497
2691
|
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2498
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<="))
|
|
2692
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "<="))
|
|
2693
|
+
return [{ ...subst }];
|
|
2499
2694
|
|
|
2500
2695
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2501
2696
|
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
@@ -2509,7 +2704,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2509
2704
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "equalTo") {
|
|
2510
2705
|
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2511
2706
|
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2512
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "=="))
|
|
2707
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "=="))
|
|
2708
|
+
return [{ ...subst }];
|
|
2513
2709
|
|
|
2514
2710
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2515
2711
|
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
@@ -2523,7 +2719,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2523
2719
|
if (g.p instanceof Iri && g.p.value === MATH_NS + "notEqualTo") {
|
|
2524
2720
|
const aInfo = parseNumericForCompareTerm(g.s);
|
|
2525
2721
|
const bInfo = parseNumericForCompareTerm(g.o);
|
|
2526
|
-
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "!="))
|
|
2722
|
+
if (aInfo && bInfo && cmpNumericInfo(aInfo, bInfo, "!="))
|
|
2723
|
+
return [{ ...subst }];
|
|
2527
2724
|
|
|
2528
2725
|
if (g.s instanceof ListTerm && g.s.elems.length === 2) {
|
|
2529
2726
|
const a2 = parseNumericForCompareTerm(g.s.elems[0]);
|
|
@@ -2545,7 +2742,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2545
2742
|
}
|
|
2546
2743
|
|
|
2547
2744
|
let lit;
|
|
2548
|
-
const allBig = values.every(v => typeof v === "bigint");
|
|
2745
|
+
const allBig = values.every((v) => typeof v === "bigint");
|
|
2549
2746
|
if (allBig) {
|
|
2550
2747
|
let total = 0n;
|
|
2551
2748
|
for (const v of values) total += v;
|
|
@@ -2581,7 +2778,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2581
2778
|
}
|
|
2582
2779
|
|
|
2583
2780
|
let lit;
|
|
2584
|
-
const allBig = values.every(v => typeof v === "bigint");
|
|
2781
|
+
const allBig = values.every((v) => typeof v === "bigint");
|
|
2585
2782
|
if (allBig) {
|
|
2586
2783
|
let prod = 1n;
|
|
2587
2784
|
for (const v of values) prod *= v;
|
|
@@ -3321,7 +3518,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3321
3518
|
else if (g.o instanceof ListTerm) inputList = g.o.elems;
|
|
3322
3519
|
else return [];
|
|
3323
3520
|
|
|
3324
|
-
if (!inputList.every(e => isGroundTerm(e))) return [];
|
|
3521
|
+
if (!inputList.every((e) => isGroundTerm(e))) return [];
|
|
3325
3522
|
|
|
3326
3523
|
const sortedList = [...inputList].sort(cmpTermForSort);
|
|
3327
3524
|
const sortedTerm = new ListTerm(sortedList);
|
|
@@ -3345,13 +3542,20 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3345
3542
|
if (!(predTerm instanceof Iri)) return [];
|
|
3346
3543
|
const pred = new Iri(predTerm.value);
|
|
3347
3544
|
if (!isBuiltinPred(pred)) return [];
|
|
3348
|
-
if (!inputList.every(e => isGroundTerm(e))) return [];
|
|
3545
|
+
if (!inputList.every((e) => isGroundTerm(e))) return [];
|
|
3349
3546
|
|
|
3350
3547
|
const results = [];
|
|
3351
3548
|
for (const el of inputList) {
|
|
3352
3549
|
const yvar = new Var("_mapY");
|
|
3353
3550
|
const goal2 = new Triple(el, pred, yvar);
|
|
3354
|
-
const sols = evalBuiltin(
|
|
3551
|
+
const sols = evalBuiltin(
|
|
3552
|
+
goal2,
|
|
3553
|
+
subst,
|
|
3554
|
+
facts,
|
|
3555
|
+
backRules,
|
|
3556
|
+
depth + 1,
|
|
3557
|
+
varGen,
|
|
3558
|
+
);
|
|
3355
3559
|
if (!sols.length) return [];
|
|
3356
3560
|
const yval = applySubstTerm(yvar, sols[0]);
|
|
3357
3561
|
if (yval instanceof Var) return [];
|
|
@@ -3484,7 +3688,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3484
3688
|
backRules,
|
|
3485
3689
|
depth + 1,
|
|
3486
3690
|
visited2,
|
|
3487
|
-
varGen
|
|
3691
|
+
varGen,
|
|
3488
3692
|
);
|
|
3489
3693
|
if (!sols.length) return [{ ...subst }];
|
|
3490
3694
|
return [];
|
|
@@ -3504,7 +3708,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3504
3708
|
backRules,
|
|
3505
3709
|
depth + 1,
|
|
3506
3710
|
visited2,
|
|
3507
|
-
varGen
|
|
3711
|
+
varGen,
|
|
3508
3712
|
);
|
|
3509
3713
|
|
|
3510
3714
|
// Collect one value per *solution*, duplicates allowed
|
|
@@ -3536,7 +3740,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3536
3740
|
backRules,
|
|
3537
3741
|
depth + 1,
|
|
3538
3742
|
visited1,
|
|
3539
|
-
varGen
|
|
3743
|
+
varGen,
|
|
3540
3744
|
);
|
|
3541
3745
|
|
|
3542
3746
|
// 2. For every such substitution, check that the second clause holds too.
|
|
@@ -3550,7 +3754,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3550
3754
|
backRules,
|
|
3551
3755
|
depth + 1,
|
|
3552
3756
|
visited2,
|
|
3553
|
-
varGen
|
|
3757
|
+
varGen,
|
|
3554
3758
|
);
|
|
3555
3759
|
// Found a counterexample: whereClause holds but thenClause does not
|
|
3556
3760
|
if (!sols2.length) return [];
|
|
@@ -3581,7 +3785,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3581
3785
|
if (g.p instanceof Iri && g.p.value === LOG_NS + "uri") {
|
|
3582
3786
|
// Direction 1: subject is an IRI -> object is its string representation
|
|
3583
3787
|
if (g.s instanceof Iri) {
|
|
3584
|
-
const uriStr = g.s.value;
|
|
3788
|
+
const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
|
|
3585
3789
|
const lit = makeStringLiteral(uriStr); // "https://www.w3.org"
|
|
3586
3790
|
const s2 = unifyTerm(goal.o, lit, subst);
|
|
3587
3791
|
return s2 !== null ? [s2] : [];
|
|
@@ -3656,9 +3860,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3656
3860
|
const sStr = termToJsString(g.s);
|
|
3657
3861
|
const oStr = termToJsString(g.o);
|
|
3658
3862
|
if (sStr === null || oStr === null) return [];
|
|
3659
|
-
return sStr.toLowerCase() === oStr.toLowerCase()
|
|
3660
|
-
? [{ ...subst }]
|
|
3661
|
-
: [];
|
|
3863
|
+
return sStr.toLowerCase() === oStr.toLowerCase() ? [{ ...subst }] : [];
|
|
3662
3864
|
}
|
|
3663
3865
|
|
|
3664
3866
|
// string:format
|
|
@@ -3693,8 +3895,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3693
3895
|
if (g.p instanceof Iri && g.p.value === STRING_NS + "jsonPointer") {
|
|
3694
3896
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3695
3897
|
|
|
3696
|
-
const jsonText = termToJsonText(g.s.elems[0]);
|
|
3697
|
-
const ptr
|
|
3898
|
+
const jsonText = termToJsonText(g.s.elems[0]); // <-- changed
|
|
3899
|
+
const ptr = termToJsStringDecoded(g.s.elems[1]);
|
|
3698
3900
|
if (jsonText === null || ptr === null) return [];
|
|
3699
3901
|
|
|
3700
3902
|
const valTerm = jsonPointerLookup(jsonText, ptr);
|
|
@@ -3740,9 +3942,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3740
3942
|
const sStr = termToJsString(g.s);
|
|
3741
3943
|
const oStr = termToJsString(g.o);
|
|
3742
3944
|
if (sStr === null || oStr === null) return [];
|
|
3743
|
-
return sStr.toLowerCase() !== oStr.toLowerCase()
|
|
3744
|
-
? [{ ...subst }]
|
|
3745
|
-
: [];
|
|
3945
|
+
return sStr.toLowerCase() !== oStr.toLowerCase() ? [{ ...subst }] : [];
|
|
3746
3946
|
}
|
|
3747
3947
|
|
|
3748
3948
|
// string:notGreaterThan (≤ in Unicode code order)
|
|
@@ -3778,9 +3978,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3778
3978
|
// string:replace
|
|
3779
3979
|
if (g.p instanceof Iri && g.p.value === STRING_NS + "replace") {
|
|
3780
3980
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
3781
|
-
const dataStr
|
|
3981
|
+
const dataStr = termToJsString(g.s.elems[0]);
|
|
3782
3982
|
const searchStr = termToJsString(g.s.elems[1]);
|
|
3783
|
-
const replStr
|
|
3983
|
+
const replStr = termToJsString(g.s.elems[2]);
|
|
3784
3984
|
if (dataStr === null || searchStr === null || replStr === null) return [];
|
|
3785
3985
|
|
|
3786
3986
|
let re;
|
|
@@ -3855,10 +4055,10 @@ function isBuiltinPred(p) {
|
|
|
3855
4055
|
|
|
3856
4056
|
return (
|
|
3857
4057
|
v.startsWith(CRYPTO_NS) ||
|
|
3858
|
-
v.startsWith(MATH_NS)
|
|
3859
|
-
v.startsWith(LOG_NS)
|
|
4058
|
+
v.startsWith(MATH_NS) ||
|
|
4059
|
+
v.startsWith(LOG_NS) ||
|
|
3860
4060
|
v.startsWith(STRING_NS) ||
|
|
3861
|
-
v.startsWith(TIME_NS)
|
|
4061
|
+
v.startsWith(TIME_NS) ||
|
|
3862
4062
|
v.startsWith(LIST_NS)
|
|
3863
4063
|
);
|
|
3864
4064
|
}
|
|
@@ -3878,10 +4078,10 @@ function standardizeRule(rule, gen) {
|
|
|
3878
4078
|
return new Var(vmap[t.name]);
|
|
3879
4079
|
}
|
|
3880
4080
|
if (t instanceof ListTerm) {
|
|
3881
|
-
return new ListTerm(t.elems.map(e => renameTerm(e, vmap, genArr)));
|
|
4081
|
+
return new ListTerm(t.elems.map((e) => renameTerm(e, vmap, genArr)));
|
|
3882
4082
|
}
|
|
3883
4083
|
if (t instanceof OpenListTerm) {
|
|
3884
|
-
const newXs = t.prefix.map(e => renameTerm(e, vmap, genArr));
|
|
4084
|
+
const newXs = t.prefix.map((e) => renameTerm(e, vmap, genArr));
|
|
3885
4085
|
if (!vmap.hasOwnProperty(t.tailVar)) {
|
|
3886
4086
|
const name = `${t.tailVar}__${genArr[0]}`;
|
|
3887
4087
|
genArr[0] += 1;
|
|
@@ -3892,13 +4092,14 @@ function standardizeRule(rule, gen) {
|
|
|
3892
4092
|
}
|
|
3893
4093
|
if (t instanceof FormulaTerm) {
|
|
3894
4094
|
return new FormulaTerm(
|
|
3895
|
-
t.triples.map(
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4095
|
+
t.triples.map(
|
|
4096
|
+
(tr) =>
|
|
4097
|
+
new Triple(
|
|
4098
|
+
renameTerm(tr.s, vmap, genArr),
|
|
4099
|
+
renameTerm(tr.p, vmap, genArr),
|
|
4100
|
+
renameTerm(tr.o, vmap, genArr),
|
|
4101
|
+
),
|
|
4102
|
+
),
|
|
3902
4103
|
);
|
|
3903
4104
|
}
|
|
3904
4105
|
return t;
|
|
@@ -3906,32 +4107,32 @@ function standardizeRule(rule, gen) {
|
|
|
3906
4107
|
|
|
3907
4108
|
const vmap2 = {};
|
|
3908
4109
|
const premise = rule.premise.map(
|
|
3909
|
-
tr =>
|
|
4110
|
+
(tr) =>
|
|
3910
4111
|
new Triple(
|
|
3911
4112
|
renameTerm(tr.s, vmap2, gen),
|
|
3912
4113
|
renameTerm(tr.p, vmap2, gen),
|
|
3913
|
-
renameTerm(tr.o, vmap2, gen)
|
|
3914
|
-
)
|
|
4114
|
+
renameTerm(tr.o, vmap2, gen),
|
|
4115
|
+
),
|
|
3915
4116
|
);
|
|
3916
4117
|
const conclusion = rule.conclusion.map(
|
|
3917
|
-
tr =>
|
|
4118
|
+
(tr) =>
|
|
3918
4119
|
new Triple(
|
|
3919
4120
|
renameTerm(tr.s, vmap2, gen),
|
|
3920
4121
|
renameTerm(tr.p, vmap2, gen),
|
|
3921
|
-
renameTerm(tr.o, vmap2, gen)
|
|
3922
|
-
)
|
|
4122
|
+
renameTerm(tr.o, vmap2, gen),
|
|
4123
|
+
),
|
|
3923
4124
|
);
|
|
3924
4125
|
return new Rule(
|
|
3925
4126
|
premise,
|
|
3926
4127
|
conclusion,
|
|
3927
4128
|
rule.isForward,
|
|
3928
4129
|
rule.isFuse,
|
|
3929
|
-
rule.headBlankLabels
|
|
4130
|
+
rule.headBlankLabels,
|
|
3930
4131
|
);
|
|
3931
4132
|
}
|
|
3932
4133
|
|
|
3933
4134
|
function listHasTriple(list, tr) {
|
|
3934
|
-
return list.some(t => triplesEqual(t, tr));
|
|
4135
|
+
return list.some((t) => triplesEqual(t, tr));
|
|
3935
4136
|
}
|
|
3936
4137
|
|
|
3937
4138
|
// ============================================================================
|
|
@@ -3951,14 +4152,23 @@ function listHasTriple(list, tr) {
|
|
|
3951
4152
|
// This is semantics-preserving for the ongoing proof state.
|
|
3952
4153
|
|
|
3953
4154
|
function gcCollectVarsInTerm(t, out) {
|
|
3954
|
-
if (t instanceof Var) {
|
|
3955
|
-
|
|
4155
|
+
if (t instanceof Var) {
|
|
4156
|
+
out.add(t.name);
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
if (t instanceof ListTerm) {
|
|
4160
|
+
for (const e of t.elems) gcCollectVarsInTerm(e, out);
|
|
4161
|
+
return;
|
|
4162
|
+
}
|
|
3956
4163
|
if (t instanceof OpenListTerm) {
|
|
3957
4164
|
for (const e of t.prefix) gcCollectVarsInTerm(e, out);
|
|
3958
4165
|
out.add(t.tailVar);
|
|
3959
4166
|
return;
|
|
3960
4167
|
}
|
|
3961
|
-
if (t instanceof FormulaTerm) {
|
|
4168
|
+
if (t instanceof FormulaTerm) {
|
|
4169
|
+
for (const tr of t.triples) gcCollectVarsInTriple(tr, out);
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
3962
4172
|
}
|
|
3963
4173
|
|
|
3964
4174
|
function gcCollectVarsInTriple(tr, out) {
|
|
@@ -4018,8 +4228,7 @@ function maybeCompactSubst(subst, goals, answerVars, depth) {
|
|
|
4018
4228
|
return gcCompactForGoals(subst, goals, answerVars);
|
|
4019
4229
|
}
|
|
4020
4230
|
|
|
4021
|
-
|
|
4022
|
-
function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
4231
|
+
function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
|
|
4023
4232
|
// Iterative DFS over proof states using an explicit stack.
|
|
4024
4233
|
// Each state carries its own substitution and remaining goals.
|
|
4025
4234
|
const results = [];
|
|
@@ -4028,7 +4237,6 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4028
4237
|
const initialSubst = subst ? { ...subst } : {};
|
|
4029
4238
|
const initialVisited = visited ? visited.slice() : [];
|
|
4030
4239
|
|
|
4031
|
-
|
|
4032
4240
|
// Variables from the original goal list (needed by the caller to instantiate conclusions)
|
|
4033
4241
|
const answerVars = new Set();
|
|
4034
4242
|
gcCollectVarsInGoals(initialGoals, answerVars);
|
|
@@ -4038,7 +4246,12 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4038
4246
|
}
|
|
4039
4247
|
|
|
4040
4248
|
const stack = [
|
|
4041
|
-
{
|
|
4249
|
+
{
|
|
4250
|
+
goals: initialGoals,
|
|
4251
|
+
subst: initialSubst,
|
|
4252
|
+
depth: depth || 0,
|
|
4253
|
+
visited: initialVisited,
|
|
4254
|
+
},
|
|
4042
4255
|
];
|
|
4043
4256
|
|
|
4044
4257
|
while (stack.length) {
|
|
@@ -4055,7 +4268,14 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4055
4268
|
|
|
4056
4269
|
// 1) Builtins
|
|
4057
4270
|
if (isBuiltinPred(goal0.p)) {
|
|
4058
|
-
const deltas = evalBuiltin(
|
|
4271
|
+
const deltas = evalBuiltin(
|
|
4272
|
+
goal0,
|
|
4273
|
+
{},
|
|
4274
|
+
facts,
|
|
4275
|
+
backRules,
|
|
4276
|
+
state.depth,
|
|
4277
|
+
varGen,
|
|
4278
|
+
);
|
|
4059
4279
|
for (const delta of deltas) {
|
|
4060
4280
|
const composed = composeSubst(state.subst, delta);
|
|
4061
4281
|
if (composed === null) continue;
|
|
@@ -4063,12 +4283,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4063
4283
|
if (!restGoals.length) {
|
|
4064
4284
|
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4065
4285
|
} else {
|
|
4066
|
-
const nextSubst = maybeCompactSubst(
|
|
4286
|
+
const nextSubst = maybeCompactSubst(
|
|
4287
|
+
composed,
|
|
4288
|
+
restGoals,
|
|
4289
|
+
answerVars,
|
|
4290
|
+
state.depth + 1,
|
|
4291
|
+
);
|
|
4067
4292
|
stack.push({
|
|
4068
4293
|
goals: restGoals,
|
|
4069
4294
|
subst: nextSubst,
|
|
4070
4295
|
depth: state.depth + 1,
|
|
4071
|
-
visited: state.visited
|
|
4296
|
+
visited: state.visited,
|
|
4072
4297
|
});
|
|
4073
4298
|
}
|
|
4074
4299
|
}
|
|
@@ -4092,12 +4317,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4092
4317
|
if (!restGoals.length) {
|
|
4093
4318
|
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4094
4319
|
} else {
|
|
4095
|
-
const nextSubst = maybeCompactSubst(
|
|
4320
|
+
const nextSubst = maybeCompactSubst(
|
|
4321
|
+
composed,
|
|
4322
|
+
restGoals,
|
|
4323
|
+
answerVars,
|
|
4324
|
+
state.depth + 1,
|
|
4325
|
+
);
|
|
4096
4326
|
stack.push({
|
|
4097
4327
|
goals: restGoals,
|
|
4098
4328
|
subst: nextSubst,
|
|
4099
4329
|
depth: state.depth + 1,
|
|
4100
|
-
visited: state.visited
|
|
4330
|
+
visited: state.visited,
|
|
4101
4331
|
});
|
|
4102
4332
|
}
|
|
4103
4333
|
}
|
|
@@ -4113,12 +4343,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4113
4343
|
if (!restGoals.length) {
|
|
4114
4344
|
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4115
4345
|
} else {
|
|
4116
|
-
const nextSubst = maybeCompactSubst(
|
|
4346
|
+
const nextSubst = maybeCompactSubst(
|
|
4347
|
+
composed,
|
|
4348
|
+
restGoals,
|
|
4349
|
+
answerVars,
|
|
4350
|
+
state.depth + 1,
|
|
4351
|
+
);
|
|
4117
4352
|
stack.push({
|
|
4118
4353
|
goals: restGoals,
|
|
4119
4354
|
subst: nextSubst,
|
|
4120
4355
|
depth: state.depth + 1,
|
|
4121
|
-
visited: state.visited
|
|
4356
|
+
visited: state.visited,
|
|
4122
4357
|
});
|
|
4123
4358
|
}
|
|
4124
4359
|
}
|
|
@@ -4127,31 +4362,38 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4127
4362
|
// 4) Backward rules (indexed by head predicate)
|
|
4128
4363
|
if (goal0.p instanceof Iri) {
|
|
4129
4364
|
ensureBackRuleIndexes(backRules);
|
|
4130
|
-
const candRules =
|
|
4131
|
-
|
|
4365
|
+
const candRules = (
|
|
4366
|
+
backRules.__byHeadPred.get(goal0.p.value) || []
|
|
4367
|
+
).concat(backRules.__wildHeadPred);
|
|
4132
4368
|
|
|
4133
4369
|
for (const r of candRules) {
|
|
4134
4370
|
if (r.conclusion.length !== 1) continue;
|
|
4135
4371
|
|
|
4136
4372
|
const rawHead = r.conclusion[0];
|
|
4137
|
-
if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value)
|
|
4373
|
+
if (rawHead.p instanceof Iri && rawHead.p.value !== goal0.p.value)
|
|
4374
|
+
continue;
|
|
4138
4375
|
|
|
4139
4376
|
const rStd = standardizeRule(r, varGen);
|
|
4140
4377
|
const head = rStd.conclusion[0];
|
|
4141
4378
|
const deltaHead = unifyTriple(head, goal0, {});
|
|
4142
4379
|
if (deltaHead === null) continue;
|
|
4143
4380
|
|
|
4144
|
-
const body = rStd.premise.map(b => applySubstTriple(b, deltaHead));
|
|
4381
|
+
const body = rStd.premise.map((b) => applySubstTriple(b, deltaHead));
|
|
4145
4382
|
const composed = composeSubst(state.subst, deltaHead);
|
|
4146
4383
|
if (composed === null) continue;
|
|
4147
4384
|
|
|
4148
4385
|
const newGoals = body.concat(restGoals);
|
|
4149
|
-
const nextSubst = maybeCompactSubst(
|
|
4386
|
+
const nextSubst = maybeCompactSubst(
|
|
4387
|
+
composed,
|
|
4388
|
+
newGoals,
|
|
4389
|
+
answerVars,
|
|
4390
|
+
state.depth + 1,
|
|
4391
|
+
);
|
|
4150
4392
|
stack.push({
|
|
4151
4393
|
goals: newGoals,
|
|
4152
4394
|
subst: nextSubst,
|
|
4153
4395
|
depth: state.depth + 1,
|
|
4154
|
-
visited: visitedForRules
|
|
4396
|
+
visited: visitedForRules,
|
|
4155
4397
|
});
|
|
4156
4398
|
}
|
|
4157
4399
|
}
|
|
@@ -4185,54 +4427,82 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4185
4427
|
const empty = {};
|
|
4186
4428
|
const visited = [];
|
|
4187
4429
|
|
|
4188
|
-
const sols = proveGoals(
|
|
4430
|
+
const sols = proveGoals(
|
|
4431
|
+
r.premise.slice(),
|
|
4432
|
+
empty,
|
|
4433
|
+
facts,
|
|
4434
|
+
backRules,
|
|
4435
|
+
0,
|
|
4436
|
+
visited,
|
|
4437
|
+
varGen,
|
|
4438
|
+
);
|
|
4189
4439
|
|
|
4190
4440
|
// Inference fuse
|
|
4191
4441
|
if (r.isFuse && sols.length) {
|
|
4192
|
-
console.log(
|
|
4442
|
+
console.log(
|
|
4443
|
+
"# Inference fuse triggered: a { ... } => false. rule fired.",
|
|
4444
|
+
);
|
|
4193
4445
|
process.exit(2);
|
|
4194
4446
|
}
|
|
4195
4447
|
|
|
4196
4448
|
for (const s of sols) {
|
|
4197
|
-
const instantiatedPremises = r.premise.map(b =>
|
|
4449
|
+
const instantiatedPremises = r.premise.map((b) =>
|
|
4450
|
+
applySubstTriple(b, s),
|
|
4451
|
+
);
|
|
4198
4452
|
|
|
4199
4453
|
for (const cpat of r.conclusion) {
|
|
4200
4454
|
const instantiated = applySubstTriple(cpat, s);
|
|
4201
4455
|
|
|
4202
4456
|
const isFwRuleTriple =
|
|
4203
4457
|
isLogImplies(instantiated.p) &&
|
|
4204
|
-
(
|
|
4205
|
-
|
|
4206
|
-
(instantiated.s instanceof Literal &&
|
|
4207
|
-
|
|
4208
|
-
|
|
4458
|
+
((instantiated.s instanceof FormulaTerm &&
|
|
4459
|
+
instantiated.o instanceof FormulaTerm) ||
|
|
4460
|
+
(instantiated.s instanceof Literal &&
|
|
4461
|
+
instantiated.s.value === "true" &&
|
|
4462
|
+
instantiated.o instanceof FormulaTerm) ||
|
|
4463
|
+
(instantiated.s instanceof FormulaTerm &&
|
|
4464
|
+
instantiated.o instanceof Literal &&
|
|
4465
|
+
instantiated.o.value === "true"));
|
|
4209
4466
|
|
|
4210
4467
|
const isBwRuleTriple =
|
|
4211
4468
|
isLogImpliedBy(instantiated.p) &&
|
|
4212
|
-
(
|
|
4213
|
-
|
|
4214
|
-
(instantiated.s instanceof FormulaTerm &&
|
|
4215
|
-
|
|
4216
|
-
|
|
4469
|
+
((instantiated.s instanceof FormulaTerm &&
|
|
4470
|
+
instantiated.o instanceof FormulaTerm) ||
|
|
4471
|
+
(instantiated.s instanceof FormulaTerm &&
|
|
4472
|
+
instantiated.o instanceof Literal &&
|
|
4473
|
+
instantiated.o.value === "true") ||
|
|
4474
|
+
(instantiated.s instanceof Literal &&
|
|
4475
|
+
instantiated.s.value === "true" &&
|
|
4476
|
+
instantiated.o instanceof FormulaTerm));
|
|
4217
4477
|
|
|
4218
4478
|
if (isFwRuleTriple || isBwRuleTriple) {
|
|
4219
4479
|
if (!hasFactIndexed(facts, instantiated)) {
|
|
4220
4480
|
factList.push(instantiated);
|
|
4221
4481
|
pushFactIndexed(facts, instantiated);
|
|
4222
|
-
derivedForward.push(
|
|
4482
|
+
derivedForward.push(
|
|
4483
|
+
new DerivedFact(instantiated, r, instantiatedPremises.slice(), {
|
|
4484
|
+
...s,
|
|
4485
|
+
}),
|
|
4486
|
+
);
|
|
4223
4487
|
changed = true;
|
|
4224
4488
|
}
|
|
4225
4489
|
|
|
4226
4490
|
// Promote rule-producing triples to live rules, treating literal true as {}.
|
|
4227
4491
|
const left =
|
|
4228
|
-
instantiated.s instanceof FormulaTerm
|
|
4229
|
-
|
|
4230
|
-
|
|
4492
|
+
instantiated.s instanceof FormulaTerm
|
|
4493
|
+
? instantiated.s.triples
|
|
4494
|
+
: instantiated.s instanceof Literal &&
|
|
4495
|
+
instantiated.s.value === "true"
|
|
4496
|
+
? []
|
|
4497
|
+
: null;
|
|
4231
4498
|
|
|
4232
4499
|
const right =
|
|
4233
|
-
instantiated.o instanceof FormulaTerm
|
|
4234
|
-
|
|
4235
|
-
|
|
4500
|
+
instantiated.o instanceof FormulaTerm
|
|
4501
|
+
? instantiated.o.triples
|
|
4502
|
+
: instantiated.o instanceof Literal &&
|
|
4503
|
+
instantiated.o.value === "true"
|
|
4504
|
+
? []
|
|
4505
|
+
: null;
|
|
4236
4506
|
|
|
4237
4507
|
if (left !== null && right !== null) {
|
|
4238
4508
|
if (isFwRuleTriple) {
|
|
@@ -4240,28 +4510,40 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4240
4510
|
const premise = reorderPremiseForConstraints(premise0);
|
|
4241
4511
|
|
|
4242
4512
|
const headBlankLabels = collectBlankLabelsInTriples(conclusion);
|
|
4243
|
-
const newRule = new Rule(
|
|
4513
|
+
const newRule = new Rule(
|
|
4514
|
+
premise,
|
|
4515
|
+
conclusion,
|
|
4516
|
+
true,
|
|
4517
|
+
false,
|
|
4518
|
+
headBlankLabels,
|
|
4519
|
+
);
|
|
4244
4520
|
|
|
4245
4521
|
const already = forwardRules.some(
|
|
4246
|
-
rr =>
|
|
4522
|
+
(rr) =>
|
|
4247
4523
|
rr.isForward === newRule.isForward &&
|
|
4248
4524
|
rr.isFuse === newRule.isFuse &&
|
|
4249
4525
|
triplesListEqual(rr.premise, newRule.premise) &&
|
|
4250
|
-
triplesListEqual(rr.conclusion, newRule.conclusion)
|
|
4526
|
+
triplesListEqual(rr.conclusion, newRule.conclusion),
|
|
4251
4527
|
);
|
|
4252
4528
|
if (!already) forwardRules.push(newRule);
|
|
4253
4529
|
} else if (isBwRuleTriple) {
|
|
4254
4530
|
const [premise, conclusion] = liftBlankRuleVars(right, left);
|
|
4255
4531
|
|
|
4256
4532
|
const headBlankLabels = collectBlankLabelsInTriples(conclusion);
|
|
4257
|
-
const newRule = new Rule(
|
|
4533
|
+
const newRule = new Rule(
|
|
4534
|
+
premise,
|
|
4535
|
+
conclusion,
|
|
4536
|
+
false,
|
|
4537
|
+
false,
|
|
4538
|
+
headBlankLabels,
|
|
4539
|
+
);
|
|
4258
4540
|
|
|
4259
4541
|
const already = backRules.some(
|
|
4260
|
-
rr =>
|
|
4542
|
+
(rr) =>
|
|
4261
4543
|
rr.isForward === newRule.isForward &&
|
|
4262
4544
|
rr.isFuse === newRule.isFuse &&
|
|
4263
4545
|
triplesListEqual(rr.premise, newRule.premise) &&
|
|
4264
|
-
triplesListEqual(rr.conclusion, newRule.conclusion)
|
|
4546
|
+
triplesListEqual(rr.conclusion, newRule.conclusion),
|
|
4265
4547
|
);
|
|
4266
4548
|
if (!already) {
|
|
4267
4549
|
backRules.push(newRule);
|
|
@@ -4275,7 +4557,12 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4275
4557
|
|
|
4276
4558
|
// Only skolemize blank nodes that occur explicitly in the rule head
|
|
4277
4559
|
const skMap = {};
|
|
4278
|
-
const inst = skolemizeTripleForHeadBlanks(
|
|
4560
|
+
const inst = skolemizeTripleForHeadBlanks(
|
|
4561
|
+
instantiated,
|
|
4562
|
+
r.headBlankLabels,
|
|
4563
|
+
skMap,
|
|
4564
|
+
skCounter,
|
|
4565
|
+
);
|
|
4279
4566
|
|
|
4280
4567
|
if (!isGroundTriple(inst)) continue;
|
|
4281
4568
|
if (hasFactIndexed(facts, inst)) continue;
|
|
@@ -4283,7 +4570,9 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4283
4570
|
factList.push(inst);
|
|
4284
4571
|
pushFactIndexed(facts, inst);
|
|
4285
4572
|
|
|
4286
|
-
derivedForward.push(
|
|
4573
|
+
derivedForward.push(
|
|
4574
|
+
new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }),
|
|
4575
|
+
);
|
|
4287
4576
|
changed = true;
|
|
4288
4577
|
}
|
|
4289
4578
|
}
|
|
@@ -4307,31 +4596,31 @@ function termToN3(t, pref) {
|
|
|
4307
4596
|
if (i.startsWith("_:")) return i;
|
|
4308
4597
|
return `<${i}>`;
|
|
4309
4598
|
}
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4599
|
+
if (t instanceof Literal) {
|
|
4600
|
+
const [lex, dt] = literalParts(t.value);
|
|
4601
|
+
|
|
4602
|
+
// Pretty-print xsd:boolean as bare true/false
|
|
4603
|
+
if (dt === XSD_NS + "boolean") {
|
|
4604
|
+
const v = stripQuotes(lex);
|
|
4605
|
+
if (v === "true" || v === "false") return v;
|
|
4606
|
+
// optional: normalize 1/0 too
|
|
4607
|
+
if (v === "1") return "true";
|
|
4608
|
+
if (v === "0") return "false";
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
if (!dt) return t.value; // keep numbers, booleans, lang-tagged strings, etc.
|
|
4612
|
+
const qdt = pref.shrinkIri(dt);
|
|
4613
|
+
if (qdt !== null) return `${lex}^^${qdt}`; // e.g. ^^rdf:JSON
|
|
4614
|
+
return `${lex}^^<${dt}>`; // fallback
|
|
4615
|
+
}
|
|
4327
4616
|
if (t instanceof Var) return `?${t.name}`;
|
|
4328
4617
|
if (t instanceof Blank) return t.label;
|
|
4329
4618
|
if (t instanceof ListTerm) {
|
|
4330
|
-
const inside = t.elems.map(e => termToN3(e, pref));
|
|
4619
|
+
const inside = t.elems.map((e) => termToN3(e, pref));
|
|
4331
4620
|
return "(" + inside.join(" ") + ")";
|
|
4332
4621
|
}
|
|
4333
4622
|
if (t instanceof OpenListTerm) {
|
|
4334
|
-
const inside = t.prefix.map(e => termToN3(e, pref));
|
|
4623
|
+
const inside = t.prefix.map((e) => termToN3(e, pref));
|
|
4335
4624
|
inside.push("?" + t.tailVar);
|
|
4336
4625
|
return "(" + inside.join(" ") + ")";
|
|
4337
4626
|
}
|
|
@@ -4362,10 +4651,11 @@ function tripleToN3(tr, prefixes) {
|
|
|
4362
4651
|
}
|
|
4363
4652
|
|
|
4364
4653
|
const s = termToN3(tr.s, prefixes);
|
|
4365
|
-
const p =
|
|
4366
|
-
|
|
4367
|
-
: isOwlSameAsPred(tr.p)
|
|
4368
|
-
|
|
4654
|
+
const p = isRdfTypePred(tr.p)
|
|
4655
|
+
? "a"
|
|
4656
|
+
: isOwlSameAsPred(tr.p)
|
|
4657
|
+
? "="
|
|
4658
|
+
: termToN3(tr.p, prefixes);
|
|
4369
4659
|
const o = termToN3(tr.o, prefixes);
|
|
4370
4660
|
|
|
4371
4661
|
return `${s} ${p} ${o} .`;
|
|
@@ -4373,7 +4663,7 @@ function tripleToN3(tr, prefixes) {
|
|
|
4373
4663
|
|
|
4374
4664
|
function printExplanation(df, prefixes) {
|
|
4375
4665
|
console.log(
|
|
4376
|
-
"# ----------------------------------------------------------------------"
|
|
4666
|
+
"# ----------------------------------------------------------------------",
|
|
4377
4667
|
);
|
|
4378
4668
|
console.log("# Proof for derived triple:");
|
|
4379
4669
|
|
|
@@ -4387,14 +4677,14 @@ function printExplanation(df, prefixes) {
|
|
|
4387
4677
|
|
|
4388
4678
|
if (!df.premises.length) {
|
|
4389
4679
|
console.log(
|
|
4390
|
-
"# This triple is the head of a forward rule with an empty premise,"
|
|
4680
|
+
"# This triple is the head of a forward rule with an empty premise,",
|
|
4391
4681
|
);
|
|
4392
4682
|
console.log(
|
|
4393
|
-
"# so it holds unconditionally whenever the program is loaded."
|
|
4683
|
+
"# so it holds unconditionally whenever the program is loaded.",
|
|
4394
4684
|
);
|
|
4395
4685
|
} else {
|
|
4396
4686
|
console.log(
|
|
4397
|
-
"# It holds because the following instance of the rule body is provable:"
|
|
4687
|
+
"# It holds because the following instance of the rule body is provable:",
|
|
4398
4688
|
);
|
|
4399
4689
|
|
|
4400
4690
|
// Premises, also indented 2 spaces after '# '
|
|
@@ -4434,7 +4724,7 @@ function printExplanation(df, prefixes) {
|
|
|
4434
4724
|
// Substitution block
|
|
4435
4725
|
const ruleVars = varsInRule(df.rule);
|
|
4436
4726
|
const visibleNames = Object.keys(df.subst)
|
|
4437
|
-
.filter(name => ruleVars.has(name))
|
|
4727
|
+
.filter((name) => ruleVars.has(name))
|
|
4438
4728
|
.sort();
|
|
4439
4729
|
|
|
4440
4730
|
if (visibleNames.length) {
|
|
@@ -4472,10 +4762,10 @@ function printExplanation(df, prefixes) {
|
|
|
4472
4762
|
}
|
|
4473
4763
|
|
|
4474
4764
|
console.log(
|
|
4475
|
-
"# Therefore the derived triple above is entailed by the rules and facts."
|
|
4765
|
+
"# Therefore the derived triple above is entailed by the rules and facts.",
|
|
4476
4766
|
);
|
|
4477
4767
|
console.log(
|
|
4478
|
-
"# ----------------------------------------------------------------------\n"
|
|
4768
|
+
"# ----------------------------------------------------------------------\n",
|
|
4479
4769
|
);
|
|
4480
4770
|
}
|
|
4481
4771
|
|
|
@@ -4487,8 +4777,8 @@ function printExplanation(df, prefixes) {
|
|
|
4487
4777
|
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
4488
4778
|
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
4489
4779
|
const RDF_FIRST = RDF_NS + "first";
|
|
4490
|
-
const RDF_REST
|
|
4491
|
-
const RDF_NIL
|
|
4780
|
+
const RDF_REST = RDF_NS + "rest";
|
|
4781
|
+
const RDF_NIL = RDF_NS + "nil";
|
|
4492
4782
|
|
|
4493
4783
|
function nodeKey(t) {
|
|
4494
4784
|
if (t instanceof Blank) return "B:" + t.label;
|
|
@@ -4498,7 +4788,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4498
4788
|
|
|
4499
4789
|
// Collect first/rest arcs from *input triples*
|
|
4500
4790
|
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
4501
|
-
const restMap
|
|
4791
|
+
const restMap = new Map(); // key(subject) -> Term (object)
|
|
4502
4792
|
for (const tr of triples) {
|
|
4503
4793
|
if (!(tr.p instanceof Iri)) continue;
|
|
4504
4794
|
const k = nodeKey(tr.s);
|
|
@@ -4508,8 +4798,8 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4508
4798
|
}
|
|
4509
4799
|
if (!firstMap.size && !restMap.size) return;
|
|
4510
4800
|
|
|
4511
|
-
const cache = new Map();
|
|
4512
|
-
const visiting = new Set();
|
|
4801
|
+
const cache = new Map(); // key(node) -> ListTerm
|
|
4802
|
+
const visiting = new Set(); // cycle guard
|
|
4513
4803
|
|
|
4514
4804
|
function buildListForKey(k) {
|
|
4515
4805
|
if (cache.has(k)) return cache.get(k);
|
|
@@ -4568,7 +4858,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4568
4858
|
}
|
|
4569
4859
|
if (t instanceof ListTerm) {
|
|
4570
4860
|
let changed = false;
|
|
4571
|
-
const elems = t.elems.map(e => {
|
|
4861
|
+
const elems = t.elems.map((e) => {
|
|
4572
4862
|
const r = rewriteTerm(e);
|
|
4573
4863
|
if (r !== e) changed = true;
|
|
4574
4864
|
return r;
|
|
@@ -4577,7 +4867,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4577
4867
|
}
|
|
4578
4868
|
if (t instanceof OpenListTerm) {
|
|
4579
4869
|
let changed = false;
|
|
4580
|
-
const prefix = t.prefix.map(e => {
|
|
4870
|
+
const prefix = t.prefix.map((e) => {
|
|
4581
4871
|
const r = rewriteTerm(e);
|
|
4582
4872
|
if (r !== e) changed = true;
|
|
4583
4873
|
return r;
|
|
@@ -4675,11 +4965,11 @@ function main() {
|
|
|
4675
4965
|
// --------------------------------------------------------------------------
|
|
4676
4966
|
// Positional args (the N3 file)
|
|
4677
4967
|
// --------------------------------------------------------------------------
|
|
4678
|
-
const positional = argv.filter(a => !a.startsWith("-"));
|
|
4968
|
+
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
4679
4969
|
|
|
4680
4970
|
if (positional.length !== 1) {
|
|
4681
4971
|
console.error(
|
|
4682
|
-
"Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>"
|
|
4972
|
+
"Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>",
|
|
4683
4973
|
);
|
|
4684
4974
|
process.exit(1);
|
|
4685
4975
|
}
|
|
@@ -4702,10 +4992,10 @@ function main() {
|
|
|
4702
4992
|
// Build internal ListTerm values from rdf:first/rdf:rest (+ rdf:nil) input triples
|
|
4703
4993
|
materializeRdfLists(triples, frules, brules);
|
|
4704
4994
|
|
|
4705
|
-
const facts = triples.filter(tr => isGroundTriple(tr));
|
|
4995
|
+
const facts = triples.filter((tr) => isGroundTriple(tr));
|
|
4706
4996
|
const derived = forwardChain(facts, frules, brules);
|
|
4707
4997
|
|
|
4708
|
-
const derivedTriples = derived.map(df => df.fact);
|
|
4998
|
+
const derivedTriples = derived.map((df) => df.fact);
|
|
4709
4999
|
const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
|
|
4710
5000
|
|
|
4711
5001
|
for (const [pfx, base] of usedPrefixes) {
|
|
@@ -4728,4 +5018,3 @@ function main() {
|
|
|
4728
5018
|
if (require.main === module) {
|
|
4729
5019
|
main();
|
|
4730
5020
|
}
|
|
4731
|
-
|