meme public API: read and print meme syntax.
Three tracks: text-to-form: meme->forms, forms->meme (all platforms) form-to-text: forms->clj (all platforms), clj->forms (JVM only) text-to-text: meme->clj (all platforms), clj->meme (JVM only)
Stages: meme.stages/run — scan + parse stages with intermediate state (no expand/rewrite)
meme public API: read and print meme syntax. Three tracks: text-to-form: meme->forms, forms->meme (all platforms) form-to-text: forms->clj (all platforms), clj->forms (JVM only) text-to-text: meme->clj (all platforms), clj->meme (JVM only) Stages: meme.stages/run — scan + parse stages with intermediate state (no expand/rewrite)
Canonical formatter: width-aware meme output.
Composes printer (form → Doc) with render (layout @ target width).
Used by meme format CLI command.
Canonical formatter: width-aware meme output. Composes printer (form → Doc) with render (layout @ target width). Used by `meme format` CLI command.
Flat formatter: single-line meme output. Composes printer (form → Doc) with render (layout @ infinite width).
Flat formatter: single-line meme output. Composes printer (form → Doc) with render (layout @ infinite width).
Meme printer: Clojure forms → Doc trees. Builds Wadler-Lindig Doc trees from Clojure forms, handling meme syntax (call notation, sugar, metadata, comments) and Clojure output mode. Delegates to render for Doc algebra and layout.
Meme printer: Clojure forms → Doc trees. Builds Wadler-Lindig Doc trees from Clojure forms, handling meme syntax (call notation, sugar, metadata, comments) and Clojure output mode. Delegates to render for Doc algebra and layout.
Wadler-Lindig document algebra and layout engine. Generic — no meme-specific knowledge. Reusable for any pretty-printing task.
Doc types form a small algebra: DocText — literal string DocLine — newline+indent (or flat-alt when flat) DocCat — concatenation DocNest — increase indent DocGroup — try flat, break if too wide DocIfBreak — conditional on flat/break mode
layout renders a Doc tree to a string at a given page width. Use ##Inf for single-line (flat) rendering.
Wadler-Lindig document algebra and layout engine. Generic — no meme-specific knowledge. Reusable for any pretty-printing task. Doc types form a small algebra: DocText — literal string DocLine — newline+indent (or flat-alt when flat) DocCat — concatenation DocNest — increase indent DocGroup — try flat, break if too wide DocIfBreak — conditional on flat/break mode layout renders a Doc tree to a string at a given page width. Use ##Inf for single-line (flat) rendering.
Consistent error infrastructure for the meme reader/tokenizer.
All error throw sites should use meme-error to ensure uniform
location tracking and message formatting.
Consistent error infrastructure for the meme reader/tokenizer. All error throw sites should use `meme-error` to ensure uniform location tracking and message formatting.
Shared form-level predicates and constructors. Cross-stage contracts that both the parser and printer depend on.
Shared form-level predicates and constructors. Cross-stage contracts that both the parser and printer depend on.
Lang registry, resolution, and EDN loading.
A lang is a map of command functions: :run (fn [source opts] → result) — run a file :repl (fn [opts] → nil) — interactive loop :format (fn [source opts] → text) — format a file :to-clj (fn [source] → clj-text) — convert meme→clj (self-contained) :to-meme (fn [source] → meme-text) — convert clj→meme (JVM only, self-contained)
Plus optional metadata: :extension ".ext" — file extension for auto-detection
Every key is optional. A lang supports exactly the commands it has keys for. The CLI dispatches by looking up the command key in the lang map.
All lang definitions — built-in and user-defined — are EDN: {:run meme.runtime.run/run-string :format meme.lang.meme-classic/format-meme :to-clj meme.lang.meme-classic/to-clj :to-meme meme.lang.meme-classic/to-meme}
User langs can also use: {:extension ".calc" ;; file extension for auto-detection :run "core.meme" ;; string → .meme file to eval before user file :rules "rules.meme" ;; string → .meme file returning rewrite rules :parser my.ns/parser-fn ;; symbol → custom parser function :format :meme-classic} ;; keyword → inherit command from built-in lang
Built-in langs (resources/meme/lang/): :meme-classic (default), :meme-rewrite, :meme-trs
Lang registry, resolution, and EDN loading.
A lang is a map of command functions:
:run (fn [source opts] → result) — run a file
:repl (fn [opts] → nil) — interactive loop
:format (fn [source opts] → text) — format a file
:to-clj (fn [source] → clj-text) — convert meme→clj (self-contained)
:to-meme (fn [source] → meme-text) — convert clj→meme (JVM only, self-contained)
Plus optional metadata:
:extension ".ext" — file extension for auto-detection
Every key is optional. A lang supports exactly the commands it has keys for.
The CLI dispatches by looking up the command key in the lang map.
All lang definitions — built-in and user-defined — are EDN:
{:run meme.runtime.run/run-string
:format meme.lang.meme-classic/format-meme
:to-clj meme.lang.meme-classic/to-clj
:to-meme meme.lang.meme-classic/to-meme}
User langs can also use:
{:extension ".calc" ;; file extension for auto-detection
:run "core.meme" ;; string → .meme file to eval before user file
:rules "rules.meme" ;; string → .meme file returning rewrite rules
:parser my.ns/parser-fn ;; symbol → custom parser function
:format :meme-classic} ;; keyword → inherit command from built-in lang
Built-in langs (resources/meme/lang/):
:meme-classic (default), :meme-rewrite, :meme-trsmeme-classic: recursive-descent parser + Wadler-Lindig printer.
The default lang. Supports all commands: :run, :repl, :format, :to-clj, :to-meme.
meme-classic: recursive-descent parser + Wadler-Lindig printer. The default lang. Supports all commands: :run, :repl, :format, :to-clj, :to-meme.
meme-rewrite: tree builder + rewrite rules.
Alternative parser that builds explicit m-call/bracket/brace tagged trees, then applies rewrite rules to transform to S-expressions. Supports all commands: :run, :repl, :format, :to-clj, :to-meme.
meme-rewrite: tree builder + rewrite rules. Alternative parser that builds explicit m-call/bracket/brace tagged trees, then applies rewrite rules to transform to S-expressions. Supports all commands: :run, :repl, :format, :to-clj, :to-meme.
meme-trs: token-stream term rewriting.
Operates at the token level: tokenize → nest → rewrite → flatten → text. Bypasses the recursive-descent parser entirely for the meme→clj direction. Supports :run, :format, :to-clj, :to-meme. No :repl yet.
meme-trs: token-stream term rewriting. Operates at the token level: tokenize → nest → rewrite → flatten → text. Bypasses the recursive-descent parser entirely for the meme→clj direction. Supports :run, :format, :to-clj, :to-meme. No :repl yet.
Syntax-quote expansion: MemeSyntaxQuote AST nodes → plain Clojure forms. Called by runtime paths (run, repl) before eval. Not needed for tooling (tooling works with AST nodes directly).
Syntax-quote expansion: MemeSyntaxQuote AST nodes → plain Clojure forms. Called by runtime paths (run, repl) before eval. Not needed for tooling (tooling works with AST nodes directly).
meme reader: recursive-descent parser. Transforms meme tokens into Clojure forms.
meme reader: recursive-descent parser. Transforms meme tokens into Clojure forms.
Value resolution: converts raw token text to Clojure values. All resolution is native — no delegation to read-string.
Value resolution: converts raw token text to Clojure values. All resolution is native — no delegation to read-string.
Term rewriting engine.
Patterns: ?x — match anything, bind to x ??x — match zero or more (splice), bind to x (f ?x ?y) — match a list with head f, bind x and y _ — match anything, don't bind
Rules: (defrule name pattern => replacement)
Engine: (rewrite rules expr) — apply rules bottom-up to fixed point (rewrite-once rules expr) — one bottom-up pass (rewrite-top rules expr) — top-level only to fixed point
Term rewriting engine. Patterns: ?x — match anything, bind to x ??x — match zero or more (splice), bind to x (f ?x ?y) — match a list with head f, bind x and y _ — match anything, don't bind Rules: (defrule name pattern => replacement) Engine: (rewrite rules expr) — apply rules bottom-up to fixed point (rewrite-once rules expr) — one bottom-up pass (rewrite-top rules expr) — top-level only to fixed point
Serialize m-call tagged trees to meme text. Companion to meme.rewrite.rules — renders the output of S→M rules.
Serialize m-call tagged trees to meme text. Companion to meme.rewrite.rules — renders the output of S→M rules.
Rewrite rule sets for S→M and M→S transformations. Each direction is a vector of rules for meme.rewrite/rewrite.
Rewrite rule sets for S→M and M→S transformations. Each direction is a vector of rules for meme.rewrite/rewrite.
Token vector → tagged tree for the rewrite-based M→S pipeline. Produces a tree with explicit m-call nodes for adjacency-based calls, structural tags (bracket, brace, set-lit, anon-fn), and prefix markers (meme/quote, meme/deref, etc.).
This is the bridge between the shared tokenizer and the rewrite engine. The existing parser (meme.parse.reader) does tree-building and M→S transformation in one pass. This module separates them: build a raw tagged tree here, then let rewrite rules handle the rest.
Token vector → tagged tree for the rewrite-based M→S pipeline. Produces a tree with explicit m-call nodes for adjacency-based calls, structural tags (bracket, brace, set-lit, anon-fn), and prefix markers (meme/quote, meme/deref, etc.). This is the bridge between the shared tokenizer and the rewrite engine. The existing parser (meme.parse.reader) does tree-building and M→S transformation in one pass. This module separates them: build a raw tagged tree here, then let rewrite rules handle the rest.
Unified CLI. Commands dispatch through lang maps.
Unified CLI. Commands dispatch through lang maps.
meme REPL: read meme, eval as Clojure, print result.
meme REPL: read meme, eval as Clojure, print result.
Default symbol resolution for syntax-quote expansion. Matches Clojure's SyntaxQuoteReader behavior: special forms stay unqualified, vars resolve to their defining namespace, unresolved symbols get current-namespace qualification. JVM/Babashka only — CLJS callers must provide their own resolver.
Default symbol resolution for syntax-quote expansion. Matches Clojure's SyntaxQuoteReader behavior: special forms stay unqualified, vars resolve to their defining namespace, unresolved symbols get current-namespace qualification. JVM/Babashka only — CLJS callers must provide their own resolver.
Run .meme files: read, eval, return last result.
Run .meme files: read, eval, return last result.
Scanner-level source-position utilities. Defines the character-level line/col model used by the tokenizer. Only \n advances the line counter — \r is a regular character that occupies a column. This matches sadvance! in the tokenizer.
Note: this is the scanner line model, not a universal line definition. The error display module (meme.errors/source-context) uses str/split-lines which has different line-ending semantics (splits on both \n and \r\n). The two models agree for LF sources but diverge for CRLF. See format-error for how the bridge is handled.
Scanner-level source-position utilities. Defines the character-level line/col model used by the tokenizer. Only \n advances the line counter — \r is a regular character that occupies a column. This matches sadvance! in the tokenizer. Note: this is the *scanner* line model, not a universal line definition. The error display module (meme.errors/source-context) uses str/split-lines which has different line-ending semantics (splits on both \n and \r\n). The two models agree for LF sources but diverge for CRLF. See format-error for how the bridge is handled.
meme tokenizer: character scanning and token production. Transforms meme source text into a flat vector of typed tokens.
meme tokenizer: character scanning and token production. Transforms meme source text into a flat vector of typed tokens.
Explicit stage composition: source → step-scan → step-parse → step-expand-syntax-quotes → forms. Each stage is a ctx → ctx function operating on a shared context map.
Context map contract:
| Key | Type | Written by | Read by |
|---|---|---|---|
| :source | String | caller | scan, parse |
| :opts | Map or nil | caller | parse, expand |
| :raw-tokens | Vector | scan | (tooling) |
| :tokens | Vector | scan | parse |
| :forms | Vector | parse, expand | expand, caller |
:raw-tokens and :tokens are identical. Both keys are written by scan; :raw-tokens is retained so tooling that reads it continues to work.
Stages are independent functions. Compose them in any order that respects the read/write dependencies above. Guest languages can:
See meme.stages.contract for formal clojure.spec definitions of the context map at each stage boundary. Enable runtime validation with: (binding [meme.stages.contract/validate true] (stages/run source))
Explicit stage composition: source → step-scan → step-parse → step-expand-syntax-quotes → forms.
Each stage is a ctx → ctx function operating on a shared context map.
Context map contract:
| Key | Type | Written by | Read by |
|--------------|----------------|---------------|----------------------|
| :source | String | caller | scan, parse |
| :opts | Map or nil | caller | parse, expand |
| :raw-tokens | Vector | scan | (tooling) |
| :tokens | Vector | scan | parse |
| :forms | Vector | parse, expand | expand, caller |
:raw-tokens and :tokens are identical. Both keys are written by scan;
:raw-tokens is retained so tooling that reads it continues to work.
Stages are independent functions. Compose them in any order that respects
the read/write dependencies above. Guest languages can:
- Replace stages (e.g. a custom parser that reads :tokens, writes :forms)
- Add stages (e.g. a rewrite stage that reads :forms, writes :forms)
- Skip stages (e.g. skip expand for tooling that works with AST nodes)
- Read intermediate state (e.g. :raw-tokens for syntax highlighting)
See meme.stages.contract for formal clojure.spec definitions of
the context map at each stage boundary. Enable runtime validation with:
(binding [meme.stages.contract/*validate* true]
(stages/run source))Formal contract for the meme stage context map.
Defines clojure.spec.alpha specs for the context at each stage boundary, a toggleable runtime validator, and explain functions for debugging.
Composable ctx → ctx stages:
scan → parse → expand
Context map contract:
| Key | Type | Written by | Read by |
|---|---|---|---|
| :source | String | caller | scan, parse |
| :opts | Map or nil | caller | parse, expand |
| :raw-tokens | Vector | scan | (tooling) |
| :tokens | Vector | scan | parse |
| :forms | Vector | parse, expand | expand, caller |
Guest languages that replace stages (e.g. a custom parser that reads :tokens and writes :forms) must produce context maps conforming to the relevant stage-output spec. Enable runtime validation during development:
(binding [contract/validate true] (stages/run source))
Formal contract for the meme stage context map.
Defines clojure.spec.alpha specs for the context at each stage boundary,
a toggleable runtime validator, and explain functions for debugging.
Composable ctx → ctx stages:
scan → parse → expand
Context map contract:
| Key | Type | Written by | Read by |
|--------------|----------------|---------------|----------------------|
| :source | String | caller | scan, parse |
| :opts | Map or nil | caller | parse, expand |
| :raw-tokens | Vector | scan | (tooling) |
| :tokens | Vector | scan | parse |
| :forms | Vector | parse, expand | expand, caller |
Guest languages that replace stages (e.g. a custom parser that reads
:tokens and writes :forms) must produce context maps conforming to
the relevant stage-output spec. Enable runtime validation during
development:
(binding [contract/*validate* true]
(stages/run source))Token-stream term rewriting system.
Three stages:
Rules are pure data — patterns and replacements that the engine interprets. No lambdas in rule definitions.
Pattern language (matches consecutive sibling nodes): {:bind :name} — match any node, bind {:bind :name :pred fn} — match node satisfying fn, bind {:bind :name :paren-group true :adj true} — match adjacent paren group, bind
Replacement language (produces sibling nodes): {:ref :name} — emit bound node {:ref :name :strip-ws true} — emit bound node, strip :ws {:paren-group [...] :ws-from :name} — build paren group from parts {:body-of :name} — emit inner children of bound group {:body-of :name :ensure-ws str} — emit inner children, ensure :ws on first
Token-stream term rewriting system.
Three stages:
1. Nest: group balanced delimiters into nested vectors
2. Rewrite: apply declarative rules on nested structure
3. Flatten: unnest back to flat token vector for emission
Rules are pure data — patterns and replacements that the engine
interprets. No lambdas in rule definitions.
Pattern language (matches consecutive sibling nodes):
{:bind :name} — match any node, bind
{:bind :name :pred fn} — match node satisfying fn, bind
{:bind :name :paren-group true :adj true} — match adjacent paren group, bind
Replacement language (produces sibling nodes):
{:ref :name} — emit bound node
{:ref :name :strip-ws true} — emit bound node, strip :ws
{:paren-group [...] :ws-from :name} — build paren group from parts
{:body-of :name} — emit inner children of bound group
{:body-of :name :ensure-ws str} — emit inner children, ensure :ws on firstcljdoc 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 |