git » hoplite.git » commit 0169179

tons of stuff

author Alan Dipert
2021-05-29 05:11:17 UTC
committer Alan Dipert
2021-05-29 05:11:17 UTC
parent 959dfd9ec5f585991c3343db52b7b93905ca20a0

tons of stuff

datalog.mjs +30 -20
el.mjs +105 -36
reactives.mjs +8 -8
tests.mjs +14 -5
todo.mjs +81 -51

diff --git a/datalog.mjs b/datalog.mjs
index 7e77a70..cdd4744 100644
--- a/datalog.mjs
+++ b/datalog.mjs
@@ -13,8 +13,6 @@ const ANY = {
 };
 
 function match(clause, fact) {
-  if (clause.length !== fact.length)
-    return;
   let matches = {};
   for (let i = 0; i < clause.length; i++) {
     if (isVar(clause[i])) {
@@ -33,39 +31,51 @@ function match(clause, fact) {
   return matches;
 }
 
-class JSONSet {
-  constructor(values = []) {
-    this.values = {};
-    values.forEach(value => this.add(value));
+function tupleEqual(x, y) {
+  if (x.length !== y.length)
+    return false;
+  for (let i = 0; i < x.length; i++) {
+    if (x[i] !== y[i])
+      return false;
   }
-  add(value) {
-    this.values[JSON.stringify(value)] = value;
+  return true;
+}
+
+class TupleSet {
+  constructor(tuples = []) {
+    this.tuples = [];
+    tuples.forEach(tuple => this.add(tuple));
+  }
+  add(tuple) {
+    if (!this.has(tuple)) {
+      this.tuples.push(tuple);
+    }
   }
-  remove(value) {
-    if (!this.has(value)) {
+  remove(tuple) {
+    if (!this.has(tuple)) {
       return false;
     } else {
-      delete this.values[JSON.stringify(value)];
+      this.tuples = this.tuples.filter(v => !tupleEqual(v, tuple));
       return true;
     }
   }
   [Symbol.iterator]() {
-    return Object.values(this.values)[Symbol.iterator]();
+    return this.tuples[Symbol.iterator]();
   }
-  has(value) {
-    return JSON.stringify(value) in this.values;
+  has(tuple) {
+    return this.tuples.some(v => tupleEqual(v, tuple));
   }
   clear() {
-    this.values = {};
+    this.tuples = [];
   }
   complement(other) {
-    return new JSONSet([...this].filter(value => !other.has(value)));
+    return new TupleSet([...this].filter(tuple => !other.has(tuple)));
   }
   size() {
-    return Object.keys(this.values).length;
+    return this.tuples.length;
   }
   toArray() {
-    return [...this];
+    return [...this.tuples];
   }
 }
 
@@ -147,7 +157,7 @@ function initRel(use, vals) {
 }
 
 function select(rel, vars) {
-  return new JSONSet([...rel].map(rx => vars.map(v => rx[v])));
+  return new TupleSet([...rel].map(rx => vars.map(v => rx[v])));
 }
 
 function parseVals(use, vals) {
@@ -203,7 +213,7 @@ function partialQuery(q) {
   return (...vals) => query(q, ...vals);
 }
 
-export { _, query, JSONSet, partialQuery };
+export { _, query, TupleSet, partialQuery };
 // let db = new JSONSet([
 //   ["sally", "age", 21],
 //   ["fred", "age", 42],
diff --git a/el.mjs b/el.mjs
index 2089485..c1a622e 100644
--- a/el.mjs
+++ b/el.mjs
@@ -26,6 +26,10 @@ function el(tag, attrs, kids) {
         textNode.textContent = newv;
       })(kid);
       el.appendChild(textNode);
+    } else if (kid instanceof EntityNodes) {
+      kid.setCallback(([func, ...args]) => {
+        func.apply(el, args);
+      })
     } else {
       el.appendChild(kid);
     }
@@ -38,7 +42,8 @@ const $el = new Proxy({}, {
     if (typeof attrs === "string"
         || attrs instanceof Node
         || attrs instanceof Input
-        || attrs instanceof Formula) {
+        || attrs instanceof Formula
+        || attrs instanceof EntityNodes) {
       kids.unshift(attrs);
       attrs = {};
     }
@@ -48,46 +53,110 @@ const $el = new Proxy({}, {
   }
 });
 
-function mapEntities(parent, db, eidView, attrs, makeChild) {
-  let pool = [],
-      childEids = new Map();
-  watch((added, removed) => {
-    for (let [eid] of removed) {
-      let child = [...childEids].filter(([k, v]) => v.value === eid)[0][0];
-      parent.removeChild(child);
+function makeAttrCells(db, eidCell) {
+  let cellCache = new Map();
+  return new Proxy({}, {
+    get: (obj, attrName) => {
+      if (cellCache.has(name))
+        return cellCache.get(name);
+      let attrValCell = input(null);
+      watch((added, removed) => {
+        if (added.size()) {
+          let [[newv]] = added;
+          attrValCell.set(newv);
+        } else if (removed.size()) {
+          attrValCell.set(null);
+        }
+      })(view({
+        find: [_.attrVal],
+        use: [_.eid],
+        where: [[_.eid, attrName, _.attrVal]]
+      })(db, eidCell));
+      cellCache.set(attrName, attrValCell);
+      return attrValCell;
     }
-    for (let [eid] of added) {
-      let child;
-      if (pool.length) {
-        child = pool.pop();
-        childEids.get(child).set(eid);
-      } else {
-        let eidCell = input(eid),
-            attrCells = {eid: eidCell};
-        for (let attr of attrs) {
-          attrCells[attr] = input(null);
-          watch((added, removed) => {
-            if (added.size()) {
-              let [[newv]] = added;
-              attrCells[attr].set(newv);
-            } else if (removed.size()) {
-              attrCells[attr].set("");
-            }
-          })(view({
-            find: [_.attrVal],
-            use: [_.eid],
-            where: [[_.eid, attr, _.attrVal]]
-          })(db, eidCell))
+  })
+}
+
+class EntityNodes {
+  constructor(db, makeNode) {
+    this.db = db;
+    this.makeNode = makeNode;
+    this.pool = [];
+    this.nodeToEidCell = new Map();
+    this.bufferedCommands = [];
+    this.callback = command => this.bufferedCommands.push(command);
+    watch((added, removed) => {
+      for (let [eid] of removed) {
+        // TODO
+      }
+      for (let [eid] of added) {
+        let node;
+        if (this.pool.length) {
+          node = this.pool.pop();
+          nodeToEidCell.get(node).set(eid);
+        } else {
+          let eidCell = input(eid),
+              attrs = makeAttrCells(db, eidCell);
+          node = this.makeNode(attrs);
+          this.nodeToEidCell.set(node, eidCell);
         }
-        child = makeChild(attrCells);
-        childEids.set(child, eidCell);
+        this.callback([Node.prototype.appendChild, node]);
       }
-      parent.appendChild(child);
+    })(view({find: [_.eid], where: [[_.eid]]})(db))
+  }
+  setCallback(f) {
+    this.callback = f;
+    while (this.bufferedCommands.length) {
+      this.callback(this.bufferedCommands.pop());
     }
-  })(eidView);
-  return parent;
+  }
+}
+
+function entities(db, makeNode) {
+  return new EntityNodes(db, makeNode);
 }
 
+// function mapEntities(parent, db, eidView, attrs, makeChild) {
+//   let pool = [],
+//       childEids = new Map();
+//   watch((added, removed) => {
+//     for (let [eid] of removed) {
+//       let child = [...childEids].filter(([k, v]) => v.value === eid)[0][0];
+//       parent.removeChild(child);
+//     }
+//     for (let [eid] of added) {
+//       let child;
+//       if (pool.length) {
+//         child = pool.pop();
+//         childEids.get(child).set(eid);
+//       } else {
+//         let eidCell = input(eid),
+//             attrCells = {eid: eidCell};
+//         for (let attr of attrs) {
+//           attrCells[attr] = input(null);
+//           watch((added, removed) => {
+//             if (added.size()) {
+//               let [[newv]] = added;
+//               attrCells[attr].set(newv);
+//             } else if (removed.size()) {
+//               attrCells[attr].set("");
+//             }
+//           })(view({
+//             find: [_.attrVal],
+//             use: [_.eid],
+//             where: [[_.eid, attr, _.attrVal]]
+//           })(db, eidCell))
+//         }
+//         child = makeChild(attrCells);
+//         childEids.set(child, eidCell);
+//       }
+//       parent.appendChild(child);
+//     }
+//   })(eidView);
+//   return parent;
+// }
+
 // function renderChildren(parent, eidView, klass) {
 //   let children = new Map();
 //   watch((added, removed) => {
@@ -104,7 +173,7 @@ function mapEntities(parent, db, eidView, attrs, makeChild) {
 //   return parent;
 // }
 
-export { $el, on, mapEntities };
+export { $el, on, entities };
 // function renderInto(parent, view, templateFunction) {
 // }
 
diff --git a/reactives.mjs b/reactives.mjs
index 4ed91b5..a2d4eb1 100644
--- a/reactives.mjs
+++ b/reactives.mjs
@@ -1,4 +1,4 @@
-import { JSONSet, partialQuery } from './datalog.mjs';
+import { TupleSet, partialQuery } from './datalog.mjs';
 
 const depGraph = new Map(),
       suspended = new Set();
@@ -99,9 +99,9 @@ class Formula {
 
 class Database {
   constructor() {
-    this.set = new JSONSet();
-    this.added = new JSONSet();
-    this.removed = new JSONSet();
+    this.set = new TupleSet();
+    this.added = new TupleSet();
+    this.removed = new TupleSet();
   }
   add(tuples) {
     let changed = false;
@@ -157,9 +157,9 @@ class View {
     // q is not reactive (for now)
     this.query = q instanceof Function ? q : partialQuery(q);
     this.sources = sources;
-    this.set = new JSONSet();
-    this.added = new JSONSet();
-    this.removed = new JSONSet();
+    this.set = new TupleSet();
+    this.added = new TupleSet();
+    this.removed = new TupleSet();
     this.watches = new Map();
     sources
       .filter(View.isReactiveSource)
@@ -226,7 +226,7 @@ class Watch {
         || this.source instanceof Formula) {
       this.callback.call(null, this.source.previousValue, this.source.value)
     } else {
-      this.callback.call(null, this.source.set, new JSONSet());
+      this.callback.call(null, this.source.set, new TupleSet());
     }
   }
   detach() {
diff --git a/tests.mjs b/tests.mjs
index 4d1dd09..479faa0 100644
--- a/tests.mjs
+++ b/tests.mjs
@@ -1,12 +1,12 @@
-import { _, query, JSONSet } from './datalog.mjs';
+import { _, query, TupleSet } from './datalog.mjs';
 const { module, test } = QUnit;
 
-QUnit.assert.queryEquals = function(expect, q, ...args) {
-};
+// QUnit.assert.queryEquals = function(expect, q, ...args) {
+// };
 
 module('datalog', () => {
 
-  let db1 = new JSONSet([
+  let db1 = new TupleSet([
     ["sally", "age", 21],
     ["fred", "age", 42],
     ["ethel", "age", 42],
@@ -15,7 +15,7 @@ module('datalog', () => {
     ["ethel", "likes", "sushi"]
   ]);
 
-  let db2 = new JSONSet([
+  let db2 = new TupleSet([
     ["ethel", "sex", "female"],
     ["fred", "sex", "male"]
   ]);
@@ -43,4 +43,13 @@ module('datalog', () => {
       [["fred"], ["ethel"]]
     );
   })
+  test('partial unify', assert => {
+    assert.deepEqual(
+      query({
+        find: [_.e],
+        where: [[_.e]]
+      }, db2).toArray(),
+      [["ethel"], ["fred"]]
+    )
+  })
 });
diff --git a/todo.mjs b/todo.mjs
index 8351bf7..8d9f025 100644
--- a/todo.mjs
+++ b/todo.mjs
@@ -1,60 +1,90 @@
-import { _ } from './datalog.mjs';
+import { query, _ } from './datalog.mjs';
 import { transaction, input, formula, database, view, watch } from './reactives.mjs';
-import { $el, on, mapEntities } from './el.mjs';
+import { $el, on, entities } from './el.mjs';
 
-function nextEid(db) {
-  return ([...db].map(([eid]) => eid).sort((x, y) => y - x)[0] || 0) + 1; 
-}
+let todos = database([
+  [0, "text", "do the thing"],
+  [0, "status", "ready"],
+  [1, "text", "do the other thing"],
+  [1, "status", "ready"],
+  [2, "text", "do that last thing"],
+  [2, "status", "done"]
+]);
 
-function todoList() {
-  let todos = database([
-    [0, "text", "do the thing"],
-    [0, "status", "ready"],
-    [1, "text", "do the other thing"],
-    [1, "status", "ready"],
-    [2, "text", "do that last thing"],
-    [2, "status", "done"]
-  ]);
-  let textInput = $el.input({type: "text"});
-  return $el.div(
-    $el.form({
-      [on.submit]: e => {
-        let newEid = nextEid(todos);
-        todos.add([
-          [nextEid, "text", textInput.value],
-          [nextEid, "status", "ready"]
-        ])
-        e.preventDefault();
-      }
-    },
-      textInput,
-      $el.input({type: "submit", value: "Add"}),
-    ),
-    mapEntities(
-      $el.ul(),
-      todos,
-      view({find: [_.eid], where: [[_.eid, "text", _._]]})(todos),
-      ["text", "status"],
-      ({eid, text, status}) => $el.li(
-        $el.input({
-          type: "button",
-          value: "x",
-          [on.click]: todos.remove([...todos].filter(([x]) => x === eid.value))
-        }),
-        $el.span({
-          style: formula(s => s === "done" ? "text-decoration: line-through" : "")(status),
-          [on.click]: () => transaction(() => {
-            todos.remove([[eid.value, "status", status.value]]);
-            todos.add([[eid.value, "status", status.value === "done" ? "ready" : "done"]]);
-          })
-        }, text)
-      )
-    )
-  );
+function todoList(todos) {
+  return $el.ul(
+    entities(todos, attrs => $el.li(
+      attrs.text
+    ))
+  )
 }
 
+// function nextEid(db) {
+//   return ([...db].map(([eid]) => eid).sort((x, y) => y - x)[0] || 0) + 1; 
+// }
+
+// function removeEntity(db, eid) {
+//   db.remove([...db].filter(([x]) => x === eid))
+// }
+
+// function upsert(db, eid, attrs) {
+//   transaction(() => {
+//     // TODO
+//   })
+// }
+
+// $el.ul(
+//   entities(todos, (attrs) => $el.li(
+//     $el.input({
+//       type: "button",
+//       value: "Delete",
+//       [on.click]: removeEntity(todos, attr.eid.value)
+//     }),
+//     $el.span({
+//       style: condp(attr.status, "done", "text-decoration: line-through"),
+//       [on.click]: upsert(todos, attr.eid.value, {status: statusTransition[attr.status.value]})
+//     }, attr.text)
+//   ))
+// )
+
+// function todoList(todos) {
+//   let textInput = $el.input({type: "text"});
+//   return $el.div(
+//     $el.form({
+//       [on.submit]: e => {
+//         let newEid = nextEid(todos);
+//         todos.add([
+//           [nextEid, "text", textInput.value],
+//           [nextEid, "status", "ready"]
+//         ])
+//         e.preventDefault();
+//       }
+//     },
+//       textInput,
+//       $el.input({type: "submit", value: "Add"}),
+//     ),
+//     mapEntities(
+//       $el.ul(),
+//       todos,
+//       view({find: [_.eid], where: [[_.eid, "text", _._]]})(todos),
+//       ["text", "status"],
+//       ({eid, text, status}) => $el.li(
+//         $el.input({
+//           type: "button",
+//           value: "x",
+//           [on.click]: removeEntity(todos, eid.value)
+//         }),
+//         $el.span({
+//           style: formula(s => s === "done" ? "text-decoration: line-through" : "")(status),
+//           [on.click]: upsert(todos, eid.value, {status: status.value === "done" ? "ready" : "done"})
+//         }, text)
+//       )
+//     )
+//   );
+// }
+
 document.addEventListener("DOMContentLoaded", () => {
-  document.body.appendChild(todoList());
+  document.body.appendChild(todoList(todos));
 });
 
 // window.todos = todos;