| 
					
				 | 
			
			
				@@ -19,7 +19,8 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   {:row 15 :col "E" :value "8968" :view :display} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   {:row 7 :col "B" :value "=C5 + D6" :view :display} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                   {:row 8 :col "B" :value "=B7 * 2" :view :display} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                  {:row 7 :col "C" :value "=D1" :view :display}]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  {:row 7 :col "C" :value "=D1" :view :display} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                  {:row 12 :col "B" :value "=C12" :view :display}]) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defonce data-atom (r/atom sample-data)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -38,13 +39,15 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ; CHANGE VALUE FUNCTIONS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defn update-value [c r datum value] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (if (nil? datum) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (swap! data-atom conj {:row r :col c :value value}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :value value) %) d))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defn toggle-display [c r view-mode] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn update-value [c r existing-datum value] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (if (nil? existing-datum) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (swap! data-atom conj {:row r :col c :value value :dirty true}) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (swap! data-atom (partial map #(if (and (= r (:row %)) (= c (:col %))) (assoc (assoc % :dirty true) :value value) %))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;TODO: consider changing this for a single "view" atom which points to zero or one cells, which will determine whether to show the formula or evaluation 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn toggle-display [data c r view-mode] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (println (str "  toggling " c r " to " view-mode)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (swap! data-atom (fn [d] (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :view view-mode) %) d)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (map #(if (and (= r (:row %)) (= c (:col %))) (assoc % :view view-mode) %) data)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ; CALCULATION / FORMULA EVALUATION FUNCTIONS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -64,44 +67,31 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn find-ref [data cell-ref] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (some (fn [{:keys [row col] :as datum}] (if (and (= row (:row cell-ref)) (= col (:col cell-ref))) datum)) data)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn copy-display-values [data display-values] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (let [removed (map #(-> % (dissoc :vars) (dissoc :refs) (dissoc :found) (dissoc :inputs)) display-values)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (into data removed))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-;TODO: TEST THIS 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-;TODO: memoize dynamically? 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defn find-cycle [data datum ances] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (let [cur {:row (:row datum) :col (:col datum)} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        this-and-above (conj ances cur) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        refs (:refs datum) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        found (not (empty? (clojure.set/intersection this-and-above (set refs)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        ] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (println "searching for cycle" datum ances found) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (if found 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      :cycle-error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      (some (fn [cell] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              (find-cycle data (find-ref data cell) this-and-above) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-              ) refs) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-      ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(defn find-formula-problems [data l] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  ;(println "searching for potential problems" l (some #(= % {:row (:row l) :col (:col l)}) (:refs l))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (println "searching for potential problems" (find-cycle data l #{})) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  ;(loop [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-     
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    ;) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (let [original (map #(dissoc % :dirty) data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        removed (map #(-> % (dissoc :found) (dissoc :inputs) (dissoc :dirty)) display-values)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (into original removed))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;TODO: memoize dynamically? probably not worth memoizing directly, and could take up too much memory over time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+;      https://stackoverflow.com/a/13123571/8172807 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn find-cycle 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  ([data datum] (find-cycle data datum #{})) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  ([data datum ances] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   (let [cur {:row (:row datum) :col (:col datum)} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         this-and-above (conj ances cur) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         refs (:refs datum) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         found (not (empty? (clojure.set/intersection this-and-above (set refs))))] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     (if found 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       :cycle-error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       (some (fn [cell] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+               (find-cycle data (find-ref data cell) this-and-above)) refs))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn find-val [data c r] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (let [l (find-cell data c r) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         v (get l :display (get l :value)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        formula? (and (string? v) (= (first v) "=")) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        formula-value (if formula? (find-formula-problems data l)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        ] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (println "found?" c r v l (get l :value) (get l :display)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        formula? (and (string? v) (= (first v) "="))] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     (cond 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       (nil? v) 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      ;(contains? l :error) :ref-error 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       formula? :not-yet 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       :else v))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -109,12 +99,10 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;TODO: figure out how to re-evaluate only when the cell modified affects other cells 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn re-evaluate [data] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (let [remove-old-displays (map #(dissoc % :display) data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        {has-formula true original-values false} (group-by #(= (first (:value %)) "=") remove-old-displays) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        ;TODO: detect circular references 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        variables (map #(assoc % :vars (parse-variables (subs (:value %) 1))) has-formula) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        first-mapped-cell-keys (map (fn [datum] (assoc datum :refs (map str->rc (:vars datum)))) variables)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    (loop [values original-values mapped-cell-keys first-mapped-cell-keys] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (let [{has-formula true original-values false} (group-by #(= (first (:value %)) "=") data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        found-cycles (map #(let [found (find-cycle data %)] (if found (assoc % :error found) %)) has-formula) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        {eligible true ineligible false} (group-by #(not (contains? %  :error)) found-cycles)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (loop [values (into original-values ineligible) mapped-cell-keys eligible] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				       (let [search-values (map (fn [datum] (assoc datum :found (map #(find-val (concat values mapped-cell-keys) (:col %) (:row %)) (:refs datum)))) mapped-cell-keys) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             {not-ready true ready nil} (group-by (fn [datum] (some #(= :not-yet %) (:found datum))) search-values) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             prepped-for-eval (map (fn [datum] (assoc datum :inputs (apply hash-map (interleave (:vars datum) (:found datum))))) ready) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -124,14 +112,29 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           updated-values 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				           (recur updated-values not-ready)))))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn add-parsed-variables [datum] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (if (= (first (:value datum)) "=") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (let [vars (parse-variables (subs (:value datum) 1)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+          refs (map str->rc vars)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+      (-> datum (assoc :vars vars) (assoc :refs refs) (dissoc :error))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    (-> datum (dissoc :vars) (dissoc :refs) (dissoc :display) (dissoc :error)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(defn add-parsed-variables-to-specific-datum 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  "Parse variables from the value of a datum and add in :vars and :refs (for swap! data-atom). 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  If the value does not contain a fomula, remove any :vars and :refs that may have been there." 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  [c r data] (map #(if (and (= (:col %) c) (= (:row %) r)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                     (add-parsed-variables %) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                     %) data)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn on-enter-cell [c r e] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (println (str "entering cell " c r)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (toggle-display c r :value)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (swap! data-atom #(toggle-display % c r :value))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn on-leave-cell [c r e] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (println (str "leaving cell " c r)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (toggle-display c r :display) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-  (swap! data-atom re-evaluate)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  (swap! data-atom #(as-> % data 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      (toggle-display data c r :display) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      (add-parsed-variables-to-specific-datum c r data) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                      (re-evaluate data)))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;; ------------------------- 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -139,7 +142,9 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn cell [c r data] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (let [datum (some #(if (and (= c (:col %)) (= r (:row %))) %) data)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    ^{:key (str c r)} [:td [:input {:value (if (= (get datum :view nil) :value) (get datum :value "") (get datum :display (get datum :value ""))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    ^{:key (str c r)} [:td [:input {:value (if (= (get datum :view nil) :value) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             (get datum :value "") 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                             (get datum :error (get datum :display (get datum :value "")))) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                                     :on-change #(update-value c r datum (.. % -target -value)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                                     :on-blur (partial on-leave-cell c r) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                                     :on-focus (partial on-enter-cell c r)}]])) 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -170,7 +175,7 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;; ------------------------- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 ;; Initialize app 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-(swap! data-atom re-evaluate) ; evalutate any formulas the first time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+(swap! data-atom #(->> % (map add-parsed-variables) (re-evaluate))) ; evalutate any formulas the first time 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 (defn mount-root [] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				   (r/render [app] (.getElementById js/document "app"))) 
			 |