Dominik G
Dominik G

Reputation: 1479

Define missing symbols in Clojure

I am trying to write a macro that, given a form, defines all missing symbols as themselve.

By now I have the following:

(def ^:private missing-symbol-pattern #"Unable to resolve symbol: (.+) in this context")

(cl/defn ^:private missing-symbol [s]
  (cl/when-let [[_ sym] (cl/re-find missing-symbol-pattern s)] sym))

(cl/defmacro try-eval [expr]
               `(try (do (cl/println '~expr) (cl/eval '~expr)) 
                 (catch Exception e#
                   (cl/if-let [sym# (do (cl/println (.getMessage e#)) (missing-symbol (.getMessage e#)))]
                              (cl/eval `(do 
                                          (def ~(cl/symbol sym#) '~(cl/symbol sym#))
                                          (cl/println ~sym#)
                                          (try-eval ~'~expr)))
                              (cl/eval `(throw ~e#))))))

(cl here is an alias for clojure.core) I know that there can be problems with side-effects, but this is not important here (although a solution without side-effect problems would be better)

If there is more than one missing (unresolvable) symbol, I get the following Exception:

java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to clojure.lang.IDeref, compiling:(shen/primitives.clj:517)

Has anyone an idea on what to change? Is there some "ready made" solution?

Cheers

Upvotes: 1

Views: 237

Answers (1)

Alex Baranosky
Alex Baranosky

Reputation: 50064

Rather than using try/catch to find symbols that won't resolve, maybe you could do something using &env symbol available inside of defmacro. The keys of &env are the locally defined symbols.

(Article about &env and &form.)

Using a combination of resolve and (keys &env) you can isolate the symbols that aren't defined, then you can choose to def the ones that are (not (or (resolve sym) (contains? &env sym))

We do something akin to this in the Midje source code, to determine which symbols in a tabular test are already defined and which ones aren't:

[Note: in the below code locals is in fact (keys &env)]

(defn- headings-rows+values [table locals]
  (letfn [(table-variable? [s]
            (and (symbol? s)
              (not (metaconstant-symbol? s))
              (not (resolve s))
              (not (contains? locals s))))]
    (split-with table-variable? (remove-pipes+where table))))

Upvotes: 3

Related Questions