Skip to content

Repository files navigation

panini

Clojars Project

Panini is a 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:

  • expose their grammar tangibly, ideally as BNF notation, allowing users to visually grasp how to use the macro
  • produce clear feedback detailing the nature of a syntax mistake, exactly where it occurred, and examples of what valid input looks like
  • have access to tooling to build these features natively so users don't have to write custom checkers by hand

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.

Example

(require '[clojure.spec.alpha :as s])
(require '[panini.core :as panini :refer [define-ruledefine-syntax]])
(define-rulebinding-pair"A single name/value binding."
(s/cat:name symbol?
:value any?))
(define-syntaxmy-let"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?))
(panini/pretty-grammar #'my-let)
;; prints a colourized grammar summary in the terminal;; my-let => [binding-pair*] form+;; binding-pair => symbol form;; Bindings followed by one or more body forms.

Parse and compile a form:

(panini/parse '(my-let [x 1 y 2] (+ x y)))
;; {:node :user/my-let,;; :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

License

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.

About

Sequence Expressions

Resources

Stars

138 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages