Complete syntax reference for writing .meme code.
Parentheses immediately after any form create a call. The preceding element becomes the head of a Clojure list.
f(x y) → (f x y)
+(1 2 3) → (+ 1 2 3)
Adjacency required: foo(x) is a call — the head is outside the parens. foo (x) is NOT a call — the space makes them separate forms.
The rule is purely syntactic — any value can be a head:
f(x) → (f x):active(m) → (:active m)[x](body) → ([x] body) (multi-arity clauses){:a 1}(:a) → ({:a 1} :a)nil(1 2) → (nil 1 2), true(:a) → (true :a)The head of a list is written outside the parens, directly adjacent to (. Whitespace prevents call formation:
foo(x) ;; call: (foo x)
foo () ;; NOT a call — symbol foo, then empty list ()
This makes () (the empty list) unambiguous in all positions:
{:value ()} ;; map with two entries: :value and ()
[x ()] ;; vector with two elements: x and ()
def(x 42)
def(state atom({:count 0}))
let([x 1, y +(x 1)] *(x y))
Produces: (let [x 1 y (+ x 1)] (* x y))
Destructuring works:
let([{:keys [id name]} person] println(id name))
Single arity:
defn(greet [name] println(str("Hello " name)))
Multiple body forms:
defn(greet [name] println(str("Hello " name)) name)
With docstring:
defn(greet "Greets a person" [name] println(str("Hello " name)))
Multi-arity:
defn(greet
[name](greet(name "!"))
[name punct](println(str("Hello " name punct))))
fn([x] +(x 1))
fn([x y] *(x y))
fn([acc item] assoc(acc :id(item) :balance(item)))
Multi-arity anonymous function (vector-as-head for each clause):
fn([x](+(x 1))
[x y](+(x y)))
defn-(helper [x] +(x 1))
Macros work in .meme files. Syntax-quote (`) is parsed natively —
meme call syntax applies inside backtick. ~ (unquote) and ~@ (unquote-splicing)
work as prefix operators.
defmacro(my-log [tag expr] list('println tag expr))
defmacro(unless [test & body] `if(~test nil do(~@body)))
if(>(x 0) "positive" "negative")
if(>(x 0) "positive")
when(>(x 0) println("positive"))
when(>(x 0) println("positive") do-something(x))
cond(
>(x 0) "positive"
==(x 0) "zero"
:else "negative")
case(x
1 "one"
2 "two"
"default")
loop([i 0, acc []]
if(>=(i 10)
acc
recur(inc(i) conj(acc i))))
for([x xs, y ys, :when >(x y)] [x y])
Produces: (for [x xs y ys :when (> x y)] [x y])
Modifiers :when, :while, and :let are passed through:
for([x xs, :let [y *(x 10)], :while <(y 100)] y)
Same binding syntax as for, but for side effects:
doseq([x items, :when active?(x)] println(x))
try(
dangerous-operation()
catch(Exception e log/error("Failed:" e) default-value)
finally(cleanup()))
catch and finally are regular calls inside try's arguments.
Threading macros are just calls:
->(account update(:balance *(1.05)) assoc(:status :processed))
->>(accounts filter(:active) map(:balance) reduce(+))
Multi-line:
->>(accounts
filter(:active)
map(fn([a] update(a :balance *(:balance(a) 1.05))))
remove(fn([a] neg?(:balance(a))))
reduce(+ 0))
ns(my.accounts
:require(
[clojure.string :as str]
[clojure.set :as set]
[my.db :refer [query connect]])
:import(
[java.util Date UUID]))
.toUpperCase("hello") ;; method call
Math/abs(-1) ;; static method
java.util.Date.() ;; constructor
.-x(point) ;; field access
def(state atom({:count 0}))
swap!(state update(:count inc))
@state
defprotocol(Drawable draw([this canvas]) bounds([this]))
defrecord(Circle [center radius])
defrecord(Circle [center radius]
Drawable
draw([this canvas] draw-circle(canvas this))
bounds([this] get-bounds(this)))
deftype(Point [x y])
deftype(Point [x y]
Drawable
draw([this canvas] render(canvas .-x(this) .-y(this))))
reify(Object toString([this] "hello"))
defmulti(area :shape)
defmethod(area :circle [{:keys [radius]}] *(Math/PI radius radius))
All of these work exactly as in Clojure:
[1 2 3], {:a 1}, #{1 2 3}, :keyword, "string"@deref, ^metadata, #'var, #_discard, 'quote#(inc(%)) → (fn [%1] (inc %1)), #(rand()) → (fn [] (rand)). Body must be a single expression. Uses meme syntax inside.#"pattern"\a, \newline, \space#inst, #uuid::foo — in the file runner, deferred to eval time so ::foo resolves in the file's declared namespace (not the caller's). In the REPL, resolved at read time (like Clojure). When using meme->forms directly without :resolve-keyword, deferred to eval time via (read-string "::foo"). On CLJS, :resolve-keyword is required (errors without it)#?(:clj x :cljs y) — parsed natively; the matching platform branch is selected at read time#:ns{}; comment'x quotes the next meme form. 'f(x y) produces (quote (f x y)). '() is (quote ()). 'nil(1 2) produces (quote (nil 1 2)). Note: '(1 2) is an error — bare parentheses need a head| Clojure | meme | Notes |
|---|---|---|
(f x y) | f(x y) | Parens follow the callable |
'(f x) | 'f(x) | Quote applies to the next meme form |
Quote applies to the next meme form. 'f(x) produces (quote (f x)).
'foo, '42, ':kw, '() all work. Any value can be a head inside quote:
'nil(1 2) produces (quote (nil 1 2)). Bare parentheses without a head
remain an error: '(1 2) fails — write 'list(1 2) or list(1 2) instead.
Backtick uses meme syntax inside. Syntax-quote (`) is parsed natively.
~ (unquote) and ~@ (unquote-splicing) work as prefix operators.
Example: `if(~test do(~@body))
#() uses meme syntax inside. #(inc(%)) is (fn [%1] (inc %1)).
The call rule applies normally within #(). Use %, %1, %2 for
params.
meme serves as a platform for guest languages. A guest language can define:
Guest languages are defined as EDN files and registered via meme.registry.
They are dispatched by file extension. See examples/languages/ for working
examples (prefix).
A lang registers one or more file extensions. Both :extension (string) and :extensions (vector) are accepted; both are normalized to a single :extensions vector:
;; EDN file — single extension
{:extension ".prefix"
:run "examples/languages/prefix/core.meme"
:format :meme}
;; Runtime — multiple extensions
(registry/register! :my-lang {:extensions [".ml" ".mlx"]
:run 'my-lang.run/run-string})
The CLI auto-detects the lang from file extension: meme run app.prefix resolves to the :prefix lang. run-file does the same. When multiple extensions are registered, all are recognized.
After install! (called automatically by run-file, the REPL, and the CLI), require and load-file handle .meme files transparently:
;; In the meme REPL or a running .meme file:
require('[my-lib.core :as lib]) ; finds my_lib/core.meme on classpath (JVM only)
load-file("examples/demo.meme") ; loads by filesystem path (JVM + Babashka)
Files with registered lang extensions take precedence over .clj when both exist. Core namespaces (clojure.*, java.*, etc.) are protected by a denylist and cannot be shadowed.
Babashka limitation: require of .meme namespaces is JVM-only (Babashka's SCI bypasses clojure.core/load). load-file works on both platforms.
For environments where runtime loading isn't available (Babashka require, nREPL, CI), precompile .meme to .clj:
bb meme compile src/ --out target/classes
Add the output directory to :paths in deps.edn or bb.edn. Standard require then works without any runtime patching.
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 |