Literate programming in style
1 About this document
<main-module>
2 Building scribble/  lp2/  manual
2.1 Reader
<reader>
2.2 Expander
<expander>
<expander-provides>
<expander-impl>
3 Putting it all together
<*>
8.3

Literate programming in style

D. Ben Knoble

 #lang scribble/lp2/manual package: scribble-lp2-manual

The scribble/lp2 language with scribble/manual style. You must call title for the new style to be applied, but really, what program document doesn’t have a title?

1 About this document

This document is written in scribble/lp2/manual and thus serves as both test and example for the language. This document also embeds an implementation of the language, which the main sources use to expose the language.

This chunk is the main-module that produces 2 when this code is run with racket <source>.scrbl.

(module+ main (+ 1 1))

2 Building scribble/lp2/manual

Let’s walkthrough how the language is built as an exercise in using the language.

First, we have to figure out how to apply the scribble/manual style to an existing scribble/lp2 document. Since title takes #:style to control the document’s style, we’ll use that with manual-doc-style to get what we want:

#lang scribble/lp2
 
@(require scribble/manual)
 
@title[#:style manual-doc-style]{My program}

Thankfully, scribble/manual provides manual-doc-style, which we know by looking at Manual Rendering Style.

This works, but we have to do it for any scribble/lp2 program we want to render with style. Let’s make a language to fix this.

2.1 Reader

All #langs need a reader, so here’s ours:

(module reader syntax/module-reader
  ; Name of the expander module
  scribble/lp2/manual
  #:read read-inside
  #:read-syntax read-syntax-inside
  #:whole-body-readers? #t
  #:language-info (scribble-base-language-info)
  #:info (scribble-base-info)
 
  (require scribble/reader
           (only-in scribble/base/reader
                    scribble-base-info
                    scribble-base-language-info))
 
  ; cf. https://github.com/racket/scribble/blob/master/scribble-lib/scribble/lp2.rkt)

We base it on the existing scribble readers, such as that of scribble/lp2, but we use our own expander module.

2.2 Expander

Next we provide a module implementing the expander:

(module expander racket/base
  <expander-provides>
  <expander-impl>)

The expander needs to provide base bindings for the language, including a #%module-begin syntax. We’ll re-use bindings from scribble/lp/lang/lang2, since that what scribble/lp2 uses, but we’ll supplement with our own #%module-begin:

(provide (except-out (all-from-out scribble/lp/lang/lang2)
                     lp2:mb)
         (rename-out [mb #%module-begin]))

Our #%module-begin needs to force title to use manual-doc-style, then invoke #%module-begin from scribble/lp/lang/lang2:

(require (rename-in scribble/lp/lang/lang2 [#%module-begin lp2:mb])
         syntax/parse/define)
 
(define-syntax-parse-rule (mb body ...)
  (lp2:mb
    (define title
      ; Avoid polluting the module-body with require's by using dynamic-require.
      ; 
      ; But it's odd that we still have to bind title, since we know title will
      ; be bound by the (require scribble/manual) in the expansion of lp2:mb.
      ; Perhaps hygiene is in the way? (require scribble/manual) in this module
      ; doesn't work either, though I suspect the strip-context call of being
      ; at fault at that point.
      (let ([curryr (dynamic-require 'racket/function 'curryr)]
            [title (dynamic-require 'scribble/manual 'title)])
        (curryr title #:style manual-doc-style)))
    body ...))

3 Putting it all together

That’s it! The actual implementation uses files that provide exactly this code. Because this is a literate program, we could require its submodules to get a reader or expander. If the actual language implementation did that, though, there would be a circular dependency! In order to understand the source of this document, we consult the language definition, which requires this document to compile, etc., ad infinitum. So we won’t do that, though it is a neat trick (e.g., pollen-tfl implements part of its codebase as a literal program).

Put the code in the following order:

<*> ::=