eyeling 1.6.3 → 1.6.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/eyeling.js +319 -228
- 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
|
+
|
|
1299
1327
|
if (a instanceof FormulaTerm) {
|
|
1300
1328
|
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1301
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
|
+
|
|
1395
|
+
if (a instanceof FormulaTerm) {
|
|
1396
|
+
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
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
|
// ============================================================================
|
|
@@ -2993,28 +3181,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2993
3181
|
}
|
|
2994
3182
|
}
|
|
2995
3183
|
|
|
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
3184
|
// math:absoluteValue
|
|
3019
3185
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'absoluteValue') {
|
|
3020
3186
|
const a = parseNum(g.s);
|
|
@@ -3030,135 +3196,61 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3030
3196
|
return [];
|
|
3031
3197
|
}
|
|
3032
3198
|
|
|
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
3199
|
// math:acos
|
|
3068
3200
|
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 [];
|
|
3201
|
+
return evalUnaryMathRel(g, subst, Math.acos, Math.cos);
|
|
3084
3202
|
}
|
|
3085
3203
|
|
|
3086
3204
|
// math:asin
|
|
3087
3205
|
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 [];
|
|
3206
|
+
return evalUnaryMathRel(g, subst, Math.asin, Math.sin);
|
|
3103
3207
|
}
|
|
3104
3208
|
|
|
3105
3209
|
// math:atan
|
|
3106
3210
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'atan') {
|
|
3107
|
-
|
|
3108
|
-
if (a !== null) {
|
|
3109
|
-
const cVal = Math.atan(a);
|
|
3110
|
-
if (Number.isFinite(cVal)) {
|
|
3111
|
-
if (g.o instanceof Var) {
|
|
3112
|
-
const s2 = { ...subst };
|
|
3113
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3114
|
-
return [s2];
|
|
3115
|
-
}
|
|
3116
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3117
|
-
return [{ ...subst }];
|
|
3118
|
-
}
|
|
3119
|
-
}
|
|
3120
|
-
}
|
|
3121
|
-
return [];
|
|
3211
|
+
return evalUnaryMathRel(g, subst, Math.atan, Math.tan);
|
|
3122
3212
|
}
|
|
3123
3213
|
|
|
3124
|
-
// math:
|
|
3125
|
-
|
|
3214
|
+
// math:sin (inverse uses principal asin)
|
|
3215
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sin') {
|
|
3216
|
+
return evalUnaryMathRel(g, subst, Math.sin, Math.asin);
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
// math:cos (inverse uses principal acos)
|
|
3220
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'cos') {
|
|
3221
|
+
return evalUnaryMathRel(g, subst, Math.cos, Math.acos);
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
// math:tan (inverse uses principal atan)
|
|
3225
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tan') {
|
|
3226
|
+
return evalUnaryMathRel(g, subst, Math.tan, Math.atan);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// math:sinh / cosh / tanh (guard for JS availability)
|
|
3230
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sinh') {
|
|
3231
|
+
if (typeof Math.sinh !== 'function' || typeof Math.asinh !== 'function') return [];
|
|
3232
|
+
return evalUnaryMathRel(g, subst, Math.sinh, Math.asinh);
|
|
3233
|
+
}
|
|
3126
3234
|
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 [];
|
|
3235
|
+
if (typeof Math.cosh !== 'function' || typeof Math.acosh !== 'function') return [];
|
|
3236
|
+
return evalUnaryMathRel(g, subst, Math.cosh, Math.acosh);
|
|
3237
|
+
}
|
|
3238
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tanh') {
|
|
3239
|
+
if (typeof Math.tanh !== 'function' || typeof Math.atanh !== 'function') return [];
|
|
3240
|
+
return evalUnaryMathRel(g, subst, Math.tanh, Math.atanh);
|
|
3142
3241
|
}
|
|
3143
3242
|
|
|
3144
|
-
// math:degrees
|
|
3145
|
-
// Convert radians -> degrees
|
|
3243
|
+
// math:degrees (inverse is radians)
|
|
3146
3244
|
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 [];
|
|
3245
|
+
const toDeg = (rad) => (rad * 180.0) / Math.PI;
|
|
3246
|
+
const toRad = (deg) => (deg * Math.PI) / 180.0;
|
|
3247
|
+
return evalUnaryMathRel(g, subst, toDeg, toRad);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// math:negation (inverse is itself)
|
|
3251
|
+
if (g.p instanceof Iri && g.p.value === MATH_NS + 'negation') {
|
|
3252
|
+
const neg = (x) => -x;
|
|
3253
|
+
return evalUnaryMathRel(g, subst, neg, neg);
|
|
3162
3254
|
}
|
|
3163
3255
|
|
|
3164
3256
|
// math:remainder
|
|
@@ -3201,63 +3293,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3201
3293
|
return s2 !== null ? [s2] : [];
|
|
3202
3294
|
}
|
|
3203
3295
|
|
|
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
3296
|
// -----------------------------------------------------------------
|
|
3262
3297
|
// 4.3 time: builtins
|
|
3263
3298
|
// -----------------------------------------------------------------
|
|
@@ -3390,12 +3425,37 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3390
3425
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3391
3426
|
const xs = g.s.elems;
|
|
3392
3427
|
const outs = [];
|
|
3428
|
+
|
|
3393
3429
|
for (let i = 0; i < xs.length; i++) {
|
|
3394
3430
|
const idxLit = new Literal(String(i)); // 0-based
|
|
3395
|
-
const
|
|
3431
|
+
const val = xs[i];
|
|
3432
|
+
|
|
3433
|
+
// Fast path: object is exactly a 2-element list (idx, value)
|
|
3434
|
+
if (g.o instanceof ListTerm && g.o.elems.length === 2) {
|
|
3435
|
+
const [idxPat, valPat] = g.o.elems;
|
|
3436
|
+
|
|
3437
|
+
const s1 = unifyTerm(idxPat, idxLit, subst);
|
|
3438
|
+
if (s1 === null) continue;
|
|
3439
|
+
|
|
3440
|
+
// If value-pattern is ground after subst: require STRICT term equality
|
|
3441
|
+
const valPat2 = applySubstTerm(valPat, s1);
|
|
3442
|
+
if (isGroundTerm(valPat2)) {
|
|
3443
|
+
if (termsEqualNoIntDecimal(valPat2, val)) outs.push({ ...s1 });
|
|
3444
|
+
continue;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
// Otherwise, allow normal unification/binding
|
|
3448
|
+
const s2 = unifyTerm(valPat, val, s1);
|
|
3449
|
+
if (s2 !== null) outs.push(s2);
|
|
3450
|
+
continue;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
// Fallback: original behavior
|
|
3454
|
+
const pair = new ListTerm([idxLit, val]);
|
|
3396
3455
|
const s2 = unifyTerm(g.o, pair, subst);
|
|
3397
3456
|
if (s2 !== null) outs.push(s2);
|
|
3398
3457
|
}
|
|
3458
|
+
|
|
3399
3459
|
return outs;
|
|
3400
3460
|
}
|
|
3401
3461
|
|
|
@@ -3418,17 +3478,35 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3418
3478
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3419
3479
|
const [listTerm, indexTerm] = g.s.elems;
|
|
3420
3480
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
3481
|
+
|
|
3421
3482
|
const xs = listTerm.elems;
|
|
3422
3483
|
const outs = [];
|
|
3423
3484
|
|
|
3424
3485
|
for (let i = 0; i < xs.length; i++) {
|
|
3425
3486
|
const idxLit = new Literal(String(i)); // index starts at 0
|
|
3426
|
-
|
|
3427
|
-
if
|
|
3428
|
-
let
|
|
3429
|
-
|
|
3430
|
-
|
|
3487
|
+
|
|
3488
|
+
// --- index side: strict if ground, otherwise unify/bind
|
|
3489
|
+
let s1 = null;
|
|
3490
|
+
const idxPat2 = applySubstTerm(indexTerm, subst);
|
|
3491
|
+
if (isGroundTerm(idxPat2)) {
|
|
3492
|
+
if (!termsEqualNoIntDecimal(idxPat2, idxLit)) continue;
|
|
3493
|
+
s1 = { ...subst };
|
|
3494
|
+
} else {
|
|
3495
|
+
s1 = unifyTerm(indexTerm, idxLit, subst);
|
|
3496
|
+
if (s1 === null) continue;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
// --- value side: strict if ground, otherwise unify/bind
|
|
3500
|
+
const o2 = applySubstTerm(g.o, s1);
|
|
3501
|
+
if (isGroundTerm(o2)) {
|
|
3502
|
+
if (termsEqualNoIntDecimal(o2, xs[i])) outs.push({ ...s1 });
|
|
3503
|
+
continue;
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
const s2 = unifyTerm(g.o, xs[i], s1);
|
|
3507
|
+
if (s2 !== null) outs.push(s2);
|
|
3431
3508
|
}
|
|
3509
|
+
|
|
3432
3510
|
return outs;
|
|
3433
3511
|
}
|
|
3434
3512
|
|
|
@@ -3439,11 +3517,18 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3439
3517
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3440
3518
|
const [listTerm, itemTerm] = g.s.elems;
|
|
3441
3519
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
3520
|
+
|
|
3521
|
+
// item must be bound
|
|
3522
|
+
const item2 = applySubstTerm(itemTerm, subst);
|
|
3523
|
+
if (!isGroundTerm(item2)) return [];
|
|
3524
|
+
|
|
3442
3525
|
const xs = listTerm.elems;
|
|
3443
3526
|
const filtered = [];
|
|
3444
3527
|
for (const e of xs) {
|
|
3445
|
-
|
|
3528
|
+
// strict term match (still allows plain "abc" == "abc"^^xsd:string)
|
|
3529
|
+
if (!termsEqualNoIntDecimal(e, item2)) filtered.push(e);
|
|
3446
3530
|
}
|
|
3531
|
+
|
|
3447
3532
|
const resList = new ListTerm(filtered);
|
|
3448
3533
|
const s2 = unifyTerm(g.o, resList, subst);
|
|
3449
3534
|
return s2 !== null ? [s2] : [];
|
|
@@ -3471,10 +3556,16 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3471
3556
|
return outs;
|
|
3472
3557
|
}
|
|
3473
3558
|
|
|
3474
|
-
// list:length
|
|
3559
|
+
// list:length (strict: do not accept integer<->decimal matches for a ground object)
|
|
3475
3560
|
if (g.p instanceof Iri && g.p.value === LIST_NS + 'length') {
|
|
3476
3561
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3477
3562
|
const nTerm = new Literal(String(g.s.elems.length));
|
|
3563
|
+
|
|
3564
|
+
const o2 = applySubstTerm(g.o, subst);
|
|
3565
|
+
if (isGroundTerm(o2)) {
|
|
3566
|
+
return termsEqualNoIntDecimal(o2, nTerm) ? [{ ...subst }] : [];
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3478
3569
|
const s2 = unifyTerm(g.o, nTerm, subst);
|
|
3479
3570
|
return s2 !== null ? [s2] : [];
|
|
3480
3571
|
}
|