summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRich Hickey <richhickey@gmail.com>2008-12-29 19:40:17 +0000
committerRich Hickey <richhickey@gmail.com>2008-12-29 19:40:17 +0000
commit49063a9bba42d14f83474802b3240f13e75bcb36 (patch)
tree0a1fb640c7d29169df84c86bed9b22bfa641d437 /src
parentdb598ed1a8d2105f552e1dcdaf2b37b781f015e0 (diff)
unified clojure.main, patch from Stephen C. Gilardi
Diffstat (limited to 'src')
-rw-r--r--src/clj/clojure/main.clj244
-rw-r--r--src/jvm/clojure/lang/LineNumberingPushbackReader.java45
-rw-r--r--src/jvm/clojure/lang/Repl.java106
-rw-r--r--src/jvm/clojure/lang/Script.java68
-rw-r--r--src/jvm/clojure/main.java41
5 files changed, 247 insertions, 257 deletions
diff --git a/src/clj/clojure/main.clj b/src/clj/clojure/main.clj
index 527beb7e..fa869e64 100644
--- a/src/clj/clojure/main.clj
+++ b/src/clj/clojure/main.clj
@@ -1,5 +1,5 @@
;; Copyright (c) Rich Hickey All rights reserved. The use and
-;; distribution terms for this software are covered by the Common Public
+;; 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
@@ -9,12 +9,19 @@
;; Originally contributed by Stephen C. Gilardi
(ns clojure.main
- (:gen-class)
- (:import (clojure.lang Compiler Compiler$CompilerException RT)))
+ (:import (clojure.lang Compiler Compiler$CompilerException
+ LineNumberingPushbackReader RT)))
+
+(declare main)
+
+(def #^{:private true}
+ eof (Object.))
(defmacro with-bindings
"Executes body in the context of thread-local bindings for several vars
- that often need to be set!"
+ that often need to be set!: *ns* *warn-on-reflection* *print-meta*
+ *print-length* *print-level* *compile-path* *command-line-args* *1
+ *2 *3 *e"
[& body]
`(binding [*ns* *ns*
*warn-on-reflection* *warn-on-reflection*
@@ -38,6 +45,38 @@
(recur cause)
cause)))
+(defn skip-if-eol
+ "If the next character on stream s is a newline, skips it, otherwise
+ leaves the stream untouched. Returns :line-start, :stream-end, or :body
+ to indicate the relative location of the next character on s. The stream
+ must either be an instance of LineNumberingPushbackReader or duplicate
+ its behavior of both supporting .unread and collapsing all of CR, LF, and
+ CRLF to a single \\newline."
+ [s]
+ (let [c (.read s)]
+ (cond
+ (= c (int \newline)) :line-start
+ (= c -1) :stream-end
+ :else (do (.unread s c) :body))))
+
+(defn skip-whitespace
+ "Skips whitespace characters on stream s. Returns :line-start, :stream-end,
+ or :body to indicate the relative location of the next character on s.
+ Interprets comma as whitespace and semicolon as comment to end of line.
+ Does not interpret #! as comment to end of line because only one
+ character of lookahead is available. The stream must either be an
+ instance of LineNumberingPushbackReader or duplicate its behavior of both
+ supporting .unread and collapsing all of CR, LF, and CRLF to a single
+ \\newline."
+ [s]
+ (loop [c (.read s)]
+ (cond
+ (= c (int \newline)) :line-start
+ (= c -1) :stream-end
+ (= c (int \;)) (do (.readLine s) :line-start)
+ (or (Character/isWhitespace c) (= c (int \,))) (recur (.read s))
+ :else (do (.unread s c) :body))))
+
(defn repl-exception
"Returns CompilerExceptions in tact, but only the root cause of other
throwables"
@@ -47,21 +86,31 @@
(root-cause throwable)))
(defn repl
- "Generic, reusable, read-eval-print loop. Options are sequential
- keyword-value pairs. Available options and their defaults:
+ "Generic, reusable, read-eval-print loop. Reads from *in* and writes to
+ *out*. *in* must either be an instance of LineNumberingPushbackReader or
+ duplicate its behavior of both supporting .unread and collapsing CR, LF,
+ and CRLF into a single \\newline. Options are sequential keyword-value
+ pairs. Available options and their defaults:
- :init, function of no arguments, initialization hook
default: #()
- - :prompt, function of no arguments, prompts for more input
+ - :need-prompt, function of no arguments, called before each
+ read-eval-print except the first, the user will be prompted if it
+ returns true.
+ default: (if (instance? LineNumberingPushbackReader *in*)
+ #(.atLineStart *in*)
+ #(identity true))
+
+ - :prompt, function of no arguments, prompts for more input.
default: #(printf \"%s=> \" (ns-name *ns*))
- :flush, function of no arguments, flushes output
default: flush
- - :read, function of one argument, returns the next object read from
- the input, or its argument iff the input is exhausted
- default: #(read *in* false %)
+ - :read, function of no arguments, returns the next object read from
+ the input
+ default: read
- :eval, funtion of one argument, returns the evaluation of its
argument
@@ -74,36 +123,40 @@
read, eval, or print throws an exception or error
default: #(.println *err* (repl-exception %))"
[& options]
- (let [{:keys [init prompt flush read eval print caught]
- :or {init #()
- prompt #(printf "%s=> " (ns-name *ns*))
- flush flush
- read #(read *in* false %)
- eval eval
- print prn
- caught #(.println *err* (repl-exception %))}}
- (apply hash-map options)
- eof (Object.)]
+ (let [{:keys [init need-prompt prompt flush read eval print caught]
+ :or {init #()
+ need-prompt (if (instance? LineNumberingPushbackReader *in*)
+ #(.atLineStart *in*)
+ #(identity true))
+ prompt #(printf "%s=> " (ns-name *ns*))
+ flush flush
+ read read
+ eval eval
+ print prn
+ caught #(.println *err* (repl-exception %))}}
+ (apply hash-map options)]
(with-bindings
- (init)
- (loop []
- (prompt)
- (flush)
- (when-not
- (= eof
- (try
- (let [input (read eof)]
- (if (= input eof)
- eof
- (let [value (eval input)]
- (print value)
- (set! *3 *2)
- (set! *2 *1)
- (set! *1 value))))
- (catch Throwable e
- (caught e)
- (set! *e e))))
- (recur))))))
+ (init)
+ (prompt)
+ (flush)
+ (loop [where (skip-whitespace *in*)]
+ (when-not (= where :stream-end)
+ (when (= where :body)
+ (try
+ (let [input (read)]
+ (skip-if-eol *in*)
+ (let [value (eval input)]
+ (print value)
+ (set! *3 *2)
+ (set! *2 *1)
+ (set! *1 value)))
+ (catch Throwable e
+ (caught e)
+ (set! *e e))))
+ (when (need-prompt)
+ (prompt)
+ (flush))
+ (recur (skip-whitespace *in*)))))))
(defn load-script
"Loads Clojure source from a file or resource given its path. Paths
@@ -120,11 +173,15 @@
(load-script path))
(defn- eval-opt
- "Eval expr, print the result if it's not nil"
- [expr]
- (let [value (with-in-str expr (eval (read)))]
- (when-not (nil? value)
- (println value))))
+ "Evals expressions in str, prints each non-nil result using prn"
+ [str]
+ (with-in-str str
+ (loop [input (read *in* false eof)]
+ (when-not (= input eof)
+ (let [value (eval input)]
+ (when-not (nil? value)
+ (prn value))
+ (recur (read *in* false eof)))))))
(defn- init-dispatch
"Returns the handler associated with an init opt"
@@ -149,58 +206,28 @@
(when-not (some #(= eval-opt (init-dispatch (first %))) inits)
(println "Clojure"))
(repl :init #(initialize args inits))
- (prn))
+ (prn)
+ (System/exit 0))
(defn- script-opt
"Run a script from a file, resource, or standard in with args and inits"
[[path & args] inits]
(with-bindings
- (initialize args inits)
- (if (= path "-")
- (load-reader *in*)
- (load-script path))))
+ (initialize args inits)
+ (if (= path "-")
+ (load-reader *in*)
+ (load-script path))))
(defn- null-opt
- "No repl or script opt present, just bind nil and run inits"
+ "No repl or script opt present, just bind args and run inits"
[args inits]
(with-bindings
- (initialize args inits)))
+ (initialize args inits)))
(defn- help-opt
"Print help text for main"
[_ _]
- (println
-"Usage: java -jar clojure.jar [option*] [arg*]
-
- With no options or args, runs an interactive Read-Eval-Print Loop
-
-init options:
-
- -i, --init path Load a file or resource
- -e, --eval expr Evaluate an expression and print its value if non-nil
-
-main options:
-
- -r, --repl Run a repl
- path Run a script from from a file or resource
- - Run a script from standard input
- -h, -?, --help Print this help message and exit
-
-operation:
-
- - Establishes thread-local bindings for commonly set!-able vars
- - Enters the user namespace
- - Binds *command-line-args* to a seq of strings containing command line
- args that appear after any main option
- - Runs all init options in order
- - Runs a repl or script if requested
-
- The init options may be repeated and mixed freely, but must appear before
- any main option. The appearance of any eval option before running a repl
- suppresses the usual repl greeting message: \"Clojure\".
-
- Paths may be absolute or relative in the filesystem or relative to
- classpath. Classpath-relative paths have prefix of @ or @/"))
+ (println (:doc (meta (var main)))))
(defn- main-dispatch
"Returns the handler associated with a main option"
@@ -214,8 +241,50 @@ operation:
"-?" help-opt} opt)
script-opt))
-(defn- -main
- "Flexible main for Clojure"
+(defn- legacy-repl
+ "Called by the clojure.lang.Repl.main stub to run a repl with args
+ specified the old way"
+ [args]
+ (let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
+ (repl-opt (concat ["-r"] args) (map vector (repeat "-i") inits))))
+
+(defn- legacy-script
+ "Called by the clojure.lang.Script.main stub to run a script with args
+ specified the old way"
+ [args]
+ (let [[inits [sep & args]] (split-with (complement #{"--"}) args)]
+ (null-opt args (map vector (repeat "-i") inits))))
+
+(defn main
+ "Usage: java -cp clojure.jar clojure.main [init-opt*] [main-opt] [arg*]
+
+ With no options or args, runs an interactive Read-Eval-Print Loop
+
+ init options:
+ -i, --init path Load a file or resource
+ -e, --eval string Evaluate expressions in string; print non-nil values
+
+ main options:
+ -r, --repl Run a repl
+ path Run a script from from a file or resource
+ - Run a script from standard input
+ -h, -?, --help Print this help message and exit
+
+ operation:
+
+ - Establishes thread-local bindings for commonly set!-able vars
+ - Enters the user namespace
+ - Binds *command-line-args* to a seq of strings containing command line
+ args that appear after any main option
+ - Runs all init options in order
+ - Runs a repl or script if requested
+
+ The init options may be repeated and mixed freely, but must appear before
+ any main option. The appearance of any eval option before running a repl
+ suppresses the usual repl greeting message: \"Clojure\".
+
+ Paths may be absolute or relative in the filesystem or relative to
+ classpath. Classpath-relative paths have prefix of @ or @/"
[& args]
(try
(if args
@@ -225,6 +294,5 @@ operation:
((main-dispatch opt) args inits)))
(repl-opt nil nil))
(catch Exception e
- (.printStackTrace e *err*))
- (finally
- (flush))))
+ (.printStackTrace e *err*)))
+ (flush))
diff --git a/src/jvm/clojure/lang/LineNumberingPushbackReader.java b/src/jvm/clojure/lang/LineNumberingPushbackReader.java
index f722e867..ad81ce9a 100644
--- a/src/jvm/clojure/lang/LineNumberingPushbackReader.java
+++ b/src/jvm/clojure/lang/LineNumberingPushbackReader.java
@@ -18,6 +18,16 @@ import java.io.IOException;
public class LineNumberingPushbackReader extends PushbackReader{
+// This class is a PushbackReader that wraps a LineNumberReader. The code
+// here to handle line terminators only mentions '\n' because
+// LineNumberReader collapses all occurrences of CR, LF, and CRLF into a
+// single '\n'.
+
+private static final int newline = (int) '\n';
+
+private boolean _atLineStart = true;
+private boolean _prev;
+
public LineNumberingPushbackReader(Reader r){
super(new LineNumberReader(r));
}
@@ -26,7 +36,40 @@ public int getLineNumber(){
return ((LineNumberReader) in).getLineNumber() + 1;
}
+public int read() throws IOException{
+ int c = super.read();
+ _prev = _atLineStart;
+ _atLineStart = (c == newline) || (c == -1);
+ return c;
+}
+
+public void unread(int c) throws IOException{
+ super.unread(c);
+ _atLineStart = _prev;
+}
+
public String readLine() throws IOException{
- return ((LineNumberReader)in).readLine();
+ int c = read();
+ String line;
+ switch (c) {
+ case -1:
+ line = null;
+ break;
+ case newline:
+ line = "";
+ break;
+ default:
+ String first = String.valueOf((char) c);
+ String rest = ((LineNumberReader)in).readLine();
+ line = (rest == null) ? first : first + rest;
+ _prev = false;
+ _atLineStart = true;
+ break;
+ }
+ return line;
+}
+
+public boolean atLineStart(){
+ return _atLineStart;
}
}
diff --git a/src/jvm/clojure/lang/Repl.java b/src/jvm/clojure/lang/Repl.java
index 4a9153b2..9abe82aa 100644
--- a/src/jvm/clojure/lang/Repl.java
+++ b/src/jvm/clojure/lang/Repl.java
@@ -12,111 +12,11 @@
package clojure.lang;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
+import clojure.main;
-public class Repl{
-static final Symbol USER = Symbol.create("user");
-static final Symbol CLOJURE = Symbol.create("clojure.core");
-
-static final Var in_ns = RT.var("clojure.core", "in-ns");
-static final Var refer = RT.var("clojure.core", "refer");
-static final Var ns = RT.var("clojure.core", "*ns*");
-static final Var compile_path = RT.var("clojure.core", "*compile-path*");
-static final Var warn_on_reflection = RT.var("clojure.core", "*warn-on-reflection*");
-static final Var print_meta = RT.var("clojure.core", "*print-meta*");
-static final Var print_length = RT.var("clojure.core", "*print-length*");
-static final Var print_level = RT.var("clojure.core", "*print-level*");
-static final Var star1 = RT.var("clojure.core", "*1");
-static final Var star2 = RT.var("clojure.core", "*2");
-static final Var star3 = RT.var("clojure.core", "*3");
-static final Var stare = RT.var("clojure.core", "*e");
+class Repl {
public static void main(String[] args) throws Exception{
-
-// RT.init();
-
- try
- {
- //*ns* must be thread-bound for in-ns to work
- //thread-bind *warn-on-reflection* so it can be set!
- //thread-bind *1,*2,*3,*e so each repl has its own history
- //must have corresponding popThreadBindings in finally clause
- Var.pushThreadBindings(
- RT.map(ns, ns.get(),
- warn_on_reflection, warn_on_reflection.get(),
- print_meta, print_meta.get(),
- print_length, print_length.get(),
- print_level, print_level.get(),
- compile_path, "classes",
- star1, null,
- star2, null,
- star3, null,
- stare, null));
-
- //create and move into the user namespace
- in_ns.invoke(USER);
- refer.invoke(CLOJURE);
-
- //load any supplied files
- for(String file : RT.processCommandLine(args))
- try
- {
- Compiler.loadFile(file);
- }
- catch(Exception e)
- {
- e.printStackTrace((PrintWriter) RT.ERR.get());
- }
-
- //repl IO support
- LineNumberingPushbackReader rdr = new LineNumberingPushbackReader(new InputStreamReader(System.in, RT.UTF8));
- OutputStreamWriter w = (OutputStreamWriter) RT.OUT.get();//new OutputStreamWriter(System.out);
- Object EOF = new Object();
-
- //start the loop
- w.write("Clojure\n");
- for(; ;)
- {
- try
- {
- w.write(Compiler.currentNS().name + "=> ");
- w.flush();
- Object r = LispReader.read(rdr, false, EOF, false);
- if(r == EOF)
- {
- w.write("\n");
- w.flush();
- break;
- }
- Object ret = Compiler.eval(r);
- RT.print(ret, w);
- w.write('\n');
- //w.flush();
- star3.set(star2.get());
- star2.set(star1.get());
- star1.set(ret);
- }
- catch(Throwable e)
- {
- Throwable c = e;
- while(c.getCause() != null)
- c = c.getCause();
- ((PrintWriter) RT.ERR.get()).println(e instanceof Compiler.CompilerException ? e : c);
- stare.set(e);
- }
- }
- }
- catch(Exception e)
- {
- e.printStackTrace((PrintWriter) RT.ERR.get());
- }
- finally
- {
- Var.popThreadBindings();
- }
- System.exit(0);
+ main.legacy_repl(args);
}
-
}
diff --git a/src/jvm/clojure/lang/Script.java b/src/jvm/clojure/lang/Script.java
index e67f85e9..84abbe59 100644
--- a/src/jvm/clojure/lang/Script.java
+++ b/src/jvm/clojure/lang/Script.java
@@ -12,73 +12,11 @@
package clojure.lang;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.IOException;
-import java.util.List;
-import java.util.Arrays;
+import clojure.main;
-/**
- * <code>Script</code> provides a way to run one or more Clojure files
- * from a command line. Example usage:
- * <p>
- * <pre>java -cp clojure.jar script1.clj @/dir/script2.clj -- [arguments]</pre>
- * </p>
- * <p>
- * The example above will:
- * <ol>
- * <li>bind *command-line-args* to a seq containing the (optional) arguments provided
- * after the two dashes (--); this provides a way to provide command-line arguments
- * to your scripts</li>
- * <li>load the Clojure file <i>at the filesystem path</i> <code>script1.clj</code></li>
- * <li>load the Clojure file with the name <code>dir/script2.clj</code> <i>from the
- * current Java classpath</i>. Files to be loaded from the classpath must be prefixed
- * with a '@' character, and must be an "absolute path" to the classpath resource.
- * Note that the "path" will be treated as absolute within the classpath, whether it is
- * prefixed with a slash or not.</li>
- * </ol>
- * </p>
- * <p>
- * Any number of Clojure files can be provided as path arguments; these
- * files are loaded in order, as if via <code>load-file</code>. Filesystem and classpath
- * paths may be provided in any order, and be intermixed as necessary.
- * </p>
- * <p>
- * Once the final script path has been loaded, the java process exits.
- * </p>
- */
-public class Script{
+class Script {
public static void main(String[] args) throws Exception{
- try
- {
- for(String file : RT.processCommandLine(args))
- {
- if (file.startsWith("@"))
- {
- // trim leading slash if it's there -- loadResourceScript prepends its
- // own slash to every name it's given
- RT.loadResourceScript(file.substring(file.startsWith("@/") ? 2 : 1));
- }
- else
- {
- Compiler.loadFile(file);
- }
- }
- }
- finally
- {
- OutputStreamWriter w = (OutputStreamWriter) RT.OUT.get();
- try
- {
- w.flush();
- w.close();
- }
- catch(IOException e)
- {
- e.printStackTrace((PrintWriter)RT.ERR.get());
- }
- }
+ main.legacy_script(args);
}
}
-
diff --git a/src/jvm/clojure/main.java b/src/jvm/clojure/main.java
new file mode 100644
index 00000000..50bc3434
--- /dev/null
+++ b/src/jvm/clojure/main.java
@@ -0,0 +1,41 @@
+/**
+ * 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.
+ **/
+
+package clojure;
+
+import clojure.lang.Symbol;
+import clojure.lang.Namespace;
+import clojure.lang.Var;
+import clojure.lang.RT;
+
+public class main{
+
+final static private Symbol CLOJURE_MAIN = Symbol.create("clojure.main");
+final static private Namespace CLOJURE_MAIN_NS = Namespace.findOrCreate(CLOJURE_MAIN);
+final static private Var REQUIRE = Var.intern(RT.CLOJURE_NS, Symbol.create("require"));
+final static private Var LEGACY_REPL = Var.intern(CLOJURE_MAIN_NS, Symbol.create("legacy-repl"));
+final static private Var LEGACY_SCRIPT = Var.intern(CLOJURE_MAIN_NS, Symbol.create("legacy-script"));
+final static private Var MAIN = Var.intern(CLOJURE_MAIN_NS, Symbol.create("main"));
+
+public static void legacy_repl(String[] args) throws Exception{
+ REQUIRE.invoke(CLOJURE_MAIN);
+ LEGACY_REPL.invoke(RT.seq(args));
+}
+
+public static void legacy_script(String[] args) throws Exception{
+ REQUIRE.invoke(CLOJURE_MAIN);
+ LEGACY_SCRIPT.invoke(RT.seq(args));
+}
+
+public static void main(String[] args) throws Exception{
+ REQUIRE.invoke(CLOJURE_MAIN);
+ MAIN.applyTo(RT.seq(args));
+}
+}