Skip to content

Latest commit

 

History

History
259 lines (196 loc) · 12.1 KB

CLAUDE.md

File metadata and controls

259 lines (196 loc) · 12.1 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

ClojureElisp is a Clojure dialect that compiles to Emacs Lisp, similar to how ClojureScript targets JavaScript. Write Clojure code in .cljel files and compile to .el files that run in Emacs.

Status: Early development - core compiler infrastructure in progress.

Development Commands

# Start REPL with dev dependencies (nrepl, cider-nrepl)
clojure -M:dev

# Run tests (Kaocha)
clojure -M:test

# Build (tools.build)
clojure -T:build

Architecture

Reader (Clojure's) → Analyzer (AST + env) → Emitter (codegen) → Elisp (.el)

Compiler Pipeline

  1. analyzer.clj (src/clojure_elisp/analyzer.clj)

    • Transforms Clojure forms into AST nodes
    • Uses dynamic *env* for tracking locals and namespace context
    • Handles special forms: def, defn, fn, let, if, when, cond, do, ns, quote, loop, recur
    • analyze multimethod dispatches on form type
  2. emitter.clj (src/clojure_elisp/emitter.clj)

    • Transforms AST nodes to Elisp source strings
    • emit-node multimethod dispatches on :op key
    • core-fn-mapping: Maps Clojure core functions to Elisp equivalents (e.g., first → car, str → clel-str)
    • mangle-name: Converts Clojure symbols to valid Elisp identifiers (? → -p, ! → -bang)
  3. core.clj (src/clojure_elisp/core.clj)

    • Public API: emit, emit-forms, compile-string, compile-file, compile-ns
    • Entry point for compilation
  4. Runtime (resources/clojure-elisp/clel.el)

    • Elisp runtime library required by compiled code
    • Provides Clojure-like functions: clel-conj, clel-get, clel-assoc, clel-str, clel-atom, etc.
    • Requires Emacs 28.1+

Quick REPL Test

(require '[clojure-elisp.core :as clel])

;; Compile a form
(clel/emit '(defn foo [x] (+ x 1)))
;; => "(defun foo (x)\n  (+ x 1))"

;; Compile a file
(clel/compile-file "src/my_package.cljel" "out/my-package.el")

Progress Log

2026-09-05: Interactive Development as the primary workflow (v0.7.0)

Trigger: public feedback said cljel "needs an external transpiler process which imo isn't really ergonomic while you're writing elisp functions, unless if you have way to invoke the transpiler, but this comes with a lot of tooling while elisp already has a good REPL-model".

The loop already existed and was CIDER-native. The README showed it in one table row, so the reader could not find it, and three defects made it feel second-class. See CHANGELOG.md for the full discussion.

nREPL split into a kernel and two transports:

  • clojure-elisp.nrepl-kernel - session registry, compile modes, handle-op. No transport, no nREPL dependency. handle-op returns a vector of response maps and writes nothing.
  • clojure-elisp.nrepl - JVM transport, wrap-cljel over the kernel. Public surface unchanged.
  • clel.nrepl-server - bencode socket loop over the same kernel. clel nrepl starts it under Babashka in ~275ms with no JVM, no .nrepl.edn, no jack-in. Sessions start active because there is no Clojure evaluator to fall through to.

Namespace parity (the worst defect): handle-eval hardcoded :expr while handle-load-file used :file, so C-c C-c defined greet and C-c C-k defined my-pkg-greet. New compile-string-in-ns compiles in the buffer's ns context, emitting forms only. The client sends the (ns ...) form as cljel-ns.

Elisp client: cider-cljel-ensure-runtime (no more void-function clel-str on first eval), cider-cljel-buffer-ns-form, inline overlays via cider--display-interactive-eval-result instead of message.

Compile path no longer carries test.check: ast/gen-node resolves malli.generator through requiring-resolve. It is test-only, and requiring it blocked every lightweight host.

cljw evaluated, blocked upstream: 36ms vs bb's 275ms and cljw nrepl ships already, but malli.core/-memoize reaches java.util.concurrent.atomic.AtomicReference, absent from ClojureWasm. Transport is ready; card 20260905132610-70d34b63.

Test stats: 598 tests, 3021 assertions, 0 failures. Neutralizing compile-string-in-ns turns the new suite red (6 failures, 1 error).

Files modified:

  • src/clojure_elisp/nrepl_kernel.clj - new, the transport-free core
  • bb/clel/nrepl_server.clj - new, standalone bencode server
  • test/clojure_elisp/nrepl_ns_parity_test.clj - new, 9 tests
  • src/clojure_elisp/nrepl.clj - reduced to the JVM transport
  • src/clojure_elisp/compile.clj, core.clj - compile-string-in-ns
  • src/clojure_elisp/ast.clj - lazy malli.generator
  • resources/clojure-elisp/cider-clojure-elisp.el - ns form, runtime, overlays
  • bb/clel/main.clj, bb.edn - clel nrepl subcommand and classpath
  • README.md, CHANGELOG.md - Interactive Development section, changelog

2026-01-29: Emacs Buffer/Process Interop Layer (clel-031)

Added comprehensive Emacs buffer/process interop for writing Emacs extensions in ClojureElisp:

Buffer operations (direct mappings):

  • Content: buffer-string, buffer-substring, buffer-substring-no-properties, erase-buffer
  • Point/movement: point, point-min, point-max, goto-char, forward-char, backward-char, forward-line, beginning-of-line, end-of-line
  • Editing: insert, insert-buffer-substring, delete-char, delete-region
  • Buffer management: current-buffer, set-buffer, get-buffer, get-buffer-create, kill-buffer, buffer-name, buffer-live-p, buffer-modified-p
  • Region: mark, set-mark, region-beginning, region-end, use-region-p
  • Narrowing: narrow-to-region, widen, buffer-narrowed-p
  • Text properties: put-text-property, get-text-property, remove-text-properties, propertize

Process operations:

  • start-process, call-process, process-send-string, process-send-region, process-send-eof
  • set-process-filter, set-process-sentinel, process-buffer, process-status, process-live-p, delete-process

File operations:

  • find-file, find-file-noselect, write-file, save-buffer, buffer-file-name
  • file-exists-p, file-readable-p, file-writable-p, file-directory-p, expand-file-name

Window operations:

  • selected-window, select-window, window-buffer, set-window-buffer, split-window, delete-window, other-window

Special forms (analyzer + emitter):

  • save-excursion — Save point/mark, execute body, restore
  • save-restriction — Save narrowing state, execute body, restore
  • with-current-buffer — Execute body in specified buffer
  • with-temp-buffer — Execute body in temporary buffer
  • save-current-buffer — Save current buffer, execute body, restore
  • with-output-to-string — Capture output as string

Test stats: 211 tests, 1278 assertions, 2 pre-existing failures

Files modified:

  • src/clojure_elisp/analyzer.clj — Added 6 special form analyzers
  • src/clojure_elisp/emitter.clj — Added 60+ function mappings and 6 emit-node methods
  • examples/buffer-demo.cljel — New comprehensive example showing buffer/process interop
  • test/clojure_elisp/emitter_test.clj — Added 10 test functions (56 new assertions)

2026-01-28: Protocol Extensions (clel-025)

Added extend-type, extend-protocol, satisfies?, and reify:

  • extend-type — Extend multiple protocols to an existing type, emits cl-defmethod with type specializer
  • extend-protocol — Extend a single protocol to multiple types
  • satisfies? — Runtime check if value satisfies a protocol (clel-satisfies-p)
  • reify — Anonymous protocol implementations with closure capture

Type mapping: Clojure types map to Elisp type specializers:

  • String → string, Number → number, Boolean → boolean, etc.
  • Custom record types use mangled names directly

Test stats: 201 tests, 1222 assertions, 2 pre-existing failures

Files modified:

  • src/clojure_elisp/analyzer.clj — Added analyzers for extend-type, extend-protocol, satisfies?, reify
  • src/clojure_elisp/emitter.clj — Added emitters with type mapping and reify counter
  • resources/clojure-elisp/clel.el — Added protocol registry and clel-satisfies-p
  • test/clojure_elisp/analyzer_test.clj — Added 4 test functions for new forms
  • test/clojure_elisp/emitter_test.clj — Added 4 test functions for emission
  • test/clojure_elisp/runtime_test.clj — Added 4 test functions for end-to-end compilation

2026-01-28: Source Location Tracking (clel-020)

Implemented source location tracking through the compiler pipeline:

  • Fixed read-all-forms to use LineNumberingPushbackReader (captures line/column from reader)
  • Forms read from files now carry :line and :column metadata
  • AST nodes propagate source locations via *source-context* dynamic var
  • analysis-error includes file:line:col in error messages
  • *emit-source-comments* emits ;;; L<line>:C<col> comments for debugging

Existing infrastructure leveraged:

  • extract-source-location function in analyzer
  • ast-node constructor merges source context
  • source-comment function in emitter

Test stats: 200 tests, 1211 assertions, 0 failures

Files modified:

  • src/clojure_elisp/core.clj - Use LineNumberingPushbackReader in read-all-forms
  • test/clojure_elisp/core_test.clj - Added 4 source location test functions
  • test/clojure_elisp/nrepl_test.clj - Fixed to avoid loading nrepl deps when not available

2026-01-01: Atom Watch Functions (clel-015)

Implemented add-watch/remove-watch for atoms via swarm (2 parallel slaves):

  • Updated atom structure: (list 'clel-atom val watchers-alist)
  • Runtime functions: clel-add-watch, clel-remove-watch, clel--notify-watchers
  • Updated clel-reset!, clel-swap! to notify watchers on state change
  • Emitter mappings: atom, deref, clojure.core/deref (for @), reset!, swap!, add-watch, remove-watch

Test stats: 94 tests, 595 assertions, 0 failures

Files modified:

  • resources/clojure-elisp/clel.el - Updated atom structure, added watch functions
  • src/clojure_elisp/emitter.clj - Added 7 atom-related core-fn-mapping entries
  • test/clojure_elisp/runtime_test.clj - Added 4 atom watch test functions (28 assertions)

2026-01-01: Predicate Functions (clel-014)

Implemented 11 predicate functions via swarm (3 parallel slaves):

  • Numeric: zero?, pos?, neg?, even?, odd? → direct Elisp cl-lib mapping
  • Boolean: some?, true?, false? → clel-some-p, clel-true-p, clel-false-p
  • Collection: coll?, sequential?, associative? → clel-coll-p, clel-sequential-p, clel-associative-p

Test stats: 90 tests, 556 assertions, 0 failures

Files modified:

  • resources/clojure-elisp/clel.el - Added 6 runtime functions
  • src/clojure_elisp/emitter.clj - Added 11 core-fn-mapping entries
  • test/clojure_elisp/runtime_test.clj - Added numeric, boolean, collection predicate tests

2026-01-01: Swarm Batch Implementation

Implemented via emacs-mcp swarm (3 parallel slaves):

  • Test infrastructure: 71 tests, 395 assertions (Kaocha)
  • Destructuring in let: vector [a b], map {:keys [x]}, :as, :or
  • Destructuring in fn/defn params: & rest → (&rest args) with nthcdr
  • try/catch/finally: condition-case + unwind-protect
  • throw: (signal 'error ...) for ex-info, Exception., rethrow
  • Multi-arity: cl-case dispatch on (length args)

Files modified:

  • src/clojure_elisp/analyzer.clj - Added special forms, destructuring expansion
  • src/clojure_elisp/emitter.clj - Added emit-node methods for new forms
  • test/ - Created analyzer_test.clj, emitter_test.clj, core_test.clj
  • kanban.org - Task tracking

Workflow notes added to project memory:

  • Swarm conventions (monitoring, unsticking slaves, broadcasts)
  • Session startup/wrap conventions (/catchup, /wrap, git)

Key Mappings

The core-fn-mapping in emitter.clj defines Clojure→Elisp translations:

  • Arithmetic: +, -, *, /, inc → 1+, dec → 1-
  • Collections: first → car, rest → cdr, cons → cons, count → length
  • Runtime functions (prefix clel-): str, conj, get, assoc, keys, vals