eyeling 1.7.13 → 1.7.15

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 (3) hide show
  1. package/README.md +1 -1
  2. package/eyeling.js +81 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -219,7 +219,7 @@ Commonly used N3/Turtle features:
219
219
  - **log**: `log:collectAllIn` `log:content` `log:dtlit` `log:equalTo` `log:forAllIn` `log:impliedBy` `log:implies` `log:includes` `log:langlit` `log:notEqualTo` `log:notIncludes` `log:outputString` `log:parsedAsN3` `log:rawType` `log:semantics` `log:semanticsOrError` `log:skolem` `log:uri`
220
220
  - **math**: `math:absoluteValue` `math:acos` `math:asin` `math:atan` `math:cos` `math:cosh` `math:degrees` `math:difference` `math:equalTo` `math:exponentiation` `math:greaterThan` `math:integerQuotient` `math:lessThan` `math:negation` `math:notEqualTo` `math:notGreaterThan` `math:notLessThan` `math:product` `math:quotient` `math:remainder` `math:rounded` `math:sin` `math:sinh` `math:sum` `math:tan` `math:tanh`
221
221
  - **string**: `string:concatenation` `string:contains` `string:containsIgnoringCase` `string:endsWith` `string:equalIgnoringCase` `string:format` `string:greaterThan` `string:jsonPointer` `string:lessThan` `string:matches` `string:notEqualIgnoringCase` `string:notGreaterThan` `string:notLessThan` `string:notMatches` `string:replace` `string:scrape` `string:startsWith`
222
- - **time**: `time:day` `time:localTime` `time:minute` `time:month` `time:second` `time:timeZone` `time:year`
222
+ - **time**: `time:day` `time:hour` `time:localTime` `time:minute` `time:month` `time:second` `time:timeZone` `time:year`
223
223
 
224
224
  ## License
225
225
 
package/eyeling.js CHANGED
@@ -3542,7 +3542,7 @@ function parseXsdDatetimeTerm(t) {
3542
3542
 
3543
3543
  function parseXsdDateTimeLexParts(t) {
3544
3544
  // Parse *lexical* components of an xsd:dateTime literal without timezone normalization.
3545
- // Returns { yearStr, month, day, minute, second, tz } or null.
3545
+ // Returns { yearStr, month, day, hour, minute, second, tz } or null.
3546
3546
  if (!(t instanceof Literal)) return null;
3547
3547
  const [lex, dt] = literalParts(t.value);
3548
3548
  if (dt !== XSD_NS + 'dateTime') return null;
@@ -3555,16 +3555,18 @@ function parseXsdDateTimeLexParts(t) {
3555
3555
  const yearStr = m[1];
3556
3556
  const month = parseInt(m[2], 10);
3557
3557
  const day = parseInt(m[3], 10);
3558
+ const hour = parseInt(m[4], 10);
3558
3559
  const minute = parseInt(m[5], 10);
3559
3560
  const second = parseInt(m[6], 10);
3560
3561
  const tz = m[7] || null;
3561
3562
 
3562
3563
  if (!(month >= 1 && month <= 12)) return null;
3563
3564
  if (!(day >= 1 && day <= 31)) return null;
3565
+ if (!(hour >= 0 && hour <= 23)) return null;
3564
3566
  if (!(minute >= 0 && minute <= 59)) return null;
3565
3567
  if (!(second >= 0 && second <= 59)) return null;
3566
3568
 
3567
- return { yearStr, month, day, minute, second, tz };
3569
+ return { yearStr, month, day, hour, minute, second, tz };
3568
3570
  }
3569
3571
 
3570
3572
  function parseDatetimeLike(t) {
@@ -4129,7 +4131,7 @@ function evalCryptoHashBuiltin(g, subst, algo) {
4129
4131
  // ===========================================================================
4130
4132
  // Backward proof & builtins mutual recursion — declarations first
4131
4133
 
4132
- function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4134
+ function evalBuiltin(goal, subst, facts, backRules, depth, varGen, maxResults) {
4133
4135
  const g = applySubstTriple(goal, subst);
4134
4136
  const pv = iriValue(g.p);
4135
4137
  if (pv === null) return null;
@@ -4689,6 +4691,32 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4689
4691
  return s2 !== null ? [s2] : [];
4690
4692
  }
4691
4693
 
4694
+ // time:hour
4695
+ // Gets as object the integer hour component of the subject xsd:dateTime.
4696
+ // Schema: $s+ time:hour $o-
4697
+ if (pv === TIME_NS + 'hour') {
4698
+ const parts = parseXsdDateTimeLexParts(g.s);
4699
+ if (!parts) return [];
4700
+ const out = internLiteral(String(parts.hour));
4701
+
4702
+ if (g.o instanceof Var) {
4703
+ const s2 = { ...subst };
4704
+ s2[g.o.name] = out;
4705
+ return [s2];
4706
+ }
4707
+ if (g.o instanceof Blank) return [{ ...subst }];
4708
+
4709
+ const oi = parseIntLiteral(g.o);
4710
+ if (oi !== null) {
4711
+ try {
4712
+ if (oi === BigInt(parts.hour)) return [{ ...subst }];
4713
+ } catch {}
4714
+ }
4715
+
4716
+ const s2 = unifyTerm(g.o, out, subst);
4717
+ return s2 !== null ? [s2] : [];
4718
+ }
4719
+
4692
4720
  // time:minute
4693
4721
  // Gets as object the integer minutes component of the subject xsd:dateTime.
4694
4722
  // Schema: $s+ time:minute $o-
@@ -5620,7 +5648,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
5620
5648
 
5621
5649
  const visited2 = [];
5622
5650
  // Start from the incoming substitution so bindings flow outward.
5623
- return proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen);
5651
+ return proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen, maxResults);
5624
5652
  }
5625
5653
 
5626
5654
  // log:notIncludes
@@ -5641,7 +5669,7 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
5641
5669
  });
5642
5670
 
5643
5671
  const visited2 = [];
5644
- const sols = proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen);
5672
+ const sols = proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, [], depth + 1, visited2, varGen, 1);
5645
5673
  return sols.length ? [] : [{ ...subst }];
5646
5674
  }
5647
5675
 
@@ -6216,10 +6244,11 @@ function maybeCompactSubst(subst, goals, answerVars, depth) {
6216
6244
  return gcCompactForGoals(subst, goals, answerVars);
6217
6245
  }
6218
6246
 
6219
- function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6247
+ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen, maxResults) {
6220
6248
  // Iterative DFS over proof states using an explicit stack.
6221
6249
  // Each state carries its own substitution and remaining goals.
6222
6250
  const results = [];
6251
+ const max = typeof maxResults === 'number' && maxResults > 0 ? maxResults : Infinity;
6223
6252
 
6224
6253
  const initialGoals = Array.isArray(goals) ? goals.slice() : [];
6225
6254
  const initialSubst = subst ? { ...subst } : {};
@@ -6230,6 +6259,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6230
6259
  gcCollectVarsInGoals(initialGoals, answerVars);
6231
6260
  if (!initialGoals.length) {
6232
6261
  results.push(gcCompactForGoals(initialSubst, [], answerVars));
6262
+
6263
+ if (results.length >= max) return results;
6233
6264
  return results;
6234
6265
  }
6235
6266
 
@@ -6247,6 +6278,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6247
6278
 
6248
6279
  if (!state.goals.length) {
6249
6280
  results.push(gcCompactForGoals(state.subst, [], answerVars));
6281
+
6282
+ if (results.length >= max) return results;
6250
6283
  continue;
6251
6284
  }
6252
6285
 
@@ -6256,13 +6289,18 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6256
6289
 
6257
6290
  // 1) Builtins
6258
6291
  if (isBuiltinPred(goal0.p)) {
6259
- const deltas = evalBuiltin(goal0, {}, facts, backRules, state.depth, varGen);
6292
+ const remaining = max - results.length;
6293
+ if (remaining <= 0) return results;
6294
+ const builtinMax = Number.isFinite(remaining) && !restGoals.length ? remaining : undefined;
6295
+ const deltas = evalBuiltin(goal0, {}, facts, backRules, state.depth, varGen, builtinMax);
6260
6296
  const nextStates = [];
6261
6297
  for (const delta of deltas) {
6262
6298
  const composed = composeSubst(state.subst, delta);
6263
6299
  if (composed === null) continue;
6264
6300
  if (!restGoals.length) {
6265
6301
  results.push(gcCompactForGoals(composed, [], answerVars));
6302
+
6303
+ if (results.length >= max) return results;
6266
6304
  } else {
6267
6305
  const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
6268
6306
  nextStates.push({
@@ -6293,6 +6331,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6293
6331
  if (composed === null) continue;
6294
6332
  if (!restGoals.length) {
6295
6333
  results.push(gcCompactForGoals(composed, [], answerVars));
6334
+
6335
+ if (results.length >= max) return results;
6296
6336
  } else {
6297
6337
  const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
6298
6338
  nextStates.push({
@@ -6314,6 +6354,8 @@ function proveGoals(goals, subst, facts, backRules, depth, visited, varGen) {
6314
6354
  if (composed === null) continue;
6315
6355
  if (!restGoals.length) {
6316
6356
  results.push(gcCompactForGoals(composed, [], answerVars));
6357
+
6358
+ if (results.length >= max) return results;
6317
6359
  } else {
6318
6360
  const nextSubst = maybeCompactSubst(composed, restGoals, answerVars, state.depth + 1);
6319
6361
  nextStates.push({
@@ -6426,7 +6468,38 @@ function forwardChain(facts, forwardRules, backRules, onDerived /* optional */)
6426
6468
  const r = forwardRules[i];
6427
6469
  const empty = {};
6428
6470
  const visited = [];
6429
- const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen);
6471
+ // Optimization: if the rule head is **structurally ground** (no vars anywhere, even inside
6472
+ // quoted formulas) and has no head blanks, then the head does not depend on which body
6473
+ // solution we pick. In that case, we only need *one* proof of the body, and once all head
6474
+ // triples are already known we can skip proving the body entirely.
6475
+ function isStrictGroundTerm(t) {
6476
+ if (t instanceof Var) return false;
6477
+ if (t instanceof Blank) return false;
6478
+ if (t instanceof OpenListTerm) return false;
6479
+ if (t instanceof ListTerm) return t.elems.every(isStrictGroundTerm);
6480
+ if (t instanceof GraphTerm) return t.triples.every(isStrictGroundTriple);
6481
+ return true; // Iri/Literal and any other atomic terms
6482
+ }
6483
+ function isStrictGroundTriple(tr) {
6484
+ return isStrictGroundTerm(tr.s) && isStrictGroundTerm(tr.p) && isStrictGroundTerm(tr.o);
6485
+ }
6486
+
6487
+ const headIsStrictGround =
6488
+ !r.isFuse && (!r.headBlankLabels || r.headBlankLabels.size === 0) && r.conclusion.every(isStrictGroundTriple);
6489
+
6490
+ if (headIsStrictGround) {
6491
+ let allKnown = true;
6492
+ for (const tr of r.conclusion) {
6493
+ if (!hasFactIndexed(facts, tr)) {
6494
+ allKnown = false;
6495
+ break;
6496
+ }
6497
+ }
6498
+ if (allKnown) continue;
6499
+ }
6500
+
6501
+ const maxSols = (r.isFuse || headIsStrictGround) ? 1 : undefined;
6502
+ const sols = proveGoals(r.premise.slice(), empty, facts, backRules, 0, visited, varGen, maxSols);
6430
6503
 
6431
6504
  // Inference fuse
6432
6505
  if (r.isFuse && sols.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.13",
3
+ "version": "1.7.15",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [