Intro

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.

Setup

Planck runs on macOS and Linux.

Planck requires no external dependencies. (There is no need for either the Java JVM or Node.js.)

Homebrew

Install

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.

Install Master

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

Building

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

Bug Reporting

If you happen to encounter any issues with Planck, issues are tracked on GitHub at https://github.com/planck-repl/planck/issues.

Running

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.

Auto-loaded user code

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.

Compile Opts EDN

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:

REPL

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.)

Line Editing

History

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.

Tab Completion

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.

Control Keys

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.

Result Display

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.

Color Themes

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.

Dumb Terminal

Normally, Planck employs the use of VT100 and ANSI codes to perform brace matching, line editing features, and to add color. If you are using Planck in an environment where these codes are not supported, or you would prefer to disable them, you can pass -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 via brew). When using rlwrap, it is necessary to pass -d to planck so that rlwrap's terminal controls become active: rlwrap planck -d.

Exit

You can exit the REPL by typeing Ctrl-D, exit, quit, or :cljs/quit.

Verbose Mode

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.

Quiet Mode

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

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.)

in-ns

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

The 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.

Auto-Referred Symbols

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.

One Liners

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"))'

Scripts

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!

Standalone Scripts

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.

Main Function

Specifying the Main Namespace

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!

Specifying the Main Function

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.

Interacting with standard input and output

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))

Command Line Arguments

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*) "!"))

Argument Processing

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

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)) 

Shell Interaction

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*))

Script Termination Delay

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))

Planck Namespaces

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

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.

planck.core

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.

planck.repl

This namespace includes a few macros that are useful when working at the REPL, such as doc, dir, source, etc.

planck.io

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.

planck.shell

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.

planck.http

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.

planck.environ

This namespace provided access to environment variables, modeled after Environ. For example

(:home planck.environ/env)

will access the HOME environment variable.

Source Dev

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

Macros

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.

Source Mapping

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.

Tagged Literals

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.

Bootstrap 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)

Testing

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

Custom Asserts

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))

Dependencies

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.

Using deps.edn

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"}}}

Shebang Deps

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 the require form).

Using Boot

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` 

Classpath Specification

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.

Abbreviated Dependency Specs

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.

Downloading Deps

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

Bundled Deps

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:

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.)

Foreign Libs

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"

Performance

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.

Caching

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

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.

Function Dispatch

:static-fns

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 implements IFn. That kind of thing.

So for compilation :static-fns can nearly always be true, 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.

:fn-invoke-direct

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).

Optimizations

Closure

You can specify the Closure compiler level to be applied to source loaded from namespaces by using -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.

Foreign Libs

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.)

Removing Asserts

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 then require that namespace passing the :reload flag.

Socket REPL

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 defing 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.

IDEs

Emacs

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.

Setup

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.

Cursive

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.

Internals

How does Planck work?

Fundamentals

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:

(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

Side Effects

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.

Affordances

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:

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.

Contributing

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!

Legal

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