git » hoplite.git » commit 0a5b716

Added '<' builtin predicate

author Alan Dipert
2021-06-14 03:27:54 UTC
committer Alan Dipert
2021-06-14 03:27:54 UTC
parent 4ee02a000635777b53bc55025e698f0b397180ba

Added '<' builtin predicate

datalog.mjs +67 -10
tests.mjs +12 -0

diff --git a/datalog.mjs b/datalog.mjs
index aa79859..27b31f8 100644
--- a/datalog.mjs
+++ b/datalog.mjs
@@ -41,6 +41,15 @@ function tupleEqual(x, y) {
   return true;
 }
 
+function relEqual(o1, o2) {
+  let [ks1, ks2] = [o1, o2].map(o => Object.keys(o));
+  if (ks1.length !== ks2.length) return false;
+  for (let k of ks1) {
+    if (o1[k] !== o2[k]) return false;
+  }
+  return true;
+}
+
 class StupidTupleSet {
   constructor(tuples = []) {
     this.tuples = [];
@@ -82,14 +91,19 @@ class StupidTupleSet {
 class Relation {
   constructor(vars) {
     this.vars = new Set(vars);
-    this.rels = {};
+    this.rels = [];
   }
   add(rel) {
-    this.rels[JSON.stringify([...this.vars].map(k => rel[k]))] = rel;
+    if (!this.has(rel)) {
+      this.rels.push(rel);
+    }
     return this;
   }
+  has(rel) {
+    return this.rels.some(v => relEqual(v, rel));
+  }
   [Symbol.iterator]() {
-    return Object.values(this.rels)[Symbol.iterator]();
+    return this.rels[Symbol.iterator]();
   }
 }
 
@@ -187,6 +201,45 @@ function parseVals(use, vals) {
   }
 }
 
+function isPredicateClause(clause) {
+  return clause.length === 1 && clause[0] instanceof Array;
+}
+
+function isSimpleClause(clause) {
+  return !isPredicateClause(clause);
+}
+
+function evalPred(predExpr) {
+  let [op, ...args] = predExpr;
+  if (op === '<') {
+    for (let i = 0; i < args.length-1; i += 2) {
+      if (!(args[i] < args[i+1])) return false;
+    }
+    return true;
+  } else {
+    throw new Error(`Unknown predicate: ${op}`);
+  }
+}
+
+function filterByPredicate(rel, predClause) {
+  return [...rel].reduce((xs, x) => {
+    let predExpr = predClause.map(y => {
+      if (isBlank(y)) {
+        throw new Error("Blanks not allowed in predicates");
+      } else if (isVar(y)) {
+        if (!x.hasOwnProperty(y.description)) {
+          throw new Error("Unknown variable ${y.description} in predicate");
+        }
+        return x[y.description];
+      } else {
+        return y;
+      }
+    });
+    if (evalPred(predExpr)) xs.add(x);
+    return xs;
+  }, new Relation(rel.vars));
+}
+
 function query({find = [], use = [], where = []}, ...vals) {
   let parsed = parseVals(use, vals);
   if (parsed.multipleSources) {
@@ -199,13 +252,17 @@ function query({find = [], use = [], where = []}, ...vals) {
     }
     return select(rels.reduce(join), varNames(find));
   } else {
-    return select(
-      [
-        initRel(parsed.valueNames, parsed.values),
-        ...where.map(c => scan(parsed.source, c))
-      ].reduce(join),
-      varNames(find)
-    );
+    let rel = [
+      initRel(parsed.valueNames, parsed.values),
+      ...where
+        .filter(isSimpleClause)
+        .map(c => scan(parsed.source, c))
+    ].reduce(join);
+    let preds = where.filter(isPredicateClause).map(x => x[0]);
+    if (preds.length) {
+      rel = preds.reduce(filterByPredicate, rel);
+    }
+    return select(rel, varNames(find));
   }
 } 
 
diff --git a/tests.mjs b/tests.mjs
index 0906a2f..51bb6bb 100644
--- a/tests.mjs
+++ b/tests.mjs
@@ -52,4 +52,16 @@ module('datalog', () => {
       [["ethel"], ["fred"]]
     )
   })
+  test('simple predicate', assert => {
+    assert.deepEqual(
+      query({
+        find: [_.e],
+        where: [
+          [_.e, "age", _.age],
+          [['<', _.age, 30]]
+        ]
+      }, db1).toArray(),
+      [["sally"]]
+    )
+  })
 });