Planck is a stand-alone ClojureScript REPL for macOS and Linux.
Planck launches instantly, providing a full-featured REPL environment that is great for experimenting with and learning the ClojureScript language.
Planck is also great for creating scripts in ClojureScript, providing an alternative to Bash for automating tasks.
Planck is not a ClojureScript compiler—it does not emit JavaScript for use with web browsers or other execution environments. For targeting those systems, the ClojureScript compiler along with REPLs like Figwheel are useful.
If you are running Windows, Linux, or macOS, also be sure to check out Lumo, a stand-alone ClojureScript REPL based on Node.js and V8 that is capable of using NPM libraries.
Planck runs on macOS and Linux.
Planck requires no external dependencies. (There is no need for either the Java JVM or Node.js.)
The easiest way to install Planck on macOS is via Homebrew:
brew install planck
On Ubuntu you can use apt-get
:
sudo add-apt-repository ppa:mfikes/planck
sudo apt-get update
sudo apt-get install planck
For other Linux distros, binary downloads are available for some distros. Otherwise, see Building below.
If you'd like to use Homebrew to install the latest unreleased version of Planck (directly from master in the GitHub repository), you can do the following:
brew remove planck
brew install --HEAD planck
To build Planck on Linux or macOS, get a copy of the source tree, install the needed dependencies and run
script/build
This results in a binary being placed in planck-c/build
.
You can then optionally install Planck, the plk
script, and associated man pages via
script/install
If you happen to encounter any issues with Planck, issues are tracked on GitHub at https://github.com/planck-repl/planck/issues.
You launch Planck by typing planck
in a terminal.
You can also launch Planck using the plk
script, which integrates with the clojure
CLI tool to add support for deps.edn
and classpath-affecting options such as -Aalias
.
If you'd like to get help on the command line arguments to Planck, do
planck -h
or
plk -h
The command line arguments to Planck are heavily modeled after the ones available for the Clojure REPL. In particular, you can provide zero or more init-opt arguments, followed by an optional main-opt argument, followed by zero or more arg arguments. Planck also accepts long arguments (preceeded by two dashes), just like the Clojure REPL.
When a Planck starts, it automatically loads any user.cljs
or user.cljc
file present on your classpath. This is an ideal location to place code that is useful for development time.
The file may contain an ns
form to load required namespaces or to establish the namespace for any def
forms that appear in the file. If no namespace is specified, cljs.user
is assumed.
Many of the command line arguments may also supplied via edn, passed via -co
/ –compile-opts
. Any opts passed via -co
/ –compile-opts
are merged onto any base opts specified directly by command-line flags.
Also, note that it is possible to configure certain behaviors via -co
/ –compile-opts
where there doesn't exist a direct command line flag.
Compile opts edn may be specified directly on the command line as in
plk --compile-opts '{:closure-defines {foo.core/x "bar"}}' -r
or by specifying a file, where an optional leading @
means that the file should be read from the classpath as in:
plk --compile-opts @/my-compile-opts.edn -r
Options that may be configured via -co
/ –compile-opts
comprise:
If you don't provide any -i
or -e
options or args to plk
when launching it (or if you explicitly specify -r
or --repl
as the main-opt), Planck will enter an interactive Read-Eval-Print Loop, or REPL.
$ plk
ClojureScript 1.10.520
cljs.user=> ▊
To the left of =>
is the current namespace.
In ClojureScript,
def
and derived forms create vars in the current namespace. In addition, unqualified non-local symbols are resolved to vars in the current namespace.
You can enter forms to be evaluated in the REPL, and any printed output will be displayed in the REPL, followed by the value of the evaluated form: Try evaluating (+ 1 2)
and 3
will be printed. Or, try (println "Hi")
and Hi
will be printed followed by nil
(the value of the println
call).
You can hit return prior to typing a complete form and input will continue on the next line. A #_=>
prompt will be used (padded to line up with the initial =>
) for secondary input lines. Multi-line input can continue this way until a full form is entered:
cljs.user=> (defn square
#_=> [x]
#_=> (* x x))
#'cljs.user/square
You can enter multiple forms on a line and they will be evaluated serially:
cljs.user=> (def x 1) (def y 2) (+ x y)
#'cljs.user/x
#'cljs.user/y
3
At any point in entering a form, Ctrl-C can be hit to discard form entry and start with a fresh prompt.
As you type closing delimiters ()
, ]
, and }
), the cursor will temporarily hop to the matching opening delimiter.
If you copy a previously-entered form from the Planck REPL, and paste it back into Planck, any pasted secondary prompts (
#_=>
), as well as the primary namespace prompt, will be detected and elided. (This makes for a cleaner experience when copying and pasting portions of previously-entered large multi-line forms.)
You can use the up and down arrow keys to navigate through previously-entered lines. The line history includes lines entered in previous Planck sessions, with the last 100 lines saved in the .planck_history
file in your home directory.
You can also type Ctrl-R in order to display a (reverse-i-search)
prompt: In this mode characters typed perform a live incremental search backwards through history. Pressing Ctrl-R again while in this mode finds additional matches. Once you've found the line you are interested in, you can type Ctrl-J to finish the search and further edit the line. Alternatively, you can hit Ctrl-G to cancel the search.
You can use the tab key to auto-complete. Try typing (in
and then hitting the tab key. You will be presented choices like into
, interpose
, inc
, etc. If you then type t
and hit the tab key, you will be presented with a more narrow list of completion candidates. Tab completion works against core names and also against names you introduce. If you do
(def supercalifragilisticexpialidocious "something quite atrocious")
then su
followed by tab will yield subs
, and other choices, including the gem above.
Planck employs the line editing library Linenoise, which provides control characters that you may expect:
You may override the set of control characters by creating a .planck_keymap
file in your home directory that contains a configuration map that looks like:
{:go-to-beginning-of-line :ctrl-q
:delete-previous-word :ctrl-y}
The keys in this map correspond to actions, and the values correspond to the control keys that cause those actions (:ctrl-a
through :ctrl-z
).
The set of actions that you can override comprises: :go-to-beginning-of-line
, :go-to-end-of-line
, :go-back-one-space
, :go-forward-one-space
, :delete-to-end-of-line
, :delete-previous-word
, :delete-backwards
, :clear-screen
, :previous-line
, :next-line
, :reverse-i-search
, :cancel-search
, :finish-search
, :transpose-characters
, and :undo-typing-on-line
.
When you evaluate a form at the REPL, the result is pretty printed using Fipp. This causes output to be wrapped and aligned in a manner that makes it easier to see the structure of the data.
The wrapping honors the width of your terminal, so if you'd like to see a form wrapped differently, resize your terminal and evaluate
*1
to have it re-printed.
If you'd like to turn off pretty printing, just set *pprint-results*
to false
:
(set! planck.repl/*pprint-results* false)
If you evaluate a form that prints lots of output—for example, (range)
—you can type Ctrl-C to interrupt output printing and return to a fresh prompt.
Planck employs various colors for the REPL prompt, results, errors, etc. If you'd prefer to work in a monochrome REPL, pass -t plain
or --theme plain
when starting Planck, or alternatively set the NO_COLOR
environment variable.
Planck attempts to automatically detect if you are running in a light or dark terminal (first checking and honoring the COLORFGBG
environment variable, if set) and picks the light or dark theme, which adjusts the colors accordingly. If this detection fails, you can always override it via -t light
or -t dark
.
-d
or --dumb-terminal
when starting Planck.If you'd prefer to use Planck with the line-editing capabilities offered by GNU Readline, you can use
rlwrap
, (which is also installable viabrew
). When usingrlwrap
, it is necessary to pass-d
toplanck
so thatrlwrap
's terminal controls become active:rlwrap planck -d
.
exit
, quit
, or :cljs/quit
.If you started Planck in verbose mode (by passing -v
or --verbose
) then you will see the JavaScript that is executed for forms that you enter in the REPL, along with other useful diagnostic information.
If you started Planck in quiet mode (by passing -q
or --quiet
) then you will not see any banners from the REPL, just your script output.
REPL specials are, in essence, special forms that exist only in the REPL. (They can't be used in regular ClojureScript code and must be entered as top-level forms.)
Planck supports in-ns
, which will switch you to a new namespace, creating it if it doesn't already exist.
cljs.user=> (in-ns 'bar.core)
nil
bar.core=> ▊
As in Clojure, Planck's in-ns
REPL special accepts any expression, so long as it evaluates to a symbol, so you can do someting like this
cljs.user=> (def my-ns 'foo.core)
#'cljs.user/my-ns
cljs.user=> (in-ns my-ns)
nil
foo.core=> ▊
load-file
REPL special can be used to load ClojureScript source from any file on the filesystem. It causes the REPL to sequentially read and evaluate the set of forms contained in the file.When you launch Planck into REPL mode, a few macros from the planck.repl
namespace are automatically referred into the cljs.user
namespace. These comprise doc
, source
, pst
, apropos
, find-doc
, and dir
.
If you switch to another namespace and find that doc
no longer works, this is because doc
is a macro in the planck.repl
namespace. You can refer it into your current namespace by doing the following:
(require '[planck.repl :refer-macros [doc]])
The same works for source
, pst
, apropos
, find-doc
, and dir
.
:repl-requires
If you would like a different set of symbols referred into cljs.user
upon startup, Planck supports :repl-requires
. For example, if you put the following into a compile-opts.edn
file
{:repl-requires [[planck.repl :refer-macros [apropos dir find-doc doc source pst]]
[cljs.pprint :refer [pprint pp]]]}
then you can launch Planck via
plk --compile-opts compile-opts.edn -r
and then pprint
and pp
will be available.
It is possible to use Planck directly on the command line, evaluating forms directly without entering a interactive REPL. To do this, pass -e
or --eval
.
For example, here is a way to calculate π, based on a popular technique used in the early days with BASIC:
$ planck -e'(* 4 (Math/atan 1))'
3.141592653589793
It is also possible to use multiple evals. This prints a directory listing:
planck -e"(require 'planck.core)" -e'(run! (comp println :path) (planck.core/file-seq "/tmp"))'
Planck can be used to run scripts written in ClojureScript. Planck and JavaScriptCore are fast to start up, and the ClojureScript reader and compiler have been optimized for bootstrapped mode, making this a perfectly feasible approach. It makes for a great alternative for shell scripts written in, say, Bash.
Perhaps the simplest way to execute a script with Planck is to create a file and to use plk
or planck
to run it. For example, say you have foo.cljs
with
(println "Hello World!")
Then you can execute it:
$ plk foo.cljs
Hello World!
What if you'd like to make a standalone executable? The Clojure reader treats #!
as a line comment, supporting the use of shebang scripts. You can change foo.cljs
to look like
#!/usr/bin/env plk
(println "Hello World!")
and then if you first set the executable bit, you can execute the file directly:
$ chmod +x foo.cljs
$ ./foo.cljs
Hello World!
$ plk bar.cljs there
Hello there!
If you'd like to directly specify dependencies in scripts using
#!
see the “Shebang Deps” section of Dependencies.
If you'd like your script to start execution by executing a main function, you can make use of Planck's -m
command-line option, specifying the namespace containing a -main
function. Let's say you have foo/core.cljs
with:
(ns foo.core)
(defn greet [name]
(println (str "Hello " name "!")))
(defn -main [name]
(greet name))
then this works:
$ plk -m foo.core ClojureScript
Hello ClojureScript!
Alternatively, you can make use of cljs.core/*main-cli-fn*
. If this Var is set to a function, and -m
hasn't been specified, then the main function will be called.
This can be especially useful for standalone scripts on Linux, where it is not possible to specify interpreter arguments in the shebang line. Consider this alternative to the above, where this file is saved as foo
:
#!/usr/bin/env plk
(ns foo.core)
(defn greet [name]
(println (str "Hello " name "!")))
(defn -main [name]
(greet name))
(set! *main-cli-fn* -main)
Then this works:
$ ./foo ClojureScript
Hello ClojureScript!
All that needs to be ensured is that the code that set!
s *main-cli-fn*
is called, either via a -e
to require the needed namespace, or by direct execution as in the example above.
When writing scripts, getting input from standard input is quite useful. Clojure has two dynamic vars, core/*in*
and core/*out*
. Whenever you use println
, you're actually printing to whatever core/*out*
is bound to. ClojureScript has its own *out*
which lives in cljs.core
, but it lacks (for obvious reaons) *in*
. Planck, though provides planck.core/*in*
which lets you interact with standard input.
So in order to demonstrate this, here is a script that simply copies each line it receives on standard input to standard out:
#!/usr/bin/env plk
(ns example.echo
(:require [planck.core :as core]))
(doseq [l (core/line-seq core/*in*)]
(println l))
If you'd like to gain access to the command line arguments passed to your script, they are available in cljs.core/*command-line-args*
.
With bar.cljs
:
(ns bar.core)
(println (str "Hello " (first *command-line-args*) "!"))
You can use the clojure.tools.cli
library to parse command line options. Here is the intro example being used with Planck:
$ plk -Sdeps '{:deps {org.clojure/tools.cli {:mvn/version "0.3.5"}}}' -m my.program -vvvp8080 foo --help --invalid-opt
{:options {:port 8080, :verbosity 3, :help true},
:arguments ["foo"],
:summary " -p, --port PORT 80 Port number\n -v Verbosity level\n -h, --help",
:errors ["Unknown option: \"--invalid-opt\""]}
where src/my/program.cljs
contains:
(ns my.program
(:require [clojure.tools.cli :refer [parse-opts]]
[fipp.edn :refer [pprint]]))
(def cli-options
;; An option with a required argument
[["-p" "--port PORT" "Port number"
:default 80
:parse-fn #(js/parseInt %)
:validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]]
;; A non-idempotent option
["-v" nil "Verbosity level"
:id :verbosity
:default 0
:assoc-fn (fn [m k _] (update-in m [k] inc))]
;; A boolean option defaulting to nil
["-h" "--help"]])
(defn -main [& args]
(pprint (parse-opts args cli-options)))
Environment variables are accessible via planck.environ/env
. For example, the following script will print the HOME
environment variable:
(ns baz.core
(:require [planck.environ :refer [env]]))
(println (:home env))
The planck.shell
namespace provides functions for interacting with the shell. Commands can be executed by running the sh
function as seen in the following example:
#!/usr/bin/env plk
(ns foo.core
(:require [planck.shell :refer [sh]]))
(defn list-files [dir]
(println "listing files in" dir)
(println (sh "ls" "-l" dir)))
(list-files (first *command-line-args*))
If you run a script that starts a timer, or launches an asynchronous shell interaction, the script will continue running so long as there are pending timers or shell activities.
For example, this script will run for 5 seconds, print "done" and then terminate:
(def x (js/setTimeout #(println "hi") 1e6))
(js/setTimeout #(println "done") 5000)
(js/clearTimeout x)
Similarly, this script will wait for 3 seconds, print :done
and terminate:
(require '[planck.shell :refer [sh-async]])
(sh-async "sleep" "3" #(prn :done))
In order to make Planck more useful for doing actual work and interacting with your computer and the outside world, some native I/O facilities have been added to the JavaScriptCore instance running in Planck and these have been exposed via a few namespaces. To make things easier to use, the functions in these namespaces adhere fairly closely to existing Clojure / ClojureScript analogs.
The code for these namespaces is included directly in the Planck binary, so they are always available—you just need to require
them.
These namespaces comprise
planck.core
planck.repl
planck.io
planck.shell
planck.http
planck.environ
To explore these namespaces, you can evaluate (dir planck.core)
, for example, to see the symbols in planck.core
, and then use the doc
macro to see the docs for any of the symbols.
This namespace includes basic I/O capabilities like slurp
, spit
and read-line
. The I/O facilities are expressed in protocols defined in planck.core
modeled after those in Clojure, like IReader
, IOutputStream
, etc., and these capabilities cooperate with facilities defined in planck.io
.
Planck core also hosts some dynamic vars of interest like *in*
and *out*
.
The planck.core/file-seq
function imitates clojure.core/file-seq
.
If you need to prompt for a password, take a look at planck.core/read-password
.
The planck.core
namespace defines dynamic functions like resolve
, ns-resolve
, and intern
.
Additionally, planck.core/exit
is a function that takes an integer exit-value
argument, so you can cause a Planck script to exit with any desired Unix exit value.
This namespace includes a few macros that are useful when working at the REPL, such as doc
, dir
, source
, etc.
This namespace defines a lot of the IOFactory
machinery, imitating clojure.java.io
.
Additionally, filesystem facilities like file
, delete-file
, and file-attributes
are available.
This namespace imitates clojure.shell
, and defining the sh
function and with-sh-dir
/ with-sh-env
macros that can be used to execute external command-line functions.
With this escape hatch, you can do nearly anything: Move files to remote hosts using scp
, etc.
This namespace provides facilities for interacting with HTTP servers. For example:
(planck.http/get "https://planck-repl.org")
will fetch the main page of the Planck website, returning the status code, headers, and body in a map structure.
This namespace provided access to environment variables, modeled after Environ. For example
(:home planck.environ/env)
will access the HOME
environment variable.
When launching Planck using plk
, you can specify a vector of source directories in deps.edn
:
{:paths ["src" "test"]}
With this, you can put this code in src/foo/core.cljs
:
(ns foo.core)
(defn square
[x]
(* x x))
Then, if you launch Planck via plk
$ plk
you can then load the code in foo.core
by doing
cljs.user=> (require 'foo.core)
If you subsequently edit src/foo/core.cljs
, say, to define a new function, or to change existing functions, you can reload that code by adding the :reload
flag:
cljs.user=> (require 'foo.core :reload)
Alternatively, when launching Planck you can use the -c
or --classpath
option, or the PLANCK_CLASSPATH
environment variable, to specify a colon-delimited list of source directories and JARs to search in when loading code using require
and require-macros
. You can also use -D
or --dependencies
provide a comma separated list of SYM:VERSION
, indicating libraries to be loaded from the local Maven repository. (See the Dependencies section of this guide.)
Using -c
, you can specify "src"
and "test"
as source directories via
planck -c src:test
If you define macros in bootstrap ClojureScript (which is the mode that Planck runs in), the macros must be written in ClojureScript (as opposed to Clojure, as is done with regular ClojureScript).
Even though the macros are defined in ClojureScript, they are defined in *.clj
files. You can, if you wish, also define macros in *.cljc
files, but when they are processed, the :cljs
branch of reader conditionals will be used.
When writing macros for self-hosted ClojureScript, they must abide the same rules that apply to all ClojureScript code. In particular, this means a macro cannot call another macro defined in the same compilation stage: If a macro calls another macro during expansion, then one approach is to define the called macro in a “higher” namespace (possibly arranged in a tower). On the other hand, if a macro simply expands to a call to another macro defined in the same namespace, then the compilation staging rules are satisfied.
If an exception is thrown, you may see a stack trace. (If not, you can use pst
to print the stack trace for an exception.) When trace lines correspond to code that originated from files, the line numbers are mapped from the executed JavaScript back to the original ClojureScript.
Planck supports tagged literals. For an overview of this feature see the Tagged Literals documentation.
Planck searches for data_readers.cljc
files at the root of the classpath, and the values of the data reader maps are associated with vars that must defined in ClojureScript.
For example, lets say a data_readers.cljc
file contains:
{foo/bar my.project.foo/bar}
Then, in order to parse #foo/bar [1 2 3]
, #'my.project.foo/bar
must be defined in ClojureScript. (This differs from JVM ClojureScript, where this must be defined in Clojure.) This could be accomplished by defining a namespace like the following that is loaded into Planck before expressions involving #foo/bar
are read.
(ns my.project.foo)
(defn bar [x] ,,,)
Note that, in either case (JVM ClojureScript, or self-hosted ClojureScript), the reader function bar
above must return code that is compilable in ClojureScript.
It is possible to make use of the cljs.js
namespace within Planck. But, since Planck is built with the :dump-core
ClojureScript compiler option set to false
, calls to the 0-arity version of cljs.js/empty-state
will produce a state atom which lacks cljs.core
analysis metadata. To produce a populated compiler state atom, you can make use of planck.core/init-empty-state
:
(require 'cljs.js 'planck.core)
(def st (cljs.js/empty-state planck.core/init-empty-state))
(cljs.js/eval-str st "(map inc [1 2 3])" nil
{:eval cljs.js/js-eval :context :expr} identity)
It is possible to write and execute unit tests using Planck.
Let's say you have a namespace with a function you'd like to test.
(ns foo.core)
(defn square
[x]
(+ x x))
You can test foo.core
by writing a test namespace:
(ns foo.core-test
(:require [cljs.test :refer-macros [deftest is]]
[foo.core]))
(deftest test-square
(is (= 0 (foo.core/square 0)))
(is (= 9 (foo.core/square 3))))
Then you can run the unit tests using run-tests
:
cljs.user=> (cljs.test/run-tests 'foo.core-test)
Testing foo.core-test
FAIL in (test-square) (:5:1)
expected: (= 9 (foo.core/square 3))
actual: (not (= 9 6))
Ran 1 tests containing 2 assertions.
1 failures, 0 errors.
nil
If you fix the definition of square
to make use of *
instead of +
, then you can run the tests again and see thing they pass:
cljs.user=> (cljs.test/run-tests 'foo.core-test)
Testing foo.core-test
Ran 1 tests containing 2 assertions.
0 failures, 0 errors.
nil
The cljs.test
library provides a mechanism for writing custom asserts that can be used with the is
macro—in the form of an assert-expr
defmulti
.
To define your own assert, simply provide a defmethod
for cljs.test$macros/assert-expr
. Here's an example:
If you evaluate (is (char? nil))
you will get a cryptic error report:
ERROR in () (isUnicodeChar@file:269:12)
expected: (char? nil)
actual: #object[TypeError TypeError: null is not an object (evaluating 'ch.length')]
You can define a custom assert for this situation:
(defmethod cljs.test$macros/assert-expr 'char?
[menv msg form]
(let [arg (second form)
result (and (not (nil? arg))
(char? arg))]
`(do
(if ~result
(cljs.test/do-report
{:type :pass
:message ~msg
:expected '~form
:actual (list '~'char? ~arg)})
(cljs.test/do-report
{:type :fail
:message ~msg
:expected '~form
:actual (list '~'not
(list '~'char? ~arg))}))
~result)))
With this, (is (char? nil))
yields:
FAIL in () (eval@[native code]:NaN:NaN)
expected: (char? nil)
actual: (not (char? nil))
Source executed via Planck can depend on other bootstrapped-compatible libraries. To do so, the library must be on Planck's classpath, available either as source on an accessible filesystem, or bundled in a JAR.
Planck can consume conventional JARs meant for use with ClojureScript obtained from Clojars or elsewhere.
Note that, since Planck employs bootstrapped ClojureScript, not all regular ClojureScript libraries will work with Planck. In particular, libraries that employ macros that either rely on Java interop, or call macros in the same compilation stage cannot work. But libraries that employ straightforward macros that expand to ClojureScript work fine.
If you use the plk
script (instead of launching planck
directly), it will delegate to the clojure
tool for dependency management. This means you can use deps.edn
to specify source paths, dependency JARS, aliases, etc. just as you can with clj
or clojure
.
If you do
plk -h
you will see that the plk
script accepts the same arguments as the clj
/ clojure
tools, along with the additional arguments supported by planck
.
So for example, to put Andare 0.9.0 and test.check
0.10.0-alpha3 on your classpath (automatically downloading them if necessary), just place a deps.edn
like the following in the directory where you launch plk
:
{:deps {andare {:mvn/version "0.9.0"}
org.clojure/test.check {:mvn/version "0.10.0-alpha3"}}}
If you'd like to specify deps.edn
dependencies directly within a #!
script, this is possible by making use of bash
and exec
, as is done in the following example:
#!/usr/bin/env bash
"exec" "plk" "-Sdeps" "{:deps {andare {:mvn/version \"0.9.0\"}}}" "-Ksf" "$0" "$@"
(require '[clojure.core.async :refer [chan go <! >!]])
(def c (chan))
(go (prn (<! c)))
(go (>! c *command-line-args*))
This stand-alone script specifies its own dependencies (via the -Sdeps
dep-opt), while also passing init-opts to plk
(prior to "$0"
, which is the path to the script, and thus the main-opt), along with , any command-line args following the main-opt via "$@"
.
Also note that the script both a valid Bash script (as the
exec
causes the script to terminate prior to any ClojureScript text being parsed), and a valid ClojureScript file (all of the values on"exec"
line are ClojureScript strings and thus harmless values preceding therequire
form).
The Boot build tool can be used to generate a classpath file that can be fed into Planck. A side effect of creating such a file is that Boot will also download the specified dependencies (see "Downloading Deps" below).
Let's look at an example that puts the cljs-time library on the classpath. On a system with boot
installed, run the following:
boot -d com.andrewmcveigh/cljs-time:"0.5.2" with-cp -w --file .classpath
The list of dependencies (one in this case) is written to .classpath
. You can use this file in invocations of planck
like so:
planck -c `cat .classpath`
Planck's classpath can be directly specified by providing a colon-separated list of directories and/or JARs via the -c
/ --classpath
argument, or by the PLANCK_CLASSPATH
environment variable. For example,
planck -c src:/path/to/foo.jar:some-lib/src
will cause Planck to search in the src
directory first, then in foo.jar
next, and finally some-lib/src
for files when processing require
, require-macros
, import
, and ns
forms.
The -D
/ --dependencies
option can be used to specify coordinates for JARs installed in your local .m2
repo: You can provide a comma separated list of SYM:VERSION
, and paths to these JARs will be appended to your classpath.
For example,
planck -c src -D andare:0.9.0,org.clojure/test.check:0.10.0-alpha3
will expand to a classpath that specifies src
followed by the paths to the Andare and test.check
dependencies in your local .m2
repository.
In order to use an explicitly-specified path to a Maven repository, you can additionally include -L
or --local-repo
, specifying the repository path.
While planck
can consume JARs from your local .m2
repo, it doesn't take care of downloading them. (An alternative is to use plk
and deps.edn
, which delegates to clojure
for deps download.)
An easy way to quickly download dependencies is to use boot
with its -d
option. For example, executing this will ensure the dependencies specified above are installed:
boot -d andare:0.9.0 -d org.clojure/test.check:0.10.0-alpha3
Planck ships with many of the deps that are available to conventional ClojureScript code. In particular this includes the majority of the Google Closure Library as well as these namespaces:
cljs.test
clojure.core.reducers
clojure.data
clojure.template
clojure.string
clojure.set
clojure.walk
clojure.zip
In addition, Planck ships with these libraries:
Note that bundled dependencies, which includes the core ClojureScript compiler namespaces, are loaded in preference to dependencies specified via deps.edn
, -c
/ --classpath
, -D
/ --dependencies
, or PLANCK_CLASSPATH
.
A consequence of this (as well as the fact that nearly all of the code that ships with Planck is AOT-compiled), means that Planck works with a fixed version of ClojureScript. (It is not possible to update the ClojureScript version by providing a path to a newer version via deps.edn
, -c
/ --classpath
, -D
/ --dependencies
, or PLANCK_CLASSPATH
.)
It is possible to use foreign libraries with Planck.
“Foreign” libraries are implemented in a language that is not ClojureScript. (In other words, JavaScript!)
Foreign libs are specified using a :foreign-libs
specification which indicates the synthetic namespace, the JavaScript file that needs to be loaded, any global exports, and an indication of any other dependencies that need to be loaded for each foreign lib.
The :foreign-libs
specification can be passed via -co
/ –compile-opts
, or can be specified in any deps.cljs
files on the classpath (including those embedded in a JAR file).
If specified for a given foreign lib, Planck will load :file-min
in preference to :file
if Planck is launched with simple
optimizations (via -O simple
or –optimizations simple
).
One easy way to make use of foreign libs packaged in this manner is via the excellent CLJSJS project. While many of the libraries packaged by CLJSJS cannot work with Planck because they either require a browser environment or Node, some utility libraries work just fine.
Here's an example: Let's say you want to use the long.js library.
If you launch Planck wtih
plk -Sdeps '{:deps {cljsjs/long {:mvn/version "3.0.3-1"}}}'
you can (require 'cljsjs.long)
to load the library and then proceed to use it using ClojureScript's JavaScript interop capabilities:
cljs.user=> (require 'cljsjs.long)
nil
cljs.user=> (str (js/Long. 0xFFFFFFFF 0x7FFFFFFF))
"9223372036854775807"
cljs.user=> (str js/Long.MAX_UNSIGNED_VALUE)
"18446744073709551615"
Planck can be very fast, especially since it depends on JavaScriptCore, which is great for minimizing startup latency. This makes it very useable for quickly starting up a REPL or running simple scripts.
Planck executes scripts by compiling the ClojureScript to JavaScript for execution in JavaScriptCore. This is done dynamically and is usually very fast.
But, if you have scripts that don't change frequently, or are making use of large libraries, and the ClojureScript is expensive to compile, it may make sense to save the resulting JavaScript so that subsequent script execution can bypass compilation.
This means that if you re-run Planck and use namespaces that have been cached, the JavaScript representing those namespaces is simply loaded into JavaScriptCore.
To enable compilation caching in Planck, you simply need to pass the -K
or --auto-cache
option. This will automatically create a .planck_cache
directory in the current working directory. (Alternatively, you can specify an existing directory into which Planck can write cache files using -k
or --cache
.)
Here's an example: Let's say you have a foo.cljs script file that you run via
planck foo.cljs
Instead, you can instruct Planck to cache:
planck -K foo.cljs
The first time you run Planck this way, it will save the results of compilation into .planck_cache
. Then subsequent executions with -K
will use the cached results instead.
In addition to caching compiled JavaScript, the associated analysis metadata and source mapping information is cached. This makes it possible for Planck to know the symbols in a namespace, their docstrings, etc., without having to consult the original source. And, if an exception occurs, the source mapping info is used in forming stack traces. For additional speed, this cached info is written using Transit.
This caching works for
cljs.user
namespace, for caching purposes)The caching mechanism works whether your are running planck
to execute a script, or if you are invoking require
in an interactive REPL session.
Planck uses a (naïve) file timestamp mechanism to know if cache files are stale, and it additionally looks at comments like the following
// Compiled by ClojureScript 1.9.946 {:static-fns true, :elide-asserts true}
in the compiled JavaScript to see if the files are applicable. If a file can’t be used, it is replaced with an updated copy.
Planck's cache invalidation strategy is naïve because it doesn’t attempt to do sophisticated dependency graph analysis. So, there may be corner cases where you have to manually delete the contents of your cache directory, especially if the cached code involved macroexpansion and macro definitions have changed, for example.
Planck's caching mechanism is compatible with the static function dispatch and assert mechanisms described below. In short, if you have cached code that does not match the current settings for static functions or asserts, then it will not be eligible for loading and will be replaced with freshly-compiled JavaScript as needed.
Planck supports the :static-fns
ClojureScript compiler option via the -s
or --static-fns
command-line flag.
With :static-fns
disabled (the default), the generated JavaScript for (foo 1 2)
will look like
cljs.user.foo.call(null,1,2)
and with it enabled you will get
cljs.user.foo(1,2)
David Nolen commented on the differences
It's an option mostly because of REPL development to allow for redefinition. For example if
:static-fns
true
we statically dispatch to specific fn arities—but what if you redef to a different set of arities? What if you change the var to store a deftype instance that implementsIFn
. That kind of thing.
So for compilation
:static-fns
can nearly always betrue
, but for the REPL it's not desirable.
In short, enabling it can lead to performance benefits, being more amenable to inlining, etc., but usually you want to leave it turned off during dev.
Even with :static-fns
enabled, unknown higher-order functions will be called using the call
construct described above. You can additionally pass the -f
or --fn-invoke-direct
command-line flag to enable the :fn-invoke-direct
ClojureScript compiler option, which causes such functions to instead be called directly.
An illustrative example is the code emitted for (defn f [g x] (g x))
. With :static-fns
disabled, g.call(null,x)
is emitted for the function body. With :static-fns
enabled, the emitted code will test determining if g
is associated with a single-arity static dispatch implementation, and if so, call it, otherwise falling back to g.call(null,x)
. But, with :fn-invoke-direct
, the fallback branch will instead involve a direct call g(x)
.
-O
or --optimizations
. The allowed values are none
, whitespace
, and simple
. (Planck doesn't support whole-program optimization, so advanced
is not an option.)Consider this example:
$ planck -q -K -c src --optimizations simple
cljs.user=> (require 'foo.core)
nil
When the require
form above is evaluated, the ClojureScript code is first transpiled to JavaScript, and then Google Closure is applied to that resulting code, using the simple optimizations level. If, for example, you (set! *print-fn-bodies* true)
and example function Vars in the foo.core
namespace, you will see that simple optimizations have been applied.
Furthermore, if you have caching enabled (the -K
option above), then code is cached with the optimization level specified. If you later run Planck with a different optimization level, cached code will be invalided and re-compiled at the new optimization level.
While enabling caching is not required, using optimizations and caching together makes sense, given that Closure optimization can take a bit of time to apply.
If optimizations is set to simple
, Planck will use :file-min
in preference to :file
when loading foreign lib dependencies. (See the Dependencies section of this guide for more information on loading foreign lib dependencies.)
ClojureScript allows you to embed runtime assertions into your code. Here is an example of triggering an assert at the Planck REPL:
cljs.user=> (assert (= 1 2) "Uh oh!")
Assert failed: Uh oh!
(= 1 2)
Also, if you use pre- and post-conditions in your code, then behind the scenes, these involve the use of
assert
.
By default, the *assert*
var is set to true
and calls to assert
can trigger as illustrated above. But if you set this var to false
then asserts will not trigger, with the macro expanding to nil
.
cljs.user=> (set! *assert* false)
false
cljs.user=> (assert (= 1 2) "Uh oh!")
nil
Note that the *assert*
var is consulted by the assert
macro at macroexpansion time. JVM-based ClojureScript does not currently support setting *assert*
dynamically as is illustrated above. And, while you can set *assert*
in source code being loaded into bootstrap environments like Planck, it will not affect that code because it is being compiled using the previously-set value for *assert*
.
If you are curious, this is because a
set!
call on*assert*
is not trapped by the compiler as is done for*unchecked-if*
.
With JVM ClojureScript, asserts are disabled globally via the :elide-asserts
compiler option. Planck supports the :elide-asserts
compiler option via the -a
or --elide-asserts
command-line flag. This flag simply initializes the *assert*
var to false
upon startup.
Note: If you'd like to disable asserts in some source code that you've already loaded at the Planck REPL, you can first
(set! *assert* false)
and thenrequire
that namespace passing the:reload
flag.
Planck supports connecting via a TCP socket.
This mimics the Socket REPL feature introduced with Clojure 1.8.
To start Planck in this mode, add the -n
, or --socket-repl
command line option, minimally specifying the port to listen on. If you'd like to have Planck additionally listen only on a specific IP address, specify it as in 192.0.2.1:9999
.
Here is an example of starting a REPL with a listening socket enabled and def
ing a var:
$ planck -n 9999
Planck socket REPL listening.
cljs.user=> (def a 3)
#'cljs.user/a
cljs.user=>
At this point, Planck is running. You can use the REPL directly in the terminal. But, you can additionally make TCP connections to it and access the same vars and general runtime environment:
$ telnet 0 9999
Trying 0.0.0.0...
Connected to localhost.
Escape character is '^]'.
cljs.user=> a
3
cljs.user=>
You can make as many connections as you'd like.
Each connection will have dedicated copies of certain session-centric vars, like *1
, *2
, *3
, *e
, as well as global vars that control assertions and printing. (The official Clojure Socket REPL capability also provides this sort of session isolation.)
You can exit a socket REPL connection by typing :repl/quit
, exit
, quit
, or :cljs/quit
.
Socket REPLs can be used by IDEs, for example. It provides a side channel that an IDE can use in order to introspect the runtime environment without interfering with your primary REPL session.
Additionally, socket REPLs could be used in other creative fashions—perhaps facilitating collaborative development without relying on other sharing technologies like tmux
.
Since socket REPLs are established from environments with unknown terminal capabilities, all of the rich terminal control and coloring (VT-100 and ANSI codes) are turned off for socket REPL sessions.
Most Clojure developers using Emacs tend to use Cider. Cider needs the Clojure instance to be running nRepl
, but Planck doesn't support that. Planck does instead implement the new Socket REPL capability, but Cider doesn't know how to interact with that.
Luckily for us, Rich Hickey thinks Cider is too complex, so Bozhidar Batsov went ahead and created inf-clojure.
To set up inf-clojure
to run with Planck, you can follow the instructions here and add
(setq inf-clojure-generic-cmd "planck -d")
to your .emacs
file, given planck
is on your path. I would be careful doing
(add-hook 'clojure-mode-hook #'inf-clojure-minor-mode)
if you use Cider for your other Clojure work, and rather invokeinf-clojure
by M-x inf-clojure-minor-mode
when you're working with Planck.You can now evaluate code directly from your source-code buffer by pressing C-x C-e
after the form you want to execute.
It is possible to integrate Cursive with Planck using Planck's Socket REPL capability. To do this, set up a conventional ClojureScript project using, say Leiningen. Then add Tubular as a dependency to the project via
[tubular "1.2.0"]
With this in place, first start up Planck in a regular terminal specifying the src
directory of your project as Planck's -c
classpath directive and use -n
to have Planck listen on a port for Socket REPL sessions. For example:
$ planck -c src -n 7777
Within Cursive, add a REPL to the project and choose “Use clojure.main in normal JVM process”. Start up the REPL, and issue
(require 'tubular.core)
(tubular.core/connect 7777)
This will piggyback a Socket REPL session in the Cursive Clojure REPL, and you will see the cljs.user=>
prompt from Planck. Use the pulldown to switch Cursive's REPL type fro clj
to cljs
, and you should be good to go. In particular you can use Cursive's REPL menu option to load files into Planck, sync namespaces, and send forms to the Planck REPL.
How does Planck work?
At a high level, there is no JVM involved. Planck makes use of ClojureScript's self-hosting capability and employs JavaScriptCore as its execution environment. JavaScriptCore is the JavaScript engine used by Safari and is already installed on all modern Macs.
When you launch Planck, it internally starts a JavaScriptCore instance and then loads JavaScript implementing the ClojureScript runtime. This JavaScript is baked into the Planck binary.
By default, Planck then starts a REPL. Planck makes entering expressions a little easier by employing a library, making it possible to edit the line as well as access previously entered lines by using the up arrow.
Planck enhances this experience by providing tab completion and brace highlighting:
)
, ]
, or }
character, Planck executes some JavaScript to find the matching counterpart. If it exists, Planck temporarily moves the cursor over that character.(The JavaScript for both of these actions is baked into the binary as well, and is originally sourced from ClojureScript.)
When you enter a complete form, ClojureScript's self-hosting kicks in: The text of the form is passed to the ClojureScript compiler (which is already loaded into JavaScriptCore, pre-compiled as JavaScript). This results in JavaScript that evaluates the form.
The form’s JavaScript is then executed. You can actually see the JavaScript if you start Planck in verbose mode by passing -v
:
$ planck -v
cljs.user=> (+ 2 3)
Evaluating (+ 2 3)
(2 + 3)
5
Entering a slightly more complicated expression, you can see that the emitted JavaScript makes use of the ClojureScript runtime:
cljs.user=> (first [4 7])
Evaluating (first [4 7])
cljs.core.first.call(null,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [4,7], null))
4
That's cool when evaluating pure expressions. What about interacting with the outside environment?
Let's say you want to read the content of a file you have on disk, and you enter these forms:
(require '[planck.core :refer [slurp]])
(slurp "foo.txt")
At the bottom, Planck has implemented certain I/O primitives and has exposed them as JavaScript functions in JavaScriptCore. One such primitive opens a file for reading. Planck has some code like this
self.inputStream = [NSInputStream
inputStreamWithFileAtPath:path]
in a "file reader" Objective-C class. The constructor for this class is exposed in JavaScript as a function with the name PLANCK_FILE_READER_OPEN
. This capability is made available to you in ClojureScript by having functions like slurp
employ ClojureScript code like
(js/PLANCK_FILE_READER_OPEN "foo.txt")
To actually read from the file, slurp
calls another js/PLANCK_FILE_READER_READ
primitive, which invokes code like
[self.inputStream read:buf
maxLength:1024]
A few Planck ClojureScript namespaces are bundled with Planck in order to provide mappings onto these I/O primitives, exposing the simple APIs—like slurp
—that you are familiar with: planck.core
, planck.io
, and planck.shell
.
In a nutshell, that’s really a big part of what Planck is: Some glue between ClojureScript and the native host environment.
Planck wraps all this with some niceties making it suitable as a scripting environment.
One aspect is the loading of custom ClojureScript source files. Let's say you have src/my_cool_code/core.cljs
, and at the REPL you invoke
(require 'my-cool-code.core)
Planck implements the require
“REPL special form,” which causes bootstrapped ClojureScript—specifically cljs.js
, via its *load-fn*
—to load your source from disk (by using Objective-C I/O primitives exposed as JavaScript).
The nice thing is that *load-fn*
is also used for :require
specs that may appear in namespace declarations in your code, as well as require-macros
and import
.
To top it off, Planck is free to implement *load-fn*
in convenient ways:
planck.core
) directly from gzipped pre-compiled JavaScript baked in the binary.With the ability to dynamically load custom ClojureScript code, executing it by mapping it onto native I/O facilities, can ClojureScript can be used as a compelling alternative for your Bash shell scripting needs.
The source for Planck is hosted on GitHub: https://github.com/planck-repl/planck.
If you'd like to contribute, check it out, and take a look at the open issues list: https://github.com/planck-repl/planck/issues.
This documentation is also hosted in the Planck GitHub repository, under the site
directory of that repository. So, if you find improvements that can be made, feel free to submit PRs!
Planck is copyright © 2015–2019 Mike Fikes and Contributors.
It is distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Planck uses several open-source libraries. You can get the details for those libraries by issuing the following at the terminal:
planck -l