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 and the REPL), require in .meme code finds both .meme and .clj namespaces on the classpath. 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.
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 |