Liking cljdoc? Tell your friends :D

meme Language Reference

Complete syntax reference for writing .meme code.

The Rule

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:

  • Symbols: f(x)(f x)
  • Keywords: :active(m)(:active m)
  • Vectors: [x](body)([x] body) (multi-arity clauses)
  • Maps/sets: {:a 1}(:a)({:a 1} :a)
  • Literals: nil(1 2)(nil 1 2), true(:a)(true :a)

Adjacency required

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 ()

Binding

def

def(x 42)
def(state atom({:count 0}))

let

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))

Functions

defn

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

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- (private)

defn-(helper [x] +(x 1))

defmacro

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)))

Control Flow

if

if(>(x 0) "positive" "negative")
if(>(x 0) "positive")

when

when(>(x 0) println("positive"))
when(>(x 0) println("positive") do-something(x))

cond

cond(
  >(x 0)   "positive"
  ==(x 0)  "zero"
  :else    "negative")

case

case(x
  1 "one"
  2 "two"
  "default")

loop/recur

loop([i 0, acc []]
  if(>=(i 10)
    acc
    recur(inc(i) conj(acc i))))

for

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)

doseq

Same binding syntax as for, but for side effects:

doseq([x items, :when active?(x)] println(x))

Error Handling

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

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))

Namespace

ns(my.accounts
  :require(
    [clojure.string :as str]
    [clojure.set :as set]
    [my.db :refer [query connect]])
  :import(
    [java.util Date UUID]))

Java Interop

.toUpperCase("hello")           ;; method call
Math/abs(-1)                    ;; static method
java.util.Date.()               ;; constructor
.-x(point)                      ;; field access

Concurrency

def(state atom({:count 0}))
swap!(state update(:count inc))
@state

Protocols and Records

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)))

Types

deftype(Point [x y])

deftype(Point [x y]
  Drawable
  draw([this canvas] render(canvas .-x(this) .-y(this))))

Reify

reify(Object toString([this] "hello"))

Multimethods

defmulti(area :shape)

defmethod(area :circle [{:keys [radius]}] *(Math/PI radius radius))

Unchanged from Clojure

All of these work exactly as in Clojure:

  • Data literals: [1 2 3], {:a 1}, #{1 2 3}, :keyword, "string"
  • Reader macros: @deref, ^metadata, #'var, #_discard, 'quote
  • Anonymous functions: #(inc(%))(fn [%1] (inc %1)), #(rand())(fn [] (rand)). Body must be a single expression. Uses meme syntax inside.
  • Regex: #"pattern"
  • Character literals: \a, \newline, \space
  • Tagged literals: #inst, #uuid
  • Auto-resolve keywords: ::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)
  • Reader conditionals: #?(:clj x :cljs y) — parsed natively; the matching platform branch is selected at read time
  • Namespaced maps: #:ns{}
  • Destructuring in all binding positions
  • Commas are whitespace
  • Line comments: ; comment
  • Quote: '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

What's Different from Clojure

ClojurememeNotes
(f x y)f(x y)Parens follow the callable
'(f x)'f(x)Quote applies to the next meme form

Design Boundaries

  • 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.

Guest Languages

meme serves as a platform for guest languages. A guest language can define:

  1. A prelude — forms eval'd before user code (standard library)
  2. A custom parser — optionally replacing the meme parser entirely

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).

Extension registration

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.

Namespace loader

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

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close