eyeling 1.25.2 → 1.25.4
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/HANDBOOK.md +12 -8
- package/dist/browser/eyeling.browser.js +335 -131
- package/eyeling.js +335 -131
- package/lib/cli.js +3 -1
- package/lib/engine.js +278 -87
- package/lib/lexer.js +42 -16
- package/lib/parser.js +12 -27
- package/package.json +1 -1
- package/test/api.test.js +40 -0
package/lib/cli.js
CHANGED
|
@@ -350,12 +350,14 @@ function main() {
|
|
|
350
350
|
outTriples = res.queryTriples;
|
|
351
351
|
outDerived = res.queryDerived;
|
|
352
352
|
} else {
|
|
353
|
+
const skipDerivedCollection = mayAutoRenderOutputStrings && !engine.getProofCommentsEnabled();
|
|
353
354
|
derived = engine.forwardChain(facts, frules, brules, null, {
|
|
354
355
|
captureExplanations: engine.getProofCommentsEnabled(),
|
|
356
|
+
collectDerived: !skipDerivedCollection,
|
|
355
357
|
prefixes,
|
|
356
358
|
});
|
|
357
359
|
outDerived = derived;
|
|
358
|
-
outTriples = derived.map((df) => df.fact);
|
|
360
|
+
outTriples = skipDerivedCollection ? [] : derived.map((df) => df.fact);
|
|
359
361
|
}
|
|
360
362
|
|
|
361
363
|
const renderedOutputTriples = hasQueries ? outTriples : facts;
|
package/lib/engine.js
CHANGED
|
@@ -1034,6 +1034,12 @@ function alphaEqGraphTriples(xs, ys, opts) {
|
|
|
1034
1034
|
// - __byPred: Map<predicateId, number[]> (indices into facts array)
|
|
1035
1035
|
// - __byPS: Map<predicateId, Map<subjectId, number[]>>
|
|
1036
1036
|
// - __byPO: Map<predicateId, Map<objectId, number[]>>
|
|
1037
|
+
// - __byPNonFastS / __byPNonFastO: Map<predicateId, number[]>
|
|
1038
|
+
// IRI-predicate facts whose subject/object cannot be indexed by fast key.
|
|
1039
|
+
// These are the fallback clauses for a constrained subject/object, just
|
|
1040
|
+
// like a Prolog clause index keeps variable-headed clauses as fallback.
|
|
1041
|
+
// - __varPred* indexes: facts whose predicate is a Var. Only these non-IRI
|
|
1042
|
+
// predicate facts can unify with a ground IRI predicate goal.
|
|
1037
1043
|
// - __keySet: Set<"S\tP\tO"> for Iri/Literal/Blank-only triples (fast dup check)
|
|
1038
1044
|
//
|
|
1039
1045
|
// Backward rules:
|
|
@@ -1049,6 +1055,8 @@ const __compoundKeyToTid = new Map();
|
|
|
1049
1055
|
// Use a negative id space so we never collide with __tid (which is positive).
|
|
1050
1056
|
let __nextCompoundTid = -1;
|
|
1051
1057
|
|
|
1058
|
+
const EMPTY_FACT_INDEX_BUCKET = Object.freeze([]);
|
|
1059
|
+
|
|
1052
1060
|
function __internCompoundTid(key) {
|
|
1053
1061
|
const hit = __compoundKeyToTid.get(key);
|
|
1054
1062
|
if (hit !== undefined) return hit;
|
|
@@ -1100,6 +1108,71 @@ function termFastKey(t) {
|
|
|
1100
1108
|
return null;
|
|
1101
1109
|
}
|
|
1102
1110
|
|
|
1111
|
+
function encodeLookupKeyPart(k) {
|
|
1112
|
+
if (typeof k === 'number') return 'T' + k;
|
|
1113
|
+
const s = String(k);
|
|
1114
|
+
return 'K' + s.length + ':' + s;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function literalLookupKey(t) {
|
|
1118
|
+
const boolInfo = parseBooleanLiteralInfo(t);
|
|
1119
|
+
if (boolInfo) return '\u0000B' + (boolInfo.value ? '1' : '0');
|
|
1120
|
+
|
|
1121
|
+
const numInfo = parseNumericLiteralInfo(t);
|
|
1122
|
+
if (numInfo) {
|
|
1123
|
+
if (numInfo.kind === 'bigint') return '\u0000N' + numInfo.dt + '\u0000' + numInfo.value.toString();
|
|
1124
|
+
|
|
1125
|
+
const n = numInfo.value;
|
|
1126
|
+
// Normal unification intentionally does not make NaN value-equal to NaN;
|
|
1127
|
+
// only identical lexical literals match through the ordinary __tid path.
|
|
1128
|
+
if (!Number.isNaN(n)) return '\u0000N' + numInfo.dt + '\u0000' + String(n);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// Covers exact literals plus plain string / xsd:string canonicalization, which
|
|
1132
|
+
// Literal construction already normalizes into a shared __tid.
|
|
1133
|
+
return termFastKey(t);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function termLookupKey(t) {
|
|
1137
|
+
// Lookup keys summarize the equality accepted by ordinary unifyTerm(), not
|
|
1138
|
+
// merely object identity. This keeps literal-index pruning complete for
|
|
1139
|
+
// value-equivalent booleans/numerics such as true/"1"^^xsd:boolean and
|
|
1140
|
+
// 1.0/1.00, while preserving exact fast ids for IRIs, blanks, and strings.
|
|
1141
|
+
if (t instanceof Iri) {
|
|
1142
|
+
if (t.value === RDF_NIL_IRI) return '\u0000L0';
|
|
1143
|
+
return t.__tid;
|
|
1144
|
+
}
|
|
1145
|
+
if (t instanceof Blank) return t.__tid;
|
|
1146
|
+
if (t instanceof Literal) return literalLookupKey(t);
|
|
1147
|
+
|
|
1148
|
+
if (t instanceof ListTerm) {
|
|
1149
|
+
const cached = t.__lookupKey;
|
|
1150
|
+
if (cached !== undefined) return cached === false ? null : cached;
|
|
1151
|
+
|
|
1152
|
+
const xs = t.elems;
|
|
1153
|
+
if (xs.length === 0) {
|
|
1154
|
+
Object.defineProperty(t, '__lookupKey', { value: '\u0000L0', enumerable: false });
|
|
1155
|
+
return '\u0000L0';
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const parts = new Array(xs.length);
|
|
1159
|
+
for (let i = 0; i < xs.length; i++) {
|
|
1160
|
+
const k = termLookupKey(xs[i]);
|
|
1161
|
+
if (k === null) {
|
|
1162
|
+
Object.defineProperty(t, '__lookupKey', { value: false, enumerable: false });
|
|
1163
|
+
return null;
|
|
1164
|
+
}
|
|
1165
|
+
parts[i] = encodeLookupKeyPart(k);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const key = '\u0000L' + xs.length + '\u0001' + parts.join('\u0001');
|
|
1169
|
+
Object.defineProperty(t, '__lookupKey', { value: key, enumerable: false });
|
|
1170
|
+
return key;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1103
1176
|
function tripleFastKey(tr) {
|
|
1104
1177
|
const ks = termFastKey(tr.s);
|
|
1105
1178
|
const kp = termFastKey(tr.p);
|
|
@@ -1113,9 +1186,13 @@ function ensureFactIndexes(facts) {
|
|
|
1113
1186
|
facts.__byPred &&
|
|
1114
1187
|
facts.__byPS &&
|
|
1115
1188
|
facts.__byPO &&
|
|
1116
|
-
facts.
|
|
1117
|
-
facts.
|
|
1118
|
-
facts.
|
|
1189
|
+
facts.__byPNonFastS &&
|
|
1190
|
+
facts.__byPNonFastO &&
|
|
1191
|
+
facts.__varPred &&
|
|
1192
|
+
facts.__varPredPS &&
|
|
1193
|
+
facts.__varPredPO &&
|
|
1194
|
+
facts.__varPredNonFastS &&
|
|
1195
|
+
facts.__varPredNonFastO &&
|
|
1119
1196
|
facts.__keySet
|
|
1120
1197
|
)
|
|
1121
1198
|
return;
|
|
@@ -1135,21 +1212,41 @@ function ensureFactIndexes(facts) {
|
|
|
1135
1212
|
enumerable: false,
|
|
1136
1213
|
writable: true,
|
|
1137
1214
|
});
|
|
1138
|
-
Object.defineProperty(facts, '
|
|
1215
|
+
Object.defineProperty(facts, '__byPNonFastS', {
|
|
1216
|
+
value: new Map(),
|
|
1217
|
+
enumerable: false,
|
|
1218
|
+
writable: true,
|
|
1219
|
+
});
|
|
1220
|
+
Object.defineProperty(facts, '__byPNonFastO', {
|
|
1221
|
+
value: new Map(),
|
|
1222
|
+
enumerable: false,
|
|
1223
|
+
writable: true,
|
|
1224
|
+
});
|
|
1225
|
+
Object.defineProperty(facts, '__varPred', {
|
|
1139
1226
|
value: [],
|
|
1140
1227
|
enumerable: false,
|
|
1141
1228
|
writable: true,
|
|
1142
1229
|
});
|
|
1143
|
-
Object.defineProperty(facts, '
|
|
1230
|
+
Object.defineProperty(facts, '__varPredPS', {
|
|
1144
1231
|
value: new Map(),
|
|
1145
1232
|
enumerable: false,
|
|
1146
1233
|
writable: true,
|
|
1147
1234
|
});
|
|
1148
|
-
Object.defineProperty(facts, '
|
|
1235
|
+
Object.defineProperty(facts, '__varPredPO', {
|
|
1149
1236
|
value: new Map(),
|
|
1150
1237
|
enumerable: false,
|
|
1151
1238
|
writable: true,
|
|
1152
1239
|
});
|
|
1240
|
+
Object.defineProperty(facts, '__varPredNonFastS', {
|
|
1241
|
+
value: [],
|
|
1242
|
+
enumerable: false,
|
|
1243
|
+
writable: true,
|
|
1244
|
+
});
|
|
1245
|
+
Object.defineProperty(facts, '__varPredNonFastO', {
|
|
1246
|
+
value: [],
|
|
1247
|
+
enumerable: false,
|
|
1248
|
+
writable: true,
|
|
1249
|
+
});
|
|
1153
1250
|
Object.defineProperty(facts, '__keySet', {
|
|
1154
1251
|
value: new Set(),
|
|
1155
1252
|
enumerable: false,
|
|
@@ -1190,16 +1287,53 @@ function cloneFactIndexesForSnapshot(src, dest) {
|
|
|
1190
1287
|
Object.defineProperty(dest, '__byPred', { value: cloneArrayMap(src.__byPred), enumerable: false, writable: true });
|
|
1191
1288
|
Object.defineProperty(dest, '__byPS', { value: cloneNestedArrayMap(src.__byPS), enumerable: false, writable: true });
|
|
1192
1289
|
Object.defineProperty(dest, '__byPO', { value: cloneNestedArrayMap(src.__byPO), enumerable: false, writable: true });
|
|
1193
|
-
Object.defineProperty(dest, '
|
|
1194
|
-
|
|
1195
|
-
|
|
1290
|
+
Object.defineProperty(dest, '__byPNonFastS', {
|
|
1291
|
+
value: cloneArrayMap(src.__byPNonFastS),
|
|
1292
|
+
enumerable: false,
|
|
1293
|
+
writable: true,
|
|
1294
|
+
});
|
|
1295
|
+
Object.defineProperty(dest, '__byPNonFastO', {
|
|
1296
|
+
value: cloneArrayMap(src.__byPNonFastO),
|
|
1297
|
+
enumerable: false,
|
|
1298
|
+
writable: true,
|
|
1299
|
+
});
|
|
1300
|
+
Object.defineProperty(dest, '__varPred', { value: src.__varPred.slice(), enumerable: false, writable: true });
|
|
1301
|
+
Object.defineProperty(dest, '__varPredPS', {
|
|
1302
|
+
value: cloneArrayMap(src.__varPredPS),
|
|
1303
|
+
enumerable: false,
|
|
1304
|
+
writable: true,
|
|
1305
|
+
});
|
|
1306
|
+
Object.defineProperty(dest, '__varPredPO', {
|
|
1307
|
+
value: cloneArrayMap(src.__varPredPO),
|
|
1308
|
+
enumerable: false,
|
|
1309
|
+
writable: true,
|
|
1310
|
+
});
|
|
1311
|
+
Object.defineProperty(dest, '__varPredNonFastS', {
|
|
1312
|
+
value: src.__varPredNonFastS.slice(),
|
|
1313
|
+
enumerable: false,
|
|
1314
|
+
writable: true,
|
|
1315
|
+
});
|
|
1316
|
+
Object.defineProperty(dest, '__varPredNonFastO', {
|
|
1317
|
+
value: src.__varPredNonFastO.slice(),
|
|
1318
|
+
enumerable: false,
|
|
1319
|
+
writable: true,
|
|
1320
|
+
});
|
|
1196
1321
|
Object.defineProperty(dest, '__keySet', { value: new Set(src.__keySet), enumerable: false, writable: true });
|
|
1197
1322
|
Object.defineProperty(dest, '__keySetComplete', { value: !!src.__keySetComplete, enumerable: false, writable: true });
|
|
1198
1323
|
}
|
|
1199
1324
|
|
|
1325
|
+
function addToIndexArrayMap(map, key, value) {
|
|
1326
|
+
let bucket = map.get(key);
|
|
1327
|
+
if (!bucket) {
|
|
1328
|
+
bucket = [];
|
|
1329
|
+
map.set(key, bucket);
|
|
1330
|
+
}
|
|
1331
|
+
bucket.push(value);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1200
1334
|
function indexFact(facts, tr, idx, addKeySet = true) {
|
|
1201
|
-
const sk =
|
|
1202
|
-
const ok =
|
|
1335
|
+
const sk = termLookupKey(tr.s);
|
|
1336
|
+
const ok = termLookupKey(tr.o);
|
|
1203
1337
|
let pkForKey = null;
|
|
1204
1338
|
|
|
1205
1339
|
if (tr.p instanceof Iri) {
|
|
@@ -1220,12 +1354,9 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
1220
1354
|
ps = new Map();
|
|
1221
1355
|
facts.__byPS.set(pk, ps);
|
|
1222
1356
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
ps.set(sk, psb);
|
|
1227
|
-
}
|
|
1228
|
-
psb.push(idx);
|
|
1357
|
+
addToIndexArrayMap(ps, sk, idx);
|
|
1358
|
+
} else {
|
|
1359
|
+
addToIndexArrayMap(facts.__byPNonFastS, pk, idx);
|
|
1229
1360
|
}
|
|
1230
1361
|
|
|
1231
1362
|
if (ok !== null) {
|
|
@@ -1234,32 +1365,23 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
1234
1365
|
po = new Map();
|
|
1235
1366
|
facts.__byPO.set(pk, po);
|
|
1236
1367
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
po.set(ok, pob);
|
|
1241
|
-
}
|
|
1242
|
-
pob.push(idx);
|
|
1368
|
+
addToIndexArrayMap(po, ok, idx);
|
|
1369
|
+
} else {
|
|
1370
|
+
addToIndexArrayMap(facts.__byPNonFastO, pk, idx);
|
|
1243
1371
|
}
|
|
1244
|
-
} else {
|
|
1245
|
-
facts.
|
|
1372
|
+
} else if (tr.p instanceof Var) {
|
|
1373
|
+
facts.__varPred.push(idx);
|
|
1246
1374
|
|
|
1247
1375
|
if (sk !== null) {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
facts.__wildPS.set(sk, psb);
|
|
1252
|
-
}
|
|
1253
|
-
psb.push(idx);
|
|
1376
|
+
addToIndexArrayMap(facts.__varPredPS, sk, idx);
|
|
1377
|
+
} else {
|
|
1378
|
+
facts.__varPredNonFastS.push(idx);
|
|
1254
1379
|
}
|
|
1255
1380
|
|
|
1256
1381
|
if (ok !== null) {
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
facts.__wildPO.set(ok, pob);
|
|
1261
|
-
}
|
|
1262
|
-
pob.push(idx);
|
|
1382
|
+
addToIndexArrayMap(facts.__varPredPO, ok, idx);
|
|
1383
|
+
} else {
|
|
1384
|
+
facts.__varPredNonFastO.push(idx);
|
|
1263
1385
|
}
|
|
1264
1386
|
}
|
|
1265
1387
|
|
|
@@ -1269,55 +1391,86 @@ function indexFact(facts, tr, idx, addKeySet = true) {
|
|
|
1269
1391
|
}
|
|
1270
1392
|
}
|
|
1271
1393
|
|
|
1394
|
+
function mergeIndexBuckets(primary, fallback) {
|
|
1395
|
+
const a = primary && primary.length ? primary : null;
|
|
1396
|
+
const b = fallback && fallback.length ? fallback : null;
|
|
1397
|
+
if (!a && !b) return EMPTY_FACT_INDEX_BUCKET;
|
|
1398
|
+
if (!a) return b;
|
|
1399
|
+
if (!b) return a;
|
|
1400
|
+
const out = new Array(a.length + b.length);
|
|
1401
|
+
for (let i = 0; i < a.length; i++) out[i] = a[i];
|
|
1402
|
+
for (let i = 0; i < b.length; i++) out[a.length + i] = b[i];
|
|
1403
|
+
return out;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function selectPositionIndexedCandidates(all, exactByS, fallbackS, sk, exactByO, fallbackO, ok) {
|
|
1407
|
+
if (sk === null && ok === null) return all && all.length ? all : EMPTY_FACT_INDEX_BUCKET;
|
|
1408
|
+
|
|
1409
|
+
let sBucket = null;
|
|
1410
|
+
if (sk !== null) sBucket = mergeIndexBuckets(exactByS || null, fallbackS || null);
|
|
1411
|
+
|
|
1412
|
+
let oBucket = null;
|
|
1413
|
+
if (ok !== null) oBucket = mergeIndexBuckets(exactByO || null, fallbackO || null);
|
|
1414
|
+
|
|
1415
|
+
if (sk !== null && ok !== null) return sBucket.length <= oBucket.length ? sBucket : oBucket;
|
|
1416
|
+
return sk !== null ? sBucket : oBucket;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1272
1419
|
function candidateFacts(facts, goal) {
|
|
1273
1420
|
ensureFactIndexes(facts);
|
|
1274
1421
|
|
|
1275
1422
|
if (goal.p instanceof Iri) {
|
|
1276
1423
|
const pk = goal.p.__tid;
|
|
1277
1424
|
|
|
1278
|
-
const sk =
|
|
1279
|
-
const ok =
|
|
1425
|
+
const sk = termLookupKey(goal.s);
|
|
1426
|
+
const ok = termLookupKey(goal.o);
|
|
1280
1427
|
|
|
1281
|
-
/** @type {number[] | null} */
|
|
1282
1428
|
let byPS = null;
|
|
1283
1429
|
if (sk !== null) {
|
|
1284
1430
|
const ps = facts.__byPS.get(pk);
|
|
1285
1431
|
if (ps) byPS = ps.get(sk) || null;
|
|
1286
1432
|
}
|
|
1433
|
+
const byPNonFastS = sk !== null ? facts.__byPNonFastS.get(pk) || null : null;
|
|
1287
1434
|
|
|
1288
|
-
/** @type {number[] | null} */
|
|
1289
1435
|
let byPO = null;
|
|
1290
1436
|
if (ok !== null) {
|
|
1291
1437
|
const po = facts.__byPO.get(pk);
|
|
1292
1438
|
if (po) byPO = po.get(ok) || null;
|
|
1293
1439
|
}
|
|
1440
|
+
const byPNonFastO = ok !== null ? facts.__byPNonFastO.get(pk) || null : null;
|
|
1294
1441
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1442
|
+
const exact = selectPositionIndexedCandidates(
|
|
1443
|
+
facts.__byPred.get(pk) || null,
|
|
1444
|
+
byPS,
|
|
1445
|
+
byPNonFastS,
|
|
1446
|
+
sk,
|
|
1447
|
+
byPO,
|
|
1448
|
+
byPNonFastO,
|
|
1449
|
+
ok,
|
|
1450
|
+
);
|
|
1300
1451
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (sk !== null) wildPS = facts.__wildPS.get(sk) || null;
|
|
1452
|
+
let varPredPS = null;
|
|
1453
|
+
if (sk !== null) varPredPS = facts.__varPredPS.get(sk) || null;
|
|
1304
1454
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (ok !== null) wildPO = facts.__wildPO.get(ok) || null;
|
|
1455
|
+
let varPredPO = null;
|
|
1456
|
+
if (ok !== null) varPredPO = facts.__varPredPO.get(ok) || null;
|
|
1308
1457
|
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1458
|
+
const wild = selectPositionIndexedCandidates(
|
|
1459
|
+
facts.__varPred,
|
|
1460
|
+
varPredPS,
|
|
1461
|
+
sk !== null ? facts.__varPredNonFastS : null,
|
|
1462
|
+
sk,
|
|
1463
|
+
varPredPO,
|
|
1464
|
+
ok !== null ? facts.__varPredNonFastO : null,
|
|
1465
|
+
ok,
|
|
1466
|
+
);
|
|
1314
1467
|
|
|
1315
1468
|
return {
|
|
1316
|
-
exact
|
|
1317
|
-
wild
|
|
1318
|
-
exactLen: exact
|
|
1319
|
-
wildLen: wild
|
|
1320
|
-
totalLen:
|
|
1469
|
+
exact,
|
|
1470
|
+
wild,
|
|
1471
|
+
exactLen: exact.length,
|
|
1472
|
+
wildLen: wild.length,
|
|
1473
|
+
totalLen: exact.length + wild.length,
|
|
1321
1474
|
};
|
|
1322
1475
|
}
|
|
1323
1476
|
|
|
@@ -1335,20 +1488,28 @@ function hasFactIndexed(facts, tr) {
|
|
|
1335
1488
|
|
|
1336
1489
|
if (tr.p instanceof Iri) {
|
|
1337
1490
|
const pk = tr.p.__tid;
|
|
1491
|
+
const sk = termLookupKey(tr.s);
|
|
1492
|
+
let best = null;
|
|
1338
1493
|
|
|
1339
|
-
|
|
1494
|
+
if (sk !== null) {
|
|
1495
|
+
const ps = facts.__byPS.get(pk);
|
|
1496
|
+
if (!ps) return false;
|
|
1497
|
+
const psb = ps.get(sk);
|
|
1498
|
+
if (!psb || psb.length === 0) return false;
|
|
1499
|
+
best = psb;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const ok = termLookupKey(tr.o);
|
|
1340
1503
|
if (ok !== null) {
|
|
1341
1504
|
const po = facts.__byPO.get(pk);
|
|
1342
|
-
if (po)
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
// triples as duplicates modulo blank renaming, or you'll incorrectly
|
|
1347
|
-
// drop facts like: _:sk_0 :x 8.0 (because _:b8 :x 8.0 exists).
|
|
1348
|
-
return pob.some((i) => triplesEqual(facts[i], tr));
|
|
1349
|
-
}
|
|
1505
|
+
if (!po) return false;
|
|
1506
|
+
const pob = po.get(ok);
|
|
1507
|
+
if (!pob || pob.length === 0) return false;
|
|
1508
|
+
if (!best || pob.length < best.length) best = pob;
|
|
1350
1509
|
}
|
|
1351
1510
|
|
|
1511
|
+
if (best) return best.some((i) => triplesEqual(facts[i], tr));
|
|
1512
|
+
|
|
1352
1513
|
const pb = facts.__byPred.get(pk) || [];
|
|
1353
1514
|
return pb.some((i) => triplesEqual(facts[i], tr));
|
|
1354
1515
|
}
|
|
@@ -1457,11 +1618,25 @@ function mergeSinglePremiseAgendaBuckets() {
|
|
|
1457
1618
|
return out;
|
|
1458
1619
|
}
|
|
1459
1620
|
|
|
1621
|
+
function termContainsVarForAgenda(t) {
|
|
1622
|
+
if (t instanceof Var) return true;
|
|
1623
|
+
if (t instanceof ListTerm) return t.elems.some(termContainsVarForAgenda);
|
|
1624
|
+
if (t instanceof OpenListTerm) return true;
|
|
1625
|
+
if (t instanceof GraphTerm)
|
|
1626
|
+
return t.triples.some(
|
|
1627
|
+
(tr) =>
|
|
1628
|
+
termContainsVarForAgenda(tr.s) || termContainsVarForAgenda(tr.p) || termContainsVarForAgenda(tr.o),
|
|
1629
|
+
);
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1460
1633
|
function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
1461
1634
|
const index = {
|
|
1462
1635
|
byPred: new Map(),
|
|
1636
|
+
byPredAll: new Map(),
|
|
1463
1637
|
byPS: new Map(),
|
|
1464
1638
|
byPO: new Map(),
|
|
1639
|
+
allIriPred: [],
|
|
1465
1640
|
wildPred: [],
|
|
1466
1641
|
wildPS: new Map(),
|
|
1467
1642
|
wildPO: new Map(),
|
|
@@ -1483,8 +1658,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1483
1658
|
if (!isSinglePremiseAgendaRuleSafe(r, backRules)) continue;
|
|
1484
1659
|
|
|
1485
1660
|
const goal = r.premise[0];
|
|
1486
|
-
const goalSKey =
|
|
1487
|
-
const goalOKey =
|
|
1661
|
+
const goalSKey = termLookupKey(goal.s);
|
|
1662
|
+
const goalOKey = termLookupKey(goal.o);
|
|
1488
1663
|
const fastSubjectVar = goal.p instanceof Iri && goal.s instanceof Var && goalOKey !== null ? goal.s.name : null;
|
|
1489
1664
|
const fastObjectVar = goal.p instanceof Iri && goal.o instanceof Var && goalSKey !== null ? goal.o.name : null;
|
|
1490
1665
|
const entry = {
|
|
@@ -1503,6 +1678,8 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1503
1678
|
index.size += 1;
|
|
1504
1679
|
|
|
1505
1680
|
if (entry.goalPredTid !== null) {
|
|
1681
|
+
addToMapArray(index.byPredAll, entry.goalPredTid, entry);
|
|
1682
|
+
index.allIriPred.push(entry);
|
|
1506
1683
|
if (entry.goalSKey === null && entry.goalOKey === null) addToMapArray(index.byPred, entry.goalPredTid, entry);
|
|
1507
1684
|
if (entry.goalSKey !== null) {
|
|
1508
1685
|
let ps = index.byPS.get(entry.goalPredTid);
|
|
@@ -1533,25 +1710,37 @@ function makeSinglePremiseAgendaIndex(forwardRules, backRules) {
|
|
|
1533
1710
|
function getSinglePremiseAgendaCandidates(index, fact) {
|
|
1534
1711
|
if (!index || index.size === 0) return null;
|
|
1535
1712
|
|
|
1536
|
-
const sk =
|
|
1537
|
-
const ok =
|
|
1713
|
+
const sk = termLookupKey(fact.s);
|
|
1714
|
+
const ok = termLookupKey(fact.o);
|
|
1538
1715
|
|
|
1539
1716
|
let exact = null;
|
|
1540
1717
|
if (fact.p instanceof Iri) {
|
|
1541
1718
|
const pk = fact.p.__tid;
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1719
|
+
if ((sk === null && termContainsVarForAgenda(fact.s)) || (ok === null && termContainsVarForAgenda(fact.o))) {
|
|
1720
|
+
// A fact with a variable-bearing subject/object (most importantly a
|
|
1721
|
+
// top-level variable fact such as `?S :p ?O.`) can match rules whose
|
|
1722
|
+
// premise is fixed in that position. The ordinary `(p,s)` / `(p,o)` lookup
|
|
1723
|
+
// would miss those rules, so fall back to all agenda-indexed rules for
|
|
1724
|
+
// this predicate. Do not do this merely for non-fast quoted formulas:
|
|
1725
|
+
// they are not wildcards, and broad fallback would over-fire rules that
|
|
1726
|
+
// rely on protected blank-node/formula unification semantics.
|
|
1727
|
+
exact = index.byPredAll.get(pk) || null;
|
|
1728
|
+
} else {
|
|
1729
|
+
const byPred = index.byPred.get(pk) || null;
|
|
1730
|
+
let byPS = null;
|
|
1545
1731
|
const ps = index.byPS.get(pk);
|
|
1546
1732
|
if (ps) byPS = ps.get(sk) || null;
|
|
1547
|
-
|
|
1548
|
-
let byPO = null;
|
|
1549
|
-
if (ok !== null) {
|
|
1733
|
+
let byPO = null;
|
|
1550
1734
|
const po = index.byPO.get(pk);
|
|
1551
1735
|
if (po) byPO = po.get(ok) || null;
|
|
1552
|
-
}
|
|
1553
1736
|
|
|
1554
|
-
|
|
1737
|
+
exact = mergeSinglePremiseAgendaBuckets(byPred, byPS, byPO);
|
|
1738
|
+
}
|
|
1739
|
+
} else if (fact.p instanceof Var) {
|
|
1740
|
+
// A variable-predicate fact can match any IRI-predicate agenda rule.
|
|
1741
|
+
// This is deliberately broad and relies on final unification below; such
|
|
1742
|
+
// facts are uncommon and correctness matters more than over-indexing them.
|
|
1743
|
+
exact = index.allIriPred.length ? index.allIriPred : null;
|
|
1555
1744
|
}
|
|
1556
1745
|
|
|
1557
1746
|
const wildPred = index.wildPred.length ? index.wildPred : null;
|
|
@@ -2901,6 +3090,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
2901
3090
|
__attachGoalTable(backRules, goalTable);
|
|
2902
3091
|
|
|
2903
3092
|
const captureExplanations = !(opts && opts.captureExplanations === false);
|
|
3093
|
+
const collectDerived = !(opts && opts.collectDerived === false);
|
|
3094
|
+
const hasDerivedCallback = typeof onDerived === 'function';
|
|
2904
3095
|
const derivedForward = [];
|
|
2905
3096
|
const varGen = [0];
|
|
2906
3097
|
const skCounter = [0];
|
|
@@ -3079,8 +3270,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3079
3270
|
if (!hasFactIndexed(facts, instantiated)) {
|
|
3080
3271
|
pushFactIndexed(facts, instantiated);
|
|
3081
3272
|
const df = makeDerivedRecord(instantiated, r, getInstantiatedPremises(), s, captureExplanations);
|
|
3082
|
-
derivedForward.push(df);
|
|
3083
|
-
if (
|
|
3273
|
+
if (collectDerived) derivedForward.push(df);
|
|
3274
|
+
if (hasDerivedCallback) onDerived(df);
|
|
3084
3275
|
changedHere = true;
|
|
3085
3276
|
}
|
|
3086
3277
|
|
|
@@ -3143,8 +3334,8 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */,
|
|
|
3143
3334
|
|
|
3144
3335
|
pushFactIndexed(facts, inst);
|
|
3145
3336
|
const df = makeDerivedRecord(inst, r, getInstantiatedPremises(), s, captureExplanations);
|
|
3146
|
-
derivedForward.push(df);
|
|
3147
|
-
if (
|
|
3337
|
+
if (collectDerived) derivedForward.push(df);
|
|
3338
|
+
if (hasDerivedCallback) onDerived(df);
|
|
3148
3339
|
|
|
3149
3340
|
changedHere = true;
|
|
3150
3341
|
}
|
package/lib/lexer.js
CHANGED
|
@@ -121,15 +121,6 @@ function isIdentChar(c) {
|
|
|
121
121
|
return c === ':' || isPnChars(c);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function canContinueAfterDot(next) {
|
|
125
|
-
// PN_LOCAL allows '.' but it cannot appear at the end.
|
|
126
|
-
// We include '.' only if it is followed by something that could continue a name.
|
|
127
|
-
if (next === null) return false;
|
|
128
|
-
if (isIdentChar(next)) return true;
|
|
129
|
-
if (next === '%' || next === '\\') return true;
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
124
|
function isForbiddenNoncharacterCodePoint(cp) {
|
|
134
125
|
return (cp & 0xffff) === 0xfffe || (cp & 0xffff) === 0xffff;
|
|
135
126
|
}
|
|
@@ -1205,7 +1196,9 @@ function lex(inputText, opts = {}) {
|
|
|
1205
1196
|
// Avoid copying large ASCII/BMP inputs into an Array. Array.from() is
|
|
1206
1197
|
// only needed when the text contains surrogate pairs and we want the old
|
|
1207
1198
|
// code-point iteration behavior for non-BMP characters.
|
|
1208
|
-
const
|
|
1199
|
+
const hasSurrogates = /[\uD800-\uDFFF]/.test(inputText);
|
|
1200
|
+
const inputMayContainInvalidStringChar = hasSurrogates || /[\u0000\uFFFE\uFFFF]/.test(inputText);
|
|
1201
|
+
const chars = hasSurrogates ? Array.from(inputText) : inputText;
|
|
1209
1202
|
const n = chars.length;
|
|
1210
1203
|
let i = 0;
|
|
1211
1204
|
const tokens = [];
|
|
@@ -1240,14 +1233,47 @@ function lex(inputText, opts = {}) {
|
|
|
1240
1233
|
// Hard stops: delimiters cannot appear unescaped inside PNAME tokens.
|
|
1241
1234
|
if (cc === '{' || cc === '}' || cc === '(' || cc === ')' || cc === '[' || cc === ']' || cc === ';' || cc === ',') break;
|
|
1242
1235
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1236
|
+
const code = cc.charCodeAt(0);
|
|
1237
|
+
|
|
1238
|
+
// Common ASCII QName/identifier characters. Keep this branch inline so
|
|
1239
|
+
// ordinary N3 files do not call through the full Unicode PN_CHARS predicate
|
|
1240
|
+
// for every character.
|
|
1241
|
+
if (
|
|
1242
|
+
code === 58 || // ':'
|
|
1243
|
+
code === 95 || // '_'
|
|
1244
|
+
code === 45 || // '-'
|
|
1245
|
+
(code >= 48 && code <= 57) ||
|
|
1246
|
+
(code >= 65 && code <= 90) ||
|
|
1247
|
+
(code >= 97 && code <= 122)
|
|
1248
|
+
) {
|
|
1249
|
+
if (out !== null) out.push(cc);
|
|
1247
1250
|
i++;
|
|
1248
1251
|
continue;
|
|
1249
1252
|
}
|
|
1250
1253
|
|
|
1254
|
+
// Dot is allowed inside PN_LOCAL, but not at the end.
|
|
1255
|
+
if (cc === '.') {
|
|
1256
|
+
const next = peek(1);
|
|
1257
|
+
if (next === null) break;
|
|
1258
|
+
const ncode = next.charCodeAt(0);
|
|
1259
|
+
if (
|
|
1260
|
+
next === '%' ||
|
|
1261
|
+
next === '\\' ||
|
|
1262
|
+
ncode === 58 ||
|
|
1263
|
+
ncode === 95 ||
|
|
1264
|
+
ncode === 45 ||
|
|
1265
|
+
(ncode >= 48 && ncode <= 57) ||
|
|
1266
|
+
(ncode >= 65 && ncode <= 90) ||
|
|
1267
|
+
(ncode >= 97 && ncode <= 122) ||
|
|
1268
|
+
isIdentChar(next)
|
|
1269
|
+
) {
|
|
1270
|
+
if (out !== null) out.push('.');
|
|
1271
|
+
i++;
|
|
1272
|
+
continue;
|
|
1273
|
+
}
|
|
1274
|
+
break;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1251
1277
|
// Percent escape: %HH
|
|
1252
1278
|
if (cc === '%') {
|
|
1253
1279
|
const h1 = peek(1);
|
|
@@ -1495,7 +1521,7 @@ function lex(inputText, opts = {}) {
|
|
|
1495
1521
|
}
|
|
1496
1522
|
const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
|
|
1497
1523
|
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
1498
|
-
assertValidStringLiteralValue(decoded, start);
|
|
1524
|
+
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
1499
1525
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
1500
1526
|
tokens.push(new Token('Literal', s, start));
|
|
1501
1527
|
continue;
|
|
@@ -1585,7 +1611,7 @@ function lex(inputText, opts = {}) {
|
|
|
1585
1611
|
}
|
|
1586
1612
|
const rawContent = sChars === null ? sliceChars(contentStart, closed ? i - 1 : i) : sChars.join('');
|
|
1587
1613
|
const decoded = sChars === null ? rawContent : decodeN3StringEscapes(rawContent, start);
|
|
1588
|
-
assertValidStringLiteralValue(decoded, start);
|
|
1614
|
+
if (sChars !== null || inputMayContainInvalidStringChar) assertValidStringLiteralValue(decoded, start);
|
|
1589
1615
|
const s = JSON.stringify(decoded); // canonical short quoted form
|
|
1590
1616
|
tokens.push(new Token('Literal', s, start));
|
|
1591
1617
|
continue;
|