eyeling 1.6.3 → 1.6.5
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/eyeling.js +330 -237
- package/package.json +1 -1
package/eyeling.js
CHANGED
|
@@ -1263,24 +1263,50 @@ function termsEqual(a, b) {
|
|
|
1263
1263
|
if (a === b) return true;
|
|
1264
1264
|
if (!a || !b) return false;
|
|
1265
1265
|
if (a.constructor !== b.constructor) return false;
|
|
1266
|
+
|
|
1266
1267
|
if (a instanceof Iri) return a.value === b.value;
|
|
1268
|
+
|
|
1267
1269
|
if (a instanceof Literal) {
|
|
1268
1270
|
if (a.value === b.value) return true;
|
|
1271
|
+
|
|
1272
|
+
// Plain "abc" == "abc"^^xsd:string (but not language-tagged strings)
|
|
1269
1273
|
if (literalsEquivalentAsXsdString(a.value, b.value)) return true;
|
|
1270
1274
|
|
|
1271
|
-
// Keep in sync with unifyTerm(): numeric-value equality
|
|
1272
|
-
const
|
|
1273
|
-
const
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1275
|
+
// Keep in sync with unifyTerm(): numeric-value equality, datatype-aware.
|
|
1276
|
+
const ai = parseNumericLiteralInfo(a);
|
|
1277
|
+
const bi = parseNumericLiteralInfo(b);
|
|
1278
|
+
|
|
1279
|
+
if (ai && bi) {
|
|
1280
|
+
// Same datatype => compare values
|
|
1281
|
+
if (ai.dt === bi.dt) {
|
|
1282
|
+
if (ai.kind === 'bigint' && bi.kind === 'bigint') return ai.value === bi.value;
|
|
1283
|
+
|
|
1284
|
+
const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
|
|
1285
|
+
const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
|
|
1286
|
+
return !Number.isNaN(an) && !Number.isNaN(bn) && an === bn;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// integer <-> decimal: allow exact equality (but NOT with double)
|
|
1290
|
+
const intDt = XSD_NS + 'integer';
|
|
1291
|
+
const decDt = XSD_NS + 'decimal';
|
|
1292
|
+
if ((ai.dt === intDt && bi.dt === decDt) || (ai.dt === decDt && bi.dt === intDt)) {
|
|
1293
|
+
const intInfo = ai.dt === intDt ? ai : bi;
|
|
1294
|
+
const decInfo = ai.dt === decDt ? ai : bi;
|
|
1295
|
+
|
|
1296
|
+
const dec = parseXsdDecimalToBigIntScale(decInfo.lexStr);
|
|
1297
|
+
if (dec) {
|
|
1298
|
+
const scaledInt = intInfo.value * pow10n(dec.scale);
|
|
1299
|
+
return scaledInt === dec.num;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1279
1302
|
}
|
|
1303
|
+
|
|
1280
1304
|
return false;
|
|
1281
1305
|
}
|
|
1306
|
+
|
|
1282
1307
|
if (a instanceof Var) return a.name === b.name;
|
|
1283
1308
|
if (a instanceof Blank) return a.label === b.label;
|
|
1309
|
+
|
|
1284
1310
|
if (a instanceof ListTerm) {
|
|
1285
1311
|
if (a.elems.length !== b.elems.length) return false;
|
|
1286
1312
|
for (let i = 0; i < a.elems.length; i++) {
|
|
@@ -1288,6 +1314,7 @@ function termsEqual(a, b) {
|
|
|
1288
1314
|
}
|
|
1289
1315
|
return true;
|
|
1290
1316
|
}
|
|
1317
|
+
|
|
1291
1318
|
if (a instanceof OpenListTerm) {
|
|
1292
1319
|
if (a.tailVar !== b.tailVar) return false;
|
|
1293
1320
|
if (a.prefix.length !== b.prefix.length) return false;
|
|
@@ -1296,9 +1323,79 @@ function termsEqual(a, b) {
|
|
|
1296
1323
|
}
|
|
1297
1324
|
return true;
|
|
1298
1325
|
}
|
|
1326
|
+
|
|
1327
|
+
if (a instanceof FormulaTerm) {
|
|
1328
|
+
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function termsEqualNoIntDecimal(a, b) {
|
|
1335
|
+
if (a === b) return true;
|
|
1336
|
+
if (!a || !b) return false;
|
|
1337
|
+
if (a.constructor !== b.constructor) return false;
|
|
1338
|
+
|
|
1339
|
+
if (a instanceof Iri) return a.value === b.value;
|
|
1340
|
+
|
|
1341
|
+
if (a instanceof Literal) {
|
|
1342
|
+
if (a.value === b.value) return true;
|
|
1343
|
+
|
|
1344
|
+
// Plain "abc" == "abc"^^xsd:string (but not language-tagged)
|
|
1345
|
+
if (literalsEquivalentAsXsdString(a.value, b.value)) return true;
|
|
1346
|
+
|
|
1347
|
+
// Numeric equality ONLY when datatypes agree (no integer<->decimal here)
|
|
1348
|
+
const ai = parseNumericLiteralInfo(a);
|
|
1349
|
+
const bi = parseNumericLiteralInfo(b);
|
|
1350
|
+
if (ai && bi && ai.dt === bi.dt) {
|
|
1351
|
+
// integer: exact bigint
|
|
1352
|
+
if (ai.kind === 'bigint' && bi.kind === 'bigint') return ai.value === bi.value;
|
|
1353
|
+
|
|
1354
|
+
// decimal: compare exactly via num/scale if possible
|
|
1355
|
+
if (ai.dt === XSD_NS + 'decimal') {
|
|
1356
|
+
const da = parseXsdDecimalToBigIntScale(ai.lexStr);
|
|
1357
|
+
const db = parseXsdDecimalToBigIntScale(bi.lexStr);
|
|
1358
|
+
if (da && db) {
|
|
1359
|
+
const scale = Math.max(da.scale, db.scale);
|
|
1360
|
+
const na = da.num * pow10n(scale - da.scale);
|
|
1361
|
+
const nb = db.num * pow10n(scale - db.scale);
|
|
1362
|
+
return na === nb;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// double/float-ish: JS number (same as your normal same-dt path)
|
|
1367
|
+
const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
|
|
1368
|
+
const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
|
|
1369
|
+
return !Number.isNaN(an) && !Number.isNaN(bn) && an === bn;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
if (a instanceof Var) return a.name === b.name;
|
|
1376
|
+
if (a instanceof Blank) return a.label === b.label;
|
|
1377
|
+
|
|
1378
|
+
if (a instanceof ListTerm) {
|
|
1379
|
+
if (a.elems.length !== b.elems.length) return false;
|
|
1380
|
+
for (let i = 0; i < a.elems.length; i++) {
|
|
1381
|
+
if (!termsEqualNoIntDecimal(a.elems[i], b.elems[i])) return false;
|
|
1382
|
+
}
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
if (a instanceof OpenListTerm) {
|
|
1387
|
+
if (a.tailVar !== b.tailVar) return false;
|
|
1388
|
+
if (a.prefix.length !== b.prefix.length) return false;
|
|
1389
|
+
for (let i = 0; i < a.prefix.length; i++) {
|
|
1390
|
+
if (!termsEqualNoIntDecimal(a.prefix[i], b.prefix[i])) return false;
|
|
1391
|
+
}
|
|
1392
|
+
return true;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1299
1395
|
if (a instanceof FormulaTerm) {
|
|
1300
1396
|
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1301
1397
|
}
|
|
1398
|
+
|
|
1302
1399
|
return false;
|
|
1303
1400
|
}
|
|
1304
1401
|
|
|
@@ -1906,13 +2003,30 @@ function unifyTerm(a, b, subst) {
|
|
|
1906
2003
|
const ai = parseNumericLiteralInfo(a);
|
|
1907
2004
|
const bi = parseNumericLiteralInfo(b);
|
|
1908
2005
|
|
|
1909
|
-
if (ai && bi
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
2006
|
+
if (ai && bi) {
|
|
2007
|
+
// same datatype: keep existing behavior
|
|
2008
|
+
if (ai.dt === bi.dt) {
|
|
2009
|
+
if (ai.kind === 'bigint' && bi.kind === 'bigint') {
|
|
2010
|
+
if (ai.value === bi.value) return { ...subst };
|
|
2011
|
+
} else {
|
|
2012
|
+
const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
|
|
2013
|
+
const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
|
|
2014
|
+
if (!Number.isNaN(an) && !Number.isNaN(bn) && an === bn) return { ...subst };
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
// integer <-> decimal: allow exact equality (but NOT with double)
|
|
2019
|
+
const intDt = XSD_NS + 'integer';
|
|
2020
|
+
const decDt = XSD_NS + 'decimal';
|
|
2021
|
+
if ((ai.dt === intDt && bi.dt === decDt) || (ai.dt === decDt && bi.dt === intDt)) {
|
|
2022
|
+
const intInfo = ai.dt === intDt ? ai : bi;
|
|
2023
|
+
const decInfo = ai.dt === decDt ? ai : bi;
|
|
2024
|
+
|
|
2025
|
+
const dec = parseXsdDecimalToBigIntScale(decInfo.lexStr);
|
|
2026
|
+
if (dec) {
|
|
2027
|
+
const scaledInt = intInfo.value * pow10n(dec.scale);
|
|
2028
|
+
if (scaledInt === dec.num) return { ...subst };
|
|
2029
|
+
}
|
|
1916
2030
|
}
|
|
1917
2031
|
}
|
|
1918
2032
|
}
|
|
@@ -2388,6 +2502,44 @@ function formatNum(n) {
|
|
|
2388
2502
|
return String(n);
|
|
2389
2503
|
}
|
|
2390
2504
|
|
|
2505
|
+
function parseXsdDecimalToBigIntScale(s) {
|
|
2506
|
+
let t = String(s).trim();
|
|
2507
|
+
let sign = 1n;
|
|
2508
|
+
|
|
2509
|
+
if (t.startsWith('+')) t = t.slice(1);
|
|
2510
|
+
else if (t.startsWith('-')) {
|
|
2511
|
+
sign = -1n;
|
|
2512
|
+
t = t.slice(1);
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
// xsd:decimal lexical: (\d+(\.\d*)?|\.\d+)
|
|
2516
|
+
if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(t)) return null;
|
|
2517
|
+
|
|
2518
|
+
let intPart = '0';
|
|
2519
|
+
let fracPart = '';
|
|
2520
|
+
|
|
2521
|
+
if (t.includes('.')) {
|
|
2522
|
+
const parts = t.split('.');
|
|
2523
|
+
intPart = parts[0] === '' ? '0' : parts[0];
|
|
2524
|
+
fracPart = parts[1] || '';
|
|
2525
|
+
} else {
|
|
2526
|
+
intPart = t;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// normalize
|
|
2530
|
+
intPart = intPart.replace(/^0+(?=\d)/, '');
|
|
2531
|
+
fracPart = fracPart.replace(/0+$/, ''); // drop trailing zeros
|
|
2532
|
+
|
|
2533
|
+
const scale = fracPart.length;
|
|
2534
|
+
const digits = intPart + fracPart || '0';
|
|
2535
|
+
|
|
2536
|
+
return { num: sign * BigInt(digits), scale };
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
function pow10n(k) {
|
|
2540
|
+
return 10n ** BigInt(k);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2391
2543
|
function parseXsdDateTerm(t) {
|
|
2392
2544
|
if (!(t instanceof Literal)) return null;
|
|
2393
2545
|
const [lex, dt] = literalParts(t.value);
|
|
@@ -2572,28 +2724,23 @@ function parseNumericLiteralInfo(t) {
|
|
|
2572
2724
|
let lexStr;
|
|
2573
2725
|
|
|
2574
2726
|
if (dt2 !== null) {
|
|
2575
|
-
|
|
2576
|
-
if (
|
|
2577
|
-
dt2 !== XSD_NS + 'integer' &&
|
|
2578
|
-
dt2 !== XSD_NS + 'decimal' &&
|
|
2579
|
-
dt2 !== XSD_NS + 'double'
|
|
2580
|
-
) return null;
|
|
2581
|
-
|
|
2582
|
-
// typed numerics are like "1.1"^^<...>
|
|
2727
|
+
if (dt2 !== XSD_NS + 'integer' && dt2 !== XSD_NS + 'decimal' && dt2 !== XSD_NS + 'double') return null;
|
|
2583
2728
|
lexStr = stripQuotes(lex);
|
|
2584
2729
|
} else {
|
|
2585
|
-
// Raw numeric token like 42, 1.1, 1.1e0 (NOT quoted strings, NOT lang-tagged)
|
|
2586
2730
|
if (typeof v !== 'string') return null;
|
|
2587
|
-
if (v.startsWith('"')) return null;
|
|
2731
|
+
if (v.startsWith('"')) return null; // exclude quoted strings
|
|
2588
2732
|
if (!/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(v)) return null;
|
|
2589
2733
|
|
|
2590
|
-
dt2 =
|
|
2734
|
+
if (/[eE]/.test(v)) dt2 = XSD_NS + 'double';
|
|
2735
|
+
else if (v.includes('.')) dt2 = XSD_NS + 'decimal';
|
|
2736
|
+
else dt2 = XSD_NS + 'integer';
|
|
2737
|
+
|
|
2591
2738
|
lexStr = v;
|
|
2592
2739
|
}
|
|
2593
2740
|
|
|
2594
2741
|
if (dt2 === XSD_NS + 'integer') {
|
|
2595
2742
|
try {
|
|
2596
|
-
return { dt: dt2, kind: 'bigint', value: BigInt(lexStr) };
|
|
2743
|
+
return { dt: dt2, kind: 'bigint', value: BigInt(lexStr), lexStr };
|
|
2597
2744
|
} catch {
|
|
2598
2745
|
return null;
|
|
2599
2746
|
}
|
|
@@ -2601,7 +2748,48 @@ function parseNumericLiteralInfo(t) {
|
|
|
2601
2748
|
|
|
2602
2749
|
const num = Number(lexStr);
|
|
2603
2750
|
if (Number.isNaN(num)) return null;
|
|
2604
|
-
return { dt: dt2, kind: 'number', value: num };
|
|
2751
|
+
return { dt: dt2, kind: 'number', value: num, lexStr };
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
|
|
2755
|
+
const sIsUnbound = g.s instanceof Var || g.s instanceof Blank;
|
|
2756
|
+
const oIsUnbound = g.o instanceof Var || g.o instanceof Blank;
|
|
2757
|
+
|
|
2758
|
+
const a = parseNum(g.s); // subject numeric?
|
|
2759
|
+
const b = parseNum(g.o); // object numeric?
|
|
2760
|
+
|
|
2761
|
+
// Forward: s numeric => compute o
|
|
2762
|
+
if (a !== null) {
|
|
2763
|
+
const outVal = forwardFn(a);
|
|
2764
|
+
if (!Number.isFinite(outVal)) return [];
|
|
2765
|
+
if (g.o instanceof Var) {
|
|
2766
|
+
const s2 = { ...subst };
|
|
2767
|
+
s2[g.o.name] = new Literal(formatNum(outVal));
|
|
2768
|
+
return [s2];
|
|
2769
|
+
}
|
|
2770
|
+
if (g.o instanceof Blank) return [{ ...subst }];
|
|
2771
|
+
if (numEqualTerm(g.o, outVal)) return [{ ...subst }];
|
|
2772
|
+
return [];
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
// Reverse (bounded): o numeric => compute s
|
|
2776
|
+
if (b !== null && typeof inverseFn === 'function') {
|
|
2777
|
+
const inVal = inverseFn(b);
|
|
2778
|
+
if (!Number.isFinite(inVal)) return [];
|
|
2779
|
+
if (g.s instanceof Var) {
|
|
2780
|
+
const s2 = { ...subst };
|
|
2781
|
+
s2[g.s.name] = new Literal(formatNum(inVal));
|
|
2782
|
+
return [s2];
|
|
2783
|
+
}
|
|
2784
|
+
if (g.s instanceof Blank) return [{ ...subst }];
|
|
2785
|
+
if (numEqualTerm(g.s, inVal)) return [{ ...subst }];
|
|
2786
|
+
return [];
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
// Fully unbound: treat as satisfiable (avoid infinite enumeration)
|
|
2790
|
+
if (sIsUnbound && oIsUnbound) return [{ ...subst }];
|
|
2791
|
+
|
|
2792
|
+
return [];
|
|
2605
2793
|
}
|
|
2606
2794
|
|
|
2607
2795
|
// ============================================================================
|
|
@@ -2836,10 +3024,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2836
3024
|
s2[g.o.name] = lit;
|
|
2837
3025
|
return [s2];
|
|
2838
3026
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
}
|
|
2842
|
-
return [];
|
|
3027
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3028
|
+
return s2 !== null ? [s2] : [];
|
|
2843
3029
|
}
|
|
2844
3030
|
}
|
|
2845
3031
|
|
|
@@ -2890,9 +3076,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2890
3076
|
s2[g.o.name] = durTerm;
|
|
2891
3077
|
return [s2];
|
|
2892
3078
|
}
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
}
|
|
3079
|
+
const s2 = unifyTerm(g.o, durTerm, subst);
|
|
3080
|
+
return s2 !== null ? [s2] : [];
|
|
2896
3081
|
}
|
|
2897
3082
|
return [];
|
|
2898
3083
|
}
|
|
@@ -2910,9 +3095,14 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2910
3095
|
s2[g.o.name] = new Literal(formatNum(c));
|
|
2911
3096
|
return [s2];
|
|
2912
3097
|
}
|
|
2913
|
-
|
|
2914
|
-
|
|
3098
|
+
const lit = new Literal(formatNum(c));
|
|
3099
|
+
if (g.o instanceof Var) {
|
|
3100
|
+
const s2 = { ...subst };
|
|
3101
|
+
s2[g.o.name] = lit;
|
|
3102
|
+
return [s2];
|
|
2915
3103
|
}
|
|
3104
|
+
const s2 = unifyTerm(g.o, lit, subst);
|
|
3105
|
+
return s2 !== null ? [s2] : [];
|
|
2916
3106
|
}
|
|
2917
3107
|
return [];
|
|
2918
3108
|
}
|
|
@@ -2993,28 +3183,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2993
3183
|
}
|
|
2994
3184
|
}
|
|
2995
3185
|
|
|
2996
|
-
// math:negation
|
|
2997
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'negation') {
|
|
2998
|
-
const a = parseNum(g.s);
|
|
2999
|
-
if (a !== null && g.o instanceof Var) {
|
|
3000
|
-
const s2 = { ...subst };
|
|
3001
|
-
s2[g.o.name] = new Literal(formatNum(-a));
|
|
3002
|
-
return [s2];
|
|
3003
|
-
}
|
|
3004
|
-
const b = parseNum(g.o);
|
|
3005
|
-
if (g.s instanceof Var && b !== null) {
|
|
3006
|
-
const s2 = { ...subst };
|
|
3007
|
-
s2[g.s.name] = new Literal(formatNum(-b));
|
|
3008
|
-
return [s2];
|
|
3009
|
-
}
|
|
3010
|
-
const a2 = parseNum(g.s);
|
|
3011
|
-
const b2 = parseNum(g.o);
|
|
3012
|
-
if (a2 !== null && b2 !== null) {
|
|
3013
|
-
if (Math.abs(-a2 - b2) < 1e-9) return [{ ...subst }];
|
|
3014
|
-
}
|
|
3015
|
-
return [];
|
|
3016
|
-
}
|
|
3017
|
-
|
|
3018
3186
|
// math:absoluteValue
|
|
3019
3187
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'absoluteValue') {
|
|
3020
3188
|
const a = parseNum(g.s);
|
|
@@ -3030,135 +3198,61 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3030
3198
|
return [];
|
|
3031
3199
|
}
|
|
3032
3200
|
|
|
3033
|
-
// math:cos
|
|
3034
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'cos') {
|
|
3035
|
-
const a = parseNum(g.s);
|
|
3036
|
-
if (a !== null) {
|
|
3037
|
-
const cVal = Math.cos(a);
|
|
3038
|
-
if (g.o instanceof Var) {
|
|
3039
|
-
const s2 = { ...subst };
|
|
3040
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3041
|
-
return [s2];
|
|
3042
|
-
}
|
|
3043
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3044
|
-
return [{ ...subst }];
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
return [];
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
|
-
// math:sin
|
|
3051
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sin') {
|
|
3052
|
-
const a = parseNum(g.s);
|
|
3053
|
-
if (a !== null) {
|
|
3054
|
-
const cVal = Math.sin(a);
|
|
3055
|
-
if (g.o instanceof Var) {
|
|
3056
|
-
const s2 = { ...subst };
|
|
3057
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3058
|
-
return [s2];
|
|
3059
|
-
}
|
|
3060
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3061
|
-
return [{ ...subst }];
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
return [];
|
|
3065
|
-
}
|
|
3066
|
-
|
|
3067
3201
|
// math:acos
|
|
3068
3202
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'acos') {
|
|
3069
|
-
|
|
3070
|
-
if (a !== null) {
|
|
3071
|
-
const cVal = Math.acos(a);
|
|
3072
|
-
if (Number.isFinite(cVal)) {
|
|
3073
|
-
if (g.o instanceof Var) {
|
|
3074
|
-
const s2 = { ...subst };
|
|
3075
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3076
|
-
return [s2];
|
|
3077
|
-
}
|
|
3078
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3079
|
-
return [{ ...subst }];
|
|
3080
|
-
}
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
return [];
|
|
3203
|
+
return evalUnaryMathRel(g, subst, Math.acos, Math.cos);
|
|
3084
3204
|
}
|
|
3085
3205
|
|
|
3086
3206
|
// math:asin
|
|
3087
3207
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'asin') {
|
|
3088
|
-
|
|
3089
|
-
if (a !== null) {
|
|
3090
|
-
const cVal = Math.asin(a);
|
|
3091
|
-
if (Number.isFinite(cVal)) {
|
|
3092
|
-
if (g.o instanceof Var) {
|
|
3093
|
-
const s2 = { ...subst };
|
|
3094
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3095
|
-
return [s2];
|
|
3096
|
-
}
|
|
3097
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3098
|
-
return [{ ...subst }];
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
}
|
|
3102
|
-
return [];
|
|
3208
|
+
return evalUnaryMathRel(g, subst, Math.asin, Math.sin);
|
|
3103
3209
|
}
|
|
3104
3210
|
|
|
3105
3211
|
// math:atan
|
|
3106
3212
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'atan') {
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3213
|
+
return evalUnaryMathRel(g, subst, Math.atan, Math.tan);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// math:sin (inverse uses principal asin)
|
|
3217
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sin') {
|
|
3218
|
+
return evalUnaryMathRel(g, subst, Math.sin, Math.asin);
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// math:cos (inverse uses principal acos)
|
|
3222
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'cos') {
|
|
3223
|
+
return evalUnaryMathRel(g, subst, Math.cos, Math.acos);
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
// math:tan (inverse uses principal atan)
|
|
3227
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tan') {
|
|
3228
|
+
return evalUnaryMathRel(g, subst, Math.tan, Math.atan);
|
|
3122
3229
|
}
|
|
3123
3230
|
|
|
3124
|
-
// math:cosh
|
|
3125
|
-
|
|
3231
|
+
// math:sinh / cosh / tanh (guard for JS availability)
|
|
3232
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sinh') {
|
|
3233
|
+
if (typeof Math.sinh !== 'function' || typeof Math.asinh !== 'function') return [];
|
|
3234
|
+
return evalUnaryMathRel(g, subst, Math.sinh, Math.asinh);
|
|
3235
|
+
}
|
|
3126
3236
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'cosh') {
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3134
|
-
return [s2];
|
|
3135
|
-
}
|
|
3136
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3137
|
-
return [{ ...subst }];
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
return [];
|
|
3237
|
+
if (typeof Math.cosh !== 'function' || typeof Math.acosh !== 'function') return [];
|
|
3238
|
+
return evalUnaryMathRel(g, subst, Math.cosh, Math.acosh);
|
|
3239
|
+
}
|
|
3240
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tanh') {
|
|
3241
|
+
if (typeof Math.tanh !== 'function' || typeof Math.atanh !== 'function') return [];
|
|
3242
|
+
return evalUnaryMathRel(g, subst, Math.tanh, Math.atanh);
|
|
3142
3243
|
}
|
|
3143
3244
|
|
|
3144
|
-
// math:degrees
|
|
3145
|
-
// Convert radians -> degrees
|
|
3245
|
+
// math:degrees (inverse is radians)
|
|
3146
3246
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'degrees') {
|
|
3147
|
-
const
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3157
|
-
return [{ ...subst }];
|
|
3158
|
-
}
|
|
3159
|
-
}
|
|
3160
|
-
}
|
|
3161
|
-
return [];
|
|
3247
|
+
const toDeg = (rad) => (rad * 180.0) / Math.PI;
|
|
3248
|
+
const toRad = (deg) => (deg * Math.PI) / 180.0;
|
|
3249
|
+
return evalUnaryMathRel(g, subst, toDeg, toRad);
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// math:negation (inverse is itself)
|
|
3253
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'negation') {
|
|
3254
|
+
const neg = (x) => -x;
|
|
3255
|
+
return evalUnaryMathRel(g, subst, neg, neg);
|
|
3162
3256
|
}
|
|
3163
3257
|
|
|
3164
3258
|
// math:remainder
|
|
@@ -3201,63 +3295,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3201
3295
|
return s2 !== null ? [s2] : [];
|
|
3202
3296
|
}
|
|
3203
3297
|
|
|
3204
|
-
// math:sinh
|
|
3205
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sinh') {
|
|
3206
|
-
const a = parseNum(g.s);
|
|
3207
|
-
if (a !== null && typeof Math.sinh === 'function') {
|
|
3208
|
-
const cVal = Math.sinh(a);
|
|
3209
|
-
if (Number.isFinite(cVal)) {
|
|
3210
|
-
if (g.o instanceof Var) {
|
|
3211
|
-
const s2 = { ...subst };
|
|
3212
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3213
|
-
return [s2];
|
|
3214
|
-
}
|
|
3215
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3216
|
-
return [{ ...subst }];
|
|
3217
|
-
}
|
|
3218
|
-
}
|
|
3219
|
-
}
|
|
3220
|
-
return [];
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
// math:tan
|
|
3224
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tan') {
|
|
3225
|
-
const a = parseNum(g.s);
|
|
3226
|
-
if (a !== null) {
|
|
3227
|
-
const cVal = Math.tan(a);
|
|
3228
|
-
if (Number.isFinite(cVal)) {
|
|
3229
|
-
if (g.o instanceof Var) {
|
|
3230
|
-
const s2 = { ...subst };
|
|
3231
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3232
|
-
return [s2];
|
|
3233
|
-
}
|
|
3234
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3235
|
-
return [{ ...subst }];
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
}
|
|
3239
|
-
return [];
|
|
3240
|
-
}
|
|
3241
|
-
|
|
3242
|
-
// math:tanh
|
|
3243
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tanh') {
|
|
3244
|
-
const a = parseNum(g.s);
|
|
3245
|
-
if (a !== null && typeof Math.tanh === 'function') {
|
|
3246
|
-
const cVal = Math.tanh(a);
|
|
3247
|
-
if (Number.isFinite(cVal)) {
|
|
3248
|
-
if (g.o instanceof Var) {
|
|
3249
|
-
const s2 = { ...subst };
|
|
3250
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3251
|
-
return [s2];
|
|
3252
|
-
}
|
|
3253
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3254
|
-
return [{ ...subst }];
|
|
3255
|
-
}
|
|
3256
|
-
}
|
|
3257
|
-
}
|
|
3258
|
-
return [];
|
|
3259
|
-
}
|
|
3260
|
-
|
|
3261
3298
|
// -----------------------------------------------------------------
|
|
3262
3299
|
// 4.3 time: builtins
|
|
3263
3300
|
// -----------------------------------------------------------------
|
|
@@ -3390,12 +3427,37 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3390
3427
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3391
3428
|
const xs = g.s.elems;
|
|
3392
3429
|
const outs = [];
|
|
3430
|
+
|
|
3393
3431
|
for (let i = 0; i < xs.length; i++) {
|
|
3394
3432
|
const idxLit = new Literal(String(i)); // 0-based
|
|
3395
|
-
const
|
|
3433
|
+
const val = xs[i];
|
|
3434
|
+
|
|
3435
|
+
// Fast path: object is exactly a 2-element list (idx, value)
|
|
3436
|
+
if (g.o instanceof ListTerm && g.o.elems.length === 2) {
|
|
3437
|
+
const [idxPat, valPat] = g.o.elems;
|
|
3438
|
+
|
|
3439
|
+
const s1 = unifyTerm(idxPat, idxLit, subst);
|
|
3440
|
+
if (s1 === null) continue;
|
|
3441
|
+
|
|
3442
|
+
// If value-pattern is ground after subst: require STRICT term equality
|
|
3443
|
+
const valPat2 = applySubstTerm(valPat, s1);
|
|
3444
|
+
if (isGroundTerm(valPat2)) {
|
|
3445
|
+
if (termsEqualNoIntDecimal(valPat2, val)) outs.push({ ...s1 });
|
|
3446
|
+
continue;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// Otherwise, allow normal unification/binding
|
|
3450
|
+
const s2 = unifyTerm(valPat, val, s1);
|
|
3451
|
+
if (s2 !== null) outs.push(s2);
|
|
3452
|
+
continue;
|
|
3453
|
+
}
|
|
3454
|
+
|
|
3455
|
+
// Fallback: original behavior
|
|
3456
|
+
const pair = new ListTerm([idxLit, val]);
|
|
3396
3457
|
const s2 = unifyTerm(g.o, pair, subst);
|
|
3397
3458
|
if (s2 !== null) outs.push(s2);
|
|
3398
3459
|
}
|
|
3460
|
+
|
|
3399
3461
|
return outs;
|
|
3400
3462
|
}
|
|
3401
3463
|
|
|
@@ -3418,17 +3480,35 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3418
3480
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3419
3481
|
const [listTerm, indexTerm] = g.s.elems;
|
|
3420
3482
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
3483
|
+
|
|
3421
3484
|
const xs = listTerm.elems;
|
|
3422
3485
|
const outs = [];
|
|
3423
3486
|
|
|
3424
3487
|
for (let i = 0; i < xs.length; i++) {
|
|
3425
3488
|
const idxLit = new Literal(String(i)); // index starts at 0
|
|
3426
|
-
|
|
3427
|
-
if
|
|
3428
|
-
let
|
|
3429
|
-
|
|
3430
|
-
|
|
3489
|
+
|
|
3490
|
+
// --- index side: strict if ground, otherwise unify/bind
|
|
3491
|
+
let s1 = null;
|
|
3492
|
+
const idxPat2 = applySubstTerm(indexTerm, subst);
|
|
3493
|
+
if (isGroundTerm(idxPat2)) {
|
|
3494
|
+
if (!termsEqualNoIntDecimal(idxPat2, idxLit)) continue;
|
|
3495
|
+
s1 = { ...subst };
|
|
3496
|
+
} else {
|
|
3497
|
+
s1 = unifyTerm(indexTerm, idxLit, subst);
|
|
3498
|
+
if (s1 === null) continue;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
// --- value side: strict if ground, otherwise unify/bind
|
|
3502
|
+
const o2 = applySubstTerm(g.o, s1);
|
|
3503
|
+
if (isGroundTerm(o2)) {
|
|
3504
|
+
if (termsEqualNoIntDecimal(o2, xs[i])) outs.push({ ...s1 });
|
|
3505
|
+
continue;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
const s2 = unifyTerm(g.o, xs[i], s1);
|
|
3509
|
+
if (s2 !== null) outs.push(s2);
|
|
3431
3510
|
}
|
|
3511
|
+
|
|
3432
3512
|
return outs;
|
|
3433
3513
|
}
|
|
3434
3514
|
|
|
@@ -3439,11 +3519,18 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3439
3519
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3440
3520
|
const [listTerm, itemTerm] = g.s.elems;
|
|
3441
3521
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
3522
|
+
|
|
3523
|
+
// item must be bound
|
|
3524
|
+
const item2 = applySubstTerm(itemTerm, subst);
|
|
3525
|
+
if (!isGroundTerm(item2)) return [];
|
|
3526
|
+
|
|
3442
3527
|
const xs = listTerm.elems;
|
|
3443
3528
|
const filtered = [];
|
|
3444
3529
|
for (const e of xs) {
|
|
3445
|
-
|
|
3530
|
+
// strict term match (still allows plain "abc" == "abc"^^xsd:string)
|
|
3531
|
+
if (!termsEqualNoIntDecimal(e, item2)) filtered.push(e);
|
|
3446
3532
|
}
|
|
3533
|
+
|
|
3447
3534
|
const resList = new ListTerm(filtered);
|
|
3448
3535
|
const s2 = unifyTerm(g.o, resList, subst);
|
|
3449
3536
|
return s2 !== null ? [s2] : [];
|
|
@@ -3471,10 +3558,16 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3471
3558
|
return outs;
|
|
3472
3559
|
}
|
|
3473
3560
|
|
|
3474
|
-
// list:length
|
|
3561
|
+
// list:length (strict: do not accept integer<->decimal matches for a ground object)
|
|
3475
3562
|
if (g.p instanceof Iri && g.p.value === LIST_NS + 'length') {
|
|
3476
3563
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3477
3564
|
const nTerm = new Literal(String(g.s.elems.length));
|
|
3565
|
+
|
|
3566
|
+
const o2 = applySubstTerm(g.o, subst);
|
|
3567
|
+
if (isGroundTerm(o2)) {
|
|
3568
|
+
return termsEqualNoIntDecimal(o2, nTerm) ? [{ ...subst }] : [];
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3478
3571
|
const s2 = unifyTerm(g.o, nTerm, subst);
|
|
3479
3572
|
return s2 !== null ? [s2] : [];
|
|
3480
3573
|
}
|