Skip to content

Commit b869406

Browse files
ikappakifogus
authored andcommitted
1 parent 11f8824 commit b869406

File tree

1 file changed

+193
-0
lines changed

1 file changed

+193
-0
lines changed
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
;; Copyright (c) Rich Hickey. All rights reserved.
2+
;; The use and distribution terms for this software are covered by the
3+
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4+
;; which can be found in the file epl-v10.html at the root of this distribution.
5+
;; By using this software in any fashion, you are agreeing to be bound by
6+
;; the terms of this license.
7+
;; You must not remove this notice, or any other, from this software.
8+
9+
(ns cljs.repl.nashorn
10+
(:require [clojure.java.io :as io]
11+
[clojure.string :as string]
12+
[clojure.stacktrace]
13+
[clojure.data.json :as json]
14+
[cljs.analyzer :as ana]
15+
[cljs.env :as env]
16+
[cljs.util :as util]
17+
[cljs.repl :as repl]
18+
[cljs.cli :as cli]
19+
[cljs.compiler :as comp]
20+
[cljs.closure :as closure]
21+
[cljs.stacktrace :as st])
22+
(:import [javax.script ScriptEngine ScriptEngineManager ScriptException ScriptEngineFactory]))
23+
24+
(util/compile-if (Class/forName "jdk.nashorn.api.scripting.NashornException")
25+
(do
26+
(import 'jdk.nashorn.api.scripting.NashornException)
27+
;; Implementation
28+
29+
(defn create-engine
30+
([] (create-engine nil))
31+
([{:keys [code-cache] :or {code-cache true}}]
32+
(let [args (when code-cache ["-pcc"])
33+
factories (.getEngineFactories (ScriptEngineManager.))
34+
factory (get (zipmap (map #(.getEngineName %) factories) factories) "Oracle Nashorn")]
35+
(if-let [engine (if-not (empty? args)
36+
(.getScriptEngine ^ScriptEngineFactory factory (into-array args))
37+
(.getScriptEngine ^ScriptEngineFactory factory))]
38+
(let [context (.getContext engine)]
39+
(.setWriter context *out*)
40+
(.setErrorWriter context *err*)
41+
engine)
42+
(throw (IllegalArgumentException.
43+
"Cannot find the Nashorn script engine, use a JDK version 8 or higher."))))))
44+
45+
(defn eval-str [^ScriptEngine engine ^String s]
46+
(.eval engine s))
47+
48+
(defn eval-resource
49+
"Evaluate a file on the classpath in the engine."
50+
[engine path debug]
51+
(let [r (io/resource path)]
52+
(eval-str engine (slurp r))
53+
(when debug (println "loaded: " path))))
54+
55+
(defn init-engine [engine {:keys [output-dir] :as opts} debug]
56+
(eval-str engine (format "var CLJS_DEBUG = %s;" (boolean debug)))
57+
(eval-str engine (format "var CLJS_OUTPUT_DIR = \"%s\";" output-dir))
58+
(eval-resource engine "goog/base.js" debug)
59+
(eval-resource engine "goog/deps.js" debug)
60+
(eval-resource engine "cljs/bootstrap_nashorn.js" debug)
61+
(eval-str engine
62+
(format "goog.global.CLOSURE_UNCOMPILED_DEFINES = %s;"
63+
(json/write-str (:closure-defines opts))))
64+
engine)
65+
66+
(defn tear-down-engine [engine]
67+
(eval-str engine "nashorn_tear_down();"))
68+
69+
(defn load-js-file [engine file]
70+
(eval-str engine (format "nashorn_load(\"%s\");" file)))
71+
72+
;; Create a minimal build of Clojurescript from the core library.
73+
;; Copied from clj.cljs.repl.node.
74+
(defn bootstrap-repl [engine output-dir opts]
75+
(env/ensure
76+
(let [deps-file ".nashorn_repl_deps.js"
77+
core (io/resource "cljs/core.cljs")
78+
core-js (closure/compile core
79+
(assoc opts :output-file
80+
(closure/src-file->target-file
81+
core (dissoc opts :output-dir))))
82+
deps (closure/add-dependencies opts core-js)]
83+
;; output unoptimized code and the deps file
84+
;; for all compiled namespaces
85+
(apply closure/output-unoptimized
86+
(assoc opts :output-to (.getPath (io/file output-dir deps-file)))
87+
deps)
88+
;; load the deps file so we can goog.require cljs.core etc.
89+
(load-js-file engine deps-file))))
90+
91+
(defn load-ns [engine ns]
92+
(eval-str engine
93+
(format "goog.require(\"%s\");" (comp/munge (first ns)))))
94+
95+
;; Nashorn script stacktraces have a relative path which includes the output-dir
96+
(defn- strip-file-name [^String file-name output-dir]
97+
(let [with-slash (str output-dir "/")]
98+
(if (.startsWith file-name with-slash)
99+
(string/replace-first file-name with-slash "")
100+
file-name)))
101+
102+
(def repl-filename "<cljs repl>")
103+
104+
(defrecord NashornEnv [engine debug]
105+
repl/IReplEnvOptions
106+
(-repl-options [this]
107+
{:output-dir ".cljs_nashorn_repl"
108+
:target :nashorn})
109+
repl/IJavaScriptEnv
110+
(-setup [this {:keys [output-dir bootstrap output-to] :as opts}]
111+
(init-engine engine opts debug)
112+
(let [env (ana/empty-env)]
113+
(if output-to
114+
(load-js-file engine output-to)
115+
(bootstrap-repl engine output-dir opts))
116+
(repl/evaluate-form this env repl-filename
117+
'(.require js/goog "cljs.core"))
118+
;; monkey-patch goog.isProvided_ to suppress useless errors
119+
(repl/evaluate-form this env repl-filename
120+
'(set! js/goog.isProvided_ (fn [ns] false)))
121+
;; monkey-patch goog.require to be more sensible
122+
(repl/evaluate-form this env repl-filename
123+
'(do
124+
(set! *loaded-libs* #{"cljs.core"})
125+
(set! (.-require js/goog)
126+
(fn [name reload]
127+
(when (or (not (contains? *loaded-libs* name)) reload)
128+
(set! *loaded-libs* (conj (or *loaded-libs* #{}) name))
129+
(js/CLOSURE_IMPORT_SCRIPT
130+
(if (some? goog/debugLoader_)
131+
(.getPathFromDeps_ goog/debugLoader_ name)
132+
(goog.object/get (.. js/goog -dependencies_ -nameToPath) name))))))))))
133+
(-evaluate [{engine :engine :as this} filename line js]
134+
(when debug (println "Evaluating: " js))
135+
(try
136+
{:status :success
137+
:value (if-let [r (eval-str engine js)] (.toString r) "")}
138+
(catch ScriptException e
139+
(let [^Throwable root-cause (clojure.stacktrace/root-cause e)]
140+
{:status :exception
141+
:value (.getMessage root-cause)
142+
:stacktrace (NashornException/getScriptStackString root-cause)}))
143+
(catch Throwable e
144+
(let [^Throwable root-cause (clojure.stacktrace/root-cause e)]
145+
{:status :exception
146+
:value (.getMessage root-cause)
147+
:stacktrace
148+
(apply str
149+
(interpose "\n"
150+
(map str
151+
(.getStackTrace root-cause))))}))))
152+
(-load [{engine :engine :as this} ns url]
153+
(load-ns engine ns))
154+
(-tear-down [this]
155+
(tear-down-engine engine))
156+
repl/IParseStacktrace
157+
(-parse-stacktrace [this frames-str ret opts]
158+
(st/parse-stacktrace this frames-str
159+
(assoc ret :ua-product :nashorn) opts))
160+
repl/IParseError
161+
(-parse-error [_ err _]
162+
(update-in err [:stacktrace]
163+
(fn [st]
164+
(string/join "\n" (drop 1 (string/split st #"\n")))))))
165+
166+
(defn repl-env* [{:keys [debug] :as opts}]
167+
(let [engine (create-engine opts)]
168+
(merge
169+
(NashornEnv. engine debug)
170+
opts)))
171+
172+
(defn repl-env
173+
"Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript."
174+
[& {:as opts}]
175+
(repl-env* opts))
176+
177+
;; -------------------------------------------------------------------------
178+
;; Command Line Support
179+
180+
(defn -main [& args]
181+
(apply cli/main repl-env args)))
182+
183+
(do
184+
(defn repl-env* [{:keys [debug] :as opts}]
185+
(throw (ex-info "Nashorn not supported" {:type :repl-error})))
186+
187+
(defn repl-env
188+
"Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript."
189+
[& {:as opts}]
190+
(throw (ex-info "Nashorn not available under this Java runtime" {:type :repl-error})))
191+
192+
(defn -main []
193+
(throw (ex-info "Nashorn not available under this Java runtime" {:type :repl-error})))))

0 commit comments

Comments
 (0)