All notable changes to ClojureElisp are documented here.
Format follows Keep a Changelog; versioning follows Semantic Versioning.
Follow-up to 0.7.0. The interactive loop had a second half of the parity bug, and the Babashka story shipped incomplete.
Interactive eval emitted unprefixed calls to sibling definitions. 0.7.0
fixed the name a form DEFINES; it did not fix the names a form CALLS. The
analyzer pre-scans the definitions it is handed, so compiling against the
(ns ...) form alone left a sibling call bare:
(defn shout [n] (upcase (greet n)))
=> (defun demo-greeter-shout (n) (upcase (greet n))) ; void-function
The client now sends the whole buffer as cljel-context, so calls resolve
the way they do in the compiled file. When the buffer cannot be compiled the
server falls back to its leading (ns ...) form and then to no context, so a
half-typed form elsewhere does not block evaluating a good one.
bbin install could not find clel.main. bbin resolves the project as
a :local/root dependency, which reads deps.edn :paths; the Babashka
entry points were only on bb.edn's. Pre-existing, and it made the
recommended install route non-functional. bb is now on deps.edn :paths.
The published jar carried no Babashka entry points. clel.nrepl-server
and clel.main were absent from the 0.7.0 artifact, so the Clojars
coordinate could not start the standalone server. The jar now ships bb.
clojure-elisp.core/bundle-runtime! writes clojure-elisp-runtime.el
from the classpath into a directory of your choosing. Previously this was
private and reachable only through compile-project-from-config, so a
consumer had to name a path into the ClojureElisp checkout to get the
runtime.examples/bb-demo, a complete ClojureElisp project running on Babashka:
compile, show, eval-form, runtime, nrepl, and a demo task that loads the
output into a real Emacs and calls the functions.compile-string-in-ns-result, the Railway variant, alongside the
existing compile-file-string-result.clojure-elisp.nrepl-kernel restratified along Collect / Promote /
Pipeline / Boundary, and moved onto the project's own error vocabulary
(hive-dsl.result plus the clojure-elisp.errors schemas) instead of the
ad-hoc {:status :ok} maps it shipped with. compile-code is kept as the
compatibility surface. The namespace now carries m/=> contracts and joins
the instrumented set, so they are enforced by the suite rather than declared..el files track the release.603 tests, 3042 assertions, 0 failures. The demo was run end to end: compiled
.cljel, bundled runtime, loaded both into Emacs 28+, called the functions and
confirmed commandp on the interactive one. The standalone server was driven
over a socket from the demo project. Neutralizing the context handling turns
the parity suite red (13 failures, 1 error).
The theme of this release is that the interactive loop is the primary way to write ClojureElisp, and the tooling around it is no longer heavier than the elisp REPL it replaces.
Public feedback on the project said, in substance: an external transpiler process is not ergonomic while you are writing elisp functions, unless there is a way to invoke the transpiler, and that comes with a lot of tooling when elisp already has a good REPL model.
Half of that was already wrong about ClojureElisp. The nREPL middleware
(clojure-elisp.nrepl) and the CIDER minor mode
(resources/clojure-elisp/cider-clojure-elisp.el) have shipped for several
releases: C-c C-c compiles the form at point and evaluates the resulting Elisp
in the running Emacs, redefining the function in the live image with nothing
written to disk. That is the same loop the critique credits elisp with.
The other half was fair, on three counts, and this release addresses all of them:
.nrepl.edn edit, cider-jack-in, then
M-x cider-cljel-start.clel nrepl, a ClojureElisp nREPL server that needs no JVM. Starts in
roughly 275 ms under Babashka. Connect with M-x cider-connect-clj; there is
no deps.edn to write, no .nrepl.edn middleware entry, and no jack-in.
Implemented as clel.nrepl-server, a bencode socket loop speaking clone,
close, describe, ls-sessions, eval, load-file, cljel-start and
cljel-stop.clojure-elisp.nrepl-kernel, the transport-independent core: session
registry, compile modes, and op semantics. handle-op returns a vector of
response maps and writes nothing, so a transport only has to merge its
correlation keys and serialize. Both servers now run this one definition of
compile-code.compile-string-in-ns (in clojure-elisp.compile, re-exported from
clojure-elisp.core): compiles forms in the namespace context of an
(ns ...) source string, emitting the forms alone with no file header and no
(provide ...).cider-cljel-runtime-file, a defcustom pointing at
clojure-elisp-runtime.el for setups where it is not on load-path.fence-region command from the ns form
through M-x to clel compile. Every code block in it is verified compiler
output.C-c C-e and C-c C-k no longer define different functions. handle-eval
hardcoded :expr mode while handle-load-file used :file, so evaluating
(defn greet ...) inside (ns my.pkg) installed greet while compiling the
same buffer installed my-pkg-greet. The running image and the compiled
artifact disagreed about every namespaced definition. The CIDER client now
sends the buffer's (ns ...) form as cljel-ns and the kernel compiles in
that context, so all four paths agree.void-function clel-str.
Expression mode emits no runtime require and cider-cljel-start loaded
nothing, so any first eval touching str failed. cider-cljel-ensure-runtime
now loads the runtime from load-path or cider-cljel-runtime-file, and
reports clearly when it cannot find it instead of leaving a void-function to
surface later.message where that is unavailable.clojure-elisp.ast/gen-node resolves malli.generator lazily through
requiring-resolve rather than requiring it at load time. It is used by tests
only, and requiring it put clojure.test.check on the compile path, which no
lightweight host provides. Pinned by a test asserting no compile-path
namespace aliases malli.generator.bb.edn now puts src and resources on :paths with hive-dsl and
malli, so the compiler loads in the Babashka process rather than shelling
out to a jar.clojure-elisp.nrepl is now a transport only. wrap-cljel and the
handle-* functions delegate to the kernel; the public surface
(cljel-sessions, cljel-active?, compile-code, handle-eval,
handle-load-file, handle-start, handle-stop, wrap-cljel) is unchanged.clojure-elisp-runtime.el regenerated from runtime.cljel with the
current emitter. The only differences beyond the version header are
(when x y) forms emitted as the equivalent (if x y nil); behaviour is
identical. The checked-in file had been generated by an older emitter.clojure-elisp-mode.el and cider-clojure-elisp.el were
stale at 0.5.0 and now track the release.Sessions on the standalone server start with compilation active, because that
server has no Clojure evaluator to fall through to. M-x cider-cljel-start is
therefore no longer a required step there, and cider-cljel-mode is only about
keybindings. The JVM middleware is unchanged in this respect: a session there
still opts in.
ClojureWasm was evaluated as a faster host, since cljw starts in 36 ms and
already ships cljw nrepl. The transport is ready for it and needs no changes.
malli is not: malli.core/-memoize reaches
java.util.concurrent.atomic.AtomicReference, which ClojureWasm does not
provide, and that is malli's own memoization rather than anything ClojureElisp
can route around. Clearing the earlier blockers in that chain is what produced
the malli.generator change above, which was worth making on its own.
598 tests, 3021 assertions, 0 failures. The standalone server was driven over a
real socket through clone, namespace-aware eval, bare eval, load-file, a
compilation error and describe. Neutralizing compile-string-in-ns so it
ignores its namespace argument turns the new suite red (6 failures, 1 error),
so the namespace-parity coverage is not vacuous.
main. The
v0.6.2 release had failed because git push origin HEAD:main is rejected by
branch protection. README install coordinates use a placeholder and the
Clojars badge carries the current version, guarded by a test that fails if a
concrete version is ever pinned again.load-file compiles as a whole file, and every compiled form is evaluated.main always ships.cond is rejected loudly instead of being mis-compiled.dev/migrate_cond.clj, a source rewriter for the elisp-cond migration.Releases before 0.6.1 are recorded in the
GitHub releases and in the
Progress Log in CLAUDE.md.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |