Just to make sure we're on the same page, it seems like your example fails for the same reason that
#lang scribble/lp2 @(require racket/string) @(chunk <*> string-join) fails. That `require` turns out to do nothing, and there's no binding of `string-join` in the "phase" of the program is that stitched together by the chunks. In this single-file example, the solution could be #lang scribble/lp2 @(chunk <r> (require racket/string)) @(chunk <*> <r> string-join) but that puts `require` text in the generated document, which could be undesirable in some context. Even if including the `require` was ok, changing your original example to .... (chunk <r> (require (only-in racket/string string-join))) (define-syntax-parser cross-phase-macro [(_ <>:id) @#`begin{@(func-not-exported) @chunk[<> <r> (string-join '("string-join is not provided" "at runtime" "by #lang scribble/lp2"))] }]) does not work, because `chunk` binding doesn't work that way. I wonder whether the solution is an extension of `scribble/lp2` to support a `require-for-chunk` form where `chunk` records any such imports in its context and adds then to the stitched-together program. .... (require-for-chunk (only-in racket/string string-join)) (define-syntax-parser cross-phase-macro [(_ <>:id) @#`begin{@(func-not-exported) @chunk[<> (string-join .... would work because `chunk` would pick up the `(only-in racket/string string-join)` from its context and attach it to the chunk, so that `(only-in racket/string string-join)` would be added to the stitched-together context. Maybe this would work by having `require-for-chunk` expand to something like `(begin-for-syntax (register-require #'chunk ....))` to register in a compile-time free-id table mapping from the #'chunk identifier to a list of imports. Then, the `chunk` macro would consult the table using the identifier that invoked the macro. (I have not tried this to be sure that it would work.) At Mon, 18 Mar 2019 02:20:50 -0400, Philip McGrath wrote: > 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/1de75817866d9d782da7 > 5f1799daeb5eb26e6fe1/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. -- 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.