Skip to content

Commit

Permalink
Support code reflection in the cljs repl.
Browse files Browse the repository at this point in the history
A few changes have been made to support runtime code reflection in a
cljs repl. These include small changes to cljs.analyzer, a separation of
the server element of cljs.repl.browser into cljs.repl.server, and the
addition of two new namespaces: cljs.repl.reflect (in src/clj) and
clojure.reflect (in src/cljs).

cljs.analyzer:
- Arbitrary metadata declared on symbols will now be added to the AST.
  This supports the addition of docstrings.
- Fix a subtle bug in cljs.analyzer/analyze-file, where an uncommon
  code-path would lead to the failed coercion of an absolute-path into a
  URL. An absolute path, including a `file://` protocol, can now be
  passed into the function successfully.

cljs.repl:
- Add function to analyze source on repl-env -setup. This is used to
  support reflection on user-defined cljs source files, as well as to
  populate the cljs.analyzer/namespaces atom on repl startup.

cljs.repl.browser:
- The server element of this namespace has been factored out into
  cljs.repl.server to support other services that may require that
  functionality.

cljs.repl.server:
- Expose a simple HTTP method and predicate dispatch system to register
  handler functions for incoming requests. (Note: this system seems to
  be relatively brittle, and future change may be warranted.)

cljs.repl.reflect:
- Registers a server handler for incoming requests to "/reflect".
- Queries cljs.analyzer/namespaces for meta information relevant to a
  symbol, responding to requests with compiled javascript.
- Can use "fixed point" macroexpansion on cljs macro forms.

clojure.reflect:
- Expose a set of simple functions for querying meta information of a
  symbol, as well as macroexpanding a cljs form.
  • Loading branch information
zachallaun authored and David Nolen committed Jul 25, 2012
1 parent 0f73237 commit b5b20fd
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 196 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -9,3 +9,4 @@ closure
/coresimple.js /coresimple.js
/out /out
.repl .repl
*.swp
3 changes: 2 additions & 1 deletion samples/repl/src/repl/test.cljs
Expand Up @@ -7,7 +7,8 @@
;; You must not remove this notice, or any other, from this software. ;; You must not remove this notice, or any other, from this software.


(ns repl.test (ns repl.test
(:require [clojure.browser.repl :as repl])) (:require [clojure.browser.repl :as repl]
[clojure.reflect :as reflect]))


(repl/connect "http://localhost:9000/repl") (repl/connect "http://localhost:9000/repl")


Expand Down
16 changes: 16 additions & 0 deletions script/browser-repl
@@ -0,0 +1,16 @@
#!/bin/sh

if [ "$CLOJURESCRIPT_HOME" = "" ]; then
CLOJURESCRIPT_HOME="`dirname $0`/.."
fi

CLJSC_CP=''
for next in lib/*: src/clj: src/cljs: test/cljs; do
CLJSC_CP=$CLJSC_CP$CLOJURESCRIPT_HOME'/'$next
done

java -server -cp $CLJSC_CP clojure.main -e "
(require '[cljs.repl :as r])
(require '[cljs.repl.browser :as b])
(r/repl (b/repl-env))
"
11 changes: 6 additions & 5 deletions src/clj/cljs/analyzer.clj
Expand Up @@ -290,6 +290,7 @@
([_ sym doc init] {:sym sym :doc doc :init init})) ([_ sym doc init] {:sym sym :doc doc :init init}))
args (apply pfn form) args (apply pfn form)
sym (:sym args) sym (:sym args)
sym-meta (meta sym)
tag (-> sym meta :tag) tag (-> sym meta :tag)
protocol (-> sym meta :protocol) protocol (-> sym meta :protocol)
dynamic (-> sym meta :dynamic) dynamic (-> sym meta :dynamic)
Expand Down Expand Up @@ -326,6 +327,7 @@
(let [m (assoc (or m {}) :name name)] (let [m (assoc (or m {}) :name name)]
(merge m (merge m
(when tag {:tag tag}) (when tag {:tag tag})
(when sym-meta sym-meta)
(when dynamic {:dynamic true}) (when dynamic {:dynamic true})
(when-let [line (:line env)] (when-let [line (:line env)]
{:file *cljs-file* :line line}) {:file *cljs-file* :line line})
Expand Down Expand Up @@ -533,7 +535,7 @@
(when (and known-num-fields (not= known-num-fields argc)) (when (and known-num-fields (not= known-num-fields argc))
(warning env (warning env
(str "WARNING: Wrong number of args (" argc ") passed to " ctor))) (str "WARNING: Wrong number of args (" argc ") passed to " ctor)))

{:env env :op :new :form form :ctor ctorexpr :args argexprs {:env env :op :new :form form :ctor ctorexpr :args argexprs
:children (into [ctorexpr] argexprs)}))) :children (into [ctorexpr] argexprs)})))


Expand Down Expand Up @@ -677,7 +679,7 @@
:type true :type true
:num-fields (count fields))] :num-fields (count fields))]
(merge m (merge m
{:protocols (-> tsym meta :protocols)} {:protocols (-> tsym meta :protocols)}
(when-let [line (:line env)] (when-let [line (:line env)]
{:file *cljs-file* {:file *cljs-file*
:line line}))))) :line line})))))
Expand Down Expand Up @@ -935,8 +937,8 @@
:else {:op :constant :env env :form form})))) :else {:op :constant :env env :form form}))))


(defn analyze-file (defn analyze-file
[f] [^String f]
(let [res (if (= \/ (first f)) f (io/resource f))] (let [res (if (re-find #"^file://" f) (java.net.URL. f) (io/resource f))]
(assert res (str "Can't find " f " in classpath")) (assert res (str "Can't find " f " in classpath"))
(binding [*cljs-ns* 'cljs.user (binding [*cljs-ns* 'cljs.user
*cljs-file* (.getPath ^java.net.URL res) *cljs-file* (.getPath ^java.net.URL res)
Expand All @@ -950,4 +952,3 @@
(when-not (identical? eof r) (when-not (identical? eof r)
(analyze env r) (analyze env r)
(recur (read pbr false eof false)))))))))) (recur (read pbr false eof false))))))))))

16 changes: 13 additions & 3 deletions src/clj/cljs/repl.clj
Expand Up @@ -8,6 +8,7 @@


(ns cljs.repl (ns cljs.repl
(:refer-clojure :exclude [load-file]) (:refer-clojure :exclude [load-file])
(:import java.io.File)
(:require [clojure.string :as string] (:require [clojure.string :as string]
[clojure.java.io :as io] [clojure.java.io :as io]
[cljs.compiler :as comp] [cljs.compiler :as comp]
Expand Down Expand Up @@ -149,6 +150,15 @@
'clojure.core/load-file load-file-fn 'clojure.core/load-file load-file-fn
'load-namespace (fn [repl-env ns] (load-namespace repl-env ns))})) 'load-namespace (fn [repl-env ns] (load-namespace repl-env ns))}))


(defn analyze-source
"Given a source directory, analyzes all .cljs files. Used to populate
cljs.analyzer/namespaces so as to support code reflection."
[src-dir]
(if-let [src-dir (and (not (empty? src-dir))
(File. src-dir))]
(doseq [file (comp/cljs-files-in src-dir)]
(ana/analyze-file (str "file://" (.getAbsolutePath file))))))

(defn repl (defn repl
"Note - repl will reload core.cljs every time, even if supplied old repl-env" "Note - repl will reload core.cljs every time, even if supplied old repl-env"
[repl-env & {:keys [verbose warn-on-undeclared special-fns]}] [repl-env & {:keys [verbose warn-on-undeclared special-fns]}]
Expand All @@ -166,12 +176,12 @@
(let [{:keys [status form]} (read-next-form)] (let [{:keys [status form]} (read-next-form)]
(cond (cond
(= form :cljs/quit) :quit (= form :cljs/quit) :quit

(= status :error) (recur) (= status :error) (recur)

(and (seq? form) (is-special-fn? (first form))) (and (seq? form) (is-special-fn? (first form)))
(do (apply (get special-fns (first form)) repl-env (rest form)) (newline) (recur)) (do (apply (get special-fns (first form)) repl-env (rest form)) (newline) (recur))

:else :else
(do (eval-and-print repl-env env form) (recur))))) (do (eval-and-print repl-env env form) (recur)))))
(-tear-down repl-env)))) (-tear-down repl-env))))
Expand Down

0 comments on commit b5b20fd

Please sign in to comment.