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.
- package/eyeling.js +102 -3
- 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
|
|
4912
|
-
const lit = makeStringLiteral(uriStr); // "
|
|
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));
|