eyeling 1.6.15 → 1.6.16

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 +164 -287
  2. package/package.json +1 -1
package/eyeling.js CHANGED
@@ -2041,6 +2041,16 @@ function unifyFormulaTriples(xs, ys, subst) {
2041
2041
  }
2042
2042
 
2043
2043
  function unifyTerm(a, b, subst) {
2044
+ return unifyTermWithOptions(a, b, subst, { boolValueEq: true, intDecimalEq: false });
2045
+ }
2046
+
2047
+ function unifyTermListAppend(a, b, subst) {
2048
+ // Keep list:append behavior: allow integer<->decimal exact equality,
2049
+ // but do NOT add boolean-value equivalence (preserves current semantics).
2050
+ return unifyTermWithOptions(a, b, subst, { boolValueEq: false, intDecimalEq: true });
2051
+ }
2052
+
2053
+ function unifyTermWithOptions(a, b, subst, opts) {
2044
2054
  a = applySubstTerm(a, subst);
2045
2055
  b = applySubstTerm(b, subst);
2046
2056
 
@@ -2054,9 +2064,8 @@ function unifyTerm(a, b, subst) {
2054
2064
  s2[v] = t;
2055
2065
  return s2;
2056
2066
  }
2057
-
2058
2067
  if (b instanceof Var) {
2059
- return unifyTerm(b, a, subst);
2068
+ return unifyTermWithOptions(b, a, subst, opts);
2060
2069
  }
2061
2070
 
2062
2071
  // Exact matches
@@ -2064,109 +2073,25 @@ function unifyTerm(a, b, subst) {
2064
2073
  if (a instanceof Literal && b instanceof Literal && a.value === b.value) return { ...subst };
2065
2074
  if (a instanceof Blank && b instanceof Blank && a.label === b.label) return { ...subst };
2066
2075
 
2067
- // String-literal match (RDF 1.1): treat plain strings and xsd:string as equal (but not @lang)
2076
+ // Plain string vs xsd:string equivalence
2068
2077
  if (a instanceof Literal && b instanceof Literal) {
2069
2078
  if (literalsEquivalentAsXsdString(a.value, b.value)) return { ...subst };
2070
2079
  }
2071
2080
 
2072
- // Boolean-value match: treat untyped true/false tokens and xsd:boolean as equal.
2073
- if (a instanceof Literal && b instanceof Literal) {
2081
+ // Boolean-value equivalence (ONLY for normal unifyTerm)
2082
+ if (opts.boolValueEq && a instanceof Literal && b instanceof Literal) {
2074
2083
  const ai = parseBooleanLiteralInfo(a);
2075
2084
  const bi = parseBooleanLiteralInfo(b);
2076
2085
  if (ai && bi && ai.value === bi.value) return { ...subst };
2077
2086
  }
2078
2087
 
2079
- // Numeric-value match for literals, BUT ONLY when datatypes agree (or infer to agree)
2080
- if (a instanceof Literal && b instanceof Literal) {
2081
- const ai = parseNumericLiteralInfo(a);
2082
- const bi = parseNumericLiteralInfo(b);
2083
-
2084
- if (ai && bi) {
2085
- // same datatype: keep existing behavior
2086
- if (ai.dt === bi.dt) {
2087
- if (ai.kind === 'bigint' && bi.kind === 'bigint') {
2088
- if (ai.value === bi.value) return { ...subst };
2089
- } else {
2090
- const an = ai.kind === 'bigint' ? Number(ai.value) : ai.value;
2091
- const bn = bi.kind === 'bigint' ? Number(bi.value) : bi.value;
2092
- if (!Number.isNaN(an) && !Number.isNaN(bn) && an === bn) return { ...subst };
2093
- }
2094
- }
2095
- }
2096
- }
2097
-
2098
- // Open list vs concrete list
2099
- if (a instanceof OpenListTerm && b instanceof ListTerm) {
2100
- return unifyOpenWithList(a.prefix, a.tailVar, b.elems, subst);
2101
- }
2102
- if (a instanceof ListTerm && b instanceof OpenListTerm) {
2103
- return unifyOpenWithList(b.prefix, b.tailVar, a.elems, subst);
2104
- }
2105
-
2106
- // Open list vs open list (same tail var)
2107
- if (a instanceof OpenListTerm && b instanceof OpenListTerm) {
2108
- if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length) return null;
2109
- let s2 = { ...subst };
2110
- for (let i = 0; i < a.prefix.length; i++) {
2111
- s2 = unifyTerm(a.prefix[i], b.prefix[i], s2);
2112
- if (s2 === null) return null;
2113
- }
2114
- return s2;
2115
- }
2116
-
2117
- // List terms
2118
- if (a instanceof ListTerm && b instanceof ListTerm) {
2119
- if (a.elems.length !== b.elems.length) return null;
2120
- let s2 = { ...subst };
2121
- for (let i = 0; i < a.elems.length; i++) {
2122
- s2 = unifyTerm(a.elems[i], b.elems[i], s2);
2123
- if (s2 === null) return null;
2124
- }
2125
- return s2;
2126
- }
2127
-
2128
- // Formulas:
2129
- // 1) If they are alpha-equivalent, succeed without leaking internal bindings.
2130
- // 2) Otherwise fall back to full unification (may bind vars).
2131
- if (a instanceof FormulaTerm && b instanceof FormulaTerm) {
2132
- if (alphaEqFormulaTriples(a.triples, b.triples)) return { ...subst };
2133
- return unifyFormulaTriples(a.triples, b.triples, subst);
2134
- }
2135
- return null;
2136
- }
2137
-
2138
- function unifyTermListAppend(a, b, subst) {
2139
- a = applySubstTerm(a, subst);
2140
- b = applySubstTerm(b, subst);
2141
-
2142
- // Variable binding (same as unifyTerm)
2143
- if (a instanceof Var) {
2144
- const v = a.name;
2145
- const t = b;
2146
- if (t instanceof Var && t.name === v) return { ...subst };
2147
- if (containsVarTerm(t, v)) return null;
2148
- const s2 = { ...subst };
2149
- s2[v] = t;
2150
- return s2;
2151
- }
2152
- if (b instanceof Var) return unifyTermListAppend(b, a, subst);
2153
-
2154
- // Exact matches
2155
- if (a instanceof Iri && b instanceof Iri && a.value === b.value) return { ...subst };
2156
- if (a instanceof Literal && b instanceof Literal && a.value === b.value) return { ...subst };
2157
- if (a instanceof Blank && b instanceof Blank && a.label === b.label) return { ...subst };
2158
-
2159
- // Plain string vs xsd:string equivalence
2160
- if (a instanceof Literal && b instanceof Literal) {
2161
- if (literalsEquivalentAsXsdString(a.value, b.value)) return { ...subst };
2162
- }
2163
-
2164
- // Numeric match: same-dt OR integer<->decimal exact equality (for list:append only)
2088
+ // Numeric-value match:
2089
+ // - always allow equality when datatype matches (existing behavior)
2090
+ // - optionally allow integer<->decimal exact equality (list:append only)
2165
2091
  if (a instanceof Literal && b instanceof Literal) {
2166
2092
  const ai = parseNumericLiteralInfo(a);
2167
2093
  const bi = parseNumericLiteralInfo(b);
2168
2094
  if (ai && bi) {
2169
- // same datatype
2170
2095
  if (ai.dt === bi.dt) {
2171
2096
  if (ai.kind === 'bigint' && bi.kind === 'bigint') {
2172
2097
  if (ai.value === bi.value) return { ...subst };
@@ -2177,16 +2102,17 @@ function unifyTermListAppend(a, b, subst) {
2177
2102
  }
2178
2103
  }
2179
2104
 
2180
- // integer <-> decimal exact equality
2181
- const intDt = XSD_NS + 'integer';
2182
- const decDt = XSD_NS + 'decimal';
2183
- if ((ai.dt === intDt && bi.dt === decDt) || (ai.dt === decDt && bi.dt === intDt)) {
2184
- const intInfo = ai.dt === intDt ? ai : bi;
2185
- const decInfo = ai.dt === decDt ? ai : bi;
2186
- const dec = parseXsdDecimalToBigIntScale(decInfo.lexStr);
2187
- if (dec) {
2188
- const scaledInt = intInfo.value * pow10n(dec.scale);
2189
- if (scaledInt === dec.num) return { ...subst };
2105
+ if (opts.intDecimalEq) {
2106
+ const intDt = XSD_NS + 'integer';
2107
+ const decDt = XSD_NS + 'decimal';
2108
+ if ((ai.dt === intDt && bi.dt === decDt) || (ai.dt === decDt && bi.dt === intDt)) {
2109
+ const intInfo = ai.dt === intDt ? ai : bi; // bigint
2110
+ const decInfo = ai.dt === decDt ? ai : bi; // number + lexStr
2111
+ const dec = parseXsdDecimalToBigIntScale(decInfo.lexStr);
2112
+ if (dec) {
2113
+ const scaledInt = intInfo.value * pow10n(dec.scale);
2114
+ if (scaledInt === dec.num) return { ...subst };
2115
+ }
2190
2116
  }
2191
2117
  }
2192
2118
  }
@@ -2205,7 +2131,7 @@ function unifyTermListAppend(a, b, subst) {
2205
2131
  if (a.tailVar !== b.tailVar || a.prefix.length !== b.prefix.length) return null;
2206
2132
  let s2 = { ...subst };
2207
2133
  for (let i = 0; i < a.prefix.length; i++) {
2208
- s2 = unifyTermListAppend(a.prefix[i], b.prefix[i], s2);
2134
+ s2 = unifyTermWithOptions(a.prefix[i], b.prefix[i], s2, opts);
2209
2135
  if (s2 === null) return null;
2210
2136
  }
2211
2137
  return s2;
@@ -2216,7 +2142,7 @@ function unifyTermListAppend(a, b, subst) {
2216
2142
  if (a.elems.length !== b.elems.length) return null;
2217
2143
  let s2 = { ...subst };
2218
2144
  for (let i = 0; i < a.elems.length; i++) {
2219
- s2 = unifyTermListAppend(a.elems[i], b.elems[i], s2);
2145
+ s2 = unifyTermWithOptions(a.elems[i], b.elems[i], s2, opts);
2220
2146
  if (s2 === null) return null;
2221
2147
  }
2222
2148
  return s2;
@@ -2886,14 +2812,7 @@ function parseNumOrDuration(t) {
2886
2812
 
2887
2813
  function formatDurationLiteralFromSeconds(secs) {
2888
2814
  const neg = secs < 0;
2889
- const absSecs = Math.abs(secs);
2890
- const days = Math.round(absSecs / 86400.0);
2891
- const lex = neg ? `" -P${days}D"` : `"P${days}D"`;
2892
- const cleanLex = neg ? `" -P${days}D"` : `"P${days}D"`; // minor detail; we just follow shape
2893
- const lex2 = neg ? `" -P${days}D"` : `"P${days}D"`;
2894
- const actualLex = neg ? `" -P${days}D"` : `"P${days}D"`;
2895
- // keep simpler, no spaces:
2896
- const finalLex = neg ? `" -P${days}D"` : `"P${days}D"`;
2815
+ const days = Math.round(Math.abs(secs) / 86400.0);
2897
2816
  const literalLex = neg ? `"-P${days}D"` : `"P${days}D"`;
2898
2817
  return new Literal(`${literalLex}^^<${XSD_NS}duration>`);
2899
2818
  }
@@ -3091,6 +3010,38 @@ function evalUnaryMathRel(g, subst, forwardFn, inverseFn /* may be null */) {
3091
3010
  return [];
3092
3011
  }
3093
3012
 
3013
+ function evalListFirstLikeBuiltin(sTerm, oTerm, subst) {
3014
+ if (!(sTerm instanceof ListTerm)) return [];
3015
+ if (!sTerm.elems.length) return [];
3016
+ const first = sTerm.elems[0];
3017
+ const s2 = unifyTerm(oTerm, first, subst);
3018
+ return s2 !== null ? [s2] : [];
3019
+ }
3020
+
3021
+ function evalListRestLikeBuiltin(sTerm, oTerm, subst) {
3022
+ // Closed list: (a b c) -> (b c)
3023
+ if (sTerm instanceof ListTerm) {
3024
+ if (!sTerm.elems.length) return [];
3025
+ const rest = new ListTerm(sTerm.elems.slice(1));
3026
+ const s2 = unifyTerm(oTerm, rest, subst);
3027
+ return s2 !== null ? [s2] : [];
3028
+ }
3029
+
3030
+ // Open list: (a b ... ?T) -> (b ... ?T)
3031
+ if (sTerm instanceof OpenListTerm) {
3032
+ if (!sTerm.prefix.length) return [];
3033
+ if (sTerm.prefix.length === 1) {
3034
+ const s2 = unifyTerm(oTerm, new Var(sTerm.tailVar), subst);
3035
+ return s2 !== null ? [s2] : [];
3036
+ }
3037
+ const rest = new OpenListTerm(sTerm.prefix.slice(1), sTerm.tailVar);
3038
+ const s2 = unifyTerm(oTerm, rest, subst);
3039
+ return s2 !== null ? [s2] : [];
3040
+ }
3041
+
3042
+ return [];
3043
+ }
3044
+
3094
3045
  // ============================================================================
3095
3046
  // Backward proof & builtins mutual recursion — declarations first
3096
3047
  // ============================================================================
@@ -3785,79 +3736,18 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
3785
3736
  return [];
3786
3737
  }
3787
3738
 
3788
- // list:first
3739
+ // list:first and rdf:first
3789
3740
  // true iff $s is a list and $o is the first member of that list.
3790
3741
  // Schema: $s+ list:first $o-
3791
- if (g.p instanceof Iri && g.p.value === LIST_NS + 'first') {
3792
- if (!(g.s instanceof ListTerm)) return [];
3793
- if (!g.s.elems.length) return [];
3794
- const first = g.s.elems[0];
3795
- const s2 = unifyTerm(g.o, first, subst);
3796
- return s2 !== null ? [s2] : [];
3742
+ if (g.p instanceof Iri && (g.p.value === LIST_NS + 'first' || g.p.value === RDF_NS + 'first')) {
3743
+ return evalListFirstLikeBuiltin(g.s, g.o, subst);
3797
3744
  }
3798
3745
 
3799
- // list:rest
3746
+ // list:rest and rdf:rest
3800
3747
  // true iff $s is a (non-empty) list and $o is the rest (tail) of that list.
3801
3748
  // Schema: $s+ list:rest $o-
3802
- if (g.p instanceof Iri && g.p.value === LIST_NS + 'rest') {
3803
- // Closed list: (a b c) -> (b c)
3804
- if (g.s instanceof ListTerm) {
3805
- if (!g.s.elems.length) return [];
3806
- const rest = new ListTerm(g.s.elems.slice(1));
3807
- const s2 = unifyTerm(g.o, rest, subst);
3808
- return s2 !== null ? [s2] : [];
3809
- }
3810
-
3811
- // Open list: (a b ... ?T) -> (b ... ?T)
3812
- if (g.s instanceof OpenListTerm) {
3813
- if (!g.s.prefix.length) return []; // can't compute rest without a known head
3814
-
3815
- if (g.s.prefix.length === 1) {
3816
- // (a ... ?T) rest is exactly ?T
3817
- const s2 = unifyTerm(g.o, new Var(g.s.tailVar), subst);
3818
- return s2 !== null ? [s2] : [];
3819
- }
3820
-
3821
- const rest = new OpenListTerm(g.s.prefix.slice(1), g.s.tailVar);
3822
- const s2 = unifyTerm(g.o, rest, subst);
3823
- return s2 !== null ? [s2] : [];
3824
- }
3825
-
3826
- return [];
3827
- }
3828
-
3829
- // rdf:first (alias of list:first)
3830
- // Schema: $s+ rdf:first $o-
3831
- if (g.p instanceof Iri && g.p.value === RDF_NS + 'first') {
3832
- if (!(g.s instanceof ListTerm)) return [];
3833
- if (!g.s.elems.length) return [];
3834
- const first = g.s.elems[0];
3835
- const s2 = unifyTerm(g.o, first, subst);
3836
- return s2 !== null ? [s2] : [];
3837
- }
3838
-
3839
- // rdf:rest (alias of list:rest)
3840
- // Schema: $s+ rdf:rest $o-
3841
- if (g.p instanceof Iri && g.p.value === RDF_NS + 'rest') {
3842
- // Closed list: (a b c) -> (b c)
3843
- if (g.s instanceof ListTerm) {
3844
- if (!g.s.elems.length) return [];
3845
- const rest = new ListTerm(g.s.elems.slice(1));
3846
- const s2 = unifyTerm(g.o, rest, subst);
3847
- return s2 !== null ? [s2] : [];
3848
- }
3849
- // Open list: (a b ... ?T) -> (b ... ?T)
3850
- if (g.s instanceof OpenListTerm) {
3851
- if (!g.s.prefix.length) return [];
3852
- if (g.s.prefix.length === 1) {
3853
- const s2 = unifyTerm(g.o, new Var(g.s.tailVar), subst);
3854
- return s2 !== null ? [s2] : [];
3855
- }
3856
- const rest = new OpenListTerm(g.s.prefix.slice(1), g.s.tailVar);
3857
- const s2 = unifyTerm(g.o, rest, subst);
3858
- return s2 !== null ? [s2] : [];
3859
- }
3860
- return [];
3749
+ if (g.p instanceof Iri && (g.p.value === LIST_NS + 'rest' || g.p.value === RDF_NS + 'rest')) {
3750
+ return evalListRestLikeBuiltin(g.s, g.o, subst);
3861
3751
  }
3862
3752
 
3863
3753
  // list:iterate
@@ -5065,134 +4955,121 @@ function forwardChain(facts, forwardRules, backRules) {
5065
4955
 
5066
4956
  function runFixpoint() {
5067
4957
  let anyChange = false;
4958
+
5068
4959
  while (true) {
5069
4960
  let changed = false;
5070
4961
 
5071
- while (true) {
5072
- let changed = false;
4962
+ for (let i = 0; i < forwardRules.length; i++) {
4963
+ const r = forwardRules[i];
4964
+ const empty = {};
4965
+ const visited = [];
4966
+ const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen);
5073
4967
 
5074
- for (let i = 0; i < forwardRules.length; i++) {
5075
- const r = forwardRules[i];
5076
- const empty = {};
5077
- const visited = [];
5078
-
5079
- const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen);
4968
+ // Inference fuse
4969
+ if (r.isFuse && sols.length) {
4970
+ console.log('# Inference fuse triggered: a { ... } => false. rule fired.');
4971
+ process.exit(2);
4972
+ }
5080
4973
 
5081
- // Inference fuse
5082
- if (r.isFuse && sols.length) {
5083
- console.log('# Inference fuse triggered: a { ... } => false. rule fired.');
5084
- process.exit(2);
5085
- }
4974
+ for (const s of sols) {
4975
+ // IMPORTANT: one skolem map per *rule firing*
4976
+ const skMap = {};
4977
+ const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
4978
+ const fireKey = firingKey(i, instantiatedPremises);
4979
+
4980
+ for (const cpat of r.conclusion) {
4981
+ const instantiated = applySubstTriple(cpat, s);
4982
+
4983
+ const isFwRuleTriple =
4984
+ isLogImplies(instantiated.p) &&
4985
+ ((instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
4986
+ (instantiated.s instanceof Literal && instantiated.s.value === 'true' && instantiated.o instanceof FormulaTerm) ||
4987
+ (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === 'true'));
4988
+
4989
+ const isBwRuleTriple =
4990
+ isLogImpliedBy(instantiated.p) &&
4991
+ ((instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
4992
+ (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === 'true') ||
4993
+ (instantiated.s instanceof Literal && instantiated.s.value === 'true' && instantiated.o instanceof FormulaTerm));
4994
+
4995
+ if (isFwRuleTriple || isBwRuleTriple) {
4996
+ if (!hasFactIndexed(facts, instantiated)) {
4997
+ factList.push(instantiated);
4998
+ pushFactIndexed(facts, instantiated);
4999
+ derivedForward.push(new DerivedFact(instantiated, r, instantiatedPremises.slice(), { ...s }));
5000
+ changed = true;
5001
+ }
5086
5002
 
5087
- for (const s of sols) {
5088
- // IMPORTANT: one skolem map per *rule firing* so head blank nodes
5089
- // (e.g., from [ :p ... ; :q ... ]) stay connected across all head triples.
5090
- const skMap = {};
5091
- const instantiatedPremises = r.premise.map((b) => applySubstTriple(b, s));
5092
- const fireKey = firingKey(i, instantiatedPremises);
5093
-
5094
- for (const cpat of r.conclusion) {
5095
- const instantiated = applySubstTriple(cpat, s);
5096
-
5097
- const isFwRuleTriple =
5098
- isLogImplies(instantiated.p) &&
5099
- ((instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
5100
- (instantiated.s instanceof Literal && instantiated.s.value === 'true' && instantiated.o instanceof FormulaTerm) ||
5101
- (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === 'true'));
5102
-
5103
- const isBwRuleTriple =
5104
- isLogImpliedBy(instantiated.p) &&
5105
- ((instantiated.s instanceof FormulaTerm && instantiated.o instanceof FormulaTerm) ||
5106
- (instantiated.s instanceof FormulaTerm && instantiated.o instanceof Literal && instantiated.o.value === 'true') ||
5107
- (instantiated.s instanceof Literal && instantiated.s.value === 'true' && instantiated.o instanceof FormulaTerm));
5108
-
5109
- if (isFwRuleTriple || isBwRuleTriple) {
5110
- if (!hasFactIndexed(facts, instantiated)) {
5111
- factList.push(instantiated);
5112
- pushFactIndexed(facts, instantiated);
5113
- derivedForward.push(
5114
- new DerivedFact(instantiated, r, instantiatedPremises.slice(), {
5115
- ...s,
5116
- }),
5003
+ // Promote rule-producing triples to live rules, treating literal true as {}.
5004
+ const left =
5005
+ instantiated.s instanceof FormulaTerm
5006
+ ? instantiated.s.triples
5007
+ : instantiated.s instanceof Literal && instantiated.s.value === 'true'
5008
+ ? []
5009
+ : null;
5010
+
5011
+ const right =
5012
+ instantiated.o instanceof FormulaTerm
5013
+ ? instantiated.o.triples
5014
+ : instantiated.o instanceof Literal && instantiated.o.value === 'true'
5015
+ ? []
5016
+ : null;
5017
+
5018
+ if (left !== null && right !== null) {
5019
+ if (isFwRuleTriple) {
5020
+ const [premise0, conclusion] = liftBlankRuleVars(left, right);
5021
+ const premise = reorderPremiseForConstraints(premise0);
5022
+ const headBlankLabels = collectBlankLabelsInTriples(conclusion);
5023
+ const newRule = new Rule(premise, conclusion, true, false, headBlankLabels);
5024
+
5025
+ const already = forwardRules.some(
5026
+ (rr) =>
5027
+ rr.isForward === newRule.isForward &&
5028
+ rr.isFuse === newRule.isFuse &&
5029
+ triplesListEqual(rr.premise, newRule.premise) &&
5030
+ triplesListEqual(rr.conclusion, newRule.conclusion),
5117
5031
  );
5118
- changed = true;
5119
- }
5120
-
5121
- // Promote rule-producing triples to live rules, treating literal true as {}.
5122
- const left =
5123
- instantiated.s instanceof FormulaTerm
5124
- ? instantiated.s.triples
5125
- : instantiated.s instanceof Literal && instantiated.s.value === 'true'
5126
- ? []
5127
- : null;
5128
-
5129
- const right =
5130
- instantiated.o instanceof FormulaTerm
5131
- ? instantiated.o.triples
5132
- : instantiated.o instanceof Literal && instantiated.o.value === 'true'
5133
- ? []
5134
- : null;
5135
-
5136
- if (left !== null && right !== null) {
5137
- if (isFwRuleTriple) {
5138
- const [premise0, conclusion] = liftBlankRuleVars(left, right);
5139
- const premise = reorderPremiseForConstraints(premise0);
5140
-
5141
- const headBlankLabels = collectBlankLabelsInTriples(conclusion);
5142
- const newRule = new Rule(premise, conclusion, true, false, headBlankLabels);
5143
-
5144
- const already = forwardRules.some(
5145
- (rr) =>
5146
- rr.isForward === newRule.isForward &&
5147
- rr.isFuse === newRule.isFuse &&
5148
- triplesListEqual(rr.premise, newRule.premise) &&
5149
- triplesListEqual(rr.conclusion, newRule.conclusion),
5150
- );
5151
- if (!already) forwardRules.push(newRule);
5152
- } else if (isBwRuleTriple) {
5153
- const [premise, conclusion] = liftBlankRuleVars(right, left);
5154
-
5155
- const headBlankLabels = collectBlankLabelsInTriples(conclusion);
5156
- const newRule = new Rule(premise, conclusion, false, false, headBlankLabels);
5157
-
5158
- const already = backRules.some(
5159
- (rr) =>
5160
- rr.isForward === newRule.isForward &&
5161
- rr.isFuse === newRule.isFuse &&
5162
- triplesListEqual(rr.premise, newRule.premise) &&
5163
- triplesListEqual(rr.conclusion, newRule.conclusion),
5164
- );
5165
- if (!already) {
5166
- backRules.push(newRule);
5167
- indexBackRule(backRules, newRule);
5168
- }
5032
+ if (!already) forwardRules.push(newRule);
5033
+ } else if (isBwRuleTriple) {
5034
+ const [premise, conclusion] = liftBlankRuleVars(right, left);
5035
+ const headBlankLabels = collectBlankLabelsInTriples(conclusion);
5036
+ const newRule = new Rule(premise, conclusion, false, false, headBlankLabels);
5037
+
5038
+ const already = backRules.some(
5039
+ (rr) =>
5040
+ rr.isForward === newRule.isForward &&
5041
+ rr.isFuse === newRule.isFuse &&
5042
+ triplesListEqual(rr.premise, newRule.premise) &&
5043
+ triplesListEqual(rr.conclusion, newRule.conclusion),
5044
+ );
5045
+ if (!already) {
5046
+ backRules.push(newRule);
5047
+ indexBackRule(backRules, newRule);
5169
5048
  }
5170
5049
  }
5171
-
5172
- continue; // skip normal fact handling
5173
5050
  }
5174
5051
 
5175
- // Only skolemize blank nodes that occur explicitly in the rule head
5176
- const inst = skolemizeTripleForHeadBlanks(instantiated, r.headBlankLabels, skMap, skCounter, fireKey, headSkolemCache);
5052
+ continue; // skip normal fact handling
5053
+ }
5177
5054
 
5178
- if (!isGroundTriple(inst)) continue;
5179
- if (hasFactIndexed(facts, inst)) continue;
5055
+ // Only skolemize blank nodes that occur explicitly in the rule head
5056
+ const inst = skolemizeTripleForHeadBlanks(instantiated, r.headBlankLabels, skMap, skCounter, fireKey, headSkolemCache);
5180
5057
 
5181
- factList.push(inst);
5182
- pushFactIndexed(facts, inst);
5058
+ if (!isGroundTriple(inst)) continue;
5059
+ if (hasFactIndexed(facts, inst)) continue;
5183
5060
 
5184
- derivedForward.push(new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }));
5185
- changed = true;
5186
- }
5061
+ factList.push(inst);
5062
+ pushFactIndexed(facts, inst);
5063
+ derivedForward.push(new DerivedFact(inst, r, instantiatedPremises.slice(), { ...s }));
5064
+ changed = true;
5187
5065
  }
5188
5066
  }
5189
-
5190
- if (!changed) break;
5191
5067
  }
5192
5068
 
5193
- if (changed) anyChange = true;
5194
5069
  if (!changed) break;
5070
+ anyChange = true;
5195
5071
  }
5072
+
5196
5073
  return anyChange;
5197
5074
  }
5198
5075
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.6.15",
3
+ "version": "1.6.16",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [