Skip to content

BuddhiLW/clojure-elisp

Repository files navigation

ClojureElisp

A Clojure dialect that compiles to Emacs Lisp — like ClojureScript targets JavaScript, ClojureElisp targets Emacs.

Write .cljel files using Clojure syntax, compile them to .el files that run natively in Emacs 28.1+.

Quick Start

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

;; Compile a single form
(clel/emit '(defn greet [name] (str "Hello, " name "!")))
;; => "(defun greet (name)\n  (clel-str \"Hello, \" name \"!\"))"

;; Compile a string of code
(clel/compile-string "(defn inc2 [x] (+ x 2))")
;; => "(defun inc2 (x)\n  (+ x 2))"

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

;; Compile an entire project in dependency order
(clel/compile-project ["src"] "out")

Example

;; my-package.cljel
(ns my.package
  (:require [clojure.string :as str]))

(defn greet [name]
  (let [msg (str "Hello, " name "!")]
    (message msg)))

(defn process-buffer []
  (-> (buffer-string)
      str/upper-case
      insert))

Compiles to:

;;; my-package.el --- -*- lexical-binding: t; -*-
;; Generated by ClojureElisp

(require 'clojure-elisp-runtime)

;;; Code:

(defun my-package-greet (name)
  (let* ((msg (clel-str "Hello, " name "!")))
    (message msg)))

(defun my-package-process-buffer ()
  (insert (upcase (buffer-string))))

(provide 'my-package)
;;; my-package.el ends here

Features

Language

  • Functions: defn, fn (lambda), multi-arity, variadic (& rest), destructuring in params
  • Bindings: let with sequential bindings, vector/map destructuring, :keys, :as, :or
  • Control flow: if, when, cond, case, do, and, or
  • Looping: loop/recur, letfn with mutual recursion
  • Macros: defmacro (compile-time only), syntax-quote/unquote, macroexpand-1, macroexpand
  • Error handling: try/catch/finally, throw, ex-info
  • Namespaces: ns with :require, :as, :refer; namespace-prefixed definitions
  • Protocols & types: defprotocol, defrecord, deftype with ^:mutable fields, set!
  • Multimethods: defmulti/defmethod via cl-defgeneric/cl-defmethod
  • Lazy sequences: lazy-seq, realized?, doall, dorun
  • Atoms: atom, deref/@, reset!, swap!, add-watch, remove-watch
  • Elisp interop: .method dot-notation, elisp/fn namespace, .-property access

Core Functions (100+)

Clojure core functions mapped to Elisp equivalents:

Category Functions
Arithmetic +, -, *, /, mod, inc, dec
Comparison =, <, >, <=, >=, not=
Predicates nil?, string?, number?, zero?, pos?, neg?, even?, odd?, coll?, some?
Collections first, rest, next, cons, conj, count, nth, get, assoc, dissoc, keys, vals, into, seq, empty?
Sequences map, filter, remove, reduce, take, drop, concat, mapcat, sort, group-by, frequencies
Seq predicates every?, some, not-every?, not-any?
Strings str, subs, format, pr-str, println
Higher-order apply, identity, constantly, partial, comp

Compiler

  • 3-stage pipeline: Reader (Clojure's) → Analyzer (AST + env) → Emitter (codegen)
  • Source location tracking with optional ;;; L<line>:C<col> comments
  • Dependency-aware project compilation with topological sort
  • Name mangling: valid?valid-p, reset!reset-bang, my.ns/foomy-ns-foo

Architecture

┌─────────────┐    ┌──────────────┐    ┌─────────────┐    ┌──────────────┐
│   Reader    │───▶│   Analyzer   │───▶│   Emitter   │───▶│  Elisp Code  │
│ (Clojure's) │    │ (AST + env)  │    │ (codegen)   │    │   (.el)      │
└─────────────┘    └──────────────┘    └─────────────┘    └──────────────┘
Component File Role
Analyzer src/clojure_elisp/analyzer.clj Parse forms → AST nodes, macro expansion, destructuring, env tracking
Emitter src/clojure_elisp/emitter.clj AST nodes → Elisp source strings
Core src/clojure_elisp/core.clj Public API, file/project compilation, dependency resolution
Runtime resources/clojure-elisp/clojure-elisp-runtime.el 55+ Elisp functions implementing Clojure semantics
Emacs mode resources/clojure-elisp/clojure-elisp-mode.el Major mode for .cljel files
CIDER resources/clojure-elisp/cider-clojure-elisp.el nREPL middleware for CIDER integration

Installation

Standalone Compiler (Uberjar)

Download the latest clel-<version>.jar from GitHub Releases, then:

# Install to standard location
mkdir -p ~/.local/lib
cp clel-0.3.1.jar ~/.local/lib/clel.jar

# Compile a file
java -jar ~/.local/lib/clel.jar compile src/my_app.cljel -o out/my-app.el

# Compile a directory
java -jar ~/.local/lib/clel.jar compile src/ -o out/

# Check version
java -jar ~/.local/lib/clel.jar version

The Go CLI (clel) automatically detects the jar at ~/.local/lib/clel.jar or via the CLEL_JAR env var, removing the need for a local repo clone:

# Uses jar if found, falls back to clojure -M -e
clel compile src/my_app.cljel -o out/my-app.el

Runtime (Emacs Package)

Compiled .el files require the ClojureElisp runtime. Install via MELPA (once available):

(package-install 'clojure-elisp)

Or manually copy from the repo:

cp resources/clojure-elisp/clojure-elisp-runtime.el ~/.emacs.d/site-lisp/

Building from Source

# Build uberjar
clojure -T:build uber
# => target/clel-0.3.1.jar

Development

# Start REPL with dev dependencies (nREPL, CIDER)
clojure -M:dev

# Run tests (Kaocha — 350 tests, 2100 assertions)
clojure -M:test

# Build uberjar
clojure -T:build uber

Requirements

  • Clojure 1.12+
  • Java 21+ (for building/running the uberjar)
  • Emacs 28.1+ (for compiled output)

License

MIT

About

A Clojure dialect that compiles to Emacs Lisp

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •