eyeling 1.7.1 → 1.7.3

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 +102 -3
  2. package/package.json +1 -1
package/eyeling.js CHANGED
@@ -4638,6 +4638,58 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4638
4638
  return [{ ...subst }];
4639
4639
  }
4640
4640
 
4641
+ // log:conjunction
4642
+ // Schema: ( $s.i+ )+ log:conjunction $o?
4643
+ // $o is a formula containing a copy of each formula in the subject list.
4644
+ // Duplicates are removed.
4645
+ if (pv === LOG_NS + 'conjunction') {
4646
+ if (!(g.s instanceof ListTerm)) return [];
4647
+
4648
+ const parts = g.s.elems;
4649
+ if (!parts.length) return [];
4650
+
4651
+ /** @type {Triple[]} */
4652
+ const merged = [];
4653
+
4654
+ // Fast-path dedup for IRI/Literal-only triples.
4655
+ const fastKeySet = new Set();
4656
+
4657
+ for (const part of parts) {
4658
+ // Support the empty formula token `true`.
4659
+ if (part instanceof Literal && part.value === 'true') continue;
4660
+
4661
+ if (!(part instanceof GraphTerm)) return [];
4662
+
4663
+ for (const tr of part.triples) {
4664
+ const k = tripleFastKey(tr);
4665
+ if (k !== null) {
4666
+ if (fastKeySet.has(k)) continue;
4667
+ fastKeySet.add(k);
4668
+ merged.push(tr);
4669
+ continue;
4670
+ }
4671
+
4672
+ // Fallback: structural equality (still respects plain-string == xsd:string).
4673
+ let dup = false;
4674
+ for (const ex of merged) {
4675
+ if (triplesEqual(tr, ex)) {
4676
+ dup = true;
4677
+ break;
4678
+ }
4679
+ }
4680
+ if (!dup) merged.push(tr);
4681
+ }
4682
+ }
4683
+
4684
+ const outFormula = new GraphTerm(merged);
4685
+
4686
+ // Allow blank nodes as a don't-care output (common in builtin schemas).
4687
+ if (g.o instanceof Blank) return [{ ...subst }];
4688
+
4689
+ const s2 = unifyTerm(g.o, outFormula, subst);
4690
+ return s2 !== null ? [s2] : [];
4691
+ }
4692
+
4641
4693
  // log:dtlit
4642
4694
  // Schema: ( $s.1? $s.2? )? log:dtlit $o?
4643
4695
  // true iff $o is a datatyped literal with string value $s.1 and datatype IRI $s.2
@@ -4804,6 +4856,41 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4804
4856
  return results;
4805
4857
  }
4806
4858
 
4859
+ // log:includes (provable in scope)
4860
+ // Schema: $s+ log:includes $o+
4861
+ // When the subject is a formula, the scope is that concrete formula (syntactic containment).
4862
+ // Otherwise, the scope is the current document scope (facts + backward rules).
4863
+ if (pv === LOG_NS + 'includes') {
4864
+ // Empty formula is always included.
4865
+ if (g.o instanceof Literal && g.o.value === 'true') return [{ ...subst }];
4866
+ if (!(g.o instanceof GraphTerm)) return [];
4867
+
4868
+ /** @type {Triple[] | null} */
4869
+ let scopeFacts = null;
4870
+ /** @type {Rule[]} */
4871
+ let scopeBackRules = backRules;
4872
+
4873
+ // If the subject is a formula, treat it as a concrete scope graph.
4874
+ // Also support `true` as the empty formula.
4875
+ if (g.s instanceof GraphTerm) {
4876
+ scopeFacts = g.s.triples.slice();
4877
+ ensureFactIndexes(scopeFacts);
4878
+ Object.defineProperty(scopeFacts, '__scopedSnapshot', { value: scopeFacts, enumerable: false, writable: true });
4879
+ scopeBackRules = []; // concrete scope = syntactic containment (no extra rules)
4880
+ } else if (g.s instanceof Literal && g.s.value === 'true') {
4881
+ scopeFacts = [];
4882
+ ensureFactIndexes(scopeFacts);
4883
+ Object.defineProperty(scopeFacts, '__scopedSnapshot', { value: scopeFacts, enumerable: false, writable: true });
4884
+ scopeBackRules = [];
4885
+ } else {
4886
+ scopeFacts = facts; // dynamic scope
4887
+ }
4888
+
4889
+ const visited2 = [];
4890
+ // Start from the incoming substitution so bindings flow outward.
4891
+ return proveGoals(Array.from(g.o.triples), { ...subst }, scopeFacts, scopeBackRules, depth + 1, visited2, varGen);
4892
+ }
4893
+
4807
4894
  // log:notIncludes (not provable in scope)
4808
4895
  // Delay until we have a frozen scope snapshot to avoid early success.
4809
4896
  if (pv === LOG_NS + 'notIncludes') {
@@ -4847,6 +4934,11 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4847
4934
  if (!scopeFacts) return []; // DELAY until snapshot exists
4848
4935
  }
4849
4936
 
4937
+ // If sols is a blank node succeed without collecting/binding.
4938
+ if (listTerm instanceof Blank) {
4939
+ return [{ ...subst }];
4940
+ }
4941
+
4850
4942
  const visited2 = [];
4851
4943
  const sols = proveGoals(Array.from(clauseTerm.triples), {}, scopeFacts, scopeBackRules, depth + 1, visited2, varGen);
4852
4944
 
@@ -4908,8 +5000,8 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4908
5000
  if (pv === LOG_NS + 'uri') {
4909
5001
  // Direction 1: subject is an IRI -> object is its string representation
4910
5002
  if (g.s instanceof Iri) {
4911
- const uriStr = g.s.value; // raw IRI string, e.g. "https://www.w3.org"
4912
- const lit = makeStringLiteral(uriStr); // "https://www.w3.org"
5003
+ const uriStr = g.s.value; // raw IRI string
5004
+ const lit = makeStringLiteral(uriStr); // "..."
4913
5005
  const s2 = unifyTerm(goal.o, lit, subst);
4914
5006
  return s2 !== null ? [s2] : [];
4915
5007
  }
@@ -4918,6 +5010,14 @@ function evalBuiltin(goal, subst, facts, backRules, depth, varGen) {
4918
5010
  if (g.o instanceof Literal) {
4919
5011
  const uriStr = termToJsString(g.o); // JS string from the literal
4920
5012
  if (uriStr === null) return [];
5013
+
5014
+ // Reject strings that cannot be safely serialized as <...> in Turtle/N3.
5015
+ // Turtle IRIREF forbids control/space and these characters: < > " { } | ^ ` \
5016
+ // (and eyeling also prints IRIs starting with "_:" as blank-node labels)
5017
+ if (uriStr.startsWith('_:') || /[\u0000-\u0020<>"{}|^`\\]/.test(uriStr)) {
5018
+ return [];
5019
+ }
5020
+
4921
5021
  const iri = internIri(uriStr);
4922
5022
  const s2 = unifyTerm(goal.s, iri, subst);
4923
5023
  return s2 !== null ? [s2] : [];
@@ -5819,7 +5919,6 @@ function printExplanation(df, prefixes) {
5819
5919
  console.log('# ----------------------------------------------------------------------\n');
5820
5920
  }
5821
5921
 
5822
-
5823
5922
  function offsetToLineCol(text, offset) {
5824
5923
  const chars = Array.from(text);
5825
5924
  const n = Math.max(0, Math.min(typeof offset === 'number' ? offset : 0, chars.length));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eyeling",
3
- "version": "1.7.1",
3
+ "version": "1.7.3",
4
4
  "description": "A minimal Notation3 (N3) reasoner in JavaScript.",
5
5
  "main": "./index.js",
6
6
  "keywords": [