In information technology, reader macros are a feature of the Lisp family of programming languages (especially Common Lisp) that allow programmers to extend or modify the syntax recognized by the language's reader—the component that parses source code text into internal data structures. By associating custom functions with specific characters, reader macros enable syntactic abbreviations, domain-specific notations, and alternative representations without altering the core language parser. They are a powerful metaprogramming tool, distinct from compile-time macros, operating at the lexical parsing stage.

1 Definition and purpose

1.1 What is a reader macro

A reader macro is a user‑defined or system‑defined function attached to a character (or a sequence of characters) in the input stream. When the Lisp reader encounters that character, it invokes the associated function instead of treating the character as a normal constituent of the syntax. The function receives the input stream and any dispatch parameters, and returns a Lisp object to be used in place of the original textual sequence.

1.1.1 Relationship to the Lisp reader

The Lisp reader is the part of the system that converts a stream of characters into s‑expressions (symbols, numbers, lists, strings, etc.). Reader macros are hooks into this process: they allow arbitrary transformations at the character‑level, before the standard parsing rules apply. This contrasts with compile‑time macros, which operate on already‑parsed s‑expressions.

1.2 Motivation and advantages

Reader macros provide a way to introduce domain‑specific syntax or abbreviations without modifying the language implementation. Common motivations include:

  • Syntactic sugar: Shortening frequently‑used forms (e.g., ' for quote).
  • Embedding foreign notations: Allowing syntax for regular expressions, JSON, or other data formats inline.
  • Custom literals: Creating new literal representations for complex data structures.
  • Extensibility: Enabling libraries to define their own syntax without waiting for language standard updates.

Because reader macros operate at the lexical level, they are extremely flexible but can also reduce code portability and readability if overused.

2 Mechanism

2.1 Reader characters and dispatch

The Lisp reader maintains a table of “macro characters” – characters that, when read, trigger a special function rather than being accumulated as part of a token. There are two main kinds of reader macro characters:

2.1.1 Single‑character reader macros

A single character (e.g., the single quote ') is assigned a reader macro function. Whenever the reader sees that character, it calls the function, which typically reads the following object and returns a transformed expression (e.g., 'x becomes (quote x)).

2.1.2 Dispatch reader macros (two‑character sequences)

A dispatch macro character (most commonly #) is followed by a second character (or a sequence of characters) that identifies a specific reader macro. For example, #( starts a vector literal, #* starts a bit‑vector, and #\ starts a character literal. Dispatch macros allow a single dispatch character to serve as a prefix for many different reader macros, extended via set‑dispatch‑macro‑character.

2.2 Defining a reader macro

Common Lisp provides two primary functions for defining reader macros:

2.2.1 Using set‑dispatch‑macro‑character

(set‑dispatch‑macro-character disp‑char sub‑char function &optional readtable) associates a function with a two‑character sequence. For example, after (set‑dispatch‑macro-character #\# #\U #'read‑url), the input #Uhttp://example.com would invoke read‑url, which could return a parse‑tree representing a URL.

2.2.2 Using set‑macro‑character

(set‑macro-character char function &optional non‑terminating-p readtable) associates a reader macro with a single character. The function receives the input stream and the character (or nil for dispatch). If non‑terminating-p is true, the character may also appear inside symbols; otherwise it always terminates a symbol.

2.3 Reading process and macro invocation

When the reader processes a stream:

  1. It reads whitespace and skips comments.
  2. It checks the next character against the current readtable’s macro character table.
  3. If the character is a macro character, the associated function is called with the stream as argument.
  4. The function reads as many additional characters as needed (e.g., arguments, delimiters) and returns a Lisp object.
  5. The object is placed into the enclosing s‑expression at that position.

If the character is not a macro character, it is treated as part of a token (symbol, number, etc.) according to standard rules.

3 Standard built‑in reader macros

Common Lisp includes a rich set of built‑in reader macros that define much of the language’s surface syntax.

3.1 Quote and backquote

3.1.1 ' (single quote)

The single quote ' is a reader macro that expands 'x into (quote x). It is the most common shorthand for preventing evaluation.

3.1.2 ` ` and ,` (backquote and comma)

The backquote ` ` introduces a template literal from which values can be inserted using , (comma) and spliced using ,@` (comma‑at). These reader macros together support quasiquotation, a powerful tool for generating code.

3.2 Character syntax

3.2.1 #\ (character literal)

The dispatch sequence #\ introduces a character literal. For example, #\a denotes the character a, and #\Space denotes a space character.

3.2.2 # with other dispatch characters

The # dispatch character is used for many built‑in notations:

  • #( ) – vector literals
  • #* – bit‑vectors
  • #: – uninterned symbols
  • #. – expression evaluation at read time
  • #+ and #- – feature‑based conditional reading

3.3 Vector and array syntax

Beyond #(, Common Lisp also uses #2A(...) for multidimensional arrays, supported by dispatch reader macros.

3.4 Function and lambda shorthand

The reader macro #' (sharp‑quote, # followed by ') expands #'f into (function f). This is used to reference a function object.

4 Common implementations

4.1 In Common Lisp

All standard Common Lisp implementations (e.g., SBCL, CCL, LispWorks) support reader macros as described. The readtable is a first‑class object that can be modified locally with with‑standard‑io‑syntax or copied per lexical scope.

4.2 In other Lisp dialects

4.2.1 Scheme (read syntax)

R⁶RS Scheme does not have a standardized reader macro system, but many implementations (e.g., Racket, Chez Scheme) provide custom read syntax through read‑time procedures or readtables. Racket, for example, offers #lang directives and a readtable API.

4.2.2 Clojure (reader conditionals and tagged literals)

Clojure uses reader conditionals (#? and #?@) to include platform‑specific code, and tagged literals (#uuid "...") for data formats. These are provided by the Clojure reader built‑in but can be extended via *data‑readers* and custom tags.

4.3 In non‑Lisp languages (analogous concepts)

Some non‑Lisp languages have mechanisms conceptually similar to reader macros:

  • C++ user‑defined literals (e.g., 123_km) allow suffix processing at compile time.
  • Julia has a staged macro system that can operate on parsed expressions, but does not have character‑level reader macros.
  • Ruby provides % notations (e.g., %w{...}) which are built‑in syntactic sugar.

However, these are less general than Lisp’s reader macros, as they are usually restricted to specific contexts or predefined character sets.

5 Use cases

5.1 Domain‑specific language (DSL) embedding

Reader macros enable a library to introduce its own syntax (e.g., a DSL for regular expressions, SQL, or arithmetic) that is compiled directly into Lisp forms at read time, without requiring a separate parser.

5.2 Syntax extensions (e.g., infix notation, XML literals)

Libraries can add infix operators (e.g., {x + y}) or embed XML/HTML snippets using reader macros that transform the textual representation into native Lisp structures.

5.3 Adding annotations or metadata

Reader macros can associate metadata with forms, for example #^documentation attached to a function definition, or #:private to mark symbols.

5.4 Simulation of foreign data formats (JSON, regex)

A reader macro can parse an inline JSON string and return a Lisp hash‑table, or convert a regex literal into a compiled pattern object, enabling more natural syntax in code.

6 Examples

6.1 Simple numeric literal extension

The following defines a reader macro for hexadecimal numbers using #x:

(set-dispatch-macro-character #\# #\x
  #'(lambda (stream char subchar)
      (let ((hex-str (read stream t nil t)))
        (parse-integer (symbol-name hex-str) :radix 16))))

With this, #xFF would be read as the integer 255.

6.2 Adding a custom string interpolation reader

A reader macro can re‑write a string literal to include variable substitution:

(defun interpolate-reader (stream char)
  (let ((string (read stream t nil t)))
    `(format nil ,(regex-replace "~\{(\w+)\}" string "\1"))))

(set-macro-character #\$
  #'interpolate-reader)

Now $"Hello, ~{name}~!" would expand to a format call.

6.3 Creating a dispatch for unit suffixes

A dispatch #m could read a number followed by a unit:

(set-dispatch-macro-character #\# #\m
  #'(lambda (stream char subchar)
      (let ((val (read stream t nil t)))
        `(make-quantity ,val :unit 'meters))))

Then #m 100 reads as (make-quantity 100 :unit 'meters).

7 Best practices and pitfalls

7.1 Consequences for code readability

Reader macros change the syntax of the language, which can confuse readers unfamiliar with the extensions. They should be used judiciously, and their definitions should be clearly documented. Over‑use can make code look like a new language, hindering maintainability.

7.2 Interaction with editor tools and syntax highlighting

Most editors and IDE tools (e.g., Emacs, slime) assume standard Lisp syntax. Custom reader macros can break syntax highlighting, indentation, and structural editing. Library authors often provide editor configuration or use established conventions (e.g., dispatch on # with mnemonic sub‑characters) to minimise disruption.

7.3 Portability across Lisp implementations

While the reader macro API is standardised in Common Lisp, some implementation details (e.g., the handling of readtables, character representation, and stream behaviour) may vary. Code that relies on reader macros may not port directly to another Lisp system without adaptation, especially if the macros assume a specific implementation’s readtable state.

7.4 Debugging and macro hygiene

Because reader macros execute at read time, errors can be difficult to debug: the macro function runs before normal compilation or evaluation. Stack traces may reference the macro function rather than the original source. Additionally, reader macros do not automatically respect lexical scope; they affect the entire readtable. Careful use of local readtable modifications (via let over *readtable*) can mitigate some issues. Unlike compile‑time macros, there is no concept of “hygiene” for reader macros; they can inadvertently capture symbols if not written carefully.