eyeling 1.6.2 → 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 +430 -213
- package/package.json +1 -1
package/eyeling.js
CHANGED
|
@@ -1263,10 +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;
|
|
1267
|
-
|
|
1268
|
+
|
|
1269
|
+
if (a instanceof Literal) {
|
|
1270
|
+
if (a.value === b.value) return true;
|
|
1271
|
+
|
|
1272
|
+
// Plain "abc" == "abc"^^xsd:string (but not language-tagged strings)
|
|
1273
|
+
if (literalsEquivalentAsXsdString(a.value, b.value)) return true;
|
|
1274
|
+
|
|
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
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1268
1307
|
if (a instanceof Var) return a.name === b.name;
|
|
1269
1308
|
if (a instanceof Blank) return a.label === b.label;
|
|
1309
|
+
|
|
1270
1310
|
if (a instanceof ListTerm) {
|
|
1271
1311
|
if (a.elems.length !== b.elems.length) return false;
|
|
1272
1312
|
for (let i = 0; i < a.elems.length; i++) {
|
|
@@ -1274,6 +1314,7 @@ function termsEqual(a, b) {
|
|
|
1274
1314
|
}
|
|
1275
1315
|
return true;
|
|
1276
1316
|
}
|
|
1317
|
+
|
|
1277
1318
|
if (a instanceof OpenListTerm) {
|
|
1278
1319
|
if (a.tailVar !== b.tailVar) return false;
|
|
1279
1320
|
if (a.prefix.length !== b.prefix.length) return false;
|
|
@@ -1282,9 +1323,79 @@ function termsEqual(a, b) {
|
|
|
1282
1323
|
}
|
|
1283
1324
|
return true;
|
|
1284
1325
|
}
|
|
1326
|
+
|
|
1285
1327
|
if (a instanceof FormulaTerm) {
|
|
1286
1328
|
return alphaEqFormulaTriples(a.triples, b.triples);
|
|
1287
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
|
+
|
|
1288
1399
|
return false;
|
|
1289
1400
|
}
|
|
1290
1401
|
|
|
@@ -1447,7 +1558,7 @@ function hasAlphaEquiv(triples, tr) {
|
|
|
1447
1558
|
|
|
1448
1559
|
function termFastKey(t) {
|
|
1449
1560
|
if (t instanceof Iri) return 'I:' + t.value;
|
|
1450
|
-
if (t instanceof Literal) return 'L:' + t.value;
|
|
1561
|
+
if (t instanceof Literal) return 'L:' + normalizeLiteralForFastKey(t.value);
|
|
1451
1562
|
return null;
|
|
1452
1563
|
}
|
|
1453
1564
|
|
|
@@ -1882,18 +1993,40 @@ function unifyTerm(a, b, subst) {
|
|
|
1882
1993
|
if (a instanceof Literal && b instanceof Literal && a.value === b.value) return { ...subst };
|
|
1883
1994
|
if (a instanceof Blank && b instanceof Blank && a.label === b.label) return { ...subst };
|
|
1884
1995
|
|
|
1885
|
-
//
|
|
1886
|
-
// e.g. "1.1"^^xsd:double ≈ 1.1e0
|
|
1996
|
+
// String-literal match (RDF 1.1): treat plain strings and xsd:string as equal (but not @lang)
|
|
1887
1997
|
if (a instanceof Literal && b instanceof Literal) {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1998
|
+
if (literalsEquivalentAsXsdString(a.value, b.value)) return { ...subst };
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
// Numeric-value match for literals, BUT ONLY when datatypes agree (or infer to agree)
|
|
2002
|
+
if (a instanceof Literal && b instanceof Literal) {
|
|
2003
|
+
const ai = parseNumericLiteralInfo(a);
|
|
2004
|
+
const bi = parseNumericLiteralInfo(b);
|
|
2005
|
+
|
|
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
|
+
}
|
|
1897
2030
|
}
|
|
1898
2031
|
}
|
|
1899
2032
|
}
|
|
@@ -2000,6 +2133,65 @@ function literalParts(lit) {
|
|
|
2000
2133
|
return [lex, dt];
|
|
2001
2134
|
}
|
|
2002
2135
|
|
|
2136
|
+
function literalHasLangTag(lit) {
|
|
2137
|
+
// True iff the literal is a quoted string literal with a language tag suffix,
|
|
2138
|
+
// e.g. "hello"@en or """hello"""@en.
|
|
2139
|
+
// (The parser rejects language tags combined with datatypes.)
|
|
2140
|
+
if (typeof lit !== 'string') return false;
|
|
2141
|
+
if (lit.indexOf('^^') >= 0) return false;
|
|
2142
|
+
if (!lit.startsWith('"')) return false;
|
|
2143
|
+
|
|
2144
|
+
if (lit.startsWith('"""')) {
|
|
2145
|
+
const end = lit.lastIndexOf('"""');
|
|
2146
|
+
if (end < 0) return false;
|
|
2147
|
+
const after = end + 3;
|
|
2148
|
+
return after < lit.length && lit[after] === '@';
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
const lastQuote = lit.lastIndexOf('"');
|
|
2152
|
+
if (lastQuote < 0) return false;
|
|
2153
|
+
const after = lastQuote + 1;
|
|
2154
|
+
return after < lit.length && lit[after] === '@';
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
function isPlainStringLiteralValue(lit) {
|
|
2158
|
+
// Plain string literal: quoted, no datatype, no lang.
|
|
2159
|
+
if (typeof lit !== 'string') return false;
|
|
2160
|
+
if (lit.indexOf('^^') >= 0) return false;
|
|
2161
|
+
if (!isQuotedLexical(lit)) return false;
|
|
2162
|
+
return !literalHasLangTag(lit);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
function literalsEquivalentAsXsdString(aLit, bLit) {
|
|
2166
|
+
// Treat "abc" and "abc"^^xsd:string as equal, but do NOT conflate language-tagged strings.
|
|
2167
|
+
if (typeof aLit !== 'string' || typeof bLit !== 'string') return false;
|
|
2168
|
+
|
|
2169
|
+
const [alex, adt] = literalParts(aLit);
|
|
2170
|
+
const [blex, bdt] = literalParts(bLit);
|
|
2171
|
+
if (alex !== blex) return false;
|
|
2172
|
+
|
|
2173
|
+
const aPlain = adt === null && isPlainStringLiteralValue(aLit);
|
|
2174
|
+
const bPlain = bdt === null && isPlainStringLiteralValue(bLit);
|
|
2175
|
+
const aXsdStr = adt === XSD_NS + 'string';
|
|
2176
|
+
const bXsdStr = bdt === XSD_NS + 'string';
|
|
2177
|
+
|
|
2178
|
+
return (aPlain && bXsdStr) || (bPlain && aXsdStr);
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function normalizeLiteralForFastKey(lit) {
|
|
2182
|
+
// Canonicalize so that "abc" and "abc"^^xsd:string share the same index/dedup key.
|
|
2183
|
+
if (typeof lit !== 'string') return lit;
|
|
2184
|
+
const [lex, dt] = literalParts(lit);
|
|
2185
|
+
|
|
2186
|
+
if (dt === XSD_NS + 'string') {
|
|
2187
|
+
return `${lex}^^<${XSD_NS}string>`;
|
|
2188
|
+
}
|
|
2189
|
+
if (dt === null && isPlainStringLiteralValue(lit)) {
|
|
2190
|
+
return `${lex}^^<${XSD_NS}string>`;
|
|
2191
|
+
}
|
|
2192
|
+
return lit;
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2003
2195
|
function stripQuotes(lex) {
|
|
2004
2196
|
if (lex.length >= 6 && lex.startsWith('"""') && lex.endsWith('"""')) {
|
|
2005
2197
|
return lex.slice(3, -3);
|
|
@@ -2310,6 +2502,44 @@ function formatNum(n) {
|
|
|
2310
2502
|
return String(n);
|
|
2311
2503
|
}
|
|
2312
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
|
+
|
|
2313
2543
|
function parseXsdDateTerm(t) {
|
|
2314
2544
|
if (!(t instanceof Literal)) return null;
|
|
2315
2545
|
const [lex, dt] = literalParts(t.value);
|
|
@@ -2478,6 +2708,90 @@ function numEqualTerm(t, n, eps = 1e-9) {
|
|
|
2478
2708
|
return v !== null && Math.abs(v - n) < eps;
|
|
2479
2709
|
}
|
|
2480
2710
|
|
|
2711
|
+
function numericDatatypeFromLex(lex) {
|
|
2712
|
+
if (/[eE]/.test(lex)) return XSD_NS + 'double';
|
|
2713
|
+
if (lex.includes('.')) return XSD_NS + 'decimal';
|
|
2714
|
+
return XSD_NS + 'integer';
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
function parseNumericLiteralInfo(t) {
|
|
2718
|
+
if (!(t instanceof Literal)) return null;
|
|
2719
|
+
|
|
2720
|
+
const v = t.value;
|
|
2721
|
+
const [lex, dt] = literalParts(v);
|
|
2722
|
+
|
|
2723
|
+
let dt2 = dt;
|
|
2724
|
+
let lexStr;
|
|
2725
|
+
|
|
2726
|
+
if (dt2 !== null) {
|
|
2727
|
+
if (dt2 !== XSD_NS + 'integer' && dt2 !== XSD_NS + 'decimal' && dt2 !== XSD_NS + 'double') return null;
|
|
2728
|
+
lexStr = stripQuotes(lex);
|
|
2729
|
+
} else {
|
|
2730
|
+
if (typeof v !== 'string') return null;
|
|
2731
|
+
if (v.startsWith('"')) return null; // exclude quoted strings
|
|
2732
|
+
if (!/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(v)) return null;
|
|
2733
|
+
|
|
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
|
+
|
|
2738
|
+
lexStr = v;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
if (dt2 === XSD_NS + 'integer') {
|
|
2742
|
+
try {
|
|
2743
|
+
return { dt: dt2, kind: 'bigint', value: BigInt(lexStr), lexStr };
|
|
2744
|
+
} catch {
|
|
2745
|
+
return null;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
const num = Number(lexStr);
|
|
2750
|
+
if (Number.isNaN(num)) return null;
|
|
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 [];
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2481
2795
|
// ============================================================================
|
|
2482
2796
|
// Backward proof & builtins mutual recursion — declarations first
|
|
2483
2797
|
// ============================================================================
|
|
@@ -2867,28 +3181,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2867
3181
|
}
|
|
2868
3182
|
}
|
|
2869
3183
|
|
|
2870
|
-
// math:negation
|
|
2871
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'negation') {
|
|
2872
|
-
const a = parseNum(g.s);
|
|
2873
|
-
if (a !== null && g.o instanceof Var) {
|
|
2874
|
-
const s2 = { ...subst };
|
|
2875
|
-
s2[g.o.name] = new Literal(formatNum(-a));
|
|
2876
|
-
return [s2];
|
|
2877
|
-
}
|
|
2878
|
-
const b = parseNum(g.o);
|
|
2879
|
-
if (g.s instanceof Var && b !== null) {
|
|
2880
|
-
const s2 = { ...subst };
|
|
2881
|
-
s2[g.s.name] = new Literal(formatNum(-b));
|
|
2882
|
-
return [s2];
|
|
2883
|
-
}
|
|
2884
|
-
const a2 = parseNum(g.s);
|
|
2885
|
-
const b2 = parseNum(g.o);
|
|
2886
|
-
if (a2 !== null && b2 !== null) {
|
|
2887
|
-
if (Math.abs(-a2 - b2) < 1e-9) return [{ ...subst }];
|
|
2888
|
-
}
|
|
2889
|
-
return [];
|
|
2890
|
-
}
|
|
2891
|
-
|
|
2892
3184
|
// math:absoluteValue
|
|
2893
3185
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'absoluteValue') {
|
|
2894
3186
|
const a = parseNum(g.s);
|
|
@@ -2904,135 +3196,61 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
2904
3196
|
return [];
|
|
2905
3197
|
}
|
|
2906
3198
|
|
|
2907
|
-
// math:cos
|
|
2908
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'cos') {
|
|
2909
|
-
const a = parseNum(g.s);
|
|
2910
|
-
if (a !== null) {
|
|
2911
|
-
const cVal = Math.cos(a);
|
|
2912
|
-
if (g.o instanceof Var) {
|
|
2913
|
-
const s2 = { ...subst };
|
|
2914
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
2915
|
-
return [s2];
|
|
2916
|
-
}
|
|
2917
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
2918
|
-
return [{ ...subst }];
|
|
2919
|
-
}
|
|
2920
|
-
}
|
|
2921
|
-
return [];
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
// math:sin
|
|
2925
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sin') {
|
|
2926
|
-
const a = parseNum(g.s);
|
|
2927
|
-
if (a !== null) {
|
|
2928
|
-
const cVal = Math.sin(a);
|
|
2929
|
-
if (g.o instanceof Var) {
|
|
2930
|
-
const s2 = { ...subst };
|
|
2931
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
2932
|
-
return [s2];
|
|
2933
|
-
}
|
|
2934
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
2935
|
-
return [{ ...subst }];
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
return [];
|
|
2939
|
-
}
|
|
2940
|
-
|
|
2941
3199
|
// math:acos
|
|
2942
3200
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'acos') {
|
|
2943
|
-
|
|
2944
|
-
if (a !== null) {
|
|
2945
|
-
const cVal = Math.acos(a);
|
|
2946
|
-
if (Number.isFinite(cVal)) {
|
|
2947
|
-
if (g.o instanceof Var) {
|
|
2948
|
-
const s2 = { ...subst };
|
|
2949
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
2950
|
-
return [s2];
|
|
2951
|
-
}
|
|
2952
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
2953
|
-
return [{ ...subst }];
|
|
2954
|
-
}
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2957
|
-
return [];
|
|
3201
|
+
return evalUnaryMathRel(g, subst, Math.acos, Math.cos);
|
|
2958
3202
|
}
|
|
2959
3203
|
|
|
2960
3204
|
// math:asin
|
|
2961
3205
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'asin') {
|
|
2962
|
-
|
|
2963
|
-
if (a !== null) {
|
|
2964
|
-
const cVal = Math.asin(a);
|
|
2965
|
-
if (Number.isFinite(cVal)) {
|
|
2966
|
-
if (g.o instanceof Var) {
|
|
2967
|
-
const s2 = { ...subst };
|
|
2968
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
2969
|
-
return [s2];
|
|
2970
|
-
}
|
|
2971
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
2972
|
-
return [{ ...subst }];
|
|
2973
|
-
}
|
|
2974
|
-
}
|
|
2975
|
-
}
|
|
2976
|
-
return [];
|
|
3206
|
+
return evalUnaryMathRel(g, subst, Math.asin, Math.sin);
|
|
2977
3207
|
}
|
|
2978
3208
|
|
|
2979
3209
|
// math:atan
|
|
2980
3210
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'atan') {
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
2988
|
-
return [s2];
|
|
2989
|
-
}
|
|
2990
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
2991
|
-
return [{ ...subst }];
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
}
|
|
2995
|
-
return [];
|
|
3211
|
+
return evalUnaryMathRel(g, subst, Math.atan, Math.tan);
|
|
3212
|
+
}
|
|
3213
|
+
|
|
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);
|
|
2996
3217
|
}
|
|
2997
3218
|
|
|
2998
|
-
// math:
|
|
2999
|
-
|
|
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
|
+
}
|
|
3000
3234
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'cosh') {
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3008
|
-
return [s2];
|
|
3009
|
-
}
|
|
3010
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3011
|
-
return [{ ...subst }];
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
}
|
|
3015
|
-
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);
|
|
3016
3241
|
}
|
|
3017
3242
|
|
|
3018
|
-
// math:degrees
|
|
3019
|
-
// Convert radians -> degrees
|
|
3243
|
+
// math:degrees (inverse is radians)
|
|
3020
3244
|
if (g.p instanceof Iri && g.p.value === MATH_NS + 'degrees') {
|
|
3021
|
-
const
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3031
|
-
return [{ ...subst }];
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
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);
|
|
3036
3254
|
}
|
|
3037
3255
|
|
|
3038
3256
|
// math:remainder
|
|
@@ -3075,63 +3293,6 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3075
3293
|
return s2 !== null ? [s2] : [];
|
|
3076
3294
|
}
|
|
3077
3295
|
|
|
3078
|
-
// math:sinh
|
|
3079
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'sinh') {
|
|
3080
|
-
const a = parseNum(g.s);
|
|
3081
|
-
if (a !== null && typeof Math.sinh === 'function') {
|
|
3082
|
-
const cVal = Math.sinh(a);
|
|
3083
|
-
if (Number.isFinite(cVal)) {
|
|
3084
|
-
if (g.o instanceof Var) {
|
|
3085
|
-
const s2 = { ...subst };
|
|
3086
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3087
|
-
return [s2];
|
|
3088
|
-
}
|
|
3089
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3090
|
-
return [{ ...subst }];
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
}
|
|
3094
|
-
return [];
|
|
3095
|
-
}
|
|
3096
|
-
|
|
3097
|
-
// math:tan
|
|
3098
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tan') {
|
|
3099
|
-
const a = parseNum(g.s);
|
|
3100
|
-
if (a !== null) {
|
|
3101
|
-
const cVal = Math.tan(a);
|
|
3102
|
-
if (Number.isFinite(cVal)) {
|
|
3103
|
-
if (g.o instanceof Var) {
|
|
3104
|
-
const s2 = { ...subst };
|
|
3105
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3106
|
-
return [s2];
|
|
3107
|
-
}
|
|
3108
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3109
|
-
return [{ ...subst }];
|
|
3110
|
-
}
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
return [];
|
|
3114
|
-
}
|
|
3115
|
-
|
|
3116
|
-
// math:tanh
|
|
3117
|
-
if (g.p instanceof Iri && g.p.value === MATH_NS + 'tanh') {
|
|
3118
|
-
const a = parseNum(g.s);
|
|
3119
|
-
if (a !== null && typeof Math.tanh === 'function') {
|
|
3120
|
-
const cVal = Math.tanh(a);
|
|
3121
|
-
if (Number.isFinite(cVal)) {
|
|
3122
|
-
if (g.o instanceof Var) {
|
|
3123
|
-
const s2 = { ...subst };
|
|
3124
|
-
s2[g.o.name] = new Literal(formatNum(cVal));
|
|
3125
|
-
return [s2];
|
|
3126
|
-
}
|
|
3127
|
-
if (numEqualTerm(g.o, cVal)) {
|
|
3128
|
-
return [{ ...subst }];
|
|
3129
|
-
}
|
|
3130
|
-
}
|
|
3131
|
-
}
|
|
3132
|
-
return [];
|
|
3133
|
-
}
|
|
3134
|
-
|
|
3135
3296
|
// -----------------------------------------------------------------
|
|
3136
3297
|
// 4.3 time: builtins
|
|
3137
3298
|
// -----------------------------------------------------------------
|
|
@@ -3264,12 +3425,37 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3264
3425
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3265
3426
|
const xs = g.s.elems;
|
|
3266
3427
|
const outs = [];
|
|
3428
|
+
|
|
3267
3429
|
for (let i = 0; i < xs.length; i++) {
|
|
3268
3430
|
const idxLit = new Literal(String(i)); // 0-based
|
|
3269
|
-
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]);
|
|
3270
3455
|
const s2 = unifyTerm(g.o, pair, subst);
|
|
3271
3456
|
if (s2 !== null) outs.push(s2);
|
|
3272
3457
|
}
|
|
3458
|
+
|
|
3273
3459
|
return outs;
|
|
3274
3460
|
}
|
|
3275
3461
|
|
|
@@ -3292,17 +3478,35 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3292
3478
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3293
3479
|
const [listTerm, indexTerm] = g.s.elems;
|
|
3294
3480
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
3481
|
+
|
|
3295
3482
|
const xs = listTerm.elems;
|
|
3296
3483
|
const outs = [];
|
|
3297
3484
|
|
|
3298
3485
|
for (let i = 0; i < xs.length; i++) {
|
|
3299
3486
|
const idxLit = new Literal(String(i)); // index starts at 0
|
|
3300
|
-
|
|
3301
|
-
if
|
|
3302
|
-
let
|
|
3303
|
-
|
|
3304
|
-
|
|
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);
|
|
3305
3508
|
}
|
|
3509
|
+
|
|
3306
3510
|
return outs;
|
|
3307
3511
|
}
|
|
3308
3512
|
|
|
@@ -3313,11 +3517,18 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3313
3517
|
if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
|
|
3314
3518
|
const [listTerm, itemTerm] = g.s.elems;
|
|
3315
3519
|
if (!(listTerm instanceof ListTerm)) return [];
|
|
3520
|
+
|
|
3521
|
+
// item must be bound
|
|
3522
|
+
const item2 = applySubstTerm(itemTerm, subst);
|
|
3523
|
+
if (!isGroundTerm(item2)) return [];
|
|
3524
|
+
|
|
3316
3525
|
const xs = listTerm.elems;
|
|
3317
3526
|
const filtered = [];
|
|
3318
3527
|
for (const e of xs) {
|
|
3319
|
-
|
|
3528
|
+
// strict term match (still allows plain "abc" == "abc"^^xsd:string)
|
|
3529
|
+
if (!termsEqualNoIntDecimal(e, item2)) filtered.push(e);
|
|
3320
3530
|
}
|
|
3531
|
+
|
|
3321
3532
|
const resList = new ListTerm(filtered);
|
|
3322
3533
|
const s2 = unifyTerm(g.o, resList, subst);
|
|
3323
3534
|
return s2 !== null ? [s2] : [];
|
|
@@ -3345,10 +3556,16 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
|
|
|
3345
3556
|
return outs;
|
|
3346
3557
|
}
|
|
3347
3558
|
|
|
3348
|
-
// list:length
|
|
3559
|
+
// list:length (strict: do not accept integer<->decimal matches for a ground object)
|
|
3349
3560
|
if (g.p instanceof Iri && g.p.value === LIST_NS + 'length') {
|
|
3350
3561
|
if (!(g.s instanceof ListTerm)) return [];
|
|
3351
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
|
+
|
|
3352
3569
|
const s2 = unifyTerm(g.o, nTerm, subst);
|
|
3353
3570
|
return s2 !== null ? [s2] : [];
|
|
3354
3571
|
}
|