eyeling 1.5.37 → 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/list-iterate.n3 +38 -0
- package/examples/oslo-steps-library-scholarly.n3 +288 -0
- package/examples/oslo-steps-workflow-composition.n3 +192 -217
- package/examples/output/list-iterate.n3 +131 -0
- package/examples/output/oslo-steps-library-scholarly.n3 +1292 -0
- package/examples/output/oslo-steps-workflow-composition.n3 +96 -56
- package/eyeling.js +621 -337
- package/package.json +2 -2
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;
|
|
@@ -3146,23 +3343,18 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3146
3343
|
}
|
|
3147
3344
|
|
|
3148
3345
|
// list:iterate
|
|
3149
|
-
//
|
|
3150
|
-
//
|
|
3151
|
-
//
|
|
3346
|
+
// Multi-solution builtin:
|
|
3347
|
+
// For a list subject $s, generate solutions by unifying $o with (index value).
|
|
3348
|
+
// This allows $o to be a variable (e.g., ?Y) or a pattern (e.g., (?i "Dewey")).
|
|
3152
3349
|
if (g.p instanceof Iri && g.p.value === LIST_NS + "iterate") {
|
|
3153
3350
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3154
|
-
if (!(g.o instanceof ListTerm) || g.o.elems.length !== 2) return [];
|
|
3155
|
-
const [idxTerm, valTerm] = g.o.elems;
|
|
3156
3351
|
const xs = g.s.elems;
|
|
3157
3352
|
const outs = [];
|
|
3158
|
-
|
|
3159
3353
|
for (let i = 0; i < xs.length; i++) {
|
|
3160
|
-
const idxLit = new Literal(String(i)); //
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
if (s2 === null) continue;
|
|
3165
|
-
outs.push(s2);
|
|
3354
|
+
const idxLit = new Literal(String(i)); // 0-based
|
|
3355
|
+
const pair = new ListTerm([idxLit, xs[i]]);
|
|
3356
|
+
const s2 = unifyTerm(g.o, pair, subst);
|
|
3357
|
+
if (s2 !== null) outs.push(s2);
|
|
3166
3358
|
}
|
|
3167
3359
|
return outs;
|
|
3168
3360
|
}
|
|
@@ -3326,7 +3518,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3326
3518
|
else if (g.o instanceof ListTerm) inputList = g.o.elems;
|
|
3327
3519
|
else return [];
|
|
3328
3520
|
|
|
3329
|
-
if (!inputList.every(e => isGroundTerm(e))) return [];
|
|
3521
|
+
if (!inputList.every((e) => isGroundTerm(e))) return [];
|
|
3330
3522
|
|
|
3331
3523
|
const sortedList = [...inputList].sort(cmpTermForSort);
|
|
3332
3524
|
const sortedTerm = new ListTerm(sortedList);
|
|
@@ -3350,13 +3542,20 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3350
3542
|
if (!(predTerm instanceof Iri)) return [];
|
|
3351
3543
|
const pred = new Iri(predTerm.value);
|
|
3352
3544
|
if (!isBuiltinPred(pred)) return [];
|
|
3353
|
-
if (!inputList.every(e => isGroundTerm(e))) return [];
|
|
3545
|
+
if (!inputList.every((e) => isGroundTerm(e))) return [];
|
|
3354
3546
|
|
|
3355
3547
|
const results = [];
|
|
3356
3548
|
for (const el of inputList) {
|
|
3357
3549
|
const yvar = new Var("_mapY");
|
|
3358
3550
|
const goal2 = new Triple(el, pred, yvar);
|
|
3359
|
-
const sols = evalBuiltin(
|
|
3551
|
+
const sols = evalBuiltin(
|
|
3552
|
+
goal2,
|
|
3553
|
+
subst,
|
|
3554
|
+
facts,
|
|
3555
|
+
backRules,
|
|
3556
|
+
depth + 1,
|
|
3557
|
+
varGen,
|
|
3558
|
+
);
|
|
3360
3559
|
if (!sols.length) return [];
|
|
3361
3560
|
const yval = applySubstTerm(yvar, sols[0]);
|
|
3362
3561
|
if (yval instanceof Var) return [];
|
|
@@ -3489,7 +3688,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3489
3688
|
backRules,
|
|
3490
3689
|
depth + 1,
|
|
3491
3690
|
visited2,
|
|
3492
|
-
varGen
|
|
3691
|
+
varGen,
|
|
3493
3692
|
);
|
|
3494
3693
|
if (!sols.length) return [{ ...subst }];
|
|
3495
3694
|
return [];
|
|
@@ -3509,7 +3708,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3509
3708
|
backRules,
|
|
3510
3709
|
depth + 1,
|
|
3511
3710
|
visited2,
|
|
3512
|
-
varGen
|
|
3711
|
+
varGen,
|
|
3513
3712
|
);
|
|
3514
3713
|
|
|
3515
3714
|
// Collect one value per *solution*, duplicates allowed
|
|
@@ -3541,7 +3740,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3541
3740
|
backRules,
|
|
3542
3741
|
depth + 1,
|
|
3543
3742
|
visited1,
|
|
3544
|
-
varGen
|
|
3743
|
+
varGen,
|
|
3545
3744
|
);
|
|
3546
3745
|
|
|
3547
3746
|
// 2. For every such substitution, check that the second clause holds too.
|
|
@@ -3555,7 +3754,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3555
3754
|
backRules,
|
|
3556
3755
|
depth + 1,
|
|
3557
3756
|
visited2,
|
|
3558
|
-
varGen
|
|
3757
|
+
varGen,
|
|
3559
3758
|
);
|
|
3560
3759
|
// Found a counterexample: whereClause holds but thenClause does not
|
|
3561
3760
|
if (!sols2.length) return [];
|
|
@@ -3586,7 +3785,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3586
3785
|
if (g.p instanceof Iri && g.p.value === LOG_NS + "uri") {
|
|
3587
3786
|
// Direction 1: subject is an IRI -> object is its string representation
|
|
3588
3787
|
if (g.s instanceof Iri) {
|
|
3589
|
-
const uriStr = g.s.value;
|
|
3788
|
+
const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
|
|
3590
3789
|
const lit = makeStringLiteral(uriStr); // "https://www.w3.org"
|
|
3591
3790
|
const s2 = unifyTerm(goal.o, lit, subst);
|
|
3592
3791
|
return s2 !== null ? [s2] : [];
|
|
@@ -3661,9 +3860,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3661
3860
|
const sStr = termToJsString(g.s);
|
|
3662
3861
|
const oStr = termToJsString(g.o);
|
|
3663
3862
|
if (sStr === null || oStr === null) return [];
|
|
3664
|
-
return sStr.toLowerCase() === oStr.toLowerCase()
|
|
3665
|
-
? [{ ...subst }]
|
|
3666
|
-
: [];
|
|
3863
|
+
return sStr.toLowerCase() === oStr.toLowerCase() ? [{ ...subst }] : [];
|
|
3667
3864
|
}
|
|
3668
3865
|
|
|
3669
3866
|
// string:format
|
|
@@ -3698,8 +3895,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3698
3895
|
if (g.p instanceof Iri && g.p.value === STRING_NS + "jsonPointer") {
|
|
3699
3896
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3700
3897
|
|
|
3701
|
-
const jsonText = termToJsonText(g.s.elems[0]);
|
|
3702
|
-
const ptr
|
|
3898
|
+
const jsonText = termToJsonText(g.s.elems[0]); // <-- changed
|
|
3899
|
+
const ptr = termToJsStringDecoded(g.s.elems[1]);
|
|
3703
3900
|
if (jsonText === null || ptr === null) return [];
|
|
3704
3901
|
|
|
3705
3902
|
const valTerm = jsonPointerLookup(jsonText, ptr);
|
|
@@ -3745,9 +3942,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3745
3942
|
const sStr = termToJsString(g.s);
|
|
3746
3943
|
const oStr = termToJsString(g.o);
|
|
3747
3944
|
if (sStr === null || oStr === null) return [];
|
|
3748
|
-
return sStr.toLowerCase() !== oStr.toLowerCase()
|
|
3749
|
-
? [{ ...subst }]
|
|
3750
|
-
: [];
|
|
3945
|
+
return sStr.toLowerCase() !== oStr.toLowerCase() ? [{ ...subst }] : [];
|
|
3751
3946
|
}
|
|
3752
3947
|
|
|
3753
3948
|
// string:notGreaterThan (≤ in Unicode code order)
|
|
@@ -3783,9 +3978,9 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3783
3978
|
// string:replace
|
|
3784
3979
|
if (g.p instanceof Iri && g.p.value === STRING_NS + "replace") {
|
|
3785
3980
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 3) return [];
|
|
3786
|
-
const dataStr
|
|
3981
|
+
const dataStr = termToJsString(g.s.elems[0]);
|
|
3787
3982
|
const searchStr = termToJsString(g.s.elems[1]);
|
|
3788
|
-
const replStr
|
|
3983
|
+
const replStr = termToJsString(g.s.elems[2]);
|
|
3789
3984
|
if (dataStr === null || searchStr === null || replStr === null) return [];
|
|
3790
3985
|
|
|
3791
3986
|
let re;
|
|
@@ -3860,10 +4055,10 @@ function isBuiltinPred(p) {
|
|
|
3860
4055
|
|
|
3861
4056
|
return (
|
|
3862
4057
|
v.startsWith(CRYPTO_NS) ||
|
|
3863
|
-
v.startsWith(MATH_NS)
|
|
3864
|
-
v.startsWith(LOG_NS)
|
|
4058
|
+
v.startsWith(MATH_NS) ||
|
|
4059
|
+
v.startsWith(LOG_NS) ||
|
|
3865
4060
|
v.startsWith(STRING_NS) ||
|
|
3866
|
-
v.startsWith(TIME_NS)
|
|
4061
|
+
v.startsWith(TIME_NS) ||
|
|
3867
4062
|
v.startsWith(LIST_NS)
|
|
3868
4063
|
);
|
|
3869
4064
|
}
|
|
@@ -3883,10 +4078,10 @@ function standardizeRule(rule, gen) {
|
|
|
3883
4078
|
return new Var(vmap[t.name]);
|
|
3884
4079
|
}
|
|
3885
4080
|
if (t instanceof ListTerm) {
|
|
3886
|
-
return new ListTerm(t.elems.map(e => renameTerm(e, vmap, genArr)));
|
|
4081
|
+
return new ListTerm(t.elems.map((e) => renameTerm(e, vmap, genArr)));
|
|
3887
4082
|
}
|
|
3888
4083
|
if (t instanceof OpenListTerm) {
|
|
3889
|
-
const newXs = t.prefix.map(e => renameTerm(e, vmap, genArr));
|
|
4084
|
+
const newXs = t.prefix.map((e) => renameTerm(e, vmap, genArr));
|
|
3890
4085
|
if (!vmap.hasOwnProperty(t.tailVar)) {
|
|
3891
4086
|
const name = `${t.tailVar}__${genArr[0]}`;
|
|
3892
4087
|
genArr[0] += 1;
|
|
@@ -3897,13 +4092,14 @@ function standardizeRule(rule, gen) {
|
|
|
3897
4092
|
}
|
|
3898
4093
|
if (t instanceof FormulaTerm) {
|
|
3899
4094
|
return new FormulaTerm(
|
|
3900
|
-
t.triples.map(
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
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
|
+
),
|
|
3907
4103
|
);
|
|
3908
4104
|
}
|
|
3909
4105
|
return t;
|
|
@@ -3911,32 +4107,32 @@ function standardizeRule(rule, gen) {
|
|
|
3911
4107
|
|
|
3912
4108
|
const vmap2 = {};
|
|
3913
4109
|
const premise = rule.premise.map(
|
|
3914
|
-
tr =>
|
|
4110
|
+
(tr) =>
|
|
3915
4111
|
new Triple(
|
|
3916
4112
|
renameTerm(tr.s, vmap2, gen),
|
|
3917
4113
|
renameTerm(tr.p, vmap2, gen),
|
|
3918
|
-
renameTerm(tr.o, vmap2, gen)
|
|
3919
|
-
)
|
|
4114
|
+
renameTerm(tr.o, vmap2, gen),
|
|
4115
|
+
),
|
|
3920
4116
|
);
|
|
3921
4117
|
const conclusion = rule.conclusion.map(
|
|
3922
|
-
tr =>
|
|
4118
|
+
(tr) =>
|
|
3923
4119
|
new Triple(
|
|
3924
4120
|
renameTerm(tr.s, vmap2, gen),
|
|
3925
4121
|
renameTerm(tr.p, vmap2, gen),
|
|
3926
|
-
renameTerm(tr.o, vmap2, gen)
|
|
3927
|
-
)
|
|
4122
|
+
renameTerm(tr.o, vmap2, gen),
|
|
4123
|
+
),
|
|
3928
4124
|
);
|
|
3929
4125
|
return new Rule(
|
|
3930
4126
|
premise,
|
|
3931
4127
|
conclusion,
|
|
3932
4128
|
rule.isForward,
|
|
3933
4129
|
rule.isFuse,
|
|
3934
|
-
rule.headBlankLabels
|
|
4130
|
+
rule.headBlankLabels,
|
|
3935
4131
|
);
|
|
3936
4132
|
}
|
|
3937
4133
|
|
|
3938
4134
|
function listHasTriple(list, tr) {
|
|
3939
|
-
return list.some(t => triplesEqual(t, tr));
|
|
4135
|
+
return list.some((t) => triplesEqual(t, tr));
|
|
3940
4136
|
}
|
|
3941
4137
|
|
|
3942
4138
|
// ============================================================================
|
|
@@ -3956,14 +4152,23 @@ function listHasTriple(list, tr) {
|
|
|
3956
4152
|
// This is semantics-preserving for the ongoing proof state.
|
|
3957
4153
|
|
|
3958
4154
|
function gcCollectVarsInTerm(t, out) {
|
|
3959
|
-
if (t instanceof Var) {
|
|
3960
|
-
|
|
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
|
+
}
|
|
3961
4163
|
if (t instanceof OpenListTerm) {
|
|
3962
4164
|
for (const e of t.prefix) gcCollectVarsInTerm(e, out);
|
|
3963
4165
|
out.add(t.tailVar);
|
|
3964
4166
|
return;
|
|
3965
4167
|
}
|
|
3966
|
-
if (t instanceof FormulaTerm) {
|
|
4168
|
+
if (t instanceof FormulaTerm) {
|
|
4169
|
+
for (const tr of t.triples) gcCollectVarsInTriple(tr, out);
|
|
4170
|
+
return;
|
|
4171
|
+
}
|
|
3967
4172
|
}
|
|
3968
4173
|
|
|
3969
4174
|
function gcCollectVarsInTriple(tr, out) {
|
|
@@ -4023,8 +4228,7 @@ function maybeCompactSubst(subst, goals, answerVars, depth) {
|
|
|
4023
4228
|
return gcCompactForGoals(subst, goals, answerVars);
|
|
4024
4229
|
}
|
|
4025
4230
|
|
|
4026
|
-
|
|
4027
|
-
function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
4231
|
+
function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
|
|
4028
4232
|
// Iterative DFS over proof states using an explicit stack.
|
|
4029
4233
|
// Each state carries its own substitution and remaining goals.
|
|
4030
4234
|
const results = [];
|
|
@@ -4033,7 +4237,6 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4033
4237
|
const initialSubst = subst ? { ...subst } : {};
|
|
4034
4238
|
const initialVisited = visited ? visited.slice() : [];
|
|
4035
4239
|
|
|
4036
|
-
|
|
4037
4240
|
// Variables from the original goal list (needed by the caller to instantiate conclusions)
|
|
4038
4241
|
const answerVars = new Set();
|
|
4039
4242
|
gcCollectVarsInGoals(initialGoals, answerVars);
|
|
@@ -4043,7 +4246,12 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4043
4246
|
}
|
|
4044
4247
|
|
|
4045
4248
|
const stack = [
|
|
4046
|
-
{
|
|
4249
|
+
{
|
|
4250
|
+
goals: initialGoals,
|
|
4251
|
+
subst: initialSubst,
|
|
4252
|
+
depth: depth || 0,
|
|
4253
|
+
visited: initialVisited,
|
|
4254
|
+
},
|
|
4047
4255
|
];
|
|
4048
4256
|
|
|
4049
4257
|
while (stack.length) {
|
|
@@ -4060,7 +4268,14 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4060
4268
|
|
|
4061
4269
|
// 1) Builtins
|
|
4062
4270
|
if (isBuiltinPred(goal0.p)) {
|
|
4063
|
-
const deltas = evalBuiltin(
|
|
4271
|
+
const deltas = evalBuiltin(
|
|
4272
|
+
goal0,
|
|
4273
|
+
{},
|
|
4274
|
+
facts,
|
|
4275
|
+
backRules,
|
|
4276
|
+
state.depth,
|
|
4277
|
+
varGen,
|
|
4278
|
+
);
|
|
4064
4279
|
for (const delta of deltas) {
|
|
4065
4280
|
const composed = composeSubst(state.subst, delta);
|
|
4066
4281
|
if (composed === null) continue;
|
|
@@ -4068,12 +4283,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4068
4283
|
if (!restGoals.length) {
|
|
4069
4284
|
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4070
4285
|
} else {
|
|
4071
|
-
const nextSubst = maybeCompactSubst(
|
|
4286
|
+
const nextSubst = maybeCompactSubst(
|
|
4287
|
+
composed,
|
|
4288
|
+
restGoals,
|
|
4289
|
+
answerVars,
|
|
4290
|
+
state.depth + 1,
|
|
4291
|
+
);
|
|
4072
4292
|
stack.push({
|
|
4073
4293
|
goals: restGoals,
|
|
4074
4294
|
subst: nextSubst,
|
|
4075
4295
|
depth: state.depth + 1,
|
|
4076
|
-
visited: state.visited
|
|
4296
|
+
visited: state.visited,
|
|
4077
4297
|
});
|
|
4078
4298
|
}
|
|
4079
4299
|
}
|
|
@@ -4097,12 +4317,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4097
4317
|
if (!restGoals.length) {
|
|
4098
4318
|
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4099
4319
|
} else {
|
|
4100
|
-
const nextSubst = maybeCompactSubst(
|
|
4320
|
+
const nextSubst = maybeCompactSubst(
|
|
4321
|
+
composed,
|
|
4322
|
+
restGoals,
|
|
4323
|
+
answerVars,
|
|
4324
|
+
state.depth + 1,
|
|
4325
|
+
);
|
|
4101
4326
|
stack.push({
|
|
4102
4327
|
goals: restGoals,
|
|
4103
4328
|
subst: nextSubst,
|
|
4104
4329
|
depth: state.depth + 1,
|
|
4105
|
-
visited: state.visited
|
|
4330
|
+
visited: state.visited,
|
|
4106
4331
|
});
|
|
4107
4332
|
}
|
|
4108
4333
|
}
|
|
@@ -4118,12 +4343,17 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4118
4343
|
if (!restGoals.length) {
|
|
4119
4344
|
results.push(gcCompactForGoals(composed, [], answerVars));
|
|
4120
4345
|
} else {
|
|
4121
|
-
const nextSubst = maybeCompactSubst(
|
|
4346
|
+
const nextSubst = maybeCompactSubst(
|
|
4347
|
+
composed,
|
|
4348
|
+
restGoals,
|
|
4349
|
+
answerVars,
|
|
4350
|
+
state.depth + 1,
|
|
4351
|
+
);
|
|
4122
4352
|
stack.push({
|
|
4123
4353
|
goals: restGoals,
|
|
4124
4354
|
subst: nextSubst,
|
|
4125
4355
|
depth: state.depth + 1,
|
|
4126
|
-
visited: state.visited
|
|
4356
|
+
visited: state.visited,
|
|
4127
4357
|
});
|
|
4128
4358
|
}
|
|
4129
4359
|
}
|
|
@@ -4132,31 +4362,38 @@ function proveGoals( goals, subst, facts, backRules, depth, visited, varGen ) {
|
|
|
4132
4362
|
// 4) Backward rules (indexed by head predicate)
|
|
4133
4363
|
if (goal0.p instanceof Iri) {
|
|
4134
4364
|
ensureBackRuleIndexes(backRules);
|
|
4135
|
-
const candRules =
|
|
4136
|
-
|
|
4365
|
+
const candRules = (
|
|
4366
|
+
backRules.__byHeadPred.get(goal0.p.value) || []
|
|
4367
|
+
).concat(backRules.__wildHeadPred);
|
|
4137
4368
|
|
|
4138
4369
|
for (const r of candRules) {
|
|
4139
4370
|
if (r.conclusion.length !== 1) continue;
|
|
4140
4371
|
|
|
4141
4372
|
const rawHead = r.conclusion[0];
|
|
4142
|
-
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;
|
|
4143
4375
|
|
|
4144
4376
|
const rStd = standardizeRule(r, varGen);
|
|
4145
4377
|
const head = rStd.conclusion[0];
|
|
4146
4378
|
const deltaHead = unifyTriple(head, goal0, {});
|
|
4147
4379
|
if (deltaHead === null) continue;
|
|
4148
4380
|
|
|
4149
|
-
const body = rStd.premise.map(b => applySubstTriple(b, deltaHead));
|
|
4381
|
+
const body = rStd.premise.map((b) => applySubstTriple(b, deltaHead));
|
|
4150
4382
|
const composed = composeSubst(state.subst, deltaHead);
|
|
4151
4383
|
if (composed === null) continue;
|
|
4152
4384
|
|
|
4153
4385
|
const newGoals = body.concat(restGoals);
|
|
4154
|
-
const nextSubst = maybeCompactSubst(
|
|
4386
|
+
const nextSubst = maybeCompactSubst(
|
|
4387
|
+
composed,
|
|
4388
|
+
newGoals,
|
|
4389
|
+
answerVars,
|
|
4390
|
+
state.depth + 1,
|
|
4391
|
+
);
|
|
4155
4392
|
stack.push({
|
|
4156
4393
|
goals: newGoals,
|
|
4157
4394
|
subst: nextSubst,
|
|
4158
4395
|
depth: state.depth + 1,
|
|
4159
|
-
visited: visitedForRules
|
|
4396
|
+
visited: visitedForRules,
|
|
4160
4397
|
});
|
|
4161
4398
|
}
|
|
4162
4399
|
}
|
|
@@ -4190,54 +4427,82 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4190
4427
|
const empty = {};
|
|
4191
4428
|
const visited = [];
|
|
4192
4429
|
|
|
4193
|
-
const sols = proveGoals(
|
|
4430
|
+
const sols = proveGoals(
|
|
4431
|
+
r.premise.slice(),
|
|
4432
|
+
empty,
|
|
4433
|
+
facts,
|
|
4434
|
+
backRules,
|
|
4435
|
+
0,
|
|
4436
|
+
visited,
|
|
4437
|
+
varGen,
|
|
4438
|
+
);
|
|
4194
4439
|
|
|
4195
4440
|
// Inference fuse
|
|
4196
4441
|
if (r.isFuse && sols.length) {
|
|
4197
|
-
console.log(
|
|
4442
|
+
console.log(
|
|
4443
|
+
"# Inference fuse triggered: a { ... } => false. rule fired.",
|
|
4444
|
+
);
|
|
4198
4445
|
process.exit(2);
|
|
4199
4446
|
}
|
|
4200
4447
|
|
|
4201
4448
|
for (const s of sols) {
|
|
4202
|
-
const instantiatedPremises = r.premise.map(b =>
|
|
4449
|
+
const instantiatedPremises = r.premise.map((b) =>
|
|
4450
|
+
applySubstTriple(b, s),
|
|
4451
|
+
);
|
|
4203
4452
|
|
|
4204
4453
|
for (const cpat of r.conclusion) {
|
|
4205
4454
|
const instantiated = applySubstTriple(cpat, s);
|
|
4206
4455
|
|
|
4207
4456
|
const isFwRuleTriple =
|
|
4208
4457
|
isLogImplies(instantiated.p) &&
|
|
4209
|
-
(
|
|
4210
|
-
|
|
4211
|
-
(instantiated.s instanceof Literal &&
|
|
4212
|
-
|
|
4213
|
-
|
|
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"));
|
|
4214
4466
|
|
|
4215
4467
|
const isBwRuleTriple =
|
|
4216
4468
|
isLogImpliedBy(instantiated.p) &&
|
|
4217
|
-
(
|
|
4218
|
-
|
|
4219
|
-
(instantiated.s instanceof FormulaTerm &&
|
|
4220
|
-
|
|
4221
|
-
|
|
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));
|
|
4222
4477
|
|
|
4223
4478
|
if (isFwRuleTriple || isBwRuleTriple) {
|
|
4224
4479
|
if (!hasFactIndexed(facts, instantiated)) {
|
|
4225
4480
|
factList.push(instantiated);
|
|
4226
4481
|
pushFactIndexed(facts, instantiated);
|
|
4227
|
-
derivedForward.push(
|
|
4482
|
+
derivedForward.push(
|
|
4483
|
+
new DerivedFact(instantiated, r, instantiatedPremises.slice(), {
|
|
4484
|
+
...s,
|
|
4485
|
+
}),
|
|
4486
|
+
);
|
|
4228
4487
|
changed = true;
|
|
4229
4488
|
}
|
|
4230
4489
|
|
|
4231
4490
|
// Promote rule-producing triples to live rules, treating literal true as {}.
|
|
4232
4491
|
const left =
|
|
4233
|
-
instantiated.s instanceof FormulaTerm
|
|
4234
|
-
|
|
4235
|
-
|
|
4492
|
+
instantiated.s instanceof FormulaTerm
|
|
4493
|
+
? instantiated.s.triples
|
|
4494
|
+
: instantiated.s instanceof Literal &&
|
|
4495
|
+
instantiated.s.value === "true"
|
|
4496
|
+
? []
|
|
4497
|
+
: null;
|
|
4236
4498
|
|
|
4237
4499
|
const right =
|
|
4238
|
-
instantiated.o instanceof FormulaTerm
|
|
4239
|
-
|
|
4240
|
-
|
|
4500
|
+
instantiated.o instanceof FormulaTerm
|
|
4501
|
+
? instantiated.o.triples
|
|
4502
|
+
: instantiated.o instanceof Literal &&
|
|
4503
|
+
instantiated.o.value === "true"
|
|
4504
|
+
? []
|
|
4505
|
+
: null;
|
|
4241
4506
|
|
|
4242
4507
|
if (left !== null && right !== null) {
|
|
4243
4508
|
if (isFwRuleTriple) {
|
|
@@ -4245,28 +4510,40 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4245
4510
|
const premise = reorderPremiseForConstraints(premise0);
|
|
4246
4511
|
|
|
4247
4512
|
const headBlankLabels = collectBlankLabelsInTriples(conclusion);
|
|
4248
|
-
const newRule = new Rule(
|
|
4513
|
+
const newRule = new Rule(
|
|
4514
|
+
premise,
|
|
4515
|
+
conclusion,
|
|
4516
|
+
true,
|
|
4517
|
+
false,
|
|
4518
|
+
headBlankLabels,
|
|
4519
|
+
);
|
|
4249
4520
|
|
|
4250
4521
|
const already = forwardRules.some(
|
|
4251
|
-
rr =>
|
|
4522
|
+
(rr) =>
|
|
4252
4523
|
rr.isForward === newRule.isForward &&
|
|
4253
4524
|
rr.isFuse === newRule.isFuse &&
|
|
4254
4525
|
triplesListEqual(rr.premise, newRule.premise) &&
|
|
4255
|
-
triplesListEqual(rr.conclusion, newRule.conclusion)
|
|
4526
|
+
triplesListEqual(rr.conclusion, newRule.conclusion),
|
|
4256
4527
|
);
|
|
4257
4528
|
if (!already) forwardRules.push(newRule);
|
|
4258
4529
|
} else if (isBwRuleTriple) {
|
|
4259
4530
|
const [premise, conclusion] = liftBlankRuleVars(right, left);
|
|
4260
4531
|
|
|
4261
4532
|
const headBlankLabels = collectBlankLabelsInTriples(conclusion);
|
|
4262
|
-
const newRule = new Rule(
|
|
4533
|
+
const newRule = new Rule(
|
|
4534
|
+
premise,
|
|
4535
|
+
conclusion,
|
|
4536
|
+
false,
|
|
4537
|
+
false,
|
|
4538
|
+
headBlankLabels,
|
|
4539
|
+
);
|
|
4263
4540
|
|
|
4264
4541
|
const already = backRules.some(
|
|
4265
|
-
rr =>
|
|
4542
|
+
(rr) =>
|
|
4266
4543
|
rr.isForward === newRule.isForward &&
|
|
4267
4544
|
rr.isFuse === newRule.isFuse &&
|
|
4268
4545
|
triplesListEqual(rr.premise, newRule.premise) &&
|
|
4269
|
-
triplesListEqual(rr.conclusion, newRule.conclusion)
|
|
4546
|
+
triplesListEqual(rr.conclusion, newRule.conclusion),
|
|
4270
4547
|
);
|
|
4271
4548
|
if (!already) {
|
|
4272
4549
|
backRules.push(newRule);
|
|
@@ -4280,7 +4557,12 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4280
4557
|
|
|
4281
4558
|
// Only skolemize blank nodes that occur explicitly in the rule head
|
|
4282
4559
|
const skMap = {};
|
|
4283
|
-
const inst = skolemizeTripleForHeadBlanks(
|
|
4560
|
+
const inst = skolemizeTripleForHeadBlanks(
|
|
4561
|
+
instantiated,
|
|
4562
|
+
r.headBlankLabels,
|
|
4563
|
+
skMap,
|
|
4564
|
+
skCounter,
|
|
4565
|
+
);
|
|
4284
4566
|
|
|
4285
4567
|
if (!isGroundTriple(inst)) continue;
|
|
4286
4568
|
if (hasFactIndexed(facts, inst)) continue;
|
|
@@ -4288,7 +4570,9 @@ function forwardChain(facts, forwardRules, backRules) {
|
|
|
4288
4570
|
factList.push(inst);
|
|
4289
4571
|
pushFactIndexed(facts, inst);
|
|
4290
4572
|
|
|
4291
|
-
derivedForward.push(
|
|
4573
|
+
derivedForward.push(
|
|
4574
|
+
new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }),
|
|
4575
|
+
);
|
|
4292
4576
|
changed = true;
|
|
4293
4577
|
}
|
|
4294
4578
|
}
|
|
@@ -4312,31 +4596,31 @@ function termToN3(t, pref) {
|
|
|
4312
4596
|
if (i.startsWith("_:")) return i;
|
|
4313
4597
|
return `<${i}>`;
|
|
4314
4598
|
}
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
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
|
+
}
|
|
4332
4616
|
if (t instanceof Var) return `?${t.name}`;
|
|
4333
4617
|
if (t instanceof Blank) return t.label;
|
|
4334
4618
|
if (t instanceof ListTerm) {
|
|
4335
|
-
const inside = t.elems.map(e => termToN3(e, pref));
|
|
4619
|
+
const inside = t.elems.map((e) => termToN3(e, pref));
|
|
4336
4620
|
return "(" + inside.join(" ") + ")";
|
|
4337
4621
|
}
|
|
4338
4622
|
if (t instanceof OpenListTerm) {
|
|
4339
|
-
const inside = t.prefix.map(e => termToN3(e, pref));
|
|
4623
|
+
const inside = t.prefix.map((e) => termToN3(e, pref));
|
|
4340
4624
|
inside.push("?" + t.tailVar);
|
|
4341
4625
|
return "(" + inside.join(" ") + ")";
|
|
4342
4626
|
}
|
|
@@ -4367,10 +4651,11 @@ function tripleToN3(tr, prefixes) {
|
|
|
4367
4651
|
}
|
|
4368
4652
|
|
|
4369
4653
|
const s = termToN3(tr.s, prefixes);
|
|
4370
|
-
const p =
|
|
4371
|
-
|
|
4372
|
-
: isOwlSameAsPred(tr.p)
|
|
4373
|
-
|
|
4654
|
+
const p = isRdfTypePred(tr.p)
|
|
4655
|
+
? "a"
|
|
4656
|
+
: isOwlSameAsPred(tr.p)
|
|
4657
|
+
? "="
|
|
4658
|
+
: termToN3(tr.p, prefixes);
|
|
4374
4659
|
const o = termToN3(tr.o, prefixes);
|
|
4375
4660
|
|
|
4376
4661
|
return `${s} ${p} ${o} .`;
|
|
@@ -4378,7 +4663,7 @@ function tripleToN3(tr, prefixes) {
|
|
|
4378
4663
|
|
|
4379
4664
|
function printExplanation(df, prefixes) {
|
|
4380
4665
|
console.log(
|
|
4381
|
-
"# ----------------------------------------------------------------------"
|
|
4666
|
+
"# ----------------------------------------------------------------------",
|
|
4382
4667
|
);
|
|
4383
4668
|
console.log("# Proof for derived triple:");
|
|
4384
4669
|
|
|
@@ -4392,14 +4677,14 @@ function printExplanation(df, prefixes) {
|
|
|
4392
4677
|
|
|
4393
4678
|
if (!df.premises.length) {
|
|
4394
4679
|
console.log(
|
|
4395
|
-
"# 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,",
|
|
4396
4681
|
);
|
|
4397
4682
|
console.log(
|
|
4398
|
-
"# so it holds unconditionally whenever the program is loaded."
|
|
4683
|
+
"# so it holds unconditionally whenever the program is loaded.",
|
|
4399
4684
|
);
|
|
4400
4685
|
} else {
|
|
4401
4686
|
console.log(
|
|
4402
|
-
"# 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:",
|
|
4403
4688
|
);
|
|
4404
4689
|
|
|
4405
4690
|
// Premises, also indented 2 spaces after '# '
|
|
@@ -4439,7 +4724,7 @@ function printExplanation(df, prefixes) {
|
|
|
4439
4724
|
// Substitution block
|
|
4440
4725
|
const ruleVars = varsInRule(df.rule);
|
|
4441
4726
|
const visibleNames = Object.keys(df.subst)
|
|
4442
|
-
.filter(name => ruleVars.has(name))
|
|
4727
|
+
.filter((name) => ruleVars.has(name))
|
|
4443
4728
|
.sort();
|
|
4444
4729
|
|
|
4445
4730
|
if (visibleNames.length) {
|
|
@@ -4477,10 +4762,10 @@ function printExplanation(df, prefixes) {
|
|
|
4477
4762
|
}
|
|
4478
4763
|
|
|
4479
4764
|
console.log(
|
|
4480
|
-
"# 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.",
|
|
4481
4766
|
);
|
|
4482
4767
|
console.log(
|
|
4483
|
-
"# ----------------------------------------------------------------------\n"
|
|
4768
|
+
"# ----------------------------------------------------------------------\n",
|
|
4484
4769
|
);
|
|
4485
4770
|
}
|
|
4486
4771
|
|
|
@@ -4492,8 +4777,8 @@ function printExplanation(df, prefixes) {
|
|
|
4492
4777
|
// This mutates triples/rules in-place so list:* builtins work on RDF-serialized lists too.
|
|
4493
4778
|
function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
4494
4779
|
const RDF_FIRST = RDF_NS + "first";
|
|
4495
|
-
const RDF_REST
|
|
4496
|
-
const RDF_NIL
|
|
4780
|
+
const RDF_REST = RDF_NS + "rest";
|
|
4781
|
+
const RDF_NIL = RDF_NS + "nil";
|
|
4497
4782
|
|
|
4498
4783
|
function nodeKey(t) {
|
|
4499
4784
|
if (t instanceof Blank) return "B:" + t.label;
|
|
@@ -4503,7 +4788,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4503
4788
|
|
|
4504
4789
|
// Collect first/rest arcs from *input triples*
|
|
4505
4790
|
const firstMap = new Map(); // key(subject) -> Term (object)
|
|
4506
|
-
const restMap
|
|
4791
|
+
const restMap = new Map(); // key(subject) -> Term (object)
|
|
4507
4792
|
for (const tr of triples) {
|
|
4508
4793
|
if (!(tr.p instanceof Iri)) continue;
|
|
4509
4794
|
const k = nodeKey(tr.s);
|
|
@@ -4513,8 +4798,8 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4513
4798
|
}
|
|
4514
4799
|
if (!firstMap.size && !restMap.size) return;
|
|
4515
4800
|
|
|
4516
|
-
const cache = new Map();
|
|
4517
|
-
const visiting = new Set();
|
|
4801
|
+
const cache = new Map(); // key(node) -> ListTerm
|
|
4802
|
+
const visiting = new Set(); // cycle guard
|
|
4518
4803
|
|
|
4519
4804
|
function buildListForKey(k) {
|
|
4520
4805
|
if (cache.has(k)) return cache.get(k);
|
|
@@ -4573,7 +4858,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4573
4858
|
}
|
|
4574
4859
|
if (t instanceof ListTerm) {
|
|
4575
4860
|
let changed = false;
|
|
4576
|
-
const elems = t.elems.map(e => {
|
|
4861
|
+
const elems = t.elems.map((e) => {
|
|
4577
4862
|
const r = rewriteTerm(e);
|
|
4578
4863
|
if (r !== e) changed = true;
|
|
4579
4864
|
return r;
|
|
@@ -4582,7 +4867,7 @@ function materializeRdfLists(triples, forwardRules, backwardRules) {
|
|
|
4582
4867
|
}
|
|
4583
4868
|
if (t instanceof OpenListTerm) {
|
|
4584
4869
|
let changed = false;
|
|
4585
|
-
const prefix = t.prefix.map(e => {
|
|
4870
|
+
const prefix = t.prefix.map((e) => {
|
|
4586
4871
|
const r = rewriteTerm(e);
|
|
4587
4872
|
if (r !== e) changed = true;
|
|
4588
4873
|
return r;
|
|
@@ -4680,11 +4965,11 @@ function main() {
|
|
|
4680
4965
|
// --------------------------------------------------------------------------
|
|
4681
4966
|
// Positional args (the N3 file)
|
|
4682
4967
|
// --------------------------------------------------------------------------
|
|
4683
|
-
const positional = argv.filter(a => !a.startsWith("-"));
|
|
4968
|
+
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
4684
4969
|
|
|
4685
4970
|
if (positional.length !== 1) {
|
|
4686
4971
|
console.error(
|
|
4687
|
-
"Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>"
|
|
4972
|
+
"Usage: eyeling.js [--version|-v] [--no-proof-comments|-n] <file.n3>",
|
|
4688
4973
|
);
|
|
4689
4974
|
process.exit(1);
|
|
4690
4975
|
}
|
|
@@ -4707,10 +4992,10 @@ function main() {
|
|
|
4707
4992
|
// Build internal ListTerm values from rdf:first/rdf:rest (+ rdf:nil) input triples
|
|
4708
4993
|
materializeRdfLists(triples, frules, brules);
|
|
4709
4994
|
|
|
4710
|
-
const facts = triples.filter(tr => isGroundTriple(tr));
|
|
4995
|
+
const facts = triples.filter((tr) => isGroundTriple(tr));
|
|
4711
4996
|
const derived = forwardChain(facts, frules, brules);
|
|
4712
4997
|
|
|
4713
|
-
const derivedTriples = derived.map(df => df.fact);
|
|
4998
|
+
const derivedTriples = derived.map((df) => df.fact);
|
|
4714
4999
|
const usedPrefixes = prefixes.prefixesUsedForOutput(derivedTriples);
|
|
4715
5000
|
|
|
4716
5001
|
for (const [pfx, base] of usedPrefixes) {
|
|
@@ -4733,4 +5018,3 @@ function main() {
|
|
|
4733
5018
|
if (require.main === module) {
|
|
4734
5019
|
main();
|
|
4735
5020
|
}
|
|
4736
|
-
|