@suss/datalog 0.18.0 → 0.20.0

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/dist/index.cjs CHANGED
@@ -665,38 +665,46 @@ function stratify(rules) {
665
665
  }
666
666
  return strata.filter((s) => s.length > 0);
667
667
  }
668
+ function boundValue(bindings, name) {
669
+ for (let b = bindings; b !== null; b = b.parent) {
670
+ if (b.name === name) {
671
+ return b.value;
672
+ }
673
+ }
674
+ return void 0;
675
+ }
676
+ var NO_MATCH = /* @__PURE__ */ Symbol("no-match");
668
677
  function unify(literal, tuple, bindings) {
669
678
  if (literal.terms.length !== tuple.length) {
670
- return null;
679
+ return NO_MATCH;
671
680
  }
672
- let next = null;
681
+ let next = bindings;
673
682
  for (let i = 0; i < literal.terms.length; i++) {
674
683
  const term = literal.terms[i];
675
684
  const value = tuple[i];
676
685
  if (term.type === "constant") {
677
686
  if (term.value !== value) {
678
- return null;
687
+ return NO_MATCH;
679
688
  }
680
689
  continue;
681
690
  }
682
- const bound = (next ?? bindings).get(term.name);
691
+ const bound = boundValue(next, term.name);
683
692
  if (bound === void 0) {
684
- next = next ?? new Map(bindings);
685
- next.set(term.name, value);
693
+ next = { name: term.name, value, parent: next };
686
694
  continue;
687
695
  }
688
696
  if (bound !== value) {
689
- return null;
697
+ return NO_MATCH;
690
698
  }
691
699
  }
692
- return next ?? bindings;
700
+ return next;
693
701
  }
694
702
  function groundNegated(literal, bindings) {
695
703
  return literal.terms.map((term) => {
696
704
  if (term.type === "constant") {
697
705
  return term.value;
698
706
  }
699
- const bound = bindings.get(term.name);
707
+ const bound = boundValue(bindings, term.name);
700
708
  if (bound === void 0) {
701
709
  throw new Error(
702
710
  `unbound variable "${term.name}" in negated literal "${literal.relation}": negated literals may only use variables bound by earlier positive literals`
@@ -710,7 +718,7 @@ function headTuple(head, bindings) {
710
718
  if (term.type === "constant") {
711
719
  return term.value;
712
720
  }
713
- const bound = bindings.get(term.name);
721
+ const bound = boundValue(bindings, term.name);
714
722
  if (bound === void 0) {
715
723
  throw new Error(
716
724
  `unbound variable "${term.name}" in head of "${head.relation}"`
@@ -736,23 +744,31 @@ function evaluateRule(db, deltas, r, deltaIndex) {
736
744
  const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
737
745
  for (const tuple of source) {
738
746
  const next = unify(literal, tuple, bindings);
739
- if (next !== null) {
747
+ if (next !== NO_MATCH) {
740
748
  step(literalIndex + 1, positiveIndex + 1, next);
741
749
  }
742
750
  }
743
751
  };
744
- step(0, 0, /* @__PURE__ */ new Map());
752
+ step(0, 0, null);
745
753
  return results;
746
754
  }
747
755
  function boundSource(db, literal, bindings) {
756
+ let narrowest = null;
748
757
  for (let column = 0; column < literal.terms.length; column++) {
749
758
  const term = literal.terms[column];
750
- const value = term.type === "constant" ? term.value : bindings.get(term.name);
751
- if (value !== void 0) {
752
- return db.lookup(literal.relation, column, value);
759
+ const value = term.type === "constant" ? term.value : boundValue(bindings, term.name);
760
+ if (value === void 0) {
761
+ continue;
762
+ }
763
+ const bucket = db.lookup(literal.relation, column, value);
764
+ if (bucket.length === 0) {
765
+ return bucket;
766
+ }
767
+ if (narrowest === null || bucket.length < narrowest.length) {
768
+ narrowest = bucket;
753
769
  }
754
770
  }
755
- return db.facts(literal.relation);
771
+ return narrowest ?? db.facts(literal.relation);
756
772
  }
757
773
  var EMPTY_TAGS = [];
758
774
  function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
@@ -794,7 +810,7 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
794
810
  const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
795
811
  for (const tuple of source) {
796
812
  const next = unify(literal, tuple, bindings);
797
- if (next !== null) {
813
+ if (next !== NO_MATCH) {
798
814
  if (readsTags) {
799
815
  const stored = db.tagOf(literal.relation, tuple);
800
816
  bodyTags.push(
@@ -814,7 +830,7 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
814
830
  }
815
831
  }
816
832
  };
817
- step(0, 0, /* @__PURE__ */ new Map());
833
+ step(0, 0, null);
818
834
  return results;
819
835
  }
820
836
  var evaluated = /* @__PURE__ */ new WeakMap();
package/dist/index.js CHANGED
@@ -619,38 +619,46 @@ function stratify(rules) {
619
619
  }
620
620
  return strata.filter((s) => s.length > 0);
621
621
  }
622
+ function boundValue(bindings, name) {
623
+ for (let b = bindings; b !== null; b = b.parent) {
624
+ if (b.name === name) {
625
+ return b.value;
626
+ }
627
+ }
628
+ return void 0;
629
+ }
630
+ var NO_MATCH = /* @__PURE__ */ Symbol("no-match");
622
631
  function unify(literal, tuple, bindings) {
623
632
  if (literal.terms.length !== tuple.length) {
624
- return null;
633
+ return NO_MATCH;
625
634
  }
626
- let next = null;
635
+ let next = bindings;
627
636
  for (let i = 0; i < literal.terms.length; i++) {
628
637
  const term = literal.terms[i];
629
638
  const value = tuple[i];
630
639
  if (term.type === "constant") {
631
640
  if (term.value !== value) {
632
- return null;
641
+ return NO_MATCH;
633
642
  }
634
643
  continue;
635
644
  }
636
- const bound = (next ?? bindings).get(term.name);
645
+ const bound = boundValue(next, term.name);
637
646
  if (bound === void 0) {
638
- next = next ?? new Map(bindings);
639
- next.set(term.name, value);
647
+ next = { name: term.name, value, parent: next };
640
648
  continue;
641
649
  }
642
650
  if (bound !== value) {
643
- return null;
651
+ return NO_MATCH;
644
652
  }
645
653
  }
646
- return next ?? bindings;
654
+ return next;
647
655
  }
648
656
  function groundNegated(literal, bindings) {
649
657
  return literal.terms.map((term) => {
650
658
  if (term.type === "constant") {
651
659
  return term.value;
652
660
  }
653
- const bound = bindings.get(term.name);
661
+ const bound = boundValue(bindings, term.name);
654
662
  if (bound === void 0) {
655
663
  throw new Error(
656
664
  `unbound variable "${term.name}" in negated literal "${literal.relation}": negated literals may only use variables bound by earlier positive literals`
@@ -664,7 +672,7 @@ function headTuple(head, bindings) {
664
672
  if (term.type === "constant") {
665
673
  return term.value;
666
674
  }
667
- const bound = bindings.get(term.name);
675
+ const bound = boundValue(bindings, term.name);
668
676
  if (bound === void 0) {
669
677
  throw new Error(
670
678
  `unbound variable "${term.name}" in head of "${head.relation}"`
@@ -690,23 +698,31 @@ function evaluateRule(db, deltas, r, deltaIndex) {
690
698
  const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
691
699
  for (const tuple of source) {
692
700
  const next = unify(literal, tuple, bindings);
693
- if (next !== null) {
701
+ if (next !== NO_MATCH) {
694
702
  step(literalIndex + 1, positiveIndex + 1, next);
695
703
  }
696
704
  }
697
705
  };
698
- step(0, 0, /* @__PURE__ */ new Map());
706
+ step(0, 0, null);
699
707
  return results;
700
708
  }
701
709
  function boundSource(db, literal, bindings) {
710
+ let narrowest = null;
702
711
  for (let column = 0; column < literal.terms.length; column++) {
703
712
  const term = literal.terms[column];
704
- const value = term.type === "constant" ? term.value : bindings.get(term.name);
705
- if (value !== void 0) {
706
- return db.lookup(literal.relation, column, value);
713
+ const value = term.type === "constant" ? term.value : boundValue(bindings, term.name);
714
+ if (value === void 0) {
715
+ continue;
716
+ }
717
+ const bucket = db.lookup(literal.relation, column, value);
718
+ if (bucket.length === 0) {
719
+ return bucket;
720
+ }
721
+ if (narrowest === null || bucket.length < narrowest.length) {
722
+ narrowest = bucket;
707
723
  }
708
724
  }
709
- return db.facts(literal.relation);
725
+ return narrowest ?? db.facts(literal.relation);
710
726
  }
711
727
  var EMPTY_TAGS = [];
712
728
  function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
@@ -748,7 +764,7 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
748
764
  const source = positiveIndex === deltaIndex ? deltas.get(literal.relation) ?? [] : boundSource(db, literal, bindings);
749
765
  for (const tuple of source) {
750
766
  const next = unify(literal, tuple, bindings);
751
- if (next !== null) {
767
+ if (next !== NO_MATCH) {
752
768
  if (readsTags) {
753
769
  const stored = db.tagOf(literal.relation, tuple);
754
770
  bodyTags.push(
@@ -768,7 +784,7 @@ function evaluateRuleTagged(db, deltas, r, deltaIndex, algebra) {
768
784
  }
769
785
  }
770
786
  };
771
- step(0, 0, /* @__PURE__ */ new Map());
787
+ step(0, 0, null);
772
788
  return results;
773
789
  }
774
790
  var evaluated = /* @__PURE__ */ new WeakMap();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/datalog",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "description": "A small semi-naïve Datalog evaluator with stratified negation — the rules engine behind suss's derived program facts.",
6
6
  "keywords": [