Skip to content

Commit b5b20fd

Browse files
zachallaunDavid Nolen
authored andcommitted
Support code reflection in the cljs repl.
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.
1 parent 0f73237 commit b5b20fd

9 files changed

Lines changed: 377 additions & 196 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ closure
99
/coresimple.js
1010
/out
1111
.repl
12+
*.swp

samples/repl/src/repl/test.cljs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
;; You must not remove this notice, or any other, from this software.
88

99
(ns repl.test
10-
(:require [clojure.browser.repl :as repl]))
10+
(:require [clojure.browser.repl :as repl]
11+
[clojure.reflect :as reflect]))
1112

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

script/browser-repl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
3+
if [ "$CLOJURESCRIPT_HOME" = "" ]; then
4+
CLOJURESCRIPT_HOME="`dirname $0`/.."
5+
fi
6+
7+
CLJSC_CP=''
8+
for next in lib/*: src/clj: src/cljs: test/cljs; do
9+
CLJSC_CP=$CLJSC_CP$CLOJURESCRIPT_HOME'/'$next
10+
done
11+
12+
java -server -cp $CLJSC_CP clojure.main -e "
13+
(require '[cljs.repl :as r])
14+
(require '[cljs.repl.browser :as b])
15+
(r/repl (b/repl-env))
16+
"

src/clj/cljs/analyzer.clj

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@
290290
([_ sym doc init] {:sym sym :doc doc :init init}))
291291
args (apply pfn form)
292292
sym (:sym args)
293+
sym-meta (meta sym)
293294
tag (-> sym meta :tag)
294295
protocol (-> sym meta :protocol)
295296
dynamic (-> sym meta :dynamic)
@@ -326,6 +327,7 @@
326327
(let [m (assoc (or m {}) :name name)]
327328
(merge m
328329
(when tag {:tag tag})
330+
(when sym-meta sym-meta)
329331
(when dynamic {:dynamic true})
330332
(when-let [line (:line env)]
331333
{:file *cljs-file* :line line})
@@ -533,7 +535,7 @@
533535
(when (and known-num-fields (not= known-num-fields argc))
534536
(warning env
535537
(str "WARNING: Wrong number of args (" argc ") passed to " ctor)))
536-
538+
537539
{:env env :op :new :form form :ctor ctorexpr :args argexprs
538540
:children (into [ctorexpr] argexprs)})))
539541

@@ -677,7 +679,7 @@
677679
:type true
678680
:num-fields (count fields))]
679681
(merge m
680-
{:protocols (-> tsym meta :protocols)}
682+
{:protocols (-> tsym meta :protocols)}
681683
(when-let [line (:line env)]
682684
{:file *cljs-file*
683685
:line line})))))
@@ -935,8 +937,8 @@
935937
:else {:op :constant :env env :form form}))))
936938

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

src/clj/cljs/repl.clj

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
(ns cljs.repl
1010
(:refer-clojure :exclude [load-file])
11+
(:import java.io.File)
1112
(:require [clojure.string :as string]
1213
[clojure.java.io :as io]
1314
[cljs.compiler :as comp]
@@ -149,6 +150,15 @@
149150
'clojure.core/load-file load-file-fn
150151
'load-namespace (fn [repl-env ns] (load-namespace repl-env ns))}))
151152

153+
(defn analyze-source
154+
"Given a source directory, analyzes all .cljs files. Used to populate
155+
cljs.analyzer/namespaces so as to support code reflection."
156+
[src-dir]
157+
(if-let [src-dir (and (not (empty? src-dir))
158+
(File. src-dir))]
159+
(doseq [file (comp/cljs-files-in src-dir)]
160+
(ana/analyze-file (str "file://" (.getAbsolutePath file))))))
161+
152162
(defn repl
153163
"Note - repl will reload core.cljs every time, even if supplied old repl-env"
154164
[repl-env & {:keys [verbose warn-on-undeclared special-fns]}]
@@ -166,12 +176,12 @@
166176
(let [{:keys [status form]} (read-next-form)]
167177
(cond
168178
(= form :cljs/quit) :quit
169-
179+
170180
(= status :error) (recur)
171-
181+
172182
(and (seq? form) (is-special-fn? (first form)))
173183
(do (apply (get special-fns (first form)) repl-env (rest form)) (newline) (recur))
174-
184+
175185
:else
176186
(do (eval-and-print repl-env env form) (recur)))))
177187
(-tear-down repl-env))))

0 commit comments

Comments
 (0)