diff src/clojure/main.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/clojure/main.clj	Sat Aug 21 06:25:44 2010 -0400
     1.3 @@ -0,0 +1,358 @@
     1.4 +;; Copyright (c) Rich Hickey All rights reserved. The use and
     1.5 +;; distribution terms for this software are covered by the Eclipse Public
     1.6 +;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found
     1.7 +;; in the file epl-v10.html at the root of this distribution. By using this
     1.8 +;; software in any fashion, you are agreeing to be bound by the terms of
     1.9 +;; this license. You must not remove this notice, or any other, from this
    1.10 +;; software.
    1.11 +
    1.12 +;; Originally contributed by Stephen C. Gilardi
    1.13 +
    1.14 +(ns ^{:doc "Top-level main function for Clojure REPL and scripts."
    1.15 +       :author "Stephen C. Gilardi and Rich Hickey"}
    1.16 +  clojure.main
    1.17 +  (:refer-clojure :exclude [with-bindings])
    1.18 +  (:import (clojure.lang Compiler Compiler$CompilerException
    1.19 +                         LineNumberingPushbackReader RT)))
    1.20 +
    1.21 +(declare main)
    1.22 +
    1.23 +(defmacro with-bindings
    1.24 +  "Executes body in the context of thread-local bindings for several vars
    1.25 +  that often need to be set!: *ns* *warn-on-reflection* *math-context*
    1.26 +  *print-meta* *print-length* *print-level* *compile-path*
    1.27 +  *command-line-args* *1 *2 *3 *e"
    1.28 +  [& body]
    1.29 +  `(binding [*ns* *ns*
    1.30 +             *warn-on-reflection* *warn-on-reflection*
    1.31 +             *math-context* *math-context*
    1.32 +             *print-meta* *print-meta*
    1.33 +             *print-length* *print-length*
    1.34 +             *print-level* *print-level*
    1.35 +             *compile-path* (System/getProperty "clojure.compile.path" "classes")
    1.36 +             *command-line-args* *command-line-args*
    1.37 +             *assert* *assert*
    1.38 +             *1 nil
    1.39 +             *2 nil
    1.40 +             *3 nil
    1.41 +             *e nil]
    1.42 +     ~@body))
    1.43 +
    1.44 +(defn repl-prompt
    1.45 +  "Default :prompt hook for repl"
    1.46 +  []
    1.47 +  (printf "%s=> " (ns-name *ns*)))
    1.48 +
    1.49 +(defn skip-if-eol
    1.50 +  "If the next character on stream s is a newline, skips it, otherwise
    1.51 +  leaves the stream untouched. Returns :line-start, :stream-end, or :body
    1.52 +  to indicate the relative location of the next character on s. The stream
    1.53 +  must either be an instance of LineNumberingPushbackReader or duplicate
    1.54 +  its behavior of both supporting .unread and collapsing all of CR, LF, and
    1.55 +  CRLF to a single \\newline."
    1.56 +  [s]
    1.57 +  (let [c (.read s)]
    1.58 +    (cond
    1.59 +     (= c (int \newline)) :line-start
    1.60 +     (= c -1) :stream-end
    1.61 +     :else (do (.unread s c) :body))))
    1.62 +
    1.63 +(defn skip-whitespace
    1.64 +  "Skips whitespace characters on stream s. Returns :line-start, :stream-end,
    1.65 +  or :body to indicate the relative location of the next character on s.
    1.66 +  Interprets comma as whitespace and semicolon as comment to end of line.
    1.67 +  Does not interpret #! as comment to end of line because only one
    1.68 +  character of lookahead is available. The stream must either be an
    1.69 +  instance of LineNumberingPushbackReader or duplicate its behavior of both
    1.70 +  supporting .unread and collapsing all of CR, LF, and CRLF to a single
    1.71 +  \\newline."
    1.72 +  [s]
    1.73 +  (loop [c (.read s)]
    1.74 +    (cond
    1.75 +     (= c (int \newline)) :line-start
    1.76 +     (= c -1) :stream-end
    1.77 +     (= c (int \;)) (do (.readLine s) :line-start)
    1.78 +     (or (Character/isWhitespace c) (= c (int \,))) (recur (.read s))
    1.79 +     :else (do (.unread s c) :body))))
    1.80 +
    1.81 +(defn repl-read
    1.82 +  "Default :read hook for repl. Reads from *in* which must either be an
    1.83 +  instance of LineNumberingPushbackReader or duplicate its behavior of both
    1.84 +  supporting .unread and collapsing all of CR, LF, and CRLF into a single
    1.85 +  \\newline. repl-read:
    1.86 +    - skips whitespace, then
    1.87 +      - returns request-prompt on start of line, or
    1.88 +      - returns request-exit on end of stream, or
    1.89 +      - reads an object from the input stream, then
    1.90 +        - skips the next input character if it's end of line, then
    1.91 +        - returns the object."
    1.92 +  [request-prompt request-exit]
    1.93 +  (or ({:line-start request-prompt :stream-end request-exit}
    1.94 +       (skip-whitespace *in*))
    1.95 +      (let [input (read)]
    1.96 +        (skip-if-eol *in*)
    1.97 +        input)))
    1.98 +
    1.99 +(defn- root-cause
   1.100 +  "Returns the initial cause of an exception or error by peeling off all of
   1.101 +  its wrappers"
   1.102 +  [^Throwable throwable]
   1.103 +  (loop [cause throwable]
   1.104 +    (if-let [cause (.getCause cause)]
   1.105 +      (recur cause)
   1.106 +      cause)))
   1.107 +
   1.108 +(defn repl-exception
   1.109 +  "Returns CompilerExceptions in tact, but only the root cause of other
   1.110 +  throwables"
   1.111 +  [throwable]
   1.112 +  (if (instance? Compiler$CompilerException throwable)
   1.113 +    throwable
   1.114 +    (root-cause throwable)))
   1.115 +
   1.116 +(defn repl-caught
   1.117 +  "Default :caught hook for repl"
   1.118 +  [e]
   1.119 +  (.println *err* (repl-exception e)))
   1.120 +
   1.121 +(defn repl
   1.122 +  "Generic, reusable, read-eval-print loop. By default, reads from *in*,
   1.123 +  writes to *out*, and prints exception summaries to *err*. If you use the
   1.124 +  default :read hook, *in* must either be an instance of
   1.125 +  LineNumberingPushbackReader or duplicate its behavior of both supporting
   1.126 +  .unread and collapsing CR, LF, and CRLF into a single \\newline. Options
   1.127 +  are sequential keyword-value pairs. Available options and their defaults:
   1.128 +
   1.129 +     - :init, function of no arguments, initialization hook called with
   1.130 +       bindings for set!-able vars in place.
   1.131 +       default: #()
   1.132 +
   1.133 +     - :need-prompt, function of no arguments, called before each
   1.134 +       read-eval-print except the first, the user will be prompted if it
   1.135 +       returns true.
   1.136 +       default: (if (instance? LineNumberingPushbackReader *in*)
   1.137 +                  #(.atLineStart *in*)
   1.138 +                  #(identity true))
   1.139 +
   1.140 +     - :prompt, function of no arguments, prompts for more input.
   1.141 +       default: repl-prompt
   1.142 +
   1.143 +     - :flush, function of no arguments, flushes output
   1.144 +       default: flush
   1.145 +
   1.146 +     - :read, function of two arguments, reads from *in*:
   1.147 +         - returns its first argument to request a fresh prompt
   1.148 +           - depending on need-prompt, this may cause the repl to prompt
   1.149 +             before reading again
   1.150 +         - returns its second argument to request an exit from the repl
   1.151 +         - else returns the next object read from the input stream
   1.152 +       default: repl-read
   1.153 +
   1.154 +     - :eval, funtion of one argument, returns the evaluation of its
   1.155 +       argument
   1.156 +       default: eval
   1.157 +
   1.158 +     - :print, function of one argument, prints its argument to the output
   1.159 +       default: prn
   1.160 +
   1.161 +     - :caught, function of one argument, a throwable, called when
   1.162 +       read, eval, or print throws an exception or error
   1.163 +       default: repl-caught"
   1.164 +  [& options]
   1.165 +  (let [cl (.getContextClassLoader (Thread/currentThread))]
   1.166 +    (.setContextClassLoader (Thread/currentThread) (clojure.lang.DynamicClassLoader. cl)))
   1.167 +  (let [{:keys [init need-prompt prompt flush read eval print caught]
   1.168 +         :or {init        #()
   1.169 +              need-prompt (if (instance? LineNumberingPushbackReader *in*)
   1.170 +                            #(.atLineStart ^LineNumberingPushbackReader *in*)
   1.171 +                            #(identity true))
   1.172 +              prompt      repl-prompt
   1.173 +              flush       flush
   1.174 +              read        repl-read
   1.175 +              eval        eval
   1.176 +              print       prn
   1.177 +              caught      repl-caught}}
   1.178 +        (apply hash-map options)
   1.179 +        request-prompt (Object.)
   1.180 +        request-exit (Object.)
   1.181 +        read-eval-print
   1.182 +        (fn []
   1.183 +          (try
   1.184 +           (let [input (read request-prompt request-exit)]
   1.185 +             (or (#{request-prompt request-exit} input)
   1.186 +                 (let [value (eval input)]
   1.187 +                   (print value)
   1.188 +                   (set! *3 *2)
   1.189 +                   (set! *2 *1)
   1.190 +                   (set! *1 value))))
   1.191 +           (catch Throwable e
   1.192 +             (caught e)
   1.193 +             (set! *e e))))]
   1.194 +    (with-bindings
   1.195 +     (try
   1.196 +      (init)
   1.197 +      (catch Throwable e
   1.198 +        (caught e)
   1.199 +        (set! *e e)))
   1.200 +     (use '[clojure.repl :only (source apropos dir)])
   1.201 +     (use '[clojure.java.javadoc :only (javadoc)])
   1.202 +     (use '[clojure.pprint :only (pp pprint)])
   1.203 +     (prompt)
   1.204 +     (flush)
   1.205 +     (loop []
   1.206 +       (when-not 
   1.207 +       	 (try (= (read-eval-print) request-exit)
   1.208 +	  (catch Throwable e
   1.209 +	   (caught e)
   1.210 +	   (set! *e e)
   1.211 +	   nil))
   1.212 +         (when (need-prompt)
   1.213 +           (prompt)
   1.214 +           (flush))
   1.215 +         (recur))))))
   1.216 +
   1.217 +(defn load-script
   1.218 +  "Loads Clojure source from a file or resource given its path. Paths
   1.219 +  beginning with @ or @/ are considered relative to classpath."
   1.220 +  [^String path]
   1.221 +  (if (.startsWith path "@")
   1.222 +    (RT/loadResourceScript
   1.223 +     (.substring path (if (.startsWith path "@/") 2 1)))
   1.224 +    (Compiler/loadFile path)))
   1.225 +
   1.226 +(defn- init-opt
   1.227 +  "Load a script"
   1.228 +  [path]
   1.229 +  (load-script path))
   1.230 +
   1.231 +(defn- eval-opt
   1.232 +  "Evals expressions in str, prints each non-nil result using prn"
   1.233 +  [str]
   1.234 +  (let [eof (Object.)
   1.235 +        reader (LineNumberingPushbackReader. (java.io.StringReader. str))]
   1.236 +      (loop [input (read reader false eof)]
   1.237 +        (when-not (= input eof)
   1.238 +          (let [value (eval input)]
   1.239 +            (when-not (nil? value)
   1.240 +              (prn value))
   1.241 +            (recur (read reader false eof)))))))
   1.242 +
   1.243 +(defn- init-dispatch
   1.244 +  "Returns the handler associated with an init opt"
   1.245 +  [opt]
   1.246 +  ({"-i"     init-opt
   1.247 +    "--init" init-opt
   1.248 +    "-e"     eval-opt
   1.249 +    "--eval" eval-opt} opt))
   1.250 +
   1.251 +(defn- initialize
   1.252 +  "Common initialize routine for repl, script, and null opts"
   1.253 +  [args inits]
   1.254 +  (in-ns 'user)
   1.255 +  (set! *command-line-args* args)
   1.256 +  (doseq [[opt arg] inits]
   1.257 +    ((init-dispatch opt) arg)))
   1.258 +
   1.259 +(defn- repl-opt
   1.260 +  "Start a repl with args and inits. Print greeting if no eval options were
   1.261 +  present"
   1.262 +  [[_ & args] inits]
   1.263 +  (when-not (some #(= eval-opt (init-dispatch (first %))) inits)
   1.264 +    (println "Clojure" (clojure-version)))
   1.265 +  (repl :init #(initialize args inits))
   1.266 +  (prn)
   1.267 +  (System/exit 0))
   1.268 +
   1.269 +(defn- script-opt
   1.270 +  "Run a script from a file, resource, or standard in with args and inits"
   1.271 +  [[path & args] inits]
   1.272 +  (with-bindings
   1.273 +    (initialize args inits)
   1.274 +    (if (= path "-")
   1.275 +      (load-reader *in*)
   1.276 +      (load-script path))))
   1.277 +
   1.278 +(defn- null-opt
   1.279 +  "No repl or script opt present, just bind args and run inits"
   1.280 +  [args inits]
   1.281 +  (with-bindings
   1.282 +    (initialize args inits)))
   1.283 +
   1.284 +(defn- help-opt
   1.285 +  "Print help text for main"
   1.286 +  [_ _]
   1.287 +  (println (:doc (meta (var main)))))
   1.288 +
   1.289 +(defn- main-dispatch
   1.290 +  "Returns the handler associated with a main option"
   1.291 +  [opt]
   1.292 +  (or
   1.293 +   ({"-r"     repl-opt
   1.294 +     "--repl" repl-opt
   1.295 +     nil      null-opt
   1.296 +     "-h"     help-opt
   1.297 +     "--help" help-opt
   1.298 +     "-?"     help-opt} opt)
   1.299 +   script-opt))
   1.300 +
   1.301 +(defn- legacy-repl
   1.302 +  "Called by the clojure.lang.Repl.main stub to run a repl with args
   1.303 +  specified the old way"
   1.304 +  [args]
   1.305 +  (println "WARNING: clojure.lang.Repl is deprecated.
   1.306 +Instead, use clojure.main like this:
   1.307 +java -cp clojure.jar clojure.main -i init.clj -r args...")
   1.308 +  (let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
   1.309 +    (repl-opt (concat ["-r"] args) (map vector (repeat "-i") inits))))
   1.310 +
   1.311 +(defn- legacy-script
   1.312 +  "Called by the clojure.lang.Script.main stub to run a script with args
   1.313 +  specified the old way"
   1.314 +  [args]
   1.315 +  (println "WARNING: clojure.lang.Script is deprecated.
   1.316 +Instead, use clojure.main like this:
   1.317 +java -cp clojure.jar clojure.main -i init.clj script.clj args...")
   1.318 +  (let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
   1.319 +    (null-opt args (map vector (repeat "-i") inits))))
   1.320 +
   1.321 +(defn main
   1.322 +  "Usage: java -cp clojure.jar clojure.main [init-opt*] [main-opt] [arg*]
   1.323 +
   1.324 +  With no options or args, runs an interactive Read-Eval-Print Loop
   1.325 +
   1.326 +  init options:
   1.327 +    -i, --init path   Load a file or resource
   1.328 +    -e, --eval string Evaluate expressions in string; print non-nil values
   1.329 +
   1.330 +  main options:
   1.331 +    -r, --repl        Run a repl
   1.332 +    path              Run a script from from a file or resource
   1.333 +    -                 Run a script from standard input
   1.334 +    -h, -?, --help    Print this help message and exit
   1.335 +
   1.336 +  operation:
   1.337 +
   1.338 +    - Establishes thread-local bindings for commonly set!-able vars
   1.339 +    - Enters the user namespace
   1.340 +    - Binds *command-line-args* to a seq of strings containing command line
   1.341 +      args that appear after any main option
   1.342 +    - Runs all init options in order
   1.343 +    - Runs a repl or script if requested
   1.344 +
   1.345 +  The init options may be repeated and mixed freely, but must appear before
   1.346 +  any main option. The appearance of any eval option before running a repl
   1.347 +  suppresses the usual repl greeting message: \"Clojure ~(clojure-version)\".
   1.348 +
   1.349 +  Paths may be absolute or relative in the filesystem or relative to
   1.350 +  classpath. Classpath-relative paths have prefix of @ or @/"
   1.351 +  [& args]
   1.352 +  (try
   1.353 +   (if args
   1.354 +     (loop [[opt arg & more :as args] args inits []]
   1.355 +       (if (init-dispatch opt)
   1.356 +         (recur more (conj inits [opt arg]))
   1.357 +         ((main-dispatch opt) args inits)))
   1.358 +     (repl-opt nil nil))
   1.359 +   (finally 
   1.360 +     (flush))))
   1.361 +