arichiardi / replumb

ClojureScript plumbing for your self-hosted REPLs.
http://clojurescript.io
Eclipse Public License 1.0
210 stars 16 forks source link

replumb/read-eval-call doesn't support `:refer` #170

Closed viebel closed 8 years ago

viebel commented 8 years ago

When calling replumb/read-eval-call with :refer inside the source, it doesn't work.

For instance,

(defn no-op [file-url src-cb]
  (src-cb ""))

(def repl-opts-noop (merge (replumb/options :browser
                                             ["/dbg/js" "/js/compiled/out"]
                                             io/no-op)
                            {:warning-as-error false
                             :context :statement
                             :verbose false}))
(replumb/read-eval-call repl-opts-noop identity "(ns my.aa (:require [clojure.set :refer [union]]) )")

; => {:success? false, :form (ns my.aa (:require [clojure.set :refer [union]])), :warning nil, :error #error {:message "Could not parse ns form my.aa", :data {:tag :cljs/analysis-error}, :cause #error {:message "Invalid :refer, var var clojure.set/union does not exist", :data {:tag :cljs/analysis-error}}}}

While it works fine with :as


(replumb/read-eval-call repl-opts-noop identity "(ns my.aa (:require [clojure.set :as set])) (set/union)")
; => {:success? true, :form (ns my.aa (:require [clojure.set :as set])), :warning nil, :value "#{}"}
arichiardi commented 8 years ago

Uhm, it should work, do you have the clojure source of clojure.set in /dbg/js ?

arichiardi commented 8 years ago

If yes, can you add :verbose true to the options and post here the call log?

viebel commented 8 years ago

My load function is a dummy function that returns an empty string. It works fine when requiring clojure.set with :as (because clojure.set is already loaded)

Here is the log:

(replumb/read-eval-call (merge repl-opts-noop {:verbose true}) identity "(ns my.aa (:require [clojure.set :refer [union]]))
 (set/union)")
Calling eval-str on (ns my.aa (:require [clojure.set :refer [union]])) with options {:read-file-fn! <hidden function>, :init-fns <hidden func
tion>, :write-file-fn! <hidden function>, :verbose true, :warning-as-error false, :context :statement, :load-fn! <hidden function>, :target :
default, :src-paths [/dbg/js /js/compiled/out]}
Evaluation returned:  {:error #error {:message Could not parse ns form my.aa, :data {:tag :cljs/analysis-error}, :cause #error {:message Inva
lid :refer, var var clojure.set/union does not exist, :data {:tag :cljs/analysis-error}}}}
Calling back!
 {:opts
 {:read-file-fn! "<hidden function>",
  :init-fns "<hidden function>",
  :write-file-fn! "<hidden function>",
  :verbose true,
  :warning-as-error false,
  :context :statement,
  :load-fn! "<hidden function>",
  :target :default,
  :src-paths ["/dbg/js" "/js/compiled/out"]},
 :data
 {:form (ns my.aa (:require [clojure.set :refer [union]])),
  :ns my.aa,
  :target :default,
  :on-success-fn! "<hidden function>"},
 :res
 {:error
  #error {:message "Could not parse ns form my.aa", :data {:tag :cljs/analysis-error}, :cause #error {:message "Invalid :refer, var var cloju
re.set/union does not exist", :data {:tag :cljs/analysis-error}}}}}

{:success? false, :form (ns my.aa (:require [clojure.set :refer [union]])), :warning nil, :error #error {:message "Could not parse ns form my
.aa", :data {:tag :cljs/analysis-error}, :cause #error {:message "Invalid :refer, var var clojure.set/union does not exist", :data {:tag :clj
s/analysis-error}}}}
arichiardi commented 8 years ago

Yes you are right, it is tricky though because they are loaded but they might not have metadata (analysis cache). So if I add that straight to skip-load?, the require and consequent load of the actual files will never be triggered...

arichiardi commented 8 years ago

Maybe we should try to load those namespaces at the beginning during the init phase and consider them as "loaded" if not present in any :src-paths. If somebody wants to be able to add for instance clojure.set files at runtime, she will need to copy them and force an init, but I see this last use case not very useful in general.

viebel commented 8 years ago

How do I add that straight to skip-load??

What modifications should I make to my code? Please give me an explicit piece of code :)

Maybe using repl/eval-str* ?

arichiardi commented 8 years ago

Skip load is part of load-fn so the problem might actually be in cljs.js. I am investigating.

viebel commented 8 years ago

@arichiardi did you discover something interesting?

arichiardi commented 8 years ago

Kind of delayed, probably I will today :)

arichiardi commented 8 years ago

So probably, maybe, attentively it boils down to this and then this.

I tried the following test and it works:

(h/read-eval-call-test (assoc e/*target-opts*
                              :verbose true
                              :load-fn! (fn [m cb]
                                          (cb {:lang :clj
                                               :source "(ns clojure.set) (defn union [] nil)"})))
  ["(ns my.aa (:require [clojure.set :refer [union]]))"]
  (let [out (unwrap-result @_res_)]
    (is (success? @_res_) (str _msg_ "should succeed"))
    (is (valid-eval-result? out) (str _msg_ "should be a valid result"))))
arichiardi commented 8 years ago

I have also noticed that planck handles the case separately and it actually always loads the cache that is probably what is missing here:

https://github.com/mfikes/planck/blob/master/planck-cljs/src/planck/repl.cljs#L626

viebel commented 8 years ago

I cannot call read-eval-call-test from my project as test-helpers namespace is not exported.

Is there a way to use the piece of code you share inside my project?

arichiardi commented 8 years ago

Uhm, well that's just a macro around read-eval-call really, it was just to show you that you need the clojure.set source on the classpath now, waiting for a fix....

arichiardi commented 8 years ago

So @viebel the solution is:

viebel commented 8 years ago

I'm not yet able to make it work. I have tried the following

(defn no-op [file-url src-cb]
  (src-cb ""))
(def repl-opts-noop (merge (replumb/options :browser
                                             ["/dbg/js" "/js/compiled/out"]
                                             no-op)
                            {:warning-as-error false
                             :context :statement
                             :cache {:src-paths-lookup? true}
                             :verbose false}))
(replumb/read-eval-call (merge repl-opts-noop {:verbose true}) identity "(ns my.aa (:require [clojure.set :as set :refer [union]])) (union)")

And I get this log

Calling eval-str on (ns my.aa (:require [clojure.set :as set :refer [union]])) with options {:read-file-fn! <hidden function>, :init-fns <hid
den function>, :write-file-fn! <hidden function>, :verbose true, :cache {:src-paths-lookup? true}, :warning-as-error false, :context :stateme
nt, :load-fn! <hidden function>, :target :default, :src-paths [/dbg/js /js/compiled/out]}
Evaluation returned:  {:error #error {:message Could not parse ns form my.aa, :data {:tag :cljs/analysis-error}, :cause #error {:message Inva
lid :refer, var var clojure.set/union does not exist, :data {:tag :cljs/analysis-error}}}}
Calling eval-str on (ns my.aa (:require [clojure.set :as set :refer [union]])) with options {:read-file-fn! <hidden function>, :init-fns <hid
den function>, :write-file-fn! <hidden function>, :verbose true, :warning-as-error false, :context :statement, :load-fn! <hidden function>, :
target :default, :src-paths [/dbg/js /js/compiled/out]}
Evaluation returned:  {:error #error {:message Could not parse ns form my.aa, :data {:tag :cljs/analysis-error}, :cause #error {:message Inva
lid :refer, var var clojure.set/union does not exist, :data {:tag :cljs/analysis-error}}}}
Calling back!
 {:opts
 {:read-file-fn! "<hidden function>",
  :init-fns "<hidden function>",
  :write-file-fn! "<hidden function>",
  :verbose true,
  :cache {:src-paths-lookup? true},
  :warning-as-error false,
  :context :statement,
  :load-fn! "<hidden function>",
  :target :default,
  :src-paths ["/dbg/js" "/js/compiled/out"]},
 :data
 {:form (ns my.aa (:require [clojure.set :as set :refer [union]])),
  :ns cljs.user,
  :target :default,
  :on-success-fn! "<hidden function>"},
 :res
 {:error
  #error {:message "Could not parse ns form my.aa", :data {:tag :cljs/analysis-error}, :cause #error {:message "Invalid :refer, var var cloju
re.set/union does not exist", :data {:tag :cljs/analysis-error}}}}}

:success? false, :form (ns my.aa (:require [clojure.set :as set :refer [union]])), :warning nil, :error #error {:message "Could not parse ns
 form my.aa", :data {:tag :cljs/analysis-error}, :cause #error {:message "Invalid :refer, var var clojure.set/union does not exist", :data {:
tag :cljs/analysis-error}}}}
klipse.compiler=> Calling back!
 {:opts
 {:read-file-fn! "<hidden function>",
  :init-fns "<hidden function>",
  :write-file-fn! "<hidden function>",
  :verbose true,
  :warning-as-error false,
  :context :statement,
  :load-fn! "<hidden function>",
  :target :default,
  :src-paths ["/dbg/js" "/js/compiled/out"]},
 :data
 {:form (ns my.aa (:require [clojure.set :as set :refer [union]])),
  :ns foo.core$macros,
  :target :default,
  :on-success-fn! "<hidden function>"},
 :res
 {:error
  #error {:message "Could not parse ns form my.aa", :data {:tag :cljs/analysis-error}, :cause #error {:message "Invalid :refer, var var clojure.set/union does not exist", :data {:tag :cljs/analysis-error}}}}}
arichiardi commented 8 years ago

You have to specify a io/read-file! in replumb.core/options or you won't be able to load the analysis cache. In some way ClojureScript needs to have it for :refer to work and there are two ways to achieve this:

  1. To return the clojure.set namespace in :source of your load-fn!
  2. To read and load the analysis cache from somewhere (in our case from your src
viebel commented 8 years ago

Great. It works with option #2.

I don't understand how to implement #1.

Also, is there a way to use macros inside a repl? It sounds tricky because macros are defined inside clj files.

arichiardi commented 8 years ago

Great! Probably this explains it better, I will close this one if you don't mind. http://blog.fikesfarm.com/posts/2015-09-07-messing-with-macros-at-the-repl.html