eyeling 1.23.0 → 1.23.1

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/HANDBOOK.md CHANGED
@@ -358,7 +358,9 @@ That distinction matters because quoted formulas still play **two different role
358
358
 
359
359
  The practical rule is:
360
360
 
361
- > **Eyeling lifts blanks inside quoted formulas only when the quoted formula appears directly in a premise triple position. Nested quoted formulas remain scoped data unless a query-like builtin interprets them as patterns.**
361
+ > **Eyeling lifts blanks inside quoted formulas only when the quoted formula appears directly in an ordinary premise triple position.**
362
+ >
363
+ > For `log:includes` and `log:notIncludes`, quoted formula operands keep their own blank-node scope. The builtin may treat blanks in the goal formula existentially while proving it, but blanks in an explicit scope graph remain formula-local blanks and may be returned as blank nodes rather than synthetic variables such as `?_b1`.
362
364
 
363
365
  This keeps `log:conjunction` and formula printing honest, while still allowing direct quoted-formula premise patterns such as `{ _:X :B :C } a :Statement.` to match interoperably.
364
366
 
@@ -1526,6 +1528,7 @@ Eyeling has **two modes**:
1526
1528
  1. **Explicit scope graph**: if `Scope` is a formula `{...}`
1527
1529
  - Eyeling reasons _only inside that formula_ (its triples are the fact store).
1528
1530
  - External rules are not used.
1531
+ - Blank nodes inside the explicit scope graph are preserved as graph-local blanks; if a goal variable matches one, the binding is a blank node, not a lifted rule variable.
1529
1532
 
1530
1533
  2. **Priority-gated global scope**: otherwise
1531
1534
  - Eyeling uses a _frozen snapshot_ of the current global closure.
@@ -13272,9 +13272,23 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
13272
13272
 
13273
13273
  'use strict';
13274
13274
 
13275
- const { Var, Blank, ListTerm, OpenListTerm, GraphTerm, Triple, copyQuotedGraphMetadata } = require('./prelude');
13275
+ const {
13276
+ LOG_NS,
13277
+ Iri,
13278
+ Var,
13279
+ Blank,
13280
+ ListTerm,
13281
+ OpenListTerm,
13282
+ GraphTerm,
13283
+ Triple,
13284
+ copyQuotedGraphMetadata,
13285
+ } = require('./prelude');
13276
13286
 
13277
13287
  function liftBlankRuleVars(premise, conclusion) {
13288
+ function isLogIncludesLikePredicate(p) {
13289
+ return p instanceof Iri && (p.value === LOG_NS + 'includes' || p.value === LOG_NS + 'notIncludes');
13290
+ }
13291
+
13278
13292
  // Map blank labels to stable rule-local variable names.
13279
13293
  // This runs at rule construction time; keep it simple and allocation-light.
13280
13294
  const mapping = Object.create(null);
@@ -13335,9 +13349,19 @@ ${triples.map((tr) => ` ${tripleToN3(tr, prefixes)}`).join('\n')}
13335
13349
  return t;
13336
13350
  }
13337
13351
 
13338
- const newPremise = premise.map(
13339
- (tr) => new Triple(convertTerm(tr.s, true), convertTerm(tr.p, true), convertTerm(tr.o, true)),
13340
- );
13352
+ const newPremise = premise.map((tr) => {
13353
+ // In log:includes / log:notIncludes, quoted formula operands are formulas
13354
+ // consumed by the builtin rather than ordinary triple patterns. Keep their
13355
+ // local blank nodes as Blank terms so the builtin can treat them as local
13356
+ // existentials, and bindings returned from an explicit scope are blank nodes
13357
+ // instead of synthetic rule variables such as ?_b1.
13358
+ const keepFormulaBlanks = isLogIncludesLikePredicate(tr.p);
13359
+ return new Triple(
13360
+ keepFormulaBlanks && tr.s instanceof GraphTerm ? copyQuotedTerm(tr.s) : convertTerm(tr.s, true),
13361
+ convertTerm(tr.p, true),
13362
+ keepFormulaBlanks && tr.o instanceof GraphTerm ? copyQuotedTerm(tr.o) : convertTerm(tr.o, true),
13363
+ );
13364
+ });
13341
13365
  return [newPremise, conclusion];
13342
13366
  }
13343
13367
 
package/eyeling.js CHANGED
@@ -10673,8 +10673,7 @@ function mergePrefixEnvs(target, source) {
10673
10673
 
10674
10674
  function mergeParsedDocuments(docs, opts = {}) {
10675
10675
  const documents = Array.isArray(docs) ? docs : [];
10676
- const scopeBlankNodes =
10677
- typeof opts.scopeBlankNodes === 'boolean' ? opts.scopeBlankNodes : documents.length > 1;
10676
+ const scopeBlankNodes = typeof opts.scopeBlankNodes === 'boolean' ? opts.scopeBlankNodes : documents.length > 1;
10678
10677
 
10679
10678
  const merged = emptyParsedDocument();
10680
10679
  const mergedSources = [];
@@ -10702,12 +10701,7 @@ function mergeParsedDocuments(docs, opts = {}) {
10702
10701
  }
10703
10702
 
10704
10703
  function isN3SourceListInput(input) {
10705
- return !!(
10706
- input &&
10707
- typeof input === 'object' &&
10708
- !Array.isArray(input) &&
10709
- Array.isArray(input.sources)
10710
- );
10704
+ return !!(input && typeof input === 'object' && !Array.isArray(input) && Array.isArray(input.sources));
10711
10705
  }
10712
10706
 
10713
10707
  function normalizeN3SourceItem(source, index) {
@@ -13237,9 +13231,13 @@ module.exports = {
13237
13231
 
13238
13232
  'use strict';
13239
13233
 
13240
- const { Var, Blank, ListTerm, OpenListTerm, GraphTerm, Triple, copyQuotedGraphMetadata } = require('./prelude');
13234
+ const { LOG_NS, Iri, Var, Blank, ListTerm, OpenListTerm, GraphTerm, Triple, copyQuotedGraphMetadata } = require('./prelude');
13241
13235
 
13242
13236
  function liftBlankRuleVars(premise, conclusion) {
13237
+ function isLogIncludesLikePredicate(p) {
13238
+ return p instanceof Iri && (p.value === LOG_NS + 'includes' || p.value === LOG_NS + 'notIncludes');
13239
+ }
13240
+
13243
13241
  // Map blank labels to stable rule-local variable names.
13244
13242
  // This runs at rule construction time; keep it simple and allocation-light.
13245
13243
  const mapping = Object.create(null);
@@ -13296,9 +13294,19 @@ function liftBlankRuleVars(premise, conclusion) {
13296
13294
  return t;
13297
13295
  }
13298
13296
 
13299
- const newPremise = premise.map(
13300
- (tr) => new Triple(convertTerm(tr.s, true), convertTerm(tr.p, true), convertTerm(tr.o, true)),
13301
- );
13297
+ const newPremise = premise.map((tr) => {
13298
+ // In log:includes / log:notIncludes, quoted formula operands are formulas
13299
+ // consumed by the builtin rather than ordinary triple patterns. Keep their
13300
+ // local blank nodes as Blank terms so the builtin can treat them as local
13301
+ // existentials, and bindings returned from an explicit scope are blank nodes
13302
+ // instead of synthetic rule variables such as ?_b1.
13303
+ const keepFormulaBlanks = isLogIncludesLikePredicate(tr.p);
13304
+ return new Triple(
13305
+ keepFormulaBlanks && tr.s instanceof GraphTerm ? copyQuotedTerm(tr.s) : convertTerm(tr.s, true),
13306
+ convertTerm(tr.p, true),
13307
+ keepFormulaBlanks && tr.o instanceof GraphTerm ? copyQuotedTerm(tr.o) : convertTerm(tr.o, true),
13308
+ );
13309
+ });
13302
13310
  return [newPremise, conclusion];
13303
13311
  }
13304
13312
 
package/lib/rules.js CHANGED
@@ -7,9 +7,23 @@
7
7
 
8
8
  'use strict';
9
9
 
10
- const { Var, Blank, ListTerm, OpenListTerm, GraphTerm, Triple, copyQuotedGraphMetadata } = require('./prelude');
10
+ const {
11
+ LOG_NS,
12
+ Iri,
13
+ Var,
14
+ Blank,
15
+ ListTerm,
16
+ OpenListTerm,
17
+ GraphTerm,
18
+ Triple,
19
+ copyQuotedGraphMetadata,
20
+ } = require('./prelude');
11
21
 
12
22
  function liftBlankRuleVars(premise, conclusion) {
23
+ function isLogIncludesLikePredicate(p) {
24
+ return p instanceof Iri && (p.value === LOG_NS + 'includes' || p.value === LOG_NS + 'notIncludes');
25
+ }
26
+
13
27
  // Map blank labels to stable rule-local variable names.
14
28
  // This runs at rule construction time; keep it simple and allocation-light.
15
29
  const mapping = Object.create(null);
@@ -66,9 +80,19 @@ function liftBlankRuleVars(premise, conclusion) {
66
80
  return t;
67
81
  }
68
82
 
69
- const newPremise = premise.map(
70
- (tr) => new Triple(convertTerm(tr.s, true), convertTerm(tr.p, true), convertTerm(tr.o, true)),
71
- );
83
+ const newPremise = premise.map((tr) => {
84
+ // In log:includes / log:notIncludes, quoted formula operands are formulas
85
+ // consumed by the builtin rather than ordinary triple patterns. Keep their
86
+ // local blank nodes as Blank terms so the builtin can treat them as local
87
+ // existentials, and bindings returned from an explicit scope are blank nodes
88
+ // instead of synthetic rule variables such as ?_b1.
89
+ const keepFormulaBlanks = isLogIncludesLikePredicate(tr.p);
90
+ return new Triple(
91
+ keepFormulaBlanks && tr.s instanceof GraphTerm ? copyQuotedTerm(tr.s) : convertTerm(tr.s, true),
92
+ convertTerm(tr.p, true),
93
+ keepFormulaBlanks && tr.o instanceof GraphTerm ? copyQuotedTerm(tr.o) : convertTerm(tr.o, true),
94
+ );
95
+ });
72
96
  return [newPremise, conclusion];
73
97
  }
74
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.23.0",
3
+ "version": "1.23.1",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [
package/test/api.test.js CHANGED
@@ -1643,6 +1643,30 @@ _:x :hates { _:foo :making :mess }.
1643
1643
  expect: [/:(?:test)\s+:(?:contains)\s+:(?:success-literal-3)\s*\./, /:(?:test)\s+:(?:is)\s+true\s*\./],
1644
1644
  },
1645
1645
 
1646
+ {
1647
+ name: '60a regression: log:includes explicit-scope blank node is returned as blank, not synthetic variable',
1648
+ opt: { proofComments: false },
1649
+ input: `@prefix : <http://example.org/ns#> .
1650
+ @prefix log: <http://www.w3.org/2000/10/swap/log#>.
1651
+
1652
+ {
1653
+ {
1654
+ _:b1 a :Mortal .
1655
+ } log:includes {
1656
+ ?CS ?CP ?CO .
1657
+ } .
1658
+ }
1659
+ =>
1660
+ {
1661
+ _:b2 :conclusion {
1662
+ ?CS ?CP ?CO .
1663
+ } .
1664
+ } .
1665
+ `,
1666
+ expect: [/_:sk_\d+\s+:(?:conclusion)\s+\{\s*_:b\d+\s+a\s+:(?:Mortal)\s*\.\s*\}\s*\./s],
1667
+ notExpect: [/\?_b\d+\s+a\s+:(?:Mortal)/],
1668
+ },
1669
+
1646
1670
  {
1647
1671
  name: '61 RDF/JS input + rule objects: reason() accepts quads with rules',
1648
1672
  run() {