eyeling 1.6.5 → 1.6.7

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 CHANGED
@@ -2404,6 +2404,20 @@ const XSD_INTEGER_DERIVED_DTS = new Set([
2404
2404
  XSD_NS + 'positiveInteger',
2405
2405
  ]);
2406
2406
 
2407
+ function parseXsdFloatSpecialLex(s) {
2408
+ if (s === 'INF' || s === '+INF') return Infinity;
2409
+ if (s === '-INF') return -Infinity;
2410
+ if (s === 'NaN') return NaN;
2411
+ return null;
2412
+ }
2413
+
2414
+ function formatXsdFloatSpecialLex(n) {
2415
+ if (n === Infinity) return 'INF';
2416
+ if (n === -Infinity) return '-INF';
2417
+ if (Number.isNaN(n)) return 'NaN';
2418
+ return null;
2419
+ }
2420
+
2407
2421
  function isQuotedLexical(lex) {
2408
2422
  // Note: the lexer stores long strings with literal delimiters: """..."""
2409
2423
  return (
@@ -2440,7 +2454,7 @@ function looksLikeUntypedNumericTokenLex(lex) {
2440
2454
 
2441
2455
  function parseNum(t) {
2442
2456
  // Parse as JS Number, but ONLY for xsd numeric datatypes or untyped numeric tokens.
2443
- // Rejects values such as "1"^^<...non-numeric...> or "1" (a string literal).
2457
+ // For xsd:float/xsd:double, accept INF/-INF/NaN (and +INF).
2444
2458
  if (!(t instanceof Literal)) return null;
2445
2459
 
2446
2460
  const [lex, dt] = literalParts(t.value);
@@ -2449,6 +2463,17 @@ function parseNum(t) {
2449
2463
  if (dt !== null) {
2450
2464
  if (!isXsdNumericDatatype(dt)) return null;
2451
2465
  const val = stripQuotes(lex);
2466
+
2467
+ // float/double: allow INF/-INF/NaN and allow +/-Infinity results
2468
+ if (dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) {
2469
+ const sp = parseXsdFloatSpecialLex(val);
2470
+ if (sp !== null) return sp;
2471
+ const n = Number(val);
2472
+ if (Number.isNaN(n)) return null;
2473
+ return n; // may be finite, +/-Infinity, or NaN (if val was "NaN" handled above)
2474
+ }
2475
+
2476
+ // decimal/integer-derived: keep strict finite parsing
2452
2477
  const n = Number(val);
2453
2478
  if (!Number.isFinite(n)) return null;
2454
2479
  return n;
@@ -2705,13 +2730,21 @@ function listAppendSplit(parts, resElems, subst) {
2705
2730
 
2706
2731
  function numEqualTerm(t, n, eps = 1e-9) {
2707
2732
  const v = parseNum(t);
2708
- return v !== null && Math.abs(v - n) < eps;
2733
+ if (v === null) return false;
2734
+
2735
+ // NaN is not equal to anything (including itself) for our numeric-equality use.
2736
+ if (Number.isNaN(v) || Number.isNaN(n)) return false;
2737
+
2738
+ // Infinity handling
2739
+ if (!Number.isFinite(v) || !Number.isFinite(n)) return v === n;
2740
+
2741
+ return Math.abs(v - n) < eps;
2709
2742
  }
2710
2743
 
2711
2744
  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';
2745
+ if (/[eE]/.test(lex)) return XSD_DOUBLE_DT;
2746
+ if (lex.includes('.')) return XSD_DECIMAL_DT;
2747
+ return XSD_INTEGER_DT;
2715
2748
  }
2716
2749
 
2717
2750
  function parseNumericLiteralInfo(t) {
@@ -2724,21 +2757,21 @@ function parseNumericLiteralInfo(t) {
2724
2757
  let lexStr;
2725
2758
 
2726
2759
  if (dt2 !== null) {
2727
- if (dt2 !== XSD_NS + 'integer' && dt2 !== XSD_NS + 'decimal' && dt2 !== XSD_NS + 'double') return null;
2760
+ // Accept all xsd numeric datatypes; normalize integer-derived to xsd:integer.
2761
+ if (!isXsdNumericDatatype(dt2)) return null;
2762
+ if (isXsdIntegerDatatype(dt2)) dt2 = XSD_INTEGER_DT;
2728
2763
  lexStr = stripQuotes(lex);
2729
2764
  } else {
2765
+ // Untyped numeric token (N3/Turtle numeric literal)
2730
2766
  if (typeof v !== 'string') return null;
2731
2767
  if (v.startsWith('"')) return null; // exclude quoted strings
2732
2768
  if (!/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(v)) return null;
2733
2769
 
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
-
2770
+ dt2 = numericDatatypeFromLex(v);
2738
2771
  lexStr = v;
2739
2772
  }
2740
2773
 
2741
- if (dt2 === XSD_NS + 'integer') {
2774
+ if (dt2 === XSD_INTEGER_DT) {
2742
2775
  try {
2743
2776
  return { dt: dt2, kind: 'bigint', value: BigInt(lexStr), lexStr };
2744
2777
  } catch {
@@ -2746,11 +2779,82 @@ function parseNumericLiteralInfo(t) {
2746
2779
  }
2747
2780
  }
2748
2781
 
2782
+ // float/double special lexicals
2783
+ if (dt2 === XSD_FLOAT_DT || dt2 === XSD_DOUBLE_DT) {
2784
+ const sp = parseXsdFloatSpecialLex(lexStr);
2785
+ if (sp !== null) return { dt: dt2, kind: 'number', value: sp, lexStr };
2786
+ }
2787
+
2749
2788
  const num = Number(lexStr);
2750
2789
  if (Number.isNaN(num)) return null;
2790
+
2791
+ // allow +/-Infinity for float/double
2792
+ if ((dt2 === XSD_DECIMAL_DT) && !Number.isFinite(num)) return null;
2793
+
2751
2794
  return { dt: dt2, kind: 'number', value: num, lexStr };
2752
2795
  }
2753
2796
 
2797
+ function numericRank(dt) {
2798
+ if (dt === XSD_INTEGER_DT) return 0;
2799
+ if (dt === XSD_DECIMAL_DT) return 1;
2800
+ if (dt === XSD_FLOAT_DT) return 2;
2801
+ if (dt === XSD_DOUBLE_DT) return 3;
2802
+ return -1;
2803
+ }
2804
+
2805
+ function numericDatatypeOfTerm(t) {
2806
+ if (!(t instanceof Literal)) return null;
2807
+ const [lex, dt] = literalParts(t.value);
2808
+
2809
+ if (dt !== null) {
2810
+ if (!isXsdNumericDatatype(dt)) return null;
2811
+ if (isXsdIntegerDatatype(dt)) return XSD_INTEGER_DT;
2812
+ if (dt === XSD_DECIMAL_DT || dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) return dt;
2813
+ return null;
2814
+ }
2815
+
2816
+ // Untyped numeric token
2817
+ if (!looksLikeUntypedNumericTokenLex(lex)) return null;
2818
+ return numericDatatypeFromLex(lex);
2819
+ }
2820
+
2821
+ function commonNumericDatatype(terms, outTerm) {
2822
+ let r = 0;
2823
+ const all = Array.isArray(terms) ? terms.slice() : [];
2824
+ if (outTerm) all.push(outTerm);
2825
+
2826
+ for (const t of all) {
2827
+ const dt = numericDatatypeOfTerm(t);
2828
+ if (!dt) continue;
2829
+ const rr = numericRank(dt);
2830
+ if (rr > r) r = rr;
2831
+ }
2832
+
2833
+ if (r === 3) return XSD_DOUBLE_DT;
2834
+ if (r === 2) return XSD_FLOAT_DT;
2835
+ if (r === 1) return XSD_DECIMAL_DT;
2836
+ return XSD_INTEGER_DT;
2837
+ }
2838
+
2839
+ function makeNumericOutputLiteral(val, dt) {
2840
+ if (dt === XSD_INTEGER_DT) {
2841
+ if (typeof val === 'bigint') return new Literal(val.toString());
2842
+ if (Number.isInteger(val)) return new Literal(String(val));
2843
+ // If a non-integer sneaks in, promote to decimal.
2844
+ return new Literal(`"${formatNum(val)}"^^<${XSD_DECIMAL_DT}>`);
2845
+ }
2846
+
2847
+ if (dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) {
2848
+ const sp = formatXsdFloatSpecialLex(val);
2849
+ const lex = sp !== null ? sp : formatNum(val);
2850
+ return new Literal(`"${lex}"^^<${dt}>`);
2851
+ }
2852
+
2853
+ // decimal
2854
+ const lex = typeof val === 'bigint' ? val.toString() : formatNum(val);
2855
+ return new Literal(`"${lex}"^^<${dt}>`);
2856
+ }
2857
+
2754
2858
  function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
2755
2859
  const sIsUnbound = g.s instanceof Var || g.s instanceof Blank;
2756
2860
  const oIsUnbound = g.o instanceof Var || g.o instanceof Blank;
@@ -2762,9 +2866,13 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
2762
2866
  if (a !== null) {
2763
2867
  const outVal = forwardFn(a);
2764
2868
  if (!Number.isFinite(outVal)) return [];
2869
+
2870
+ let outDt = commonNumericDatatype([g.s], g.o);
2871
+ if (outDt === XSD_INTEGER_DT && !Number.isInteger(outVal)) outDt = XSD_DECIMAL_DT;
2872
+
2765
2873
  if (g.o instanceof Var) {
2766
2874
  const s2 = { ...subst };
2767
- s2[g.o.name] = new Literal(formatNum(outVal));
2875
+ s2[g.o.name] = makeNumericOutputLiteral(outVal, outDt);
2768
2876
  return [s2];
2769
2877
  }
2770
2878
  if (g.o instanceof Blank) return [{ ...subst }];
@@ -2776,9 +2884,13 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
2776
2884
  if (b !== null && typeof inverseFn === 'function') {
2777
2885
  const inVal = inverseFn(b);
2778
2886
  if (!Number.isFinite(inVal)) return [];
2887
+
2888
+ let inDt = commonNumericDatatype([g.o], g.s);
2889
+ if (inDt === XSD_INTEGER_DT && !Number.isInteger(inVal)) inDt = XSD_DECIMAL_DT;
2890
+
2779
2891
  if (g.s instanceof Var) {
2780
2892
  const s2 = { ...subst };
2781
- s2[g.s.name] = new Literal(formatNum(inVal));
2893
+ s2[g.s.name] = makeNumericOutputLiteral(inVal, inDt);
2782
2894
  return [s2];
2783
2895
  }
2784
2896
  if (g.s instanceof Blank) return [{ ...subst }];
@@ -2959,153 +3071,225 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2959
3071
  }
2960
3072
 
2961
3073
  // math:sum
3074
+ // Schema: ( $s.i+ )+ math:sum $o-
2962
3075
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'sum') {
2963
- if (g.s instanceof ListTerm && g.s.elems.length >= 2) {
2964
- const xs = g.s.elems;
2965
- const values = [];
3076
+ if (!(g.s instanceof ListTerm) || g.s.elems.length < 2) return [];
3077
+ const xs = g.s.elems;
3078
+
3079
+ const dtOut0 = commonNumericDatatype(xs, g.o);
3080
+
3081
+ // Exact integer mode
3082
+ if (dtOut0 === XSD_INTEGER_DT) {
3083
+ let total = 0n;
2966
3084
  for (const t of xs) {
2967
- const v = parseNumberLiteral(t);
3085
+ const v = parseIntLiteral(t);
2968
3086
  if (v === null) return [];
2969
- values.push(v);
2970
- }
2971
-
2972
- let lit;
2973
- const allBig = values.every((v) => typeof v === 'bigint');
2974
- if (allBig) {
2975
- let total = 0n;
2976
- for (const v of values) total += v;
2977
- lit = new Literal(total.toString());
2978
- } else {
2979
- let total = 0.0;
2980
- for (const v of values) {
2981
- total += typeof v === 'bigint' ? Number(v) : v;
2982
- }
2983
- lit = new Literal(formatNum(total));
3087
+ total += v;
2984
3088
  }
2985
3089
 
2986
3090
  if (g.o instanceof Var) {
2987
3091
  const s2 = { ...subst };
2988
- s2[g.o.name] = lit;
3092
+ s2[g.o.name] = makeNumericOutputLiteral(total, XSD_INTEGER_DT);
2989
3093
  return [s2];
2990
3094
  }
2991
- const s2 = unifyTerm(g.o, lit, subst);
2992
- return s2 !== null ? [s2] : [];
3095
+ if (g.o instanceof Blank) return [{ ...subst }];
3096
+
3097
+ const oi = parseIntLiteral(g.o);
3098
+ if (oi !== null && oi === total) return [{ ...subst }];
3099
+
3100
+ // Fallback numeric compare
3101
+ if (numEqualTerm(g.o, Number(total))) return [{ ...subst }];
3102
+ return [];
3103
+ }
3104
+
3105
+ // Numeric mode (decimal/float/double)
3106
+ let total = 0.0;
3107
+ for (const t of xs) {
3108
+ const v = parseNum(t);
3109
+ if (v === null) return [];
3110
+ total += v;
3111
+ }
3112
+
3113
+ let dtOut = dtOut0;
3114
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(total)) dtOut = XSD_DECIMAL_DT;
3115
+ const lit = makeNumericOutputLiteral(total, dtOut);
3116
+
3117
+ if (g.o instanceof Var) {
3118
+ const s2 = { ...subst };
3119
+ s2[g.o.name] = lit;
3120
+ return [s2];
2993
3121
  }
3122
+ if (g.o instanceof Blank) return [{ ...subst }];
3123
+ if (numEqualTerm(g.o, total)) return [{ ...subst }];
2994
3124
  return [];
2995
3125
  }
2996
3126
 
2997
3127
  // math:product
3128
+ // Schema: ( $s.i+ )+ math:product $o-
2998
3129
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'product') {
2999
- if (g.s instanceof ListTerm && g.s.elems.length >= 2) {
3000
- const xs = g.s.elems;
3001
- const values = [];
3130
+ if (!(g.s instanceof ListTerm) || g.s.elems.length < 2) return [];
3131
+ const xs = g.s.elems;
3132
+
3133
+ const dtOut0 = commonNumericDatatype(xs, g.o);
3134
+
3135
+ // Exact integer mode
3136
+ if (dtOut0 === XSD_INTEGER_DT) {
3137
+ let prod = 1n;
3002
3138
  for (const t of xs) {
3003
- const v = parseNumberLiteral(t);
3139
+ const v = parseIntLiteral(t);
3004
3140
  if (v === null) return [];
3005
- values.push(v);
3006
- }
3007
-
3008
- let lit;
3009
- const allBig = values.every((v) => typeof v === 'bigint');
3010
- if (allBig) {
3011
- let prod = 1n;
3012
- for (const v of values) prod *= v;
3013
- lit = new Literal(prod.toString());
3014
- } else {
3015
- let prod = 1.0;
3016
- for (const v of values) {
3017
- prod *= typeof v === 'bigint' ? Number(v) : v;
3018
- }
3019
- lit = new Literal(formatNum(prod));
3141
+ prod *= v;
3020
3142
  }
3021
3143
 
3022
3144
  if (g.o instanceof Var) {
3023
3145
  const s2 = { ...subst };
3024
- s2[g.o.name] = lit;
3146
+ s2[g.o.name] = makeNumericOutputLiteral(prod, XSD_INTEGER_DT);
3025
3147
  return [s2];
3026
3148
  }
3027
- const s2 = unifyTerm(g.o, lit, subst);
3028
- return s2 !== null ? [s2] : [];
3149
+ if (g.o instanceof Blank) return [{ ...subst }];
3150
+
3151
+ const oi = parseIntLiteral(g.o);
3152
+ if (oi !== null && oi === prod) return [{ ...subst }];
3153
+ if (numEqualTerm(g.o, Number(prod))) return [{ ...subst }];
3154
+ return [];
3029
3155
  }
3156
+
3157
+ // Numeric mode (decimal/float/double)
3158
+ let prod = 1.0;
3159
+ for (const t of xs) {
3160
+ const v = parseNum(t);
3161
+ if (v === null) return [];
3162
+ prod *= v;
3163
+ }
3164
+
3165
+ let dtOut = dtOut0;
3166
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(prod)) dtOut = XSD_DECIMAL_DT;
3167
+ const lit = makeNumericOutputLiteral(prod, dtOut);
3168
+
3169
+ if (g.o instanceof Var) {
3170
+ const s2 = { ...subst };
3171
+ s2[g.o.name] = lit;
3172
+ return [s2];
3173
+ }
3174
+ if (g.o instanceof Blank) return [{ ...subst }];
3175
+ if (numEqualTerm(g.o, prod)) return [{ ...subst }];
3176
+ return [];
3030
3177
  }
3031
3178
 
3032
3179
  // math:difference
3180
+ // Schema: ( $s.1+ $s.2+ )+ math:difference $o-
3033
3181
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'difference') {
3034
- if (g.s instanceof ListTerm && g.s.elems.length === 2) {
3035
- const [a0, b0] = g.s.elems;
3036
-
3037
- // BigInt integer difference
3038
- const ai = parseIntLiteral(a0);
3039
- const bi = parseIntLiteral(b0);
3040
- if (ai !== null && bi !== null) {
3041
- const ci = ai - bi;
3042
- const lit = new Literal(ci.toString());
3182
+ if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3183
+ const [a0, b0] = g.s.elems;
3184
+
3185
+ // 1) Date/datetime difference -> duration (needed for examples/age.n3)
3186
+ const aDt = parseDatetimeLike(a0);
3187
+ const bDt = parseDatetimeLike(b0);
3188
+ if (aDt !== null && bDt !== null) {
3189
+ const diffSecs = (aDt.getTime() - bDt.getTime()) / 1000.0;
3190
+ const durTerm = formatDurationLiteralFromSeconds(diffSecs);
3191
+ if (g.o instanceof Var) {
3192
+ const s2 = { ...subst };
3193
+ s2[g.o.name] = durTerm;
3194
+ return [s2];
3195
+ }
3196
+ const s2 = unifyTerm(g.o, durTerm, subst);
3197
+ return s2 !== null ? [s2] : [];
3198
+ }
3199
+
3200
+ // 2) Date/datetime minus duration/seconds -> dateTime (keeps older functionality)
3201
+ if (aDt !== null) {
3202
+ const secs = parseNumOrDuration(b0);
3203
+ if (secs !== null) {
3204
+ const outSecs = aDt.getTime() / 1000.0 - secs;
3205
+ const lex = utcIsoDateTimeStringFromEpochSeconds(outSecs);
3206
+ const lit = new Literal(`"${lex}"^^<${XSD_NS}dateTime>`);
3043
3207
  if (g.o instanceof Var) {
3044
3208
  const s2 = { ...subst };
3045
3209
  s2[g.o.name] = lit;
3046
3210
  return [s2];
3047
- } else {
3048
- const s2 = unifyTerm(g.o, lit, subst);
3049
- return s2 !== null ? [s2] : [];
3050
3211
  }
3212
+ const s2 = unifyTerm(g.o, lit, subst);
3213
+ return s2 !== null ? [s2] : [];
3051
3214
  }
3215
+ }
3052
3216
 
3053
- // Numeric difference via floats
3054
- const a = parseNum(a0);
3055
- const b = parseNum(b0);
3056
- if (a !== null && b !== null) {
3057
- const c = a - b;
3058
- if (g.o instanceof Var) {
3059
- const s2 = { ...subst };
3060
- s2[g.o.name] = new Literal(formatNum(c));
3061
- return [s2];
3062
- }
3063
- if (g.o instanceof Literal && g.o.value === formatNum(c)) {
3064
- return [{ ...subst }];
3065
- }
3217
+ // 3) Exact integer difference (BigInt)
3218
+ const ai = parseIntLiteral(a0);
3219
+ const bi = parseIntLiteral(b0);
3220
+ if (ai !== null && bi !== null) {
3221
+ const ci = ai - bi;
3222
+ const lit = new Literal(ci.toString());
3223
+ if (g.o instanceof Var) {
3224
+ const s2 = { ...subst };
3225
+ s2[g.o.name] = lit;
3226
+ return [s2];
3066
3227
  }
3228
+ const s2 = unifyTerm(g.o, lit, subst);
3229
+ return s2 !== null ? [s2] : [];
3230
+ }
3067
3231
 
3068
- // Date/datetime difference -> duration
3069
- const aDt = parseDatetimeLike(a0);
3070
- const bDt = parseDatetimeLike(b0);
3071
- if (aDt !== null && bDt !== null) {
3072
- const diffSecs = (aDt.getTime() - bDt.getTime()) / 1000.0;
3073
- const durTerm = formatDurationLiteralFromSeconds(diffSecs);
3074
- if (g.o instanceof Var) {
3075
- const s2 = { ...subst };
3076
- s2[g.o.name] = durTerm;
3077
- return [s2];
3078
- }
3079
- const s2 = unifyTerm(g.o, durTerm, subst);
3080
- return s2 !== null ? [s2] : [];
3232
+ // 4) Numeric difference (your “typed output + numeric compare” version)
3233
+ const a = parseNum(a0);
3234
+ const b = parseNum(b0);
3235
+ if (a === null || b === null) return [];
3236
+
3237
+ const c = a - b;
3238
+ if (!Number.isFinite(c)) return [];
3239
+
3240
+ // If you added commonNumericDatatype/makeNumericOutputLiteral, keep using them:
3241
+ if (typeof commonNumericDatatype === 'function' && typeof makeNumericOutputLiteral === 'function') {
3242
+ let dtOut = commonNumericDatatype([a0, b0], g.o);
3243
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(c)) dtOut = XSD_DECIMAL_DT;
3244
+ const lit = makeNumericOutputLiteral(c, dtOut);
3245
+
3246
+ if (g.o instanceof Var) {
3247
+ const s2 = { ...subst };
3248
+ s2[g.o.name] = lit;
3249
+ return [s2];
3081
3250
  }
3251
+ if (g.o instanceof Blank) return [{ ...subst }];
3252
+ if (numEqualTerm(g.o, c)) return [{ ...subst }];
3082
3253
  return [];
3083
3254
  }
3255
+
3256
+ // Fallback (if you *don’t* have those helpers yet):
3257
+ const lit = new Literal(formatNum(c));
3258
+ if (g.o instanceof Var) {
3259
+ const s2 = { ...subst };
3260
+ s2[g.o.name] = lit;
3261
+ return [s2];
3262
+ }
3263
+ const s2 = unifyTerm(g.o, lit, subst);
3264
+ return s2 !== null ? [s2] : [];
3084
3265
  }
3085
3266
 
3086
3267
  // math:quotient
3268
+ // Schema: ( $s.1+ $s.2+ )+ math:quotient $o-
3087
3269
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'quotient') {
3088
- if (g.s instanceof ListTerm && g.s.elems.length === 2) {
3089
- const a = parseNum(g.s.elems[0]);
3090
- const b = parseNum(g.s.elems[1]);
3091
- if (a !== null && b !== null && b !== 0.0) {
3092
- const c = a / b;
3093
- if (g.o instanceof Var) {
3094
- const s2 = { ...subst };
3095
- s2[g.o.name] = new Literal(formatNum(c));
3096
- return [s2];
3097
- }
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];
3103
- }
3104
- const s2 = unifyTerm(g.o, lit, subst);
3105
- return s2 !== null ? [s2] : [];
3106
- }
3107
- return [];
3270
+ if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3271
+ const [a0, b0] = g.s.elems;
3272
+
3273
+ const a = parseNum(a0);
3274
+ const b = parseNum(b0);
3275
+ if (a === null || b === null) return [];
3276
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return [];
3277
+
3278
+ const c = a / b;
3279
+ if (!Number.isFinite(c)) return [];
3280
+
3281
+ let dtOut = commonNumericDatatype([a0, b0], g.o);
3282
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(c)) dtOut = XSD_DECIMAL_DT;
3283
+ const lit = makeNumericOutputLiteral(c, dtOut);
3284
+
3285
+ if (g.o instanceof Var) {
3286
+ const s2 = { ...subst };
3287
+ s2[g.o.name] = lit;
3288
+ return [s2];
3108
3289
  }
3290
+ if (g.o instanceof Blank) return [{ ...subst }];
3291
+ if (numEqualTerm(g.o, c)) return [{ ...subst }];
3292
+ return [];
3109
3293
  }
3110
3294
 
3111
3295
  // math:integerQuotient
@@ -3152,30 +3336,43 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3152
3336
  // math:exponentiation
3153
3337
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'exponentiation') {
3154
3338
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
3155
- const a = parseNum(g.s.elems[0]);
3156
- const b0 = g.s.elems[1];
3157
- const c = parseNum(g.o);
3339
+ const baseTerm = g.s.elems[0];
3340
+ const expTerm = g.s.elems[1];
3341
+
3342
+ const a = parseNum(baseTerm);
3158
3343
  let b = null;
3159
- if (a !== null && b0 instanceof Literal) b = parseNum(b0);
3344
+ if (a !== null) b = parseNum(expTerm);
3160
3345
 
3346
+ // Forward mode: base and exponent are numeric
3161
3347
  if (a !== null && b !== null) {
3162
3348
  const cVal = a ** b;
3349
+ if (!Number.isFinite(cVal)) return [];
3350
+
3351
+ let dtOut = commonNumericDatatype([baseTerm, expTerm], g.o);
3352
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(cVal)) dtOut = XSD_DECIMAL_DT;
3353
+ const lit = makeNumericOutputLiteral(cVal, dtOut);
3354
+
3163
3355
  if (g.o instanceof Var) {
3164
3356
  const s2 = { ...subst };
3165
- s2[g.o.name] = new Literal(formatNum(cVal));
3357
+ s2[g.o.name] = lit;
3166
3358
  return [s2];
3167
3359
  }
3168
- if (numEqualTerm(g.o, cVal)) {
3169
- return [{ ...subst }];
3170
- }
3360
+ if (g.o instanceof Blank) return [{ ...subst }];
3361
+ if (numEqualTerm(g.o, cVal)) return [{ ...subst }];
3171
3362
  }
3172
3363
 
3173
- // inverse mode
3174
- if (a !== null && b0 instanceof Var && c !== null) {
3364
+ // Inverse mode: solve exponent
3365
+ const c = parseNum(g.o);
3366
+ if (a !== null && expTerm instanceof Var && c !== null) {
3175
3367
  if (a > 0.0 && a !== 1.0 && c > 0.0) {
3176
3368
  const bVal = Math.log(c) / Math.log(a);
3369
+ if (!Number.isFinite(bVal)) return [];
3370
+
3371
+ let dtB = commonNumericDatatype([baseTerm, g.o], expTerm);
3372
+ if (dtB === XSD_INTEGER_DT && !Number.isInteger(bVal)) dtB = XSD_DECIMAL_DT;
3373
+
3177
3374
  const s2 = { ...subst };
3178
- s2[b0.name] = new Literal(formatNum(bVal));
3375
+ s2[expTerm.name] = makeNumericOutputLiteral(bVal, dtB);
3179
3376
  return [s2];
3180
3377
  }
3181
3378
  }
@@ -3186,15 +3383,21 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3186
3383
  // math:absoluteValue
3187
3384
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'absoluteValue') {
3188
3385
  const a = parseNum(g.s);
3189
- if (a !== null && g.o instanceof Var) {
3386
+ if (a === null) return [];
3387
+
3388
+ const outVal = Math.abs(a);
3389
+ if (!Number.isFinite(outVal)) return [];
3390
+
3391
+ let dtOut = commonNumericDatatype([g.s], g.o);
3392
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(outVal)) dtOut = XSD_DECIMAL_DT;
3393
+
3394
+ if (g.o instanceof Var) {
3190
3395
  const s2 = { ...subst };
3191
- s2[g.o.name] = new Literal(formatNum(Math.abs(a)));
3396
+ s2[g.o.name] = makeNumericOutputLiteral(outVal, dtOut);
3192
3397
  return [s2];
3193
3398
  }
3194
- const b = parseNum(g.o);
3195
- if (a !== null && b !== null) {
3196
- if (Math.abs(Math.abs(a) - b) < 1e-9) return [{ ...subst }];
3197
- }
3399
+ if (g.o instanceof Blank) return [{ ...subst }];
3400
+ if (numEqualTerm(g.o, outVal)) return [{ ...subst }];
3198
3401
  return [];
3199
3402
  }
3200
3403
 
@@ -3260,20 +3463,47 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3260
3463
  // Schema: ( $a $b ) math:remainder $r
3261
3464
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'remainder') {
3262
3465
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3263
- const a = parseNum(g.s.elems[0]);
3264
- const b = parseNum(g.s.elems[1]);
3265
- if (a === null || b === null || b === 0) return [];
3466
+ const [a0, b0] = g.s.elems;
3467
+
3468
+ // Prefer exact integer arithmetic (BigInt)
3469
+ const ai = parseIntLiteral(a0);
3470
+ const bi = parseIntLiteral(b0);
3471
+ if (ai !== null && bi !== null) {
3472
+ if (bi === 0n) return [];
3473
+ const r = ai % bi;
3474
+ const lit = makeNumericOutputLiteral(r, XSD_INTEGER_DT);
3475
+
3476
+ if (g.o instanceof Var) {
3477
+ const s2 = { ...subst };
3478
+ s2[g.o.name] = lit;
3479
+ return [s2];
3480
+ }
3481
+ if (g.o instanceof Blank) return [{ ...subst }];
3482
+
3483
+ const oi = parseIntLiteral(g.o);
3484
+ if (oi !== null && oi === r) return [{ ...subst }];
3485
+ if (numEqualTerm(g.o, Number(r))) return [{ ...subst }];
3486
+ return [];
3487
+ }
3488
+
3489
+ // Fallback: allow Number literals that still represent integers
3490
+ const a = parseNum(a0);
3491
+ const b = parseNum(b0);
3492
+ if (a === null || b === null) return [];
3493
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return [];
3494
+ if (!Number.isInteger(a) || !Number.isInteger(b)) return [];
3495
+
3266
3496
  const rVal = a % b;
3267
- if (!Number.isFinite(rVal)) return [];
3268
- const lit = new Literal(formatNum(rVal));
3497
+ const lit = makeNumericOutputLiteral(rVal, XSD_INTEGER_DT);
3269
3498
 
3270
3499
  if (g.o instanceof Var) {
3271
3500
  const s2 = { ...subst };
3272
3501
  s2[g.o.name] = lit;
3273
3502
  return [s2];
3274
3503
  }
3275
- const s2 = unifyTerm(g.o, lit, subst);
3276
- return s2 !== null ? [s2] : [];
3504
+ if (g.o instanceof Blank) return [{ ...subst }];
3505
+ if (numEqualTerm(g.o, rVal)) return [{ ...subst }];
3506
+ return [];
3277
3507
  }
3278
3508
 
3279
3509
  // math:rounded