|
12 | 12 | [clojure.stacktrace] |
13 | 13 | [cljs.analyzer :as ana] |
14 | 14 | [cljs.env :as env] |
| 15 | + [cljs.util :as util] |
15 | 16 | [cljs.repl :as repl] |
16 | 17 | [cljs.compiler :as comp] |
17 | 18 | [cljs.closure :as closure]) |
18 | 19 | (:import [java.io File] |
19 | 20 | [javax.script ScriptEngine ScriptEngineManager ScriptException ScriptEngineFactory] |
20 | | - [jdk.nashorn.api.scripting NashornException])) |
21 | | - |
22 | | -;; Nashorn Clojurescript repl binding. |
23 | | -;; |
24 | | -;; Uses the Nashorn load() function to load Javascript files into the script engine. |
25 | | -;; |
26 | | -;; Nashorn's load() function docs: |
27 | | -;; http://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html |
28 | | - |
29 | | -(comment |
30 | | - (ns init-repl-test |
31 | | - (:require [cljs.repl] |
32 | | - [cljs.repl.nashorn])) |
33 | | - |
34 | | - (cljs.repl/repl (cljs.repl.nashorn/repl-env) |
35 | | - :output-dir "resources/public/compiled" |
36 | | - :cache-analysis true) |
37 | | - ) |
38 | | - |
39 | | -;; Implementation |
40 | | - |
41 | | -(defn create-engine |
42 | | - ([] (create-engine nil)) |
43 | | - ([{:keys [code-cache] :or {code-cache true}}] |
44 | | - (let [args (when code-cache ["-pcc"]) |
45 | | - factories (.getEngineFactories (ScriptEngineManager.)) |
46 | | - factory (get (zipmap (map #(.getEngineName %) factories) factories) "Oracle Nashorn")] |
47 | | - (if-let [engine (if-not (empty? args) |
48 | | - (.getScriptEngine ^ScriptEngineFactory factory (into-array args)) |
49 | | - (.getScriptEngine ^ScriptEngineFactory factory))] |
50 | | - (let [context (.getContext engine)] |
51 | | - (.setWriter context *out*) |
52 | | - (.setErrorWriter context *err*) |
53 | | - engine) |
54 | | - (throw (IllegalArgumentException. |
55 | | - "Cannot find the Nashorn script engine, use a JDK version 8 or higher.")))))) |
56 | | - |
57 | | -(defn eval-str [^ScriptEngine engine ^String s] |
58 | | - (.eval engine s)) |
59 | | - |
60 | | -(defn eval-resource |
61 | | - "Evaluate a file on the classpath in the engine." |
62 | | - [engine path debug] |
63 | | - (let [r (io/resource path)] |
64 | | - (eval-str engine (slurp r)) |
65 | | - (when debug (println "loaded: " path)))) |
66 | | - |
67 | | -(defn init-engine [engine output-dir debug] |
68 | | - (eval-resource engine "goog/base.js" debug) |
69 | | - (eval-resource engine "goog/deps.js" debug) |
70 | | - (eval-str engine "var global = this") ; required by React |
71 | | - (eval-str engine |
72 | | - (format |
73 | | - (str "var nashorn_load = function(path) {" |
74 | | - " var outputPath = \"%s\" + \"/\" + path;" |
75 | | - (when debug " print(\"loading: \" + outputPath) ; ") |
76 | | - " load(outputPath);" |
77 | | - "};") |
78 | | - output-dir)) |
79 | | - (eval-str engine |
80 | | - (str "goog.global.CLOSURE_IMPORT_SCRIPT = function(path) {" |
81 | | - " nashorn_load(\"goog/\" + path);" |
82 | | - " return true;" |
83 | | - "};")) |
84 | | - (eval-str engine "goog.global.isProvided_ = function(name) { return false; };") |
85 | | - engine) |
86 | | - |
87 | | -(defn load-js-file [engine file] |
88 | | - (eval-str engine (format "nashorn_load(\"%s\");" file))) |
89 | | - |
90 | | -;; Create a minimal build of Clojurescript from the core library. |
91 | | -;; Copied from clj.cljs.repl.node. |
92 | | -(defn bootstrap-repl [engine output-dir opts] |
93 | | - (env/ensure |
94 | | - (let [deps-file ".nashorn_repl_deps.js" |
95 | | - core (io/resource "cljs/core.cljs") |
96 | | - core-js (closure/compile core |
97 | | - (assoc opts |
98 | | - :output-file (closure/src-file->target-file core))) |
99 | | - deps (closure/add-dependencies opts core-js)] |
100 | | - ;; output unoptimized code and the deps file |
101 | | - ;; for all compiled namespaces |
102 | | - (apply closure/output-unoptimized |
103 | | - (assoc opts :output-to (.getPath (io/file output-dir deps-file))) |
104 | | - deps) |
105 | | - ;; load the deps file so we can goog.require cljs.core etc. |
106 | | - (load-js-file engine deps-file)))) |
107 | | - |
108 | | -(defn load-ns [engine ns] |
109 | | - (eval-str engine |
110 | | - (format "goog.require(\"%s\");" (comp/munge (first ns))))) |
111 | | - |
112 | | -;; Nashorn script stacktraces have a relative path which includes the output-dir |
113 | | -(defn- strip-file-name [^String file-name output-dir] |
114 | | - (let [with-slash (str output-dir "/")] |
115 | | - (if (.startsWith file-name with-slash) |
116 | | - (string/replace-first file-name with-slash "") |
117 | | - file-name))) |
118 | | - |
119 | | -(def repl-filename "<cljs repl>") |
120 | | - |
121 | | -(defrecord NashornEnv [engine debug] |
122 | | - repl/IReplEnvOptions |
123 | | - (-repl-options [this] |
124 | | - {:output-dir ".cljs_nashorn_repl"}) |
125 | | - repl/IJavaScriptEnv |
126 | | - (-setup [this {:keys [output-dir bootstrap output-to] :as opts}] |
127 | | - (init-engine engine output-dir debug) |
128 | | - (let [env (ana/empty-env)] |
129 | | - (if output-to |
130 | | - (load-js-file engine output-to) |
131 | | - (bootstrap-repl engine output-dir opts)) |
132 | | - (repl/evaluate-form this env repl-filename |
133 | | - '(do |
134 | | - (.require js/goog "cljs.core") |
135 | | - (set! *print-newline* false) |
136 | | - (set! *print-fn* js/print))) |
137 | | - ;; monkey-patch goog.isProvided_ to suppress useless errors |
138 | | - (repl/evaluate-form this env repl-filename |
139 | | - '(set! js/goog.isProvided_ (fn [ns] false))) |
140 | | - ;; monkey-patch goog.require to be more sensible |
141 | | - (repl/evaluate-form this env repl-filename |
142 | | - '(do |
143 | | - (set! *loaded-libs* #{"cljs.core"}) |
144 | | - (set! (.-require js/goog) |
145 | | - (fn [name reload] |
146 | | - (when (or (not (contains? *loaded-libs* name)) reload) |
147 | | - (set! *loaded-libs* (conj (or *loaded-libs* #{}) name)) |
148 | | - (js/CLOSURE_IMPORT_SCRIPT |
149 | | - (aget (.. js/goog -dependencies_ -nameToPath) name))))))))) |
150 | | - (-evaluate [{engine :engine :as this} filename line js] |
151 | | - (when debug (println "Evaluating: " js)) |
152 | | - (try |
153 | | - {:status :success |
154 | | - :value (if-let [r (eval-str engine js)] (.toString r) "")} |
155 | | - (catch ScriptException e |
156 | | - (let [^Throwable root-cause (clojure.stacktrace/root-cause e)] |
157 | | - {:status :exception |
158 | | - :value (.getMessage root-cause) |
159 | | - :stacktrace (NashornException/getScriptStackString root-cause)})) |
160 | | - (catch Throwable e |
161 | | - (let [^Throwable root-cause (clojure.stacktrace/root-cause e)] |
162 | | - {:status :exception |
163 | | - :value (.getMessage root-cause) |
164 | | - :stacktrace |
165 | | - (apply str |
166 | | - (interpose "\n" |
167 | | - (map str |
168 | | - (.getStackTrace root-cause))))})))) |
169 | | - (-load [{engine :engine :as this} ns url] |
170 | | - (load-ns engine ns)) |
171 | | - (-tear-down [this]) |
172 | | - repl/IParseStacktrace |
173 | | - (-parse-stacktrace [this frames-str ret {output-dir :output-dir}] |
174 | | - (vec |
175 | | - (map |
176 | | - (fn [frame-str] |
177 | | - (let [frame-str (string/replace frame-str #"\s+at\s+" "") |
178 | | - [function file-and-line] (string/split frame-str #"\s+") |
179 | | - [file-part line-part] (string/split file-and-line #":")] |
180 | | - {:file (string/replace (.substring file-part 1) |
181 | | - (str output-dir File/separator) "") |
182 | | - :function function |
183 | | - :line (Integer/parseInt |
184 | | - (.substring line-part 0 (dec (.length line-part)))) |
185 | | - :column 0})) |
186 | | - (string/split frames-str #"\n")))) |
187 | | - repl/IParseError |
188 | | - (-parse-error [_ err _] |
189 | | - (update-in err [:stacktrace] |
190 | | - (fn [st] |
191 | | - (string/join "\n" (drop 1 (string/split st #"\n"))))))) |
192 | | - |
193 | | -(defn repl-env* [{:keys [debug] :as opts}] |
194 | | - (let [engine (create-engine opts)] |
195 | | - (merge |
196 | | - (NashornEnv. engine debug) |
197 | | - opts))) |
198 | | - |
199 | | -(defn repl-env |
200 | | - "Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript." |
201 | | - [& {:as opts}] |
202 | | - (repl-env* opts)) |
203 | | - |
204 | | -(defn -main [] |
205 | | - (repl/repl (repl-env))) |
| 21 | + [com.google.common.base Throwables])) |
| 22 | + |
| 23 | +(util/compile-if (Class/forName "jdk.nashorn.api.scripting.NashornException") |
| 24 | + (do |
| 25 | + (import 'jdk.nashorn.api.scripting.NashornException) |
| 26 | + ;; Implementation |
| 27 | + |
| 28 | + (defn create-engine |
| 29 | + ([] (create-engine nil)) |
| 30 | + ([{:keys [code-cache] :or {code-cache true}}] |
| 31 | + (let [args (when code-cache ["-pcc"]) |
| 32 | + factories (.getEngineFactories (ScriptEngineManager.)) |
| 33 | + factory (get (zipmap (map #(.getEngineName %) factories) factories) "Oracle Nashorn")] |
| 34 | + (if-let [engine (if-not (empty? args) |
| 35 | + (.getScriptEngine ^ScriptEngineFactory factory (into-array args)) |
| 36 | + (.getScriptEngine ^ScriptEngineFactory factory))] |
| 37 | + (let [context (.getContext engine)] |
| 38 | + (.setWriter context *out*) |
| 39 | + (.setErrorWriter context *err*) |
| 40 | + engine) |
| 41 | + (throw (IllegalArgumentException. |
| 42 | + "Cannot find the Nashorn script engine, use a JDK version 8 or higher.")))))) |
| 43 | + |
| 44 | + (defn eval-str [^ScriptEngine engine ^String s] |
| 45 | + (.eval engine s)) |
| 46 | + |
| 47 | + (defn eval-resource |
| 48 | + "Evaluate a file on the classpath in the engine." |
| 49 | + [engine path debug] |
| 50 | + (let [r (io/resource path)] |
| 51 | + (eval-str engine (slurp r)) |
| 52 | + (when debug (println "loaded: " path)))) |
| 53 | + |
| 54 | + (defn init-engine [engine output-dir debug] |
| 55 | + (eval-resource engine "goog/base.js" debug) |
| 56 | + (eval-resource engine "goog/deps.js" debug) |
| 57 | + (eval-str engine "var global = this") ; required by React |
| 58 | + (eval-str engine |
| 59 | + (format |
| 60 | + (str "var nashorn_load = function(path) {" |
| 61 | + " var outputPath = \"%s\" + \"/\" + path;" |
| 62 | + (when debug " print(\"loading: \" + outputPath) ; ") |
| 63 | + " load(outputPath);" |
| 64 | + "};") |
| 65 | + output-dir)) |
| 66 | + (eval-str engine |
| 67 | + (str "goog.global.CLOSURE_IMPORT_SCRIPT = function(path) {" |
| 68 | + " nashorn_load(\"goog/\" + path);" |
| 69 | + " return true;" |
| 70 | + "};")) |
| 71 | + (eval-str engine "goog.global.isProvided_ = function(name) { return false; };") |
| 72 | + engine) |
| 73 | + |
| 74 | + (defn load-js-file [engine file] |
| 75 | + (eval-str engine (format "nashorn_load(\"%s\");" file))) |
| 76 | + |
| 77 | + ;; Create a minimal build of Clojurescript from the core library. |
| 78 | + ;; Copied from clj.cljs.repl.node. |
| 79 | + (defn bootstrap-repl [engine output-dir opts] |
| 80 | + (env/ensure |
| 81 | + (let [deps-file ".nashorn_repl_deps.js" |
| 82 | + core (io/resource "cljs/core.cljs") |
| 83 | + core-js (closure/compile core |
| 84 | + (assoc opts |
| 85 | + :output-file (closure/src-file->target-file core))) |
| 86 | + deps (closure/add-dependencies opts core-js)] |
| 87 | + ;; output unoptimized code and the deps file |
| 88 | + ;; for all compiled namespaces |
| 89 | + (apply closure/output-unoptimized |
| 90 | + (assoc opts :output-to (.getPath (io/file output-dir deps-file))) |
| 91 | + deps) |
| 92 | + ;; load the deps file so we can goog.require cljs.core etc. |
| 93 | + (load-js-file engine deps-file)))) |
| 94 | + |
| 95 | + (defn load-ns [engine ns] |
| 96 | + (eval-str engine |
| 97 | + (format "goog.require(\"%s\");" (comp/munge (first ns))))) |
| 98 | + |
| 99 | + ;; Nashorn script stacktraces have a relative path which includes the output-dir |
| 100 | + (defn- strip-file-name [^String file-name output-dir] |
| 101 | + (let [with-slash (str output-dir "/")] |
| 102 | + (if (.startsWith file-name with-slash) |
| 103 | + (string/replace-first file-name with-slash "") |
| 104 | + file-name))) |
| 105 | + |
| 106 | + (def repl-filename "<cljs repl>") |
| 107 | + |
| 108 | + (defrecord NashornEnv [engine debug] |
| 109 | + repl/IReplEnvOptions |
| 110 | + (-repl-options [this] |
| 111 | + {:output-dir ".cljs_nashorn_repl"}) |
| 112 | + repl/IJavaScriptEnv |
| 113 | + (-setup [this {:keys [output-dir bootstrap output-to] :as opts}] |
| 114 | + (init-engine engine output-dir debug) |
| 115 | + (let [env (ana/empty-env)] |
| 116 | + (if output-to |
| 117 | + (load-js-file engine output-to) |
| 118 | + (bootstrap-repl engine output-dir opts)) |
| 119 | + (repl/evaluate-form this env repl-filename |
| 120 | + '(do |
| 121 | + (.require js/goog "cljs.core") |
| 122 | + (set! *print-newline* false) |
| 123 | + (set! *print-fn* js/print))) |
| 124 | + ;; monkey-patch goog.isProvided_ to suppress useless errors |
| 125 | + (repl/evaluate-form this env repl-filename |
| 126 | + '(set! js/goog.isProvided_ (fn [ns] false))) |
| 127 | + ;; monkey-patch goog.require to be more sensible |
| 128 | + (repl/evaluate-form this env repl-filename |
| 129 | + '(do |
| 130 | + (set! *loaded-libs* #{"cljs.core"}) |
| 131 | + (set! (.-require js/goog) |
| 132 | + (fn [name reload] |
| 133 | + (when (or (not (contains? *loaded-libs* name)) reload) |
| 134 | + (set! *loaded-libs* (conj (or *loaded-libs* #{}) name)) |
| 135 | + (js/CLOSURE_IMPORT_SCRIPT |
| 136 | + (aget (.. js/goog -dependencies_ -nameToPath) name))))))))) |
| 137 | + (-evaluate [{engine :engine :as this} filename line js] |
| 138 | + (when debug (println "Evaluating: " js)) |
| 139 | + (try |
| 140 | + {:status :success |
| 141 | + :value (if-let [r (eval-str engine js)] (.toString r) "")} |
| 142 | + (catch ScriptException e |
| 143 | + (let [^Throwable root-cause (clojure.stacktrace/root-cause e)] |
| 144 | + {:status :exception |
| 145 | + :value (.getMessage root-cause) |
| 146 | + :stacktrace (NashornException/getScriptStackString root-cause)})) |
| 147 | + (catch Throwable e |
| 148 | + (let [^Throwable root-cause (clojure.stacktrace/root-cause e)] |
| 149 | + {:status :exception |
| 150 | + :value (.getMessage root-cause) |
| 151 | + :stacktrace |
| 152 | + (apply str |
| 153 | + (interpose "\n" |
| 154 | + (map str |
| 155 | + (.getStackTrace root-cause))))})))) |
| 156 | + (-load [{engine :engine :as this} ns url] |
| 157 | + (load-ns engine ns)) |
| 158 | + (-tear-down [this]) |
| 159 | + repl/IParseStacktrace |
| 160 | + (-parse-stacktrace [this frames-str ret {output-dir :output-dir}] |
| 161 | + (vec |
| 162 | + (map |
| 163 | + (fn [frame-str] |
| 164 | + (let [frame-str (string/replace frame-str #"\s+at\s+" "") |
| 165 | + [function file-and-line] (string/split frame-str #"\s+") |
| 166 | + [file-part line-part] (string/split file-and-line #":")] |
| 167 | + {:file (string/replace (.substring file-part 1) |
| 168 | + (str output-dir File/separator) "") |
| 169 | + :function function |
| 170 | + :line (Integer/parseInt |
| 171 | + (.substring line-part 0 (dec (.length line-part)))) |
| 172 | + :column 0})) |
| 173 | + (string/split frames-str #"\n")))) |
| 174 | + repl/IParseError |
| 175 | + (-parse-error [_ err _] |
| 176 | + (update-in err [:stacktrace] |
| 177 | + (fn [st] |
| 178 | + (string/join "\n" (drop 1 (string/split st #"\n"))))))) |
| 179 | + |
| 180 | + (defn repl-env* [{:keys [debug] :as opts}] |
| 181 | + (let [engine (create-engine opts)] |
| 182 | + (merge |
| 183 | + (NashornEnv. engine debug) |
| 184 | + opts))) |
| 185 | + |
| 186 | + (defn repl-env |
| 187 | + "Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript." |
| 188 | + [& {:as opts}] |
| 189 | + (repl-env* opts)) |
| 190 | + |
| 191 | + (defn -main [] |
| 192 | + (repl/repl (repl-env)))) |
| 193 | + (do |
| 194 | + (defn repl-env* [{:keys [debug] :as opts}] |
| 195 | + (throw (ex-info "Nashorn not supported" {:type :repl-error}))) |
| 196 | + |
| 197 | + (defn repl-env |
| 198 | + "Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript." |
| 199 | + [& {:as opts}] |
| 200 | + (throw (ex-info "Nashorn not available under this Java runtime" {:type :repl-error}))) |
| 201 | + |
| 202 | + (defn -main [] |
| 203 | + (throw (ex-info "Nashorn not available under this Java runtime" {:type :repl-error}))))) |
| 204 | + |
| 205 | + |
| 206 | + |
0 commit comments