interstar
interstar

Reputation: 27236

How would I write an application which is a Clojure REPL?

I'd like to make an application available in the form of a Clojure REPL. Ie. something the user runs from the terminal and has a full Clojure REPL with a bunch of extra functionality included as Clojure functions.

I want the whole thing to live in a stand-alone JAR and not depend on the user already having Clojure installed or knowing how to install Clojure or import extra libraries. The whole thing should just be there when run.

Is there a straightforward way to make this? Ie. reusing the existing Clojure REPL code?

Upvotes: 3

Views: 201

Answers (1)

cfrick
cfrick

Reputation: 37073

All you have to do is to run the clojure.main/repl in your own main.

The docs explain the entry points you have:

"Generic, reusable, read-eval-print loop. By default, reads from *in*,
writes to *out*, and prints exception summaries to *err*. If you use the
default :read hook, *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 called with
     bindings for set!-able vars in place.
     default: #()
   - :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: repl-prompt
   - :flush, function of no arguments, flushes output
     default: flush
   - :read, function of two arguments, reads from *in*:
       - returns its first argument to request a fresh prompt
         - depending on need-prompt, this may cause the repl to prompt
           before reading again
       - returns its second argument to request an exit from the repl
       - else returns the next object read from the input stream
     default: repl-read
   - :eval, function of one argument, returns the evaluation of its
     argument
     default: eval
   - :print, function of one argument, prints its argument to the output
     default: prn
   - :caught, function of one argument, a throwable, called when
     read, eval, or print throws an exception or error
     default: repl-caught"

Common things to do here:

  • :init: add your own setup for the REPL to use. E.g. use some libraries
  • :prompt: display a differnt "state" than the ns
  • :print: use a pretty printer of some kind

It's worth mentioning, that every program you build (e.g. uberjaring), that contains the clojure.main (e.g. the clojure-$VERSION.jar) can run the REPL from that uberjar so you can run differn main:s from there.

Self-advertisement: If you need further inspiration, these are "modified" REPLs I have done in the past:

  • REST-REPL: Add stateful URL navigation and tooling for consuming JSON/XML APIs
  • WREPL: Use integrant to have a module based composition for the REPL

Upvotes: 4

Related Questions