View kein.sh
#!/bin/bash
# launch a clojure plain repl but with options and classpath matching project.clj
# Except when project.clj changes (and on first launch), lein is not called.
CODE='
(let [p (leiningen.core.project/read)
args (@(var leiningen.core.eval/get-jvm-args) p)
cp (with-out-str (leiningen.classpath/classpath p))]
(print "ARGS=\"")
(apply print args)
View reader and spaces.md

While writing a new reader I used clojure.lang.LispReader code as the reference and I discovered there are two type of spaces:

  • pure whitespace (anything that matches #"[ \t\n\r,]+"),
  • unvalued stuff (any sequence of: whitespace, comments, discard (#_), elided conditionals).

This leads to some quirks. For example in namespaced map syntax is:

namespaced-map:
  "#:" (?! whitespace) unvalued-stuff symbol whitespace map
View self-hosted repl.md

That's the kind of code that I'd like to make work across lumo & planck (and ideally any bootstrapped)

Is this unreasonable? Do you see any objection or technical issue with that?

  (def repl
    (letfn [(rep []
              (println (str (ns-name *ns*)) "=>")
              (read (fn [form ex]
                      (if ex
View evented-reader.cljs
(ns unrepl.evented-reader
(:require [goog.string :as gstring]))
(defprotocol Stream
(read! [stream])
(unread! [stream])
(on-more [stream f]
"f is a function of two arguments: stream and an array where to push values.
f will be called as soon as input is available.
f returns true when no additional input is required."))
View binding-conveying.clj
;; did you know that binding conveying is not immutable but read-only? (the future sees updates performed by the original thread)
=> (with-local-vars [a 1]
(future (Thread/sleep 1000) (prn 'future @a))
(var-set a 2))
2
future 2
;; you have to push new bindings to isolate:
=> (with-local-vars [a 1]
(with-bindings {a @a}
View unrepl.md

UNleash the REPL

I spent some hammock time (ski lift time actually), brooding over unrepl.

What's ok

  • cleanly multiplexed output (out, err, eval, exception etc.)
  • elisions (which can provide all sort of browsing navigation)
  • attachments
  • :bye
View demo.clj
;; Clojure 1.6.0
=> *clojure-version*
{:major 1, :minor 6, :incremental 0, :qualifier nil}
=> (def env1 (create-clojure-env))
#'net.cgrand.quarantine/env1
=> (env1 *in* *out*)
clojure.core=> *clojure-version*
{:major 1, :minor 8, :incremental 0, :qualifier nil}
clojure.core=>
View sql.clj
(ns powderkeg.sql
(:require [clojure.spec :as s]
[clojure.edn :as edn]
[powderkeg.core :as keg]
[net.cgrand.xforms :as x])
(:import
[org.apache.spark.sql functions Column Row RowFactory DataFrame]
[org.apache.spark.sql.types StructType StructField ArrayType DataType DataTypes Metadata MetadataBuilder]))
(defmulti expr first)
View demo.md

So I have a full working prototype (at https://github.com/cgrand/unrepl) but I haven’t wrote the spec yet for the input part.

Clojure 1.9.0-alpha14
user=> (require 'unrepl.repl)
nil
user=> (unrepl.repl/start)

outputs:

View REPL the ultimate protocol.md

REPL the Ultimate Protocol: Notes for a hopefully better "tooling over repl" approach for Clojure

The single point of agreement between the server and the client should be that: there exists a stream-based plain repl running and connected (socket or pipes etc. is not relevant).

From there one can upgrade to a richer repl. The interesting fact is that since the upgrade is triggered by the client there can't be any mismatch between capabilities. Even if your editor scripting power is limited (coughvimcough) you can treat the clojure snippet required for upgrade as a blob.