Permalink
Browse files

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.
  • Loading branch information...
1 parent 0f73237 commit b5b20fdc4fa5c9f8a12e527407a72a7e6957bcd6 @zachallaun zachallaun committed with David Nolen Jul 10, 2012
View
@@ -9,3 +9,4 @@ closure
/coresimple.js
/out
.repl
+*.swp
@@ -7,7 +7,8 @@
;; You must not remove this notice, or any other, from this software.
(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")
View
@@ -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))
+"
@@ -290,6 +290,7 @@
([_ sym doc init] {:sym sym :doc doc :init init}))
args (apply pfn form)
sym (:sym args)
+ sym-meta (meta sym)
tag (-> sym meta :tag)
protocol (-> sym meta :protocol)
dynamic (-> sym meta :dynamic)
@@ -326,6 +327,7 @@
(let [m (assoc (or m {}) :name name)]
(merge m
(when tag {:tag tag})
+ (when sym-meta sym-meta)
(when dynamic {:dynamic true})
(when-let [line (:line env)]
{:file *cljs-file* :line line})
@@ -533,7 +535,7 @@
(when (and known-num-fields (not= known-num-fields argc))
(warning env
(str "WARNING: Wrong number of args (" argc ") passed to " ctor)))
-
+
{:env env :op :new :form form :ctor ctorexpr :args argexprs
:children (into [ctorexpr] argexprs)})))
@@ -677,7 +679,7 @@
:type true
:num-fields (count fields))]
(merge m
- {:protocols (-> tsym meta :protocols)}
+ {:protocols (-> tsym meta :protocols)}
(when-let [line (:line env)]
{:file *cljs-file*
:line line})))))
@@ -935,8 +937,8 @@
:else {:op :constant :env env :form form}))))
(defn analyze-file
- [f]
- (let [res (if (= \/ (first f)) f (io/resource f))]
+ [^String f]
+ (let [res (if (re-find #"^file://" f) (java.net.URL. f) (io/resource f))]
(assert res (str "Can't find " f " in classpath"))
(binding [*cljs-ns* 'cljs.user
*cljs-file* (.getPath ^java.net.URL res)
@@ -950,4 +952,3 @@
(when-not (identical? eof r)
(analyze env r)
(recur (read pbr false eof false))))))))))
-
View
@@ -8,6 +8,7 @@
(ns cljs.repl
(:refer-clojure :exclude [load-file])
+ (:import java.io.File)
(:require [clojure.string :as string]
[clojure.java.io :as io]
[cljs.compiler :as comp]
@@ -149,6 +150,15 @@
'clojure.core/load-file load-file-fn
'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
"Note - repl will reload core.cljs every time, even if supplied old repl-env"
[repl-env & {:keys [verbose warn-on-undeclared special-fns]}]
@@ -166,12 +176,12 @@
(let [{:keys [status form]} (read-next-form)]
(cond
(= form :cljs/quit) :quit
-
+
(= status :error) (recur)
-
+
(and (seq? form) (is-special-fn? (first form)))
(do (apply (get special-fns (first form)) repl-env (rest form)) (newline) (recur))
-
+
:else
(do (eval-and-print repl-env env form) (recur)))))
(-tear-down repl-env))))
Oops, something went wrong. Retry.

0 comments on commit b5b20fd

Please sign in to comment.