Racket makes it easy to write macros that mix run-time code with various compile-time phases, using lexical scope to refer unambiguously to the correct binding for each identifier, including bindings that are not directly exported. Macro-generating macros are a great example, in that they expand to code at several different phases.
When trying to write macros that interleave documentation-time and run-time phases, I haven't been able to find the same elegance. I'm going to demonstrate one of the problems I've found using `#lang scribble/lp2`, but I really encountered these issues while implementing the specification #lang I've made for Digital Ricoeur: my current implementation is based quite closely on `scribble/lp2`. Imagine a library module "lib.rkt" from which you want to export a `cross-phase-macro`, which expands to both documentation-time and run-time code for your literate programs. Here's an initial attempt: (The code for this example is up at https://github.com/LiberalArtist/doc-lang-experiments/tree/1de75817866d9d782da75f1799daeb5eb26e6fe1/lp2-example1 .) #lang at-exp racket/base (provide cross-phase-macro) (require (only-in racket/string string-join) (only-in scribble/lp2 chunk) syntax/parse/define (for-syntax racket/base)) (define (func-not-exported) "This is the result of func-not-exported.") (define-syntax-parser cross-phase-macro [(_ <>:id) @#`begin{@(func-not-exported) @chunk[<> (string-join '("string-join is not provided" "at runtime" "by #lang scribble/lp2"))] }]) When you try to use `cross-phase-macro` from a literate program "program.scrbl": #lang scribble/lp2 @(require "lib.rkt") @cross-phase-macro[<*>] you get an unbound identifier error complaining about the use of `string-join`. The reason for the error is clear: `chunk` bodies are stitched together into a `doc` submodule in the `racket/base` language, and `racket/base` doesn't include `string-join`. In essence, `string-join` isn't bound at the right phase. If this were a macro-generating macro—imagine `define-syntax` instead of `chunk`—the solution would be equally clear: use a `for-syntax` require or a `begin-for-syntax` block to bind `string-join` at compile-time, and the use of the identifier in the syntax template will resolve to the right binding, even without explicitly exporting `string-join`. I have yet to find a way to do the equivalent thing when mixing run-time and documentation-time phases, rather than run-time and compile-time(s). Some of the objectives and implementation details of `scribble/lp2` (shared by my specification language) seem to make this problem harder. The run-time phase shouldn't have a dependency on the documentation-time phase, which would pull in Scribble etc. Likewise, the documentation-time phase shouldn't import the run-time phase, other than perhaps `for-label`. The `#%module-begin` implementation uses complicated tricks to expand the documentation-time phase before the run-time phase, even though the documentation-time phase becomes a `module*` submodule, because it has to discover the run-time code (uses of `chunk` or, for my language, `begin-for-runtime`) inside of the documentation-time code. The implementation actually expands the documentation-time phase twice, and it uses `strip-context` in a few places, which seems like it would undo any fancy scope manipulation that I might otherwise look to as the beginning of a solution. Currently, I work around this in my specification language by manually arranging to have the language provide any bindings I need to expand to at run-time, but this is fragile and aesthetically displeasing. Actually, I would say the same about expanding the documentation-time phase twice (and, in my case, with observable differences, e.g. to get `for label` requires working thanks to an earlier post on this list <https://groups.google.com/d/msg/racket-users/7OrQFTOGBaw/gVlotkaYBwAJ>). It seems like there must be a better way to do this, and I'm wondering if anyone has ideas. With my specification language, in particular, I have the freedom to completely change the implementation if I can get to a better place. Since much of this email has been about problems, I want to close by reiterating that our specification #lang has been a big win for us at Digital Ricoeur. While there are things about the implementation of the #lang that I'd like to improve, using the #lang has reduced bugs, greatly aided maintainability, and kept our prose documents in sync with various layers of code. Part of my motivation for trying to make the implementation more robust is that it has been so useful to us that I'd like to make the core more reusable for others. -Philip -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.