Panini is a Clojure library for writing "illuminated macros" in Clojure.
In a Clojure Conj 2013 talk, also named "Illuminated Macros", Chris Houser and Jonathan Claggett argued that macros were essentially tiny languages/compilers, and as such, should:
They built the library seqex around that idea. I used it to power my pattern matching library akar. Unfortunately, seqex never really caught on, and there have been no commits there since 2018. So I decided to fork the project, modernise it, and maintain it. Really, the only real "modernisation" I did was replace the custom sequence expressions utilities with clojure.spec.alpha.
This fork is named after Pāṇini ([päː.ɳi.n̪i]), the ancient Sanskrit grammarian whose Aṣṭādhyāyī ([ɐʂʈaːd̪.d̪ʱjaːjiː]) is one of the earliest known formal systems for describing grammar, and is often compared to later notations such as BNF and EBNF.
(require '[clojure.spec.alpha :as s])
(require '[panini.core :as panini :refer [define-rule define-syntax]])
(define-rule binding-pair
:doc "A single name/value binding."
:grammar (s/cat :name symbol?
:value any?))
(define-syntax my-let
:doc "Bindings followed by one or more body forms."
:grammar (s/cat :bindings (s/and vector?
(s/spec (s/* ::binding-pair)))
:body (s/+ any?))
:target (fn [{:keys [bindings body]}]
`(let ~(vec (mapcat (fn [{:keys [name value]}]
[name value])
bindings))
~@body)))
Inspect the grammar:
(panini/rule? binding-pair)
;; true
(panini/syntax? #'my-let)
;; true
(panini/grammar #'my-let)
;; (s/cat :bindings (s/and vector? (s/spec (s/* :user/binding-pair)))
;; :body (s/+ any?))
(println (panini/pretty-grammar #'my-let))
;; prints a colourized grammar summary in the terminal
;; my-let => bindings:[binding-pair*] body:form+
;; Bindings followed by one or more body forms.
Parse and compile a form:
(panini/parse '(my-let [x 1 y 2] (+ x y)))
;; {:bindings [{:name x, :value 1}
;; {:name y, :value 2}],
;; :body [(+ x y)]}
(panini/compile '(my-let [x 1 y 2] (+ x y)))
;; (clojure.core/let [x 1 y 2] (+ x y))
(macroexpand '(my-let [x 1 y 2] (+ x y)))
;; (clojure.core/let [x 1 y 2] (+ x y))
Invalid input returns ::panini/invalid from parse, and either throws or returns ::panini/invalid from compile:
(panini/parse '(my-let 42 (+ x y)))
;; :panini.core/invalid
(panini/compile '(my-let 42 (+ x y)) {:on-error :invalid})
;; :panini.core/invalid
Copyright (C) 2013 Jonathan Claggett
Copyright (C) 2026 Rahul Goma Phulore
Distributed under the Eclipse Public License v1.0. See LICENSE and epl-v10.html.
Can you improve this documentation? These fine people already did:
Jonathan Claggett & missingfaktorEdit 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 |