Search code examples
racketbeautiful-racket

Importing Beautiful Racket reader for new language?


I am using Beautiful Racket to draft a new DSL, bleir, based on s-expressions. I first install a new racket package using the Master Recipe.

raco pkg new bleir
cd bleir
raco pkg install

I then set up a vestigial reader and expander in the madatory file, main.rkt:

#lang br/quicklang

(module+ reader
  (provide read-syntax)
  (require br/quicklang))

(provide (rename-out [bleir-expander #%module-begin]))
(define-macro (bleir-expander PARSE-TREE)
  #'(#%module-begin
     (display PARSE-TREE)))

I then write a test file, tyst-bleir.rkt [intentional misspelling, to sidestep potential problems with the string name "test"]:

#lang bleir
42

I get a problem:

Module Language: only a module expression is allowed, either
    #lang <language-name>
 or
    (module <name> <language> ...)
 in: 42

I can fix it in two ways, "disgusting" and "mysterious". First, the disgusting fix, in tyst-bleir.rkt:

(module tyst-bleir bleir
  42)

This works, invoking both the doubly exported reader from br/quicklang and my custom expander, displaying 42.

QUESTION 1: the #lang bleir syntax is supposed to be identical to the verbose (module ...) syntax is it not? Why does #lang bleir not work to import the reader from br/quicklang?

Now, the mysterious fix:

#lang s-exp bleir
42

This evidently uses an alternative s-exp reader from module (package?) s-exp and it's ok, I guess. At least I'm not blocked. HOWEVER, I do not understand why I can't use the reader from br/quicklang in the same way.

#lang br/quicklang bleir
42
bleir: unbound identifier in: bleir

QUESTION 2: Why doesn't this work? Can I fix it? If so, how?

Here is another failed attempt, going the other way, trying to import the reader from s-exp in main-rkt:

(module+ reader
  (provide read-syntax)
  (require s-exp))
main.rkt:11:11: cannot open module file
  module path: s-exp
  path: /usr/share/racket/collects/s-exp/main.rkt
  system error: no such file or directory; rkt_err=3 in: s-exp
  no package suggestions are available .

QUESTION 3: Why doesn't this work? Can I fix it? If so, how?


Solution

  • Question 1

    You didn't fully follow the master recipe that you reference. If you continue reading, you will see:

    read-syntax must return one value: code for a module expres­sion, repre­sented as a syntax object. Typi­cally, the converted S-expres­sions from the source file are inserted into this syntax object. This syntax object must have no iden­ti­fier bind­ings. This module code must include a refer­ence to the expander that will provide the initial set of bind­ings when the module code is eval­u­ated. In pseudocode:

    #lang br
    (module reader br
      (provide read-syntax)
      (define (read-syntax name port)
        (define s-exprs (read-code-from port))
        (strip-bindings
         #`(module dsl-mod-name dsl/expander
             #,@s-exprs))))
    

    In other words, your read-syntax needs to return (strip-bindings #'(module ...)).

    Question 2

    I think you are confusing "languages that users use" and "languages that language designers use" together.

    "languages that users use" are something like #lang bleir, #lang slideshow, #lang pollen, etc. These languages provide constructs to do domain-specific tasks. For example, #lang slideshow is a language for creating presentations. #lang pollen is a language for creating books.

    "languages that language designers use" are a kind of "languages that users use", where the users are language designers. These languages provide constructs to make language creation easier. #lang br/quicklang is in this category.

    So you, as a language designer, would use #lang br/quicklang internally in your bleir/main.rkt for constructing a language bleir. But your users, who are not language designers, should not need to write (or even be aware of) #lang br/quicklang.


    The master recipe talks about two things: the reader and the expander. In a fully developed language, you would provide both the reader and the expander. s-exp can be seen as a convenient hack language to "run" non-fully developed languages that only have the expander, but don't have the reader yet.

    So, the reason why #lang s-exp bleir works is that you have a functioning expander, so #lang s-exp bleir uses your expander but completely ignores your reader submodule. The reason why #lang bleir doesn't work is that your reader submodule is buggy (see Question 1). The reason why #lang br/quicklang bleir doesn't work is that it doesn't make any sense for users to write #lang br/quicklang.

    I suggest that you simply forget about s-exp and fix your reader. AFAIK, Beau­tiful Racket didn't really talk about s-exp, and if you are following the book, using s-exp will simply make you confused.

    Question 3

    If you want a technical answer, https://docs.racket-lang.org/guide/hash-lang_syntax.html (or more generally https://docs.racket-lang.org/reference/reader.html#%28part._parse-reader%29) answers your question:

    Unlike racket, s-exp cannot be used as a module path with require. Although the syntax of language for #lang overlaps with the syntax of module paths, a language is not used directly as a module path. Instead, a language obtains a module path by trying two locations: first, it looks for a reader submodule of the main module for language. If this is not a valid module path, then language is suffixed with /lang/reader. (If neither is a valid module path, an error is raised.) The resulting module supplies read and read-syntax functions using a protocol that is similar to the one for #reader.