(def! bind-env (fn* [env b e]
  (if (empty? b)
    env
    (let* [b0 (first b)]
      (if (= '& b0)
        (assoc env (str (nth b 1)) e)
        (bind-env (assoc env (str b0) (first e)) (rest b) (rest e)))))))

(def! new-env (fn* [& args]
  (if (<= (count args) 1)
    (atom {:outer (first args)})
    (atom (apply bind-env {:outer (first args)} (rest args))))))

(def! env-find (fn* [env k]
  (env-find-str env (str k))))

(def! env-find-str (fn* [env ks]
  (if env
    (let* [data @env]
      (if (contains? data ks)
        env
        (env-find-str (get data :outer) ks))))))

(def! env-get (fn* [env k]
  (let* [ks (str k)
         e (env-find-str env ks)]
    (if e
      (get @e ks)
      (throw (str "'" ks "' not found"))))))

(def! env-set (fn* [env k v]
  (do
    (swap! env assoc (str k) v)
    v)))