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.
Files changed (2) hide show
  1. package/eyeling.js +330 -237
  2. 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 (EYE-style)
1272
- const av = parseNumberLiteral(a);
1273
- const bv = parseNumberLiteral(b);
1274
- if (av !== null && bv !== null) {
1275
- if (typeof av === 'bigint' && typeof bv === 'bigint') return av === bv;
1276
- const an = typeof av === 'bigint' ? Number(av) : av;
1277
- const bn = typeof bv === 'bigint' ? Number(bv) : bv;
1278
- return !Number.isNaN(an) && !Number.isNaN(bn) && an === bn;
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 && ai.dt === bi.dt) {
1910
- if (ai.kind === 'bigint' && bi.kind === 'bigint') {
1911
- if (ai.value === bi.value) return { ...subst };
1912
- } else {
1913
- const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
1914
- const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
1915
- if (!Number.isNaN(an) && !Number.isNaN(bn) && an === bn) return { ...subst };
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
- // Only handle the numeric datatypes we want to unify by value
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 = numericDatatypeFromLex(v);
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
- if (g.o instanceof Literal && g.o.value === lit.value) {
2840
- return [{ ...subst }];
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
- if (g.o instanceof Literal && g.o.value === durTerm.value) {
2894
- return [{ ...subst }];
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
- if (g.o instanceof Literal && g.o.value === formatNum(c)) {
2914
- return [{ ...subst }];
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
- const a = parseNum(g.s);
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
- const a = parseNum(g.s);
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
- const a = parseNum(g.s);
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 [];
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
- // Hyperbolic cosine
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
- const a = parseNum(g.s);
3128
- if (a !== null && typeof Math.cosh === 'function') {
3129
- const cVal = Math.cosh(a);
3130
- if (Number.isFinite(cVal)) {
3131
- if (g.o instanceof Var) {
3132
- const s2 = { ...subst };
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 a = parseNum(g.s);
3148
- if (a !== null) {
3149
- const cVal = (a * 180.0) / Math.PI;
3150
- if (Number.isFinite(cVal)) {
3151
- if (g.o instanceof Var) {
3152
- const s2 = { ...subst };
3153
- s2[g.o.name] = new Literal(formatNum(cVal));
3154
- return [s2];
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 pair = new ListTerm([idxLit, xs[i]]);
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
- let s1 = unifyTerm(indexTerm, idxLit, subst);
3427
- if (s1 === null) continue;
3428
- let s2 = unifyTerm(g.o, xs[i], s1);
3429
- if (s2 === null) continue;
3430
- outs.push(s2);
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
- if (!termsEqual(e, itemTerm)) filtered.push(e);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.6.3",
3
+ "version": "1.6.5",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [