eyeling 1.6.4 → 1.6.6

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
@@ -2709,9 +2709,9 @@ function numEqualTerm(t, n, eps = 1e-9) {
2709
2709
  }
2710
2710
 
2711
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';
2712
+ if (/[eE]/.test(lex)) return XSD_DOUBLE_DT;
2713
+ if (lex.includes('.')) return XSD_DECIMAL_DT;
2714
+ return XSD_INTEGER_DT;
2715
2715
  }
2716
2716
 
2717
2717
  function parseNumericLiteralInfo(t) {
@@ -2724,21 +2724,21 @@ function parseNumericLiteralInfo(t) {
2724
2724
  let lexStr;
2725
2725
 
2726
2726
  if (dt2 !== null) {
2727
- if (dt2 !== XSD_NS + 'integer' && dt2 !== XSD_NS + 'decimal' && dt2 !== XSD_NS + 'double') return null;
2727
+ // Accept all xsd numeric datatypes; normalize integer-derived to xsd:integer.
2728
+ if (!isXsdNumericDatatype(dt2)) return null;
2729
+ if (isXsdIntegerDatatype(dt2)) dt2 = XSD_INTEGER_DT;
2728
2730
  lexStr = stripQuotes(lex);
2729
2731
  } else {
2732
+ // Untyped numeric token (N3/Turtle numeric literal)
2730
2733
  if (typeof v !== 'string') return null;
2731
2734
  if (v.startsWith('"')) return null; // exclude quoted strings
2732
2735
  if (!/^[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?$/.test(v)) return null;
2733
2736
 
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
-
2737
+ dt2 = numericDatatypeFromLex(v);
2738
2738
  lexStr = v;
2739
2739
  }
2740
2740
 
2741
- if (dt2 === XSD_NS + 'integer') {
2741
+ if (dt2 === XSD_INTEGER_DT) {
2742
2742
  try {
2743
2743
  return { dt: dt2, kind: 'bigint', value: BigInt(lexStr), lexStr };
2744
2744
  } catch {
@@ -2751,6 +2751,59 @@ function parseNumericLiteralInfo(t) {
2751
2751
  return { dt: dt2, kind: 'number', value: num, lexStr };
2752
2752
  }
2753
2753
 
2754
+ function numericRank(dt) {
2755
+ if (dt === XSD_INTEGER_DT) return 0;
2756
+ if (dt === XSD_DECIMAL_DT) return 1;
2757
+ if (dt === XSD_FLOAT_DT) return 2;
2758
+ if (dt === XSD_DOUBLE_DT) return 3;
2759
+ return -1;
2760
+ }
2761
+
2762
+ function numericDatatypeOfTerm(t) {
2763
+ if (!(t instanceof Literal)) return null;
2764
+ const [lex, dt] = literalParts(t.value);
2765
+
2766
+ if (dt !== null) {
2767
+ if (!isXsdNumericDatatype(dt)) return null;
2768
+ if (isXsdIntegerDatatype(dt)) return XSD_INTEGER_DT;
2769
+ if (dt === XSD_DECIMAL_DT || dt === XSD_FLOAT_DT || dt === XSD_DOUBLE_DT) return dt;
2770
+ return null;
2771
+ }
2772
+
2773
+ // Untyped numeric token
2774
+ if (!looksLikeUntypedNumericTokenLex(lex)) return null;
2775
+ return numericDatatypeFromLex(lex);
2776
+ }
2777
+
2778
+ function commonNumericDatatype(terms, outTerm) {
2779
+ let r = 0;
2780
+ const all = Array.isArray(terms) ? terms.slice() : [];
2781
+ if (outTerm) all.push(outTerm);
2782
+
2783
+ for (const t of all) {
2784
+ const dt = numericDatatypeOfTerm(t);
2785
+ if (!dt) continue;
2786
+ const rr = numericRank(dt);
2787
+ if (rr > r) r = rr;
2788
+ }
2789
+
2790
+ if (r === 3) return XSD_DOUBLE_DT;
2791
+ if (r === 2) return XSD_FLOAT_DT;
2792
+ if (r === 1) return XSD_DECIMAL_DT;
2793
+ return XSD_INTEGER_DT;
2794
+ }
2795
+
2796
+ function makeNumericOutputLiteral(val, dt) {
2797
+ if (dt === XSD_INTEGER_DT) {
2798
+ if (typeof val === 'bigint') return new Literal(val.toString());
2799
+ if (Number.isInteger(val)) return new Literal(String(val));
2800
+ // If a non-integer sneaks in, promote to decimal.
2801
+ return new Literal(`"${formatNum(val)}"^^<${XSD_DECIMAL_DT}>`);
2802
+ }
2803
+ const lex = typeof val === 'bigint' ? val.toString() : formatNum(val);
2804
+ return new Literal(`"${lex}"^^<${dt}>`);
2805
+ }
2806
+
2754
2807
  function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
2755
2808
  const sIsUnbound = g.s instanceof Var || g.s instanceof Blank;
2756
2809
  const oIsUnbound = g.o instanceof Var || g.o instanceof Blank;
@@ -2762,9 +2815,13 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
2762
2815
  if (a !== null) {
2763
2816
  const outVal = forwardFn(a);
2764
2817
  if (!Number.isFinite(outVal)) return [];
2818
+
2819
+ let outDt = commonNumericDatatype([g.s], g.o);
2820
+ if (outDt === XSD_INTEGER_DT && !Number.isInteger(outVal)) outDt = XSD_DECIMAL_DT;
2821
+
2765
2822
  if (g.o instanceof Var) {
2766
2823
  const s2 = { ...subst };
2767
- s2[g.o.name] = new Literal(formatNum(outVal));
2824
+ s2[g.o.name] = makeNumericOutputLiteral(outVal, outDt);
2768
2825
  return [s2];
2769
2826
  }
2770
2827
  if (g.o instanceof Blank) return [{ ...subst }];
@@ -2776,9 +2833,13 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
2776
2833
  if (b !== null && typeof inverseFn === 'function') {
2777
2834
  const inVal = inverseFn(b);
2778
2835
  if (!Number.isFinite(inVal)) return [];
2836
+
2837
+ let inDt = commonNumericDatatype([g.o], g.s);
2838
+ if (inDt === XSD_INTEGER_DT && !Number.isInteger(inVal)) inDt = XSD_DECIMAL_DT;
2839
+
2779
2840
  if (g.s instanceof Var) {
2780
2841
  const s2 = { ...subst };
2781
- s2[g.s.name] = new Literal(formatNum(inVal));
2842
+ s2[g.s.name] = makeNumericOutputLiteral(inVal, inDt);
2782
2843
  return [s2];
2783
2844
  }
2784
2845
  if (g.s instanceof Blank) return [{ ...subst }];
@@ -2959,151 +3020,225 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
2959
3020
  }
2960
3021
 
2961
3022
  // math:sum
3023
+ // Schema: ( $s.i+ )+ math:sum $o-
2962
3024
  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 = [];
3025
+ if (!(g.s instanceof ListTerm) || g.s.elems.length < 2) return [];
3026
+ const xs = g.s.elems;
3027
+
3028
+ const dtOut0 = commonNumericDatatype(xs, g.o);
3029
+
3030
+ // Exact integer mode
3031
+ if (dtOut0 === XSD_INTEGER_DT) {
3032
+ let total = 0n;
2966
3033
  for (const t of xs) {
2967
- const v = parseNumberLiteral(t);
3034
+ const v = parseIntLiteral(t);
2968
3035
  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));
3036
+ total += v;
2984
3037
  }
2985
3038
 
2986
3039
  if (g.o instanceof Var) {
2987
3040
  const s2 = { ...subst };
2988
- s2[g.o.name] = lit;
3041
+ s2[g.o.name] = makeNumericOutputLiteral(total, XSD_INTEGER_DT);
2989
3042
  return [s2];
2990
3043
  }
2991
- const s2 = unifyTerm(g.o, lit, subst);
2992
- return s2 !== null ? [s2] : [];
3044
+ if (g.o instanceof Blank) return [{ ...subst }];
3045
+
3046
+ const oi = parseIntLiteral(g.o);
3047
+ if (oi !== null && oi === total) return [{ ...subst }];
3048
+
3049
+ // Fallback numeric compare
3050
+ if (numEqualTerm(g.o, Number(total))) return [{ ...subst }];
3051
+ return [];
3052
+ }
3053
+
3054
+ // Numeric mode (decimal/float/double)
3055
+ let total = 0.0;
3056
+ for (const t of xs) {
3057
+ const v = parseNum(t);
3058
+ if (v === null) return [];
3059
+ total += v;
3060
+ }
3061
+
3062
+ let dtOut = dtOut0;
3063
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(total)) dtOut = XSD_DECIMAL_DT;
3064
+ const lit = makeNumericOutputLiteral(total, dtOut);
3065
+
3066
+ if (g.o instanceof Var) {
3067
+ const s2 = { ...subst };
3068
+ s2[g.o.name] = lit;
3069
+ return [s2];
2993
3070
  }
3071
+ if (g.o instanceof Blank) return [{ ...subst }];
3072
+ if (numEqualTerm(g.o, total)) return [{ ...subst }];
2994
3073
  return [];
2995
3074
  }
2996
3075
 
2997
3076
  // math:product
3077
+ // Schema: ( $s.i+ )+ math:product $o-
2998
3078
  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 = [];
3079
+ if (!(g.s instanceof ListTerm) || g.s.elems.length < 2) return [];
3080
+ const xs = g.s.elems;
3081
+
3082
+ const dtOut0 = commonNumericDatatype(xs, g.o);
3083
+
3084
+ // Exact integer mode
3085
+ if (dtOut0 === XSD_INTEGER_DT) {
3086
+ let prod = 1n;
3002
3087
  for (const t of xs) {
3003
- const v = parseNumberLiteral(t);
3088
+ const v = parseIntLiteral(t);
3004
3089
  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));
3090
+ prod *= v;
3020
3091
  }
3021
3092
 
3022
3093
  if (g.o instanceof Var) {
3023
3094
  const s2 = { ...subst };
3024
- s2[g.o.name] = lit;
3095
+ s2[g.o.name] = makeNumericOutputLiteral(prod, XSD_INTEGER_DT);
3025
3096
  return [s2];
3026
3097
  }
3027
- if (g.o instanceof Literal && g.o.value === lit.value) {
3028
- return [{ ...subst }];
3029
- }
3098
+ if (g.o instanceof Blank) return [{ ...subst }];
3099
+
3100
+ const oi = parseIntLiteral(g.o);
3101
+ if (oi !== null && oi === prod) return [{ ...subst }];
3102
+ if (numEqualTerm(g.o, Number(prod))) return [{ ...subst }];
3030
3103
  return [];
3031
3104
  }
3105
+
3106
+ // Numeric mode (decimal/float/double)
3107
+ let prod = 1.0;
3108
+ for (const t of xs) {
3109
+ const v = parseNum(t);
3110
+ if (v === null) return [];
3111
+ prod *= v;
3112
+ }
3113
+
3114
+ let dtOut = dtOut0;
3115
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(prod)) dtOut = XSD_DECIMAL_DT;
3116
+ const lit = makeNumericOutputLiteral(prod, dtOut);
3117
+
3118
+ if (g.o instanceof Var) {
3119
+ const s2 = { ...subst };
3120
+ s2[g.o.name] = lit;
3121
+ return [s2];
3122
+ }
3123
+ if (g.o instanceof Blank) return [{ ...subst }];
3124
+ if (numEqualTerm(g.o, prod)) return [{ ...subst }];
3125
+ return [];
3032
3126
  }
3033
3127
 
3034
3128
  // math:difference
3129
+ // Schema: ( $s.1+ $s.2+ )+ math:difference $o-
3035
3130
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'difference') {
3036
- if (g.s instanceof ListTerm && g.s.elems.length === 2) {
3037
- const [a0, b0] = g.s.elems;
3038
-
3039
- // BigInt integer difference
3040
- const ai = parseIntLiteral(a0);
3041
- const bi = parseIntLiteral(b0);
3042
- if (ai !== null && bi !== null) {
3043
- const ci = ai - bi;
3044
- const lit = new Literal(ci.toString());
3131
+ if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3132
+ const [a0, b0] = g.s.elems;
3133
+
3134
+ // 1) Date/datetime difference -> duration (needed for examples/age.n3)
3135
+ const aDt = parseDatetimeLike(a0);
3136
+ const bDt = parseDatetimeLike(b0);
3137
+ if (aDt !== null && bDt !== null) {
3138
+ const diffSecs = (aDt.getTime() - bDt.getTime()) / 1000.0;
3139
+ const durTerm = formatDurationLiteralFromSeconds(diffSecs);
3140
+ if (g.o instanceof Var) {
3141
+ const s2 = { ...subst };
3142
+ s2[g.o.name] = durTerm;
3143
+ return [s2];
3144
+ }
3145
+ const s2 = unifyTerm(g.o, durTerm, subst);
3146
+ return s2 !== null ? [s2] : [];
3147
+ }
3148
+
3149
+ // 2) Date/datetime minus duration/seconds -> dateTime (keeps older functionality)
3150
+ if (aDt !== null) {
3151
+ const secs = parseNumOrDuration(b0);
3152
+ if (secs !== null) {
3153
+ const outSecs = aDt.getTime() / 1000.0 - secs;
3154
+ const lex = utcIsoDateTimeStringFromEpochSeconds(outSecs);
3155
+ const lit = new Literal(`"${lex}"^^<${XSD_NS}dateTime>`);
3045
3156
  if (g.o instanceof Var) {
3046
3157
  const s2 = { ...subst };
3047
3158
  s2[g.o.name] = lit;
3048
3159
  return [s2];
3049
- } else {
3050
- const s2 = unifyTerm(g.o, lit, subst);
3051
- return s2 !== null ? [s2] : [];
3052
3160
  }
3161
+ const s2 = unifyTerm(g.o, lit, subst);
3162
+ return s2 !== null ? [s2] : [];
3053
3163
  }
3164
+ }
3054
3165
 
3055
- // Numeric difference via floats
3056
- const a = parseNum(a0);
3057
- const b = parseNum(b0);
3058
- if (a !== null && b !== null) {
3059
- const c = a - b;
3060
- if (g.o instanceof Var) {
3061
- const s2 = { ...subst };
3062
- s2[g.o.name] = new Literal(formatNum(c));
3063
- return [s2];
3064
- }
3065
- if (g.o instanceof Literal && g.o.value === formatNum(c)) {
3066
- return [{ ...subst }];
3067
- }
3166
+ // 3) Exact integer difference (BigInt)
3167
+ const ai = parseIntLiteral(a0);
3168
+ const bi = parseIntLiteral(b0);
3169
+ if (ai !== null && bi !== null) {
3170
+ const ci = ai - bi;
3171
+ const lit = new Literal(ci.toString());
3172
+ if (g.o instanceof Var) {
3173
+ const s2 = { ...subst };
3174
+ s2[g.o.name] = lit;
3175
+ return [s2];
3068
3176
  }
3177
+ const s2 = unifyTerm(g.o, lit, subst);
3178
+ return s2 !== null ? [s2] : [];
3179
+ }
3069
3180
 
3070
- // Date/datetime difference -> duration
3071
- const aDt = parseDatetimeLike(a0);
3072
- const bDt = parseDatetimeLike(b0);
3073
- if (aDt !== null && bDt !== null) {
3074
- const diffSecs = (aDt.getTime() - bDt.getTime()) / 1000.0;
3075
- const durTerm = formatDurationLiteralFromSeconds(diffSecs);
3076
- if (g.o instanceof Var) {
3077
- const s2 = { ...subst };
3078
- s2[g.o.name] = durTerm;
3079
- return [s2];
3080
- }
3081
- if (g.o instanceof Literal && g.o.value === durTerm.value) {
3082
- return [{ ...subst }];
3083
- }
3181
+ // 4) Numeric difference (your “typed output + numeric compare” version)
3182
+ const a = parseNum(a0);
3183
+ const b = parseNum(b0);
3184
+ if (a === null || b === null) return [];
3185
+
3186
+ const c = a - b;
3187
+ if (!Number.isFinite(c)) return [];
3188
+
3189
+ // If you added commonNumericDatatype/makeNumericOutputLiteral, keep using them:
3190
+ if (typeof commonNumericDatatype === 'function' && typeof makeNumericOutputLiteral === 'function') {
3191
+ let dtOut = commonNumericDatatype([a0, b0], g.o);
3192
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(c)) dtOut = XSD_DECIMAL_DT;
3193
+ const lit = makeNumericOutputLiteral(c, dtOut);
3194
+
3195
+ if (g.o instanceof Var) {
3196
+ const s2 = { ...subst };
3197
+ s2[g.o.name] = lit;
3198
+ return [s2];
3084
3199
  }
3200
+ if (g.o instanceof Blank) return [{ ...subst }];
3201
+ if (numEqualTerm(g.o, c)) return [{ ...subst }];
3085
3202
  return [];
3086
3203
  }
3204
+
3205
+ // Fallback (if you *don’t* have those helpers yet):
3206
+ const lit = new Literal(formatNum(c));
3207
+ if (g.o instanceof Var) {
3208
+ const s2 = { ...subst };
3209
+ s2[g.o.name] = lit;
3210
+ return [s2];
3211
+ }
3212
+ const s2 = unifyTerm(g.o, lit, subst);
3213
+ return s2 !== null ? [s2] : [];
3087
3214
  }
3088
3215
 
3089
3216
  // math:quotient
3217
+ // Schema: ( $s.1+ $s.2+ )+ math:quotient $o-
3090
3218
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'quotient') {
3091
- if (g.s instanceof ListTerm && g.s.elems.length === 2) {
3092
- const a = parseNum(g.s.elems[0]);
3093
- const b = parseNum(g.s.elems[1]);
3094
- if (a !== null && b !== null && b !== 0.0) {
3095
- const c = a / b;
3096
- if (g.o instanceof Var) {
3097
- const s2 = { ...subst };
3098
- s2[g.o.name] = new Literal(formatNum(c));
3099
- return [s2];
3100
- }
3101
- if (g.o instanceof Literal && g.o.value === formatNum(c)) {
3102
- return [{ ...subst }];
3103
- }
3104
- }
3105
- return [];
3219
+ if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3220
+ const [a0, b0] = g.s.elems;
3221
+
3222
+ const a = parseNum(a0);
3223
+ const b = parseNum(b0);
3224
+ if (a === null || b === null) return [];
3225
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return [];
3226
+
3227
+ const c = a / b;
3228
+ if (!Number.isFinite(c)) return [];
3229
+
3230
+ let dtOut = commonNumericDatatype([a0, b0], g.o);
3231
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(c)) dtOut = XSD_DECIMAL_DT;
3232
+ const lit = makeNumericOutputLiteral(c, dtOut);
3233
+
3234
+ if (g.o instanceof Var) {
3235
+ const s2 = { ...subst };
3236
+ s2[g.o.name] = lit;
3237
+ return [s2];
3106
3238
  }
3239
+ if (g.o instanceof Blank) return [{ ...subst }];
3240
+ if (numEqualTerm(g.o, c)) return [{ ...subst }];
3241
+ return [];
3107
3242
  }
3108
3243
 
3109
3244
  // math:integerQuotient
@@ -3150,30 +3285,43 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3150
3285
  // math:exponentiation
3151
3286
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'exponentiation') {
3152
3287
  if (g.s instanceof ListTerm && g.s.elems.length === 2) {
3153
- const a = parseNum(g.s.elems[0]);
3154
- const b0 = g.s.elems[1];
3155
- const c = parseNum(g.o);
3288
+ const baseTerm = g.s.elems[0];
3289
+ const expTerm = g.s.elems[1];
3290
+
3291
+ const a = parseNum(baseTerm);
3156
3292
  let b = null;
3157
- if (a !== null && b0 instanceof Literal) b = parseNum(b0);
3293
+ if (a !== null) b = parseNum(expTerm);
3158
3294
 
3295
+ // Forward mode: base and exponent are numeric
3159
3296
  if (a !== null && b !== null) {
3160
3297
  const cVal = a ** b;
3298
+ if (!Number.isFinite(cVal)) return [];
3299
+
3300
+ let dtOut = commonNumericDatatype([baseTerm, expTerm], g.o);
3301
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(cVal)) dtOut = XSD_DECIMAL_DT;
3302
+ const lit = makeNumericOutputLiteral(cVal, dtOut);
3303
+
3161
3304
  if (g.o instanceof Var) {
3162
3305
  const s2 = { ...subst };
3163
- s2[g.o.name] = new Literal(formatNum(cVal));
3306
+ s2[g.o.name] = lit;
3164
3307
  return [s2];
3165
3308
  }
3166
- if (numEqualTerm(g.o, cVal)) {
3167
- return [{ ...subst }];
3168
- }
3309
+ if (g.o instanceof Blank) return [{ ...subst }];
3310
+ if (numEqualTerm(g.o, cVal)) return [{ ...subst }];
3169
3311
  }
3170
3312
 
3171
- // inverse mode
3172
- if (a !== null && b0 instanceof Var && c !== null) {
3313
+ // Inverse mode: solve exponent
3314
+ const c = parseNum(g.o);
3315
+ if (a !== null && expTerm instanceof Var && c !== null) {
3173
3316
  if (a > 0.0 && a !== 1.0 && c > 0.0) {
3174
3317
  const bVal = Math.log(c) / Math.log(a);
3318
+ if (!Number.isFinite(bVal)) return [];
3319
+
3320
+ let dtB = commonNumericDatatype([baseTerm, g.o], expTerm);
3321
+ if (dtB === XSD_INTEGER_DT && !Number.isInteger(bVal)) dtB = XSD_DECIMAL_DT;
3322
+
3175
3323
  const s2 = { ...subst };
3176
- s2[b0.name] = new Literal(formatNum(bVal));
3324
+ s2[expTerm.name] = makeNumericOutputLiteral(bVal, dtB);
3177
3325
  return [s2];
3178
3326
  }
3179
3327
  }
@@ -3184,15 +3332,21 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3184
3332
  // math:absoluteValue
3185
3333
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'absoluteValue') {
3186
3334
  const a = parseNum(g.s);
3187
- if (a !== null && g.o instanceof Var) {
3335
+ if (a === null) return [];
3336
+
3337
+ const outVal = Math.abs(a);
3338
+ if (!Number.isFinite(outVal)) return [];
3339
+
3340
+ let dtOut = commonNumericDatatype([g.s], g.o);
3341
+ if (dtOut === XSD_INTEGER_DT && !Number.isInteger(outVal)) dtOut = XSD_DECIMAL_DT;
3342
+
3343
+ if (g.o instanceof Var) {
3188
3344
  const s2 = { ...subst };
3189
- s2[g.o.name] = new Literal(formatNum(Math.abs(a)));
3345
+ s2[g.o.name] = makeNumericOutputLiteral(outVal, dtOut);
3190
3346
  return [s2];
3191
3347
  }
3192
- const b = parseNum(g.o);
3193
- if (a !== null && b !== null) {
3194
- if (Math.abs(Math.abs(a) - b) < 1e-9) return [{ ...subst }];
3195
- }
3348
+ if (g.o instanceof Blank) return [{ ...subst }];
3349
+ if (numEqualTerm(g.o, outVal)) return [{ ...subst }];
3196
3350
  return [];
3197
3351
  }
3198
3352
 
@@ -3258,20 +3412,47 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3258
3412
  // Schema: ( $a $b ) math:remainder $r
3259
3413
  if (g.p instanceof Iri && g.p.value === MATH_NS + 'remainder') {
3260
3414
  if (!(g.s instanceof ListTerm) || g.s.elems.length !== 2) return [];
3261
- const a = parseNum(g.s.elems[0]);
3262
- const b = parseNum(g.s.elems[1]);
3263
- if (a === null || b === null || b === 0) return [];
3415
+ const [a0, b0] = g.s.elems;
3416
+
3417
+ // Prefer exact integer arithmetic (BigInt)
3418
+ const ai = parseIntLiteral(a0);
3419
+ const bi = parseIntLiteral(b0);
3420
+ if (ai !== null && bi !== null) {
3421
+ if (bi === 0n) return [];
3422
+ const r = ai % bi;
3423
+ const lit = makeNumericOutputLiteral(r, XSD_INTEGER_DT);
3424
+
3425
+ if (g.o instanceof Var) {
3426
+ const s2 = { ...subst };
3427
+ s2[g.o.name] = lit;
3428
+ return [s2];
3429
+ }
3430
+ if (g.o instanceof Blank) return [{ ...subst }];
3431
+
3432
+ const oi = parseIntLiteral(g.o);
3433
+ if (oi !== null && oi === r) return [{ ...subst }];
3434
+ if (numEqualTerm(g.o, Number(r))) return [{ ...subst }];
3435
+ return [];
3436
+ }
3437
+
3438
+ // Fallback: allow Number literals that still represent integers
3439
+ const a = parseNum(a0);
3440
+ const b = parseNum(b0);
3441
+ if (a === null || b === null) return [];
3442
+ if (!Number.isFinite(a) || !Number.isFinite(b) || b === 0) return [];
3443
+ if (!Number.isInteger(a) || !Number.isInteger(b)) return [];
3444
+
3264
3445
  const rVal = a % b;
3265
- if (!Number.isFinite(rVal)) return [];
3266
- const lit = new Literal(formatNum(rVal));
3446
+ const lit = makeNumericOutputLiteral(rVal, XSD_INTEGER_DT);
3267
3447
 
3268
3448
  if (g.o instanceof Var) {
3269
3449
  const s2 = { ...subst };
3270
3450
  s2[g.o.name] = lit;
3271
3451
  return [s2];
3272
3452
  }
3273
- const s2 = unifyTerm(g.o, lit, subst);
3274
- return s2 !== null ? [s2] : [];
3453
+ if (g.o instanceof Blank) return [{ ...subst }];
3454
+ if (numEqualTerm(g.o, rVal)) return [{ ...subst }];
3455
+ return [];
3275
3456
  }
3276
3457
 
3277
3458
  // math:rounded
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [