annotate 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
rev   line source
rlm@10 1 ;; Copyright (c) Rich Hickey All rights reserved. The use and
rlm@10 2 ;; distribution terms for this software are covered by the Eclipse Public
rlm@10 3 ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found
rlm@10 4 ;; in the file epl-v10.html at the root of this distribution. By using this
rlm@10 5 ;; software in any fashion, you are agreeing to be bound by the terms of
rlm@10 6 ;; this license. You must not remove this notice, or any other, from this
rlm@10 7 ;; software.
rlm@10 8
rlm@10 9 ;; Originally contributed by Stephen C. Gilardi
rlm@10 10
rlm@10 11 (ns ^{:doc "Top-level main function for Clojure REPL and scripts."
rlm@10 12 :author "Stephen C. Gilardi and Rich Hickey"}
rlm@10 13 clojure.main
rlm@10 14 (:refer-clojure :exclude [with-bindings])
rlm@10 15 (:import (clojure.lang Compiler Compiler$CompilerException
rlm@10 16 LineNumberingPushbackReader RT)))
rlm@10 17
rlm@10 18 (declare main)
rlm@10 19
rlm@10 20 (defmacro with-bindings
rlm@10 21 "Executes body in the context of thread-local bindings for several vars
rlm@10 22 that often need to be set!: *ns* *warn-on-reflection* *math-context*
rlm@10 23 *print-meta* *print-length* *print-level* *compile-path*
rlm@10 24 *command-line-args* *1 *2 *3 *e"
rlm@10 25 [& body]
rlm@10 26 `(binding [*ns* *ns*
rlm@10 27 *warn-on-reflection* *warn-on-reflection*
rlm@10 28 *math-context* *math-context*
rlm@10 29 *print-meta* *print-meta*
rlm@10 30 *print-length* *print-length*
rlm@10 31 *print-level* *print-level*
rlm@10 32 *compile-path* (System/getProperty "clojure.compile.path" "classes")
rlm@10 33 *command-line-args* *command-line-args*
rlm@10 34 *assert* *assert*
rlm@10 35 *1 nil
rlm@10 36 *2 nil
rlm@10 37 *3 nil
rlm@10 38 *e nil]
rlm@10 39 ~@body))
rlm@10 40
rlm@10 41 (defn repl-prompt
rlm@10 42 "Default :prompt hook for repl"
rlm@10 43 []
rlm@10 44 (printf "%s=> " (ns-name *ns*)))
rlm@10 45
rlm@10 46 (defn skip-if-eol
rlm@10 47 "If the next character on stream s is a newline, skips it, otherwise
rlm@10 48 leaves the stream untouched. Returns :line-start, :stream-end, or :body
rlm@10 49 to indicate the relative location of the next character on s. The stream
rlm@10 50 must either be an instance of LineNumberingPushbackReader or duplicate
rlm@10 51 its behavior of both supporting .unread and collapsing all of CR, LF, and
rlm@10 52 CRLF to a single \\newline."
rlm@10 53 [s]
rlm@10 54 (let [c (.read s)]
rlm@10 55 (cond
rlm@10 56 (= c (int \newline)) :line-start
rlm@10 57 (= c -1) :stream-end
rlm@10 58 :else (do (.unread s c) :body))))
rlm@10 59
rlm@10 60 (defn skip-whitespace
rlm@10 61 "Skips whitespace characters on stream s. Returns :line-start, :stream-end,
rlm@10 62 or :body to indicate the relative location of the next character on s.
rlm@10 63 Interprets comma as whitespace and semicolon as comment to end of line.
rlm@10 64 Does not interpret #! as comment to end of line because only one
rlm@10 65 character of lookahead is available. The stream must either be an
rlm@10 66 instance of LineNumberingPushbackReader or duplicate its behavior of both
rlm@10 67 supporting .unread and collapsing all of CR, LF, and CRLF to a single
rlm@10 68 \\newline."
rlm@10 69 [s]
rlm@10 70 (loop [c (.read s)]
rlm@10 71 (cond
rlm@10 72 (= c (int \newline)) :line-start
rlm@10 73 (= c -1) :stream-end
rlm@10 74 (= c (int \;)) (do (.readLine s) :line-start)
rlm@10 75 (or (Character/isWhitespace c) (= c (int \,))) (recur (.read s))
rlm@10 76 :else (do (.unread s c) :body))))
rlm@10 77
rlm@10 78 (defn repl-read
rlm@10 79 "Default :read hook for repl. Reads from *in* which must either be an
rlm@10 80 instance of LineNumberingPushbackReader or duplicate its behavior of both
rlm@10 81 supporting .unread and collapsing all of CR, LF, and CRLF into a single
rlm@10 82 \\newline. repl-read:
rlm@10 83 - skips whitespace, then
rlm@10 84 - returns request-prompt on start of line, or
rlm@10 85 - returns request-exit on end of stream, or
rlm@10 86 - reads an object from the input stream, then
rlm@10 87 - skips the next input character if it's end of line, then
rlm@10 88 - returns the object."
rlm@10 89 [request-prompt request-exit]
rlm@10 90 (or ({:line-start request-prompt :stream-end request-exit}
rlm@10 91 (skip-whitespace *in*))
rlm@10 92 (let [input (read)]
rlm@10 93 (skip-if-eol *in*)
rlm@10 94 input)))
rlm@10 95
rlm@10 96 (defn- root-cause
rlm@10 97 "Returns the initial cause of an exception or error by peeling off all of
rlm@10 98 its wrappers"
rlm@10 99 [^Throwable throwable]
rlm@10 100 (loop [cause throwable]
rlm@10 101 (if-let [cause (.getCause cause)]
rlm@10 102 (recur cause)
rlm@10 103 cause)))
rlm@10 104
rlm@10 105 (defn repl-exception
rlm@10 106 "Returns CompilerExceptions in tact, but only the root cause of other
rlm@10 107 throwables"
rlm@10 108 [throwable]
rlm@10 109 (if (instance? Compiler$CompilerException throwable)
rlm@10 110 throwable
rlm@10 111 (root-cause throwable)))
rlm@10 112
rlm@10 113 (defn repl-caught
rlm@10 114 "Default :caught hook for repl"
rlm@10 115 [e]
rlm@10 116 (.println *err* (repl-exception e)))
rlm@10 117
rlm@10 118 (defn repl
rlm@10 119 "Generic, reusable, read-eval-print loop. By default, reads from *in*,
rlm@10 120 writes to *out*, and prints exception summaries to *err*. If you use the
rlm@10 121 default :read hook, *in* must either be an instance of
rlm@10 122 LineNumberingPushbackReader or duplicate its behavior of both supporting
rlm@10 123 .unread and collapsing CR, LF, and CRLF into a single \\newline. Options
rlm@10 124 are sequential keyword-value pairs. Available options and their defaults:
rlm@10 125
rlm@10 126 - :init, function of no arguments, initialization hook called with
rlm@10 127 bindings for set!-able vars in place.
rlm@10 128 default: #()
rlm@10 129
rlm@10 130 - :need-prompt, function of no arguments, called before each
rlm@10 131 read-eval-print except the first, the user will be prompted if it
rlm@10 132 returns true.
rlm@10 133 default: (if (instance? LineNumberingPushbackReader *in*)
rlm@10 134 #(.atLineStart *in*)
rlm@10 135 #(identity true))
rlm@10 136
rlm@10 137 - :prompt, function of no arguments, prompts for more input.
rlm@10 138 default: repl-prompt
rlm@10 139
rlm@10 140 - :flush, function of no arguments, flushes output
rlm@10 141 default: flush
rlm@10 142
rlm@10 143 - :read, function of two arguments, reads from *in*:
rlm@10 144 - returns its first argument to request a fresh prompt
rlm@10 145 - depending on need-prompt, this may cause the repl to prompt
rlm@10 146 before reading again
rlm@10 147 - returns its second argument to request an exit from the repl
rlm@10 148 - else returns the next object read from the input stream
rlm@10 149 default: repl-read
rlm@10 150
rlm@10 151 - :eval, funtion of one argument, returns the evaluation of its
rlm@10 152 argument
rlm@10 153 default: eval
rlm@10 154
rlm@10 155 - :print, function of one argument, prints its argument to the output
rlm@10 156 default: prn
rlm@10 157
rlm@10 158 - :caught, function of one argument, a throwable, called when
rlm@10 159 read, eval, or print throws an exception or error
rlm@10 160 default: repl-caught"
rlm@10 161 [& options]
rlm@10 162 (let [cl (.getContextClassLoader (Thread/currentThread))]
rlm@10 163 (.setContextClassLoader (Thread/currentThread) (clojure.lang.DynamicClassLoader. cl)))
rlm@10 164 (let [{:keys [init need-prompt prompt flush read eval print caught]
rlm@10 165 :or {init #()
rlm@10 166 need-prompt (if (instance? LineNumberingPushbackReader *in*)
rlm@10 167 #(.atLineStart ^LineNumberingPushbackReader *in*)
rlm@10 168 #(identity true))
rlm@10 169 prompt repl-prompt
rlm@10 170 flush flush
rlm@10 171 read repl-read
rlm@10 172 eval eval
rlm@10 173 print prn
rlm@10 174 caught repl-caught}}
rlm@10 175 (apply hash-map options)
rlm@10 176 request-prompt (Object.)
rlm@10 177 request-exit (Object.)
rlm@10 178 read-eval-print
rlm@10 179 (fn []
rlm@10 180 (try
rlm@10 181 (let [input (read request-prompt request-exit)]
rlm@10 182 (or (#{request-prompt request-exit} input)
rlm@10 183 (let [value (eval input)]
rlm@10 184 (print value)
rlm@10 185 (set! *3 *2)
rlm@10 186 (set! *2 *1)
rlm@10 187 (set! *1 value))))
rlm@10 188 (catch Throwable e
rlm@10 189 (caught e)
rlm@10 190 (set! *e e))))]
rlm@10 191 (with-bindings
rlm@10 192 (try
rlm@10 193 (init)
rlm@10 194 (catch Throwable e
rlm@10 195 (caught e)
rlm@10 196 (set! *e e)))
rlm@10 197 (use '[clojure.repl :only (source apropos dir)])
rlm@10 198 (use '[clojure.java.javadoc :only (javadoc)])
rlm@10 199 (use '[clojure.pprint :only (pp pprint)])
rlm@10 200 (prompt)
rlm@10 201 (flush)
rlm@10 202 (loop []
rlm@10 203 (when-not
rlm@10 204 (try (= (read-eval-print) request-exit)
rlm@10 205 (catch Throwable e
rlm@10 206 (caught e)
rlm@10 207 (set! *e e)
rlm@10 208 nil))
rlm@10 209 (when (need-prompt)
rlm@10 210 (prompt)
rlm@10 211 (flush))
rlm@10 212 (recur))))))
rlm@10 213
rlm@10 214 (defn load-script
rlm@10 215 "Loads Clojure source from a file or resource given its path. Paths
rlm@10 216 beginning with @ or @/ are considered relative to classpath."
rlm@10 217 [^String path]
rlm@10 218 (if (.startsWith path "@")
rlm@10 219 (RT/loadResourceScript
rlm@10 220 (.substring path (if (.startsWith path "@/") 2 1)))
rlm@10 221 (Compiler/loadFile path)))
rlm@10 222
rlm@10 223 (defn- init-opt
rlm@10 224 "Load a script"
rlm@10 225 [path]
rlm@10 226 (load-script path))
rlm@10 227
rlm@10 228 (defn- eval-opt
rlm@10 229 "Evals expressions in str, prints each non-nil result using prn"
rlm@10 230 [str]
rlm@10 231 (let [eof (Object.)
rlm@10 232 reader (LineNumberingPushbackReader. (java.io.StringReader. str))]
rlm@10 233 (loop [input (read reader false eof)]
rlm@10 234 (when-not (= input eof)
rlm@10 235 (let [value (eval input)]
rlm@10 236 (when-not (nil? value)
rlm@10 237 (prn value))
rlm@10 238 (recur (read reader false eof)))))))
rlm@10 239
rlm@10 240 (defn- init-dispatch
rlm@10 241 "Returns the handler associated with an init opt"
rlm@10 242 [opt]
rlm@10 243 ({"-i" init-opt
rlm@10 244 "--init" init-opt
rlm@10 245 "-e" eval-opt
rlm@10 246 "--eval" eval-opt} opt))
rlm@10 247
rlm@10 248 (defn- initialize
rlm@10 249 "Common initialize routine for repl, script, and null opts"
rlm@10 250 [args inits]
rlm@10 251 (in-ns 'user)
rlm@10 252 (set! *command-line-args* args)
rlm@10 253 (doseq [[opt arg] inits]
rlm@10 254 ((init-dispatch opt) arg)))
rlm@10 255
rlm@10 256 (defn- repl-opt
rlm@10 257 "Start a repl with args and inits. Print greeting if no eval options were
rlm@10 258 present"
rlm@10 259 [[_ & args] inits]
rlm@10 260 (when-not (some #(= eval-opt (init-dispatch (first %))) inits)
rlm@10 261 (println "Clojure" (clojure-version)))
rlm@10 262 (repl :init #(initialize args inits))
rlm@10 263 (prn)
rlm@10 264 (System/exit 0))
rlm@10 265
rlm@10 266 (defn- script-opt
rlm@10 267 "Run a script from a file, resource, or standard in with args and inits"
rlm@10 268 [[path & args] inits]
rlm@10 269 (with-bindings
rlm@10 270 (initialize args inits)
rlm@10 271 (if (= path "-")
rlm@10 272 (load-reader *in*)
rlm@10 273 (load-script path))))
rlm@10 274
rlm@10 275 (defn- null-opt
rlm@10 276 "No repl or script opt present, just bind args and run inits"
rlm@10 277 [args inits]
rlm@10 278 (with-bindings
rlm@10 279 (initialize args inits)))
rlm@10 280
rlm@10 281 (defn- help-opt
rlm@10 282 "Print help text for main"
rlm@10 283 [_ _]
rlm@10 284 (println (:doc (meta (var main)))))
rlm@10 285
rlm@10 286 (defn- main-dispatch
rlm@10 287 "Returns the handler associated with a main option"
rlm@10 288 [opt]
rlm@10 289 (or
rlm@10 290 ({"-r" repl-opt
rlm@10 291 "--repl" repl-opt
rlm@10 292 nil null-opt
rlm@10 293 "-h" help-opt
rlm@10 294 "--help" help-opt
rlm@10 295 "-?" help-opt} opt)
rlm@10 296 script-opt))
rlm@10 297
rlm@10 298 (defn- legacy-repl
rlm@10 299 "Called by the clojure.lang.Repl.main stub to run a repl with args
rlm@10 300 specified the old way"
rlm@10 301 [args]
rlm@10 302 (println "WARNING: clojure.lang.Repl is deprecated.
rlm@10 303 Instead, use clojure.main like this:
rlm@10 304 java -cp clojure.jar clojure.main -i init.clj -r args...")
rlm@10 305 (let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
rlm@10 306 (repl-opt (concat ["-r"] args) (map vector (repeat "-i") inits))))
rlm@10 307
rlm@10 308 (defn- legacy-script
rlm@10 309 "Called by the clojure.lang.Script.main stub to run a script with args
rlm@10 310 specified the old way"
rlm@10 311 [args]
rlm@10 312 (println "WARNING: clojure.lang.Script is deprecated.
rlm@10 313 Instead, use clojure.main like this:
rlm@10 314 java -cp clojure.jar clojure.main -i init.clj script.clj args...")
rlm@10 315 (let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
rlm@10 316 (null-opt args (map vector (repeat "-i") inits))))
rlm@10 317
rlm@10 318 (defn main
rlm@10 319 "Usage: java -cp clojure.jar clojure.main [init-opt*] [main-opt] [arg*]
rlm@10 320
rlm@10 321 With no options or args, runs an interactive Read-Eval-Print Loop
rlm@10 322
rlm@10 323 init options:
rlm@10 324 -i, --init path Load a file or resource
rlm@10 325 -e, --eval string Evaluate expressions in string; print non-nil values
rlm@10 326
rlm@10 327 main options:
rlm@10 328 -r, --repl Run a repl
rlm@10 329 path Run a script from from a file or resource
rlm@10 330 - Run a script from standard input
rlm@10 331 -h, -?, --help Print this help message and exit
rlm@10 332
rlm@10 333 operation:
rlm@10 334
rlm@10 335 - Establishes thread-local bindings for commonly set!-able vars
rlm@10 336 - Enters the user namespace
rlm@10 337 - Binds *command-line-args* to a seq of strings containing command line
rlm@10 338 args that appear after any main option
rlm@10 339 - Runs all init options in order
rlm@10 340 - Runs a repl or script if requested
rlm@10 341
rlm@10 342 The init options may be repeated and mixed freely, but must appear before
rlm@10 343 any main option. The appearance of any eval option before running a repl
rlm@10 344 suppresses the usual repl greeting message: \"Clojure ~(clojure-version)\".
rlm@10 345
rlm@10 346 Paths may be absolute or relative in the filesystem or relative to
rlm@10 347 classpath. Classpath-relative paths have prefix of @ or @/"
rlm@10 348 [& args]
rlm@10 349 (try
rlm@10 350 (if args
rlm@10 351 (loop [[opt arg & more :as args] args inits []]
rlm@10 352 (if (init-dispatch opt)
rlm@10 353 (recur more (conj inits [opt arg]))
rlm@10 354 ((main-dispatch opt) args inits)))
rlm@10 355 (repl-opt nil nil))
rlm@10 356 (finally
rlm@10 357 (flush))))
rlm@10 358