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

Bug Reporting

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

Running

You launch Planck by typing planck in a terminal.

If you'd like to get help on the command line arguments to Planck, do

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

Note that, Planck accepts long arguments (preceeded by two dashes), just like the Clojure REPL, but with Planck there must be an equals sign between the argument name and value, as in planck -​-​eval='1'.

REPL

If you don't provide any -i or -e options or args to planck 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.

$ planck
Planck 2.2.0
ClojureScript 1.9.494
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
    Exit: Control+D or :cljs/quit or exit or quit
 Results: Stored in vars *1, *2, *3, an exception in *e

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 (map and then hitting the tab key. You will be presented choices like map-indexed, map?, mapcat, etc. Hitting shift-tab returns to the originally entered text. Tab completion works aginst 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.

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.

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 planck to run it. For example, say you have foo.cljs with

(println "Hello World!")

Then you can execute it:

$ planck 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 planck
(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!
$ planck bar.cljs there
Hello there!

Main Function

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:

$ planck -m foo.core ClojureScript
Hello ClojureScript!

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 planck

(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 planck.core/*command-line-args* (mimicking the behavior of clojure.core/*command-line-args* when writing scripts with Clojure).

With bar.cljs:

(ns bar.core
  (:require [planck.core :refer [*command-line-args*]]))

(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:

$ planck -c tools.cli-0.3.5.jar -m my.program -vvvp8080 foo --help --invalid-opt
{:errors ["Unknown option: \"--invalid-opt\""],
 :arguments ["foo"],
 :summary "  -p, --port PORT  80  Port number\n  -v                   Verbosity level\n  -h, --help",
 :options {:port 8080, :verbosity 3, :help true}}

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

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 planck
(ns foo.core
  (:require [planck.shell :refer [sh]]
            [planck.core :refer [*command-line-args*]]))

(defn list-files [dir]
  (println "listing files in" dir)
  (println (sh "ls" "-l" dir)))

(list-files (first *command-line-args*))

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 *command-line-args*, *in*, *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 defines eval and other 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.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 "http://planck-repl.org")

will fetch the main page of the Planck website, returning the status code, headers, and body in a map structure.

Source Dev

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.

For example, you can put this code in src/foo/core.cljs:

(ns foo.core)

(defn square
  [x]
  (* x x))

Then, if you launch Planck specfying the src directory:

$ planck -c src

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)

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.

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 available either as source on an accessible filesystem, or bundled in a JAR, and included in Planck's classapth.

Planck can consume conventional JARs meant for use with ClojureScript obtained from Clojars or elsewhere.

Planck's classpath is specified by providing a colon-separated list of directories and/or JARs via the -c or -​-​classpath argument, or by the PLANCK_CLASSPATH environment variable. The default classpath, if none is specified, is taken as the current working directory.

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.

Paths to JARs cached locally via Leiningen, Boot, or Maven (usually under /Users/<username>/.m2/repository) can be used. See the section below on Leiningen and Boot for tips on dependency management.

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.

One example of Planck using a dependency: This documentation is written in markdown, but converted to HTML using Planck using Dmitri Sotnikov's markdown-clj library. This library is written with support for regular ClojureScript, but it also works perfectly well in bootstrapped ClojureScript.

Shipping Deps

Planck ships with many of the deps that are available to conventional ClojureScript code. In particular this includes most 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 -c, -​-​classpath, 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 -c, -​-​classpath, or PLANCK_CLASSPATH.)

Using Leiningen or Boot for JAR Dependency Management

Planck requires that JARs be available locally and on the classpath, but it doesn't take care of downloading JARs. One solution to this is to use either Leiningen or Boot to manage dependencies for you, and to have those tools emit a classpath for use with Planck.

Here is an example using Leiningen: Let's say you want to use clojurescript.csv from Planck. First make a simple Leiningen project.clj just for the purpose of loading this dependency:

(defproject foo "0.1.0-SNAPSHOT"
  :dependencies [[testdouble/clojurescript.csv "0.2.0"]])

Now, with this in place, you can launch Planck using lein classpath to automatically generate the classpath string (and also automatically download any deps that haven't yet been downloaded). Here is a sample session showing this working, along with using the library within Planck.

$ planck -c`lein classpath`
Retrieving testdouble/clojurescript.csv/0.2.0/clojurescript.csv-0.2.0.pom from clojars
Retrieving testdouble/clojurescript.csv/0.2.0/clojurescript.csv-0.2.0.jar from clojars
cljs.user=> (require '[testdouble.cljs.csv :as csv])
nil
cljs.user=> (csv/write-csv [[1 2 3] [4 5 6]])
"1,2,3\n4,5,6"

If you are using Boot, the equivalent would be

$ planck -c`boot show -c`

Caching Classpath

Both Leiningen and Boot take a bit of time to verify that dependency artifacts have been downloaded. To make launching instant, just make a start shell script that looks like the following. (With this approach, be sure to manually delete the .classpath file if you change your dependencies.)

if [ ! -f .classpath ]; then
  classpath=`lein classpath | tee .classpath`
else
  classpath=`cat .classpath`
fi

planck -c $classpath

(And if you are using Boot, replace lein classpath with boot show -c.)

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

Planck will honor a deps.cljs file embedded in a JAR file. A deps.cljs file will have a :foreign-libs specification for upstream foreign dependencies packaged in the JAR, essentially indicating the synthetic namespace, the JavaScript file that needs to be loaded, and an indication of any other dependencies that need to be loaded.

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. The first thing you'll need to do is to obtain the CLJSJS JAR containing this library. The easiest way to do this is to place the CLJSJS [cljsjs/long "3.0.3-1"] dependency vector in a project.clj file as described in the previous section on using Leiningen for JAR deps.

If you pass the Leiningen-generated classpath containing this JAR to Planck, after start up 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.494 {: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.

Static Dispatch

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.

And—importantly for Planck—it can be used to work around a particularly severe JavaScriptCore perf bug that you can encounter when evaluating the JavaScript generated for lengthy literal list forms.

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-program "planck")

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.0.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 Plank 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 Plank 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/mfikes/planck.

If you'd like to contribute, check it out, and take a look at the open issues list: https://github.com/mfikes/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–2017 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