The enclosed patch updates clojure.main to fix a bug and implement changes discussed here recently. Details below. Feedback welcome.

--Steve

[1] clojure.main no longer calls gen-class. Instead there is now a stub clojure.main class written in Java so it is always available with Clojure's runtime. Each of the entry points provided by class clojure.main ensures that namespace clojure.main is loaded (using "require") and then calls the corresponding fn in namepace clojure.main.

class clojure.main entry points:

- main : unified entry point for clojure. Can run a repl or a script, calls clojure.main/main

- legacy_repl : called by the new, stubbed clojure.lang.Repl, calls clojure.main/legacy-repl

- legacy_script : called by the new, stubbed clojure.lang.Script, calls clojure.main/legacy-script

Because the clojure.main class is always compiled along with the rest of the Clojure runtime, it can be Clojure's unified entry point whether or not ahead-of-time compiled Clojure code is available.

The pre-unification Clojure entry points (clojure.lang.Repl, clojure.lang.Script) are still available and now run via fns in namespace clojure.main. As a result, those fns will get a good workout even if users don't make any changes to how they drive clojure.jar.

[2] clojure.main/repl's handling of (read-line) was different than that of clojure.lang.Repl and not useful. This was because clojure.main/repl *always* reads from *in*, while clojure.lang.Repl created its own LineNumberingPushbackReader on System.in instead. In order to handle (read-line) well while still using *in* exclusively, clojure.main/repl needed to become aware of lines in the input stream rather than treating all whitespace the same. I've implemented that, notably without any change to LispReader.java so loading from files remains exactly the same.

Some visible changes to the repl:

- The repl now only prompts when the input stream is pointing to the start of a new line of input.

- Multiple expressions on a line will only yield a single prompt. Blank lines (or lines containing only whitespace) will be consumed and yield a new prompt.

  user=> 1 2
  1
  2
  user=>
  user=>

- (read-line) will read a fresh line if there is nothing (or only whitespace) left on the current input line, or the (post-whitespace) rest of the input line otherwise.

  user=> (read-line)
  123
  "123"
  user=> (read-line) 456
  "456"

  user=>

In the first example, the repl waited on the line after (read-line) and I typed 123. This should be the most common case an gives the same effect as clojure.lang.Repl used to. (Note that it doesn't have to be a naked call to (read-line) as shown here, (read-line) from within another function down the stack would behave the same way.)

In the second example, (read-line) read 456 from the rest of the input line and printed the string that it read. It then waited for input without prompting on the next (blank) line. This is unfortunate, but I don't see a clean way around it--it's a consequence of code running during "eval" (in this case the read-line) changing the input stream in a way the repl can't predict. (read-line) consumes the newline after 456 on the input line. Whatever is typed on the line after "456" will be evaluated and printed. Just pressing return will produce a prompt (as shown).

- Exceptions caught by the repl now cause the rest of the input line to be skipped.

  user=> 1 2 3 (/ 1 0) 4 5 6
  1
  2
  3
  java.lang.ArithmeticException: Divide by zero (NO_SOURCE_FILE:0)
  user=>

- Comments to end of line (;) are now supported at the repl. This can be convenient when using a session transcript to demonstrate something.

  user=> (use 'clojure.set) ; pull in the set functions
  nil
  user=> 3 4 ; 5 6
  3
  4
  user=>

[3] clojure.lang.LineNumberingPushbackReader extends java.io.PushbackReader, but was not always honoring the latter's contract that when a character is successfully "unread", it will be the next character read. In the case of LNPBR's readLine, the pushed back character was ignored. Most of the time this had no significant effect, but I needed to fix it to support (read-line) from the same stream. These examples show the problem and the fixed behavior:

WAS:

user=> (with-in-str "12\n3" [(read-line) (read)])
["12" 3]
user=> (with-in-str "12\n3" (.unread *in* (.read *in*)) [(read-line) (read)])
["2" 13]
user=>

IS:

user=> (with-in-str "12\n3" [(read-line) (read)])
["12" 3]
user=> (with-in-str "12\n3" (.unread *in* (.read *in*)) [(read-line) (read)])
["12" 3]

[4] The "prompt" hook for clojure.main/repl now takes an argument: a keyword that indicates where on the line the input stream is pointing. The "only prompt on a fresh line" behavior is implemented by only printing the prompt when the "where" argument is :line-start. Anyone using the prompt hook will need to update.

[5] The "-e" and "--eval" options for the command line of clojure.main now evaluate all the expressions present in their string argument and print all non-nil return values using prn.

% java -cp clojure.jar clojure.main -e "(use 'clojure.set) (union #{:a} #{:b})"
#{:b :a}
%

[6] Updated clojure/build.xml with these changes:

- changed the example in description to avoid "java -jar". It now recommends:
    java -cp clojure.jar clojure.main

- Changed names of compile_java and compile_clojure targets to compile-java and compile-clojure

  - Renamed "jar" target to "clojure" -- it builds clojure.jar

- Added a "clojure-slim" target that builds clojure-slim.jar. That jar contains compiled Java code, but no compiled Clojure code. It is functionally equivalent to clojure.jar, but it's much smaller--trading reduced size for increased startup/execution time until all the Clojure code a program uses is JIT compiled into memory by Clojure.

  - Added a legacy "jar" target that (also) builds clojure.jar

- Added an "all" target that builds both clojure.jar and clojure- slim.jar

  - Made "all" the default target

Notes:

- In contrast to most files in Clojure, LineNumberingPushbackReader.java has DOS line endings. If you have trouble applying this patch to that file, that may be the cause. Rich, please consider converting LNPBR.java to use unix line endings.
  - src/jvm/clojure/main.java is a new file produced by the patch


Attachment: unified-main.patch
Description: Binary data


Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to