On this page:
13.7.1 Readtables

The dispatch table in Delimiters and Dispatch corresponds to the default readtable. By creating a new readtable and installing it via the current-readtable parameter, the reader’s behavior can be extended.

A readtable is consulted at specific times by the reader:

The readtable is ignored at other times. In particular, after parsing a character that is mapped to the default behavior of ;, the readtable is ignored until the comment’s terminating newline is discovered. Similarly, the readtable does not affect string parsing until a closing double-quote is found. Meanwhile, if a character is mapped to the default behavior of (, then it starts sequence that is closed by any character that is mapped to a closing parenthesis ). An apparent exception is that the default parsing of | quotes a symbol until a matching character is found, but the parser is simply using the character that started the quote; it does not consult the readtable.

For many contexts, #f identifies the default readtable. In particular, #f is the initial value for the current-readtable parameter, which causes the reader to behave as described in The Reader.


(readtable? v)  boolean?

  v : any/c
Returns #t if v is a readtable, #f otherwise.


(make-readtable readtable    
  action ...+)  readtable?
  readtable : (or/c readtable? #f)
  key : (or/c char? #f)
  mode : 
(or/c (or/c 'terminating-macro
  action : 
(or/c procedure?
Creates a new readtable that is like readtable (which can be #f), except that the reader’s behavior is modified for each key according to the given mode and action. The ...+ for make-readtable applies to all three of key, mode, and action; in other words, the total number of arguments to make-readtable must be 1 modulo 3.

The possible combinations for key, mode, and action are as follows:

If multiple 'dispatch-macro mappings are provided for a single char, all but the last one are ignored. Similarly, if multiple non-'dispatch-macro mappings are provided for a single char, all but the last one are ignored.

A reader macro proc must accept six arguments, and it can optionally accept two arguments. The first two arguments are always the character that triggered the reader macro and the input port for reading. When the reader macro is triggered by read-syntax (or read-syntax/recursive), the procedure is passed four additional arguments that represent a source location for already-consumed character(s): the source name, a line number or #f, a column number or #f, and a position or #f. When the reader macro is triggered by read (or read/recursive), the procedure is passed only two arguments if it accepts two arguments, otherwise it is passed six arguments where the third is always #f. See Reader-Extension Procedures for information on the procedure’s results.

A reader macro normally reads characters from the given input port to produce a value to be used as the “reader macro-expansion” of the consumed characters. The reader macro might produce a special-comment value (see Special Comments) to cause the consumed character to be treated as whitespace, and it might use read/recursive or read-syntax/recursive.


(readtable-mapping readtable char)

(or/c char?
      (or/c 'terminating-macro
(or/c #f procedure?)
(or/c #f procedure?)
  readtable : readtable?
  char : char?
Produces information about the mappings in readtable for char. The result is three values:

Note that reader-macro procedures for the default readtable are not directly accessible. To invoke default behaviors, use read/recursive or read-syntax/recursive with a character and the #f readtable.

; Provides raise-read-error and raise-read-eof-error
> (require syntax/readerr)
> (define (skip-whitespace port)
    ; Skips whitespace characters, sensitive to the current
    ; readtable's definition of whitespace
    (let ([ch (peek-char port)])
      (unless (eof-object? ch)
        ; Consult current readtable:
        (let-values ([(like-ch/sym proc dispatch-proc)
                      (readtable-mapping (current-readtable) ch)])
          ; If like-ch/sym is whitespace, then ch is whitespace
          (when (and (char? like-ch/sym)
                     (char-whitespace? like-ch/sym))
            (read-char port)
            (skip-whitespace port))))))
> (define (skip-comments read-one port src)
    ; Recursive read, but skip comments and detect EOF
    (let loop ()
      (let ([v (read-one)])
         [(special-comment? v) (loop)]
         [(eof-object? v)
          (let-values ([(l c p) (port-next-location port)])
             "unexpected EOF in tuple" src l c p 1))]
         [else v]))))
> (define (parse port read-one src)
    ; First, check for empty tuple
    (skip-whitespace port)
    (if (eq? #\> (peek-char port))
        (let ([elem (read-one)])
          (if (special-comment? elem)
              ; Found a comment, so look for > again
              (parse port read-one src)
              ; Non-empty tuple:
              (cons elem
                    (parse-nonempty port read-one src))))))
> (define (parse-nonempty port read-one src)
    ; Need a comma or closer
    (skip-whitespace port)
    (case (peek-char port)
      [(#\>) (read-char port)
       ; Done
      [(#\,) (read-char port)
       ; Read next element and recur
       (cons (skip-comments read-one port src)
             (parse-nonempty port read-one src))]
       ; Either a comment or an error; grab location (in case
       ; of error) and read recursively to detect comments
       (let-values ([(l c p) (port-next-location port)]
                    [(v) (read-one)])
          [(special-comment? v)
           ; It was a comment, so try again
           (parse-nonempty port read-one src)]
           ; Wasn't a comment, comma, or closer; error
           ((if (eof-object? v)
            "expected `,` or `>`" src l c p 1)]))]))
> (define (make-delims-table)
    ; Table to use for recursive reads to disallow delimiters
    ;  (except those in sub-expressions)
    (letrec ([misplaced-delimiter
               [(ch port) (misplaced-delimiter ch port #f #f #f #f)]
               [(ch port src line col pos)
                 (format "misplaced `~a` in tuple" ch)
                 src line col pos 1)])])
      (make-readtable (current-readtable)
                      #\, 'terminating-macro misplaced-delimiter
                      #\> 'terminating-macro misplaced-delimiter)))
> (define (wrap l)
    `(make-tuple (list ,@l)))
> (define parse-open-tuple
     [(ch port)
      ; read mode
      (wrap (parse port
                   (lambda ()
                     (read/recursive port #f
                   (object-name port)))]
     [(ch port src line col pos)
      ; read-syntax mode
       (wrap (parse port
                    (lambda ()
                      (read-syntax/recursive src port #f
       (let-values ([(l c p) (port-next-location port)])
         (list src line col pos (and pos (- p pos)))))]))
> (define tuple-readtable
    (make-readtable #f #\< 'terminating-macro parse-open-tuple))
> (parameterize ([current-readtable tuple-readtable])
    (read (open-input-string "<1 , 2 , \"a\">")))

'(make-tuple (list 1 2 "a"))

> (parameterize ([current-readtable tuple-readtable])
    (read (open-input-string
           "< #||# 1 #||# , #||# 2 #||# , #||# \"a\" #||# >")))

'(make-tuple (list 1 2 "a"))

> (define tuple-readtable+
    (make-readtable tuple-readtable
                    #\* 'terminating-macro (lambda a
                                             (make-special-comment #f))
                    #\_ #\space #f))
> (parameterize ([current-readtable tuple-readtable+])
    (read (open-input-string "< * 1 __,__  2 __,__ * \"a\" * >")))

'(make-tuple (list 1 2 "a"))