All namespaces live under meme.alpha to signal that the API is pre-1.0 and may change. When the API stabilizes, namespaces will move to meme.
The public API for reading and printing meme syntax, organized in three tracks:
text-to-form form-to-text
meme str ──→ meme->forms ──→ forms ──→ forms->meme ──→ meme str
clj str ──→ clj->forms ──→ forms ──→ forms->clj ──→ clj str
text-to-text (compositions)
meme str ──→ meme->clj ──→ clj str
clj str ──→ clj->meme ──→ meme str
(meme.alpha.core/meme->forms s)
(meme.alpha.core/meme->forms s opts)
Read a meme source string. Returns a vector of Clojure forms. All platforms.
Options:
:resolve-keyword — function that resolves auto-resolve keyword strings ("::foo") to keywords at read time. When absent on JVM/Babashka, :: keywords are deferred to eval time via (read-string "::foo"). Required on CLJS (errors without it, since cljs.reader cannot resolve :: in the correct namespace).:read-cond — :preserve to return ReaderConditional objects instead of evaluating reader conditionals. Default: evaluate #? for the current platform. Use :preserve for lossless clj->meme->clj roundtrips of .cljc files.:resolve-symbol — function that resolves symbols during syntax-quote expansion (e.g., foo → my.ns/foo). On JVM/Babashka, run-string/run-file/start inject a default that matches Clojure's SyntaxQuoteReader (see meme.alpha.runtime.resolve/default-resolve-symbol). When calling meme->forms directly, symbols in syntax-quote are left unqualified unless this option is provided. On CLJS, no default is available.(meme->forms "+(1 2 3)")
;=> [(+ 1 2 3)]
(meme->forms "def(x 42)\nprintln(x)")
;=> [(def x 42) (println x)]
(meme.alpha.core/forms->meme forms)
Print a sequence of Clojure forms as meme text. All platforms.
(forms->meme ['(+ 1 2 3)])
;=> "+(1 2 3)"
(meme.alpha.core/forms->clj forms)
Print Clojure forms as a Clojure source string. All platforms.
(forms->clj ['(def x 42) '(println x)])
;=> "(def x 42)\n\n(println x)"
(meme.alpha.core/clj->forms clj-src)
Read a Clojure source string, return a vector of forms. JVM/Babashka only.
(clj->forms "(defn f [x] (+ x 1))")
;=> [(defn f [x] (+ x 1))]
(meme.alpha.core/format-meme forms)
(meme.alpha.core/format-meme forms opts)
Format Clojure forms as canonical (width-aware, multi-line) meme text. Uses indented parenthesized form for calls that exceed the line width. Preserves comments from :ws metadata (attached by the pipeline's scan stage). All platforms.
Options:
:width — target line width (default: 80)(format-meme ['(defn greet [name] (println (str "Hello " name)))])
;=> "defn(greet [name]\n println(str(\"Hello \" name)))"
(meme.alpha.core/meme->clj meme-src)
(meme.alpha.core/meme->clj meme-src opts)
Convert meme source string to Clojure source string. All platforms. Equivalent to (forms->clj (meme->forms meme-src opts)).
Options: same as meme->forms (:resolve-keyword, :read-cond).
(meme->clj "println(\"hello\")")
;=> "(println \"hello\")"
(meme.alpha.core/clj->meme clj-src)
Convert a Clojure source string to meme source string. JVM/Babashka only. Equivalent to (forms->meme (clj->forms clj-src)).
(clj->meme "(defn f [x] (+ x 1))")
;=> "defn(f [x] +(x 1))"
(meme.alpha.core/run-pipeline source)
(meme.alpha.core/run-pipeline source opts)
Run the full pipeline: source → scan → parse. Returns a context map with intermediate state. All platforms. Useful for tooling that needs access to raw tokens, tokens, or parsed forms.
(run-pipeline "foo(1 2)")
;=> {:source "foo(1 2)"
; :opts nil
; :raw-tokens [...tokenizer output with :ws...]
; :tokens [...same as :raw-tokens...]
; :forms [(foo 1 2)]}
Low-level reader API.
(meme.alpha.parse.reader/read-meme-string-from-tokens tokens)
(meme.alpha.parse.reader/read-meme-string-from-tokens tokens opts source)
Parse a token vector into Clojure forms. Used by meme.alpha.pipeline/step-parse. Most callers should use meme.alpha.core/meme->forms instead.
tokens — a token vector (output of meme.alpha.scan.tokenizer/tokenize)opts — same options as meme->forms (e.g., :resolve-keyword)source — original source text for error context (optional)Low-level Doc tree builder. Most callers should use formatter.flat or formatter.canon instead.
(meme.alpha.emit.printer/to-doc form mode)
Convert a Clojure form to a Wadler-Lindig Doc tree. mode is :meme (call notation) or :clj (standard Clojure with reader sugar). The Doc tree is passed to render/layout for final string output.
(meme.alpha.emit.printer/extract-comments ws)
Extract comment lines from a :ws metadata string. Returns a vector of trimmed comment strings, or nil.
Flat (single-line) formatter. Composes printer + render at infinite width.
(meme.alpha.emit.formatter.flat/format-form form)
Format a single Clojure form as flat meme text (single-line).
(format-form '(+ 1 2))
;=> "+(1 2)"
(format-form '(:balance account))
;=> ":balance(account)"
(format-form '(def x 42))
;=> "def(x 42)"
(meme.alpha.emit.formatter.flat/format-forms forms)
Format a sequence of Clojure forms as flat meme text, separated by blank lines.
(meme.alpha.emit.formatter.flat/format-clj forms)
Format Clojure forms as Clojure text with reader sugar ('quote, @deref, #'var).
Canonical (width-aware) formatter. Composes printer + render at target width. Used by meme format CLI.
(meme.alpha.emit.formatter.canon/format-form form)
(meme.alpha.emit.formatter.canon/format-form form opts)
Format a single Clojure form as canonical meme text. Width-aware — uses indented multi-line layout for forms that exceed the target width. Preserves comments from :ws metadata.
Options:
:width — target line width (default: 80)(meme.alpha.emit.formatter.canon/format-forms forms)
(meme.alpha.emit.formatter.canon/format-forms forms opts)
Format a sequence of Clojure forms as canonical meme text, separated by blank lines. Preserves comments from :ws metadata, including trailing comments after the last form.
(meme.alpha.runtime.repl/input-state s)
(meme.alpha.runtime.repl/input-state s opts)
Returns the parse state of a meme input string: :complete (parsed successfully), :incomplete (unclosed delimiter — keep reading), or :invalid (malformed, non-recoverable error). Used internally by the REPL for multi-line input handling; also useful for editor integration.
The optional opts map is forwarded to pipeline/run — useful for callers that need :: keywords or custom parsers to be resolved during input validation.
(input-state "+(1 2)") ;=> :complete
(input-state "f(") ;=> :incomplete
(input-state "(bare parens)") ;=> :invalid
(meme.alpha.runtime.repl/start)
(meme.alpha.runtime.repl/start opts)
Start the meme REPL. Reads meme syntax, evaluates as Clojure, prints results.
Options:
:read-line — custom line reader function (default: read-line, required on ClojureScript):eval — custom eval function (default: eval, required on ClojureScript):resolve-keyword — function to resolve :: keywords at read time (default: clojure.core/read-string on JVM; required on CLJS for code that uses :: keywords):prelude — vector of forms to eval before the first user input (e.g., guest language standard library)On JVM/Babashka, :resolve-symbol is automatically injected (matching Clojure's syntax-quote resolution) unless explicitly provided.
$ bb meme
meme REPL. Type meme expressions, balanced input to eval. Ctrl-D to exit.
user=> +(1 2)
3
user=> map(inc [1 2 3])
(2 3 4)
The prompt shows the current namespace (e.g., user=> on JVM/Babashka, meme=> on ClojureScript).
Run .meme files or meme source strings.
(meme.alpha.runtime.run/run-string s)
(meme.alpha.runtime.run/run-string s eval-fn)
(meme.alpha.runtime.run/run-string s opts)
Read meme source string, eval each form, return the last result. Strips leading #! shebang lines before parsing. The second argument can be an eval function (backward compatible) or an opts map.
Options (when passing a map):
:eval — eval function (default: eval; required on CLJS):resolve-keyword — function to resolve :: keywords at read time (default: none — :: keywords resolve at eval time in the file's declared namespace. Required on CLJS for code that uses :: keywords):prelude — vector of forms to eval before user code (e.g., guest language standard library):rewrite-rules — vector of rewrite rules applied to forms after expansion (see meme.alpha.rewrite):rewrite-max-iters — max rewrite iterations per form (default: 100)On JVM/Babashka, :resolve-symbol is automatically injected (matching Clojure's syntax-quote resolution) unless explicitly provided.
(run-string "def(x 42)\n+(x 1)")
;=> 43
(meme.alpha.runtime.run/run-file path)
(meme.alpha.runtime.run/run-file path eval-fn)
(meme.alpha.runtime.run/run-file path opts)
Read and eval a .meme file. Returns the last result. Uses slurp internally (JVM/Babashka only). Second argument follows same convention as run-string.
Automatically detects guest languages from file extension via meme.alpha.platform.registry/resolve-lang. If a registered language matches the extension, its prelude, rules, and/or custom parser are merged into the run options.
(run-file "test/examples/tests/01_core_rules.meme")
Explicit pipeline composition. Each stage is a ctx → ctx function operating on a shared context map with keys :source, :opts, :raw-tokens, :tokens, :forms.
(meme.alpha.pipeline/step-scan ctx)
Tokenize source text into tokens with whitespace attachment. Reads :source from ctx, assocs both :tokens and :raw-tokens (identical; :raw-tokens retained for backward compatibility). Each token carries a :ws key with the leading whitespace and comments between it and the previous token. Trailing whitespace (after the last token) is stored as :trailing-ws metadata on the token vector. This is how the pretty-printer preserves comments.
(meme.alpha.pipeline/step-parse ctx)
Parse tokens into Clojure forms. Reads :tokens, :opts, :source from ctx, assocs :forms.
(meme.alpha.pipeline/step-expand-syntax-quotes ctx)
Expand syntax-quote AST nodes (MemeSyntaxQuote) into plain Clojure forms (seq/concat/list). Also unwraps MemeRaw values. Only needed before eval — tooling paths work with AST nodes directly.
Note: run intentionally omits this stage so tooling can access the unexpanded forms. Runtime paths (run-string, run-file) include step-expand-syntax-quotes in their pipeline.
(meme.alpha.pipeline/step-rewrite ctx)
Apply rewrite rules to :forms. Rules come from (get-in ctx [:opts :rewrite-rules]). No-op if no rules are provided. Each form is rewritten independently, bottom-up to fixpoint (bounded by :rewrite-max-iters, default 100).
Used by run-string for guest language transforms. Not included in pipeline/run (tooling path).
(meme.alpha.pipeline/run source)
(meme.alpha.pipeline/run source opts)
Run the pipeline: step-scan → step-parse. Returns the complete context map. Does not include step-expand-syntax-quotes — forms contain AST nodes (MemeSyntaxQuote, MemeRaw) for tooling access. Call step-expand-syntax-quotes separately if you need eval-ready forms.
(meme.alpha.pipeline/run "+(1 2)")
;=> {:source "+(1 2)", :opts nil,
; :raw-tokens [...], :tokens [...], :forms [(+ 1 2)]}
Formal contract for the pipeline context map. Provides clojure.spec.alpha specs for the context at each stage boundary, a toggleable runtime validator, and explain functions for debugging and tooling.
| Spec | Describes |
|---|---|
::token | A single token map: :type, :value, :line, :col, :offset (required); :end-line, :end-col, :end-offset, :ws (optional) |
::token-vector | Vector of tokens |
::opts | Reader options map (nilable): :resolve-keyword, :read-cond, :resolve-symbol |
::forms | Vector of parsed forms (any Clojure value) |
::ctx-input | Context as provided by the caller: {:source string, :opts map?} |
::ctx-after-scan | Context after scan: adds :raw-tokens and :tokens |
::ctx-after-parse | Context after parse: adds :forms |
::ctx-after-expand | Context after expand: :forms replaced with expanded forms |
meme.alpha.pipeline.contract/*validate*
Dynamic var. When bound to true, pipeline stages validate context maps at input and output against the specs above. Default: false (zero overhead).
(binding [meme.alpha.pipeline.contract/*validate* true]
(pipeline/run "+(1 2)"))
(meme.alpha.pipeline.contract/validate! stage phase ctx)
Check ctx against the contract for the given stage (:scan, :parse, :expand) and phase (:input or :output). Throws ex-info with :stage, :phase, and :problems in ex-data. No-op when *validate* is false.
(meme.alpha.pipeline.contract/explain-context :scan :input {:source 42})
;=> "42 - failed: string? in: [:source] at: [:source] ..."
Return a human-readable explanation string, or nil if valid. Always runs (not gated by *validate*).
(meme.alpha.pipeline.contract/valid? :parse :output ctx) ;=> true/false
Check without throwing. Not gated by *validate*.
A guest parser replacing the parse stage must produce a context map conforming to ::ctx-after-parse:
(require '[meme.alpha.pipeline.contract :as contract])
(binding [contract/*validate* true]
(let [ctx (-> {:source src :opts opts}
pipeline/step-scan
my-custom-parse)]
ctx))
Source-position utilities shared across pipeline stages. Defines how (line, col) maps to character offsets — used by the tokenizer's attach-whitespace pass.
(meme.alpha.scan.source/line-col->offset source line col)
Convert 1-indexed line and column to a 0-indexed character offset in source. Used by attach-whitespace (tokenizer) to locate token positions in the original source string. Returns the source length if the position is past the end.
Unified CLI for meme. Implemented in meme syntax (cli.meme), loaded by a .clj shim. JVM/Babashka only.
| Command | Description |
|---|---|
meme run <file> | Run a .meme file |
meme repl | Start the meme REPL |
meme convert <file\|dir> | Convert between .meme and .clj (direction detected from extension) |
meme format <file\|dir> | Format .meme files via canonical formatter (in-place by default, --stdout to print) |
meme version | Print version |
All file commands accept directories (processed recursively) and multiple paths. convert and format accept --stdout to print to stdout instead of writing files.
Entry point: -main dispatches via babashka.cli. For Clojure JVM, use -T:meme (e.g., clojure -T:meme run :file '"hello.meme"').
Native value resolution. Converts raw token text to Clojure values — no read-string delegation. Consistent error wrapping and location info.
All resolvers take the raw token text and a loc map ({:line N :col M}) for error reporting:
(meme.alpha.parse.resolve/resolve-number raw loc) ;; "42" → 42
(meme.alpha.parse.resolve/resolve-string raw loc) ;; "\"hi\"" → "hi"
(meme.alpha.parse.resolve/resolve-char raw loc) ;; "\\newline" → \newline
(meme.alpha.parse.resolve/resolve-regex raw loc) ;; "#\"\\d+\"" → #"\d+"
(meme.alpha.parse.resolve/resolve-auto-keyword raw loc resolve-fn)
Resolve an auto-resolve keyword (::foo). If resolve-fn is provided, resolves at read time. Otherwise, defers to eval time via (read-string "::foo").
(meme.alpha.parse.resolve/resolve-tagged-literal tag data loc)
Resolve a tagged literal. JVM: produces a TaggedLiteral object via clojure.core/tagged-literal. CLJS: throws an error.
The meme reader fails fast on invalid input. Parse errors are thrown as ex-info exceptions with :line and :col data when available.
Common errors:
| Error message | Cause |
|---|---|
Expected :close-paren but got EOF | Unclosed ( |
Unquote (~) outside syntax-quote | ~ only valid inside ` |
(try
(meme.alpha.core/meme->forms "foo(")
(catch Exception e
(ex-data e)))
;=> {:line 1, :col 4}
Error recovery is not supported — the reader stops at the first error. This is documented as future work in the PRD.
Error infrastructure used by the tokenizer and reader. Portable (.cljc).
(meme.alpha.errors/source-context source line)
Extract the source line at a 1-indexed line number from source (a string). Returns the line text, or nil if out of range.
(meme.alpha.errors/meme-error message opts)
Throw ex-info with a consistent error structure. opts is a map with:
:line, :col — source location (appended to message as (line N, col M)):cause — optional upstream exception:incomplete — when true, signals the REPL that more input may complete the formAll tokenizer and reader errors go through this function.
(meme.alpha.errors/format-error exception source)
Format an exception for display. Produces a multi-line string with:
"Error: ")^) or span underline (~~~) pointing at the column(s) — uses ^ for single-column errors, ~ for multi-column spans when :end-col is present in ex-data:secondary is present in ex-data — a sequence of {:line :col :label} maps):hint is present in ex-data)If source is nil/blank or the exception lacks :line/:col, only the prefixed message is returned.
Pattern matching and term rewriting engine. Used by step-rewrite for guest language transforms. Also usable directly for symbolic computation.
(meme.alpha.rewrite/match-pattern pattern expr)
(meme.alpha.rewrite/match-pattern pattern expr bindings)
Match a pattern against an expression. Returns a bindings map {symbol value} on success, nil on failure.
Pattern syntax:
?x — match any single value, bind to x??x — splice variable, match zero or more elements in a sequence_ — wildcard, match anything, no binding(match-pattern '?x 42) ;=> {x 42}
(match-pattern '(f ?x) '(f 1)) ;=> {x 1}
(match-pattern '(+ ??xs) '(+ 1 2 3)) ;=> {xs (1 2 3)}
(meme.alpha.rewrite/substitute template bindings)
Replace pattern variables in template with values from bindings. Splice variables (??x) splice their seq into the parent list.
(meme.alpha.rewrite/make-rule name pattern replacement)
(meme.alpha.rewrite/make-rule name pattern replacement guard)
(meme.alpha.rewrite/rule pattern replacement)
(meme.alpha.rewrite/rule pattern replacement guard)
Create a rewrite rule. guard is an optional (fn [bindings] bool).
(meme.alpha.rewrite/apply-rule rule expr) ;=> rewritten expr or nil
(meme.alpha.rewrite/apply-rules rules expr) ;=> first matching rule's result or nil
(meme.alpha.rewrite/rewrite rules expr)
(meme.alpha.rewrite/rewrite rules expr max-iters)
Apply rules repeatedly (bottom-up) until fixpoint or max-iters (default 100). Returns the final expression.
(meme.alpha.rewrite/rewrite-once rules expr) ;=> [changed? result]
(meme.alpha.rewrite/rewrite-top rules expr) ;=> top-level only, to fixpoint
(meme.alpha.rewrite/defrule identity-plus (+ ?a 0) => ?a)
(meme.alpha.rewrite/defrule-guard pos-check ?x => :pos (fn [b] (pos? (b 'x))))
(meme.alpha.rewrite/ruleset
(+ ?a 0) => ?a
(* ?a 1) => ?a)
Predefined rule sets for S-expression ↔ M-expression transformations.
Rules that tag S-expression calls as m-call nodes. List patterns only match lists (not vectors).
Rules that convert m-call nodes back to S-expression lists.
Rules that flatten tagged tree nodes to Clojure forms.
(meme.alpha.rewrite.rules/transform-structures form)
Walk a tree and convert structural tags to Clojure data/AST nodes.
Token vector → tagged tree builder for the rewrite-based pipeline.
(meme.alpha.rewrite.tree/tokens->tree tokens)
Convert a flat token vector to a tagged tree. Returns a vector of top-level forms.
(meme.alpha.rewrite.tree/build-tree tokens pos)
Build a tagged tree node from tokens starting at pos. Returns [node new-pos].
(meme.alpha.rewrite.tree/rewrite-parser tokens opts source)
Parser that conforms to the pipeline contract: (fn [tokens opts source] → forms). Uses the rewrite-based pipeline: tokens → tagged tree → rules → structures. Drop-in replacement for meme.alpha.parse.reader/read-meme-string-from-tokens.
Serializes m-call tagged trees to meme text.
(meme.alpha.rewrite.emit/emit form)
Convert a form (with m-call tags) to meme text string.
(meme.alpha.rewrite.emit/emit-forms forms)
Emit a sequence of top-level forms as meme text, separated by newlines.
Guest language registration. Maps language names (keywords) to configurations. Used by run-file for automatic language dispatch based on file extension.
(meme.alpha.platform.registry/register! lang-name config)
Register a guest language. lang-name is a keyword. Config keys:
:extension — file extension (e.g. ".calc"):prelude-file — path to prelude .meme file (eval'd before user code):rules-file — path to rules .meme file (eval'd, must return rule vector):prelude — prelude forms (alternative to :prelude-file):rules — rule vector (alternative to :rules-file):parser — custom parser fn: (fn [tokens opts source] forms-vector). If nil, uses the default meme parser.(meme.alpha.platform.registry/resolve-lang path)
Given a file path, determine the guest language from its extension. Returns the language name keyword, or nil for unrecognized extensions. .meme files return nil (default meme, no guest language).
(meme.alpha.platform.registry/lang-config lang-name)
Get the config for a registered language. Returns nil if not found.
(meme.alpha.platform.registry/registered-langs)
List all registered language names (keywords).
(meme.alpha.platform.registry/clear!)
Clear all registered languages. For testing.
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 |