All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cljs.repl.nashorn.clj Maven / Gradle / Ivy

;; Copyright (c) Rich Hickey. All rights reserved.
;; The use and distribution terms for this software are covered by the
;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this distribution.
;; By using this software in any fashion, you are agreeing to be bound by
;; the terms of this license.
;; You must not remove this notice, or any other, from this software.

(ns cljs.repl.nashorn
  (:require [clojure.java.io :as io]
            [clojure.string :as string]
            [clojure.stacktrace]
            [clojure.data.json :as json]
            [cljs.analyzer :as ana]
            [cljs.env :as env]
            [cljs.util :as util]
            [cljs.repl :as repl]
            [cljs.cli :as cli]
            [cljs.compiler :as comp]
            [cljs.closure :as closure]
            [cljs.stacktrace :as st])
  (:import [javax.script ScriptEngine ScriptEngineManager ScriptException ScriptEngineFactory]))

(util/compile-if (Class/forName "jdk.nashorn.api.scripting.NashornException")
  (do
    (import 'jdk.nashorn.api.scripting.NashornException)
    ;; Implementation

    (defn create-engine
      ([] (create-engine nil))
      ([{:keys [code-cache] :or {code-cache true}}]
       (let [args (when code-cache ["-pcc"])
             factories (.getEngineFactories (ScriptEngineManager.))
             factory (get (zipmap (map #(.getEngineName %) factories) factories) "Oracle Nashorn")]
         (if-let [engine (if-not (empty? args)
                           (.getScriptEngine ^ScriptEngineFactory factory (into-array args))
                           (.getScriptEngine ^ScriptEngineFactory factory))]
           (let [context (.getContext engine)]
             (.setWriter context *out*)
             (.setErrorWriter context *err*)
             engine)
           (throw (IllegalArgumentException.
                    "Cannot find the Nashorn script engine, use a JDK version 8 or higher."))))))

    (defn eval-str [^ScriptEngine engine ^String s]
      (.eval engine s))

    (defn eval-resource
      "Evaluate a file on the classpath in the engine."
      [engine path debug]
      (let [r (io/resource path)]
        (eval-str engine (slurp r))
        (when debug (println "loaded: " path))))

    (defn init-engine [engine {:keys [output-dir] :as opts} debug]
      (eval-str engine (format "var CLJS_DEBUG = %s;" (boolean debug)))
      (eval-str engine (format "var CLJS_OUTPUT_DIR = \"%s\";" output-dir))
      (eval-resource engine "goog/base.js" debug)
      (eval-resource engine "goog/deps.js" debug)
      (eval-resource engine "cljs/bootstrap_nashorn.js" debug)
      (eval-str engine
        (format "goog.global.CLOSURE_UNCOMPILED_DEFINES = %s;"
          (json/write-str (:closure-defines opts))))
      engine)

    (defn tear-down-engine [engine]
      (eval-str engine "nashorn_tear_down();"))

    (defn load-js-file [engine file]
      (eval-str engine (format "nashorn_load(\"%s\");" file)))

    ;; Create a minimal build of Clojurescript from the core library.
    ;; Copied from clj.cljs.repl.node.
    (defn bootstrap-repl [engine output-dir opts]
      (env/ensure
        (let [deps-file ".nashorn_repl_deps.js"
              core (io/resource "cljs/core.cljs")
              core-js (closure/compile core
                        (assoc opts :output-file
                          (closure/src-file->target-file
                            core (dissoc opts :output-dir))))
              deps (closure/add-dependencies opts core-js)]
          ;; output unoptimized code and the deps file
          ;; for all compiled namespaces
          (apply closure/output-unoptimized
            (assoc opts :output-to (.getPath (io/file output-dir deps-file)))
            deps)
          ;; load the deps file so we can goog.require cljs.core etc.
          (load-js-file engine deps-file))))

    (defn load-ns [engine ns]
      (eval-str engine
        (format "goog.require(\"%s\");" (comp/munge (first ns)))))

    ;; Nashorn script stacktraces have a relative path which includes the output-dir
    (defn- strip-file-name [^String file-name output-dir]
      (let [with-slash (str output-dir "/")]
        (if (.startsWith file-name with-slash)
          (string/replace-first file-name with-slash "")
          file-name)))

    (def repl-filename "")

    (defrecord NashornEnv [engine debug]
      repl/IReplEnvOptions
      (-repl-options [this]
        {:output-dir ".cljs_nashorn_repl"
         :target :nashorn})
      repl/IJavaScriptEnv
      (-setup [this {:keys [output-dir bootstrap output-to] :as opts}]
        (init-engine engine opts debug)
        (let [env (ana/empty-env)]
          (if output-to
            (load-js-file engine output-to)
            (bootstrap-repl engine output-dir opts))
          (repl/evaluate-form this env repl-filename
            '(.require js/goog "cljs.core"))
          ;; monkey-patch goog.isProvided_ to suppress useless errors
          (repl/evaluate-form this env repl-filename
            '(set! js/goog.isProvided_ (fn [ns] false)))
          ;; monkey-patch goog.require to be more sensible
          (repl/evaluate-form this env repl-filename
            '(do
               (set! *loaded-libs* #{"cljs.core"})
               (set! (.-require js/goog)
                 (fn [name reload]
                   (when (or (not (contains? *loaded-libs* name)) reload)
                     (set! *loaded-libs* (conj (or *loaded-libs* #{}) name))
                     (js/CLOSURE_IMPORT_SCRIPT
                       (goog.object/get (.. js/goog -dependencies_ -nameToPath) name)))))))))
      (-evaluate [{engine :engine :as this} filename line js]
        (when debug (println "Evaluating: " js))
        (try
          {:status :success
           :value (if-let [r (eval-str engine js)] (.toString r) "")}
          (catch ScriptException e
            (let [^Throwable root-cause (clojure.stacktrace/root-cause e)]
              {:status :exception
               :value (.getMessage root-cause)
               :stacktrace (NashornException/getScriptStackString root-cause)}))
          (catch Throwable e
            (let [^Throwable root-cause (clojure.stacktrace/root-cause e)]
              {:status :exception
               :value (.getMessage root-cause)
               :stacktrace
               (apply str
                 (interpose "\n"
                   (map str
                     (.getStackTrace root-cause))))}))))
      (-load [{engine :engine :as this} ns url]
        (load-ns engine ns))
      (-tear-down [this]
        (tear-down-engine engine))
      repl/IParseStacktrace
      (-parse-stacktrace [this frames-str ret opts]
        (st/parse-stacktrace this frames-str
          (assoc ret :ua-product :nashorn) opts))
      repl/IParseError
      (-parse-error [_ err _]
        (update-in err [:stacktrace]
          (fn [st]
            (string/join "\n" (drop 1 (string/split st #"\n")))))))

    (defn repl-env* [{:keys [debug] :as opts}]
      (let [engine (create-engine opts)]
        (merge
          (NashornEnv. engine debug)
          opts)))

    (defn repl-env
      "Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript."
      [& {:as opts}]
      (repl-env* opts))

    ;; -------------------------------------------------------------------------
    ;; Command Line Support

    (defn -main [& args]
      (apply cli/main repl-env args)))

  (do
    (defn repl-env* [{:keys [debug] :as opts}]
      (throw (ex-info "Nashorn not supported" {:type :repl-error})))

    (defn repl-env
      "Create a Nashorn repl-env for use with the repl/repl* method in Clojurescript."
      [& {:as opts}]
      (throw (ex-info "Nashorn not available under this Java runtime" {:type :repl-error})))

    (defn -main []
      (throw (ex-info "Nashorn not available under this Java runtime" {:type :repl-error})))))







© 2015 - 2025 Weber Informatics LLC | Privacy Policy