0

I am trying to define a reader macro that reads the length of a string. The string will be enclosed in vertical bars (pipes). For example:

  • |yes| -> 3
  • || -> 0
  • |The quick brown fox| -> 19
  • |\|| -> 1 — The pipe character can be escaped by preceding it with a backslash.
  • (let ((x |world|)) (+ |hello| x)) -> 10

I managed to write this:

#lang racket

(define (handle-pipe in [count 0])
  (define cur-char (read-char in))
  (cond [(eqv? cur-char #\|)
         count]
        [else
          (when (and (eqv? cur-char #\\)  ; Handle escape ("\|").
                     (eqv? (peek-char in) #\|))
            (read-char in))  ; Consume |.
          (handle-pipe in (+ count 1))]))

(parameterize ([current-readtable
                 (make-readtable (current-readtable)
                                 #\|
                                 'terminating-macro
                                 (lambda (char in src-name line col pos)
                                   (handle-pipe in)))])
  (eval (read (open-input-string "(let ((x |world|)) (+ |hello| x))"))
        (make-base-namespace)))

This returns 10, as expected.

The problem now is: I would like to use the reader macro directly in my Racket code instead of having to feed a string into (eval (read (open-input-string ...))). For example, I would like to use the reader macro like this:

#lang racket

(define (handle-pipe in [count 0])
  (define cur-char (read-char in))
  (cond [(eqv? cur-char #\|)
         count]
        [else
          (when (and (eqv? cur-char #\\)  ; Handle escape ("\|").
                     (eqv? (peek-char in) #\|))
            (read-char in))  ; Consume |.
          (handle-pipe in (+ count 1))]))

(current-readtable
  (make-readtable (current-readtable)
                  #\|
                  'terminating-macro
                  (lambda (char in src-name line col pos)
                    (handle-pipe in))))

(let ((x |world|))  ; Using the reader macro directly in Racket.
  (+ |hello| x))

However, there is an error message when I run the program above:

my-program.rkt:20:9: world: unbound identifier
  in: world
  location...:
   my-program.rkt:20:9
  context...:
   do-raise-syntax-error
   for-loop
   [repeats 1 more time]
   finish-bodys
   lambda-clause-expander
   for-loop
   loop
   [repeats 3 more times]
   module-begin-k
   expand-module16
   expand-capturing-lifts
   temp118_0
   temp91_0
   compile15
   temp85_0
   standard-module-name-resolver

What did I do wrong? How do I use the reader macro in my code?

Flux
  • 9,805
  • 5
  • 46
  • 92

1 Answers1

1

That's not possible. The pipeline of compilation/evaluation in Racket is:

  1. Read
  2. Expand macros
  3. Evaluate expanded program

Your configuration of current-readtable is done in step 3, so it cannot influence things that happened already in step 1.

The reason your first code works is that eval starts the pipeline again on the datum you provided it to.

Note that the reader macro is actually intended to be used when you create a new #lang. But since you want it to work with #lang racket, it's not applicable.

Sorawee Porncharoenwase
  • 6,305
  • 1
  • 14
  • 28
  • Does this mean creating a new `#lang ...` is necessary if I want to define and use my own reader macros? – Flux Feb 03 '21 at 15:52
  • Why was I able to modify the readtable in https://stackoverflow.com/a/57215040, but not here? – Flux Feb 03 '21 at 15:54
  • I found that you could change the readtable in the repl, but for source files you need to use #lang. This is because the source file has already been read by the time you change the reader. I have a simple example of creating a #lang to modify the readtable. I could post it to github if you like. Just let me know. – Roger Keays Feb 03 '21 at 17:58
  • As @RogerKeays said, where did you use `#u` and `#s`? In the REPL or in the actual code (which is what you asked in this question)? – Sorawee Porncharoenwase Feb 04 '21 at 11:33
  • @RogerKeays A simple example would be most helpful. Thank you for the offer. – Flux Feb 04 '21 at 11:46
  • @SoraweePorncharoenwase Okay, it only works in the REPL, but not in source files. I only noticed this now. – Flux Feb 04 '21 at 11:48
  • @RogerKeays Thank you! That is very helpful. Suggestion: add installation instructions, and a usage example that uses `#lang ...`. – Flux Feb 06 '21 at 14:30
  • @Flux the examples are in /tests. I'll see if I can publish to the racket package server so you can install it more easily. – Roger Keays Feb 06 '21 at 14:32
  • @Flux, I finally got this published to the racket package collection, and added some installation instructions to the readme. I'm still working on an init script to make it work in the repl. – Roger Keays Feb 22 '21 at 17:20