mbutterick / pollen-users

please use https://forums.matthewbutterick.com/c/typesetting/ instead
https://forums.matthewbutterick.com/c/typesetting/
53 stars 0 forks source link

Poly definitions in separate files by output type #111

Open bluebear94 opened 2 years ago

bluebear94 commented 2 years ago

Is it possible to define tags that depend on the output format by using a separate file for each format?

For example, if I specify html and tex output formats, then can I put the HTML-specific definitions in pollen-html.rkt and the LaTeX-specific definitions in pollen-latex.rkt, including whichever file is appropriate for the output?

bluebear94 commented 2 years ago

I'd imagine that to do this, you'd need to:

  1. Inspect the exports of both pollen-html.rkt and pollen-latex.rkt at phase n+1
  2. For each symbol that's exported from either one, use a macro to define a function that dispatches to the appropriate implementation, or a stub if an implementation is unavailable for the current output format
  3. Re-export those functions

The disadvantage is that the generated functions in the main pollen.rkt wouldn't be able to know what signature to expect, so errors having to do with signatures would blame pollen.rkt and not the calling code. Exported non-function variables (such as (define br '(br)) wouldn't play well with this approach as well; they'd have to be re-exported as functions, requiring existing code to be changed.

Alternatively:

  1. Inspect the exports of both pollen-html.rkt and pollen-latex.rkt at phase n+1
  2. For each symbol that's exported from either one, use a macro to define a function that returns a stub tag that's replaced with the real implementation during post-processing

but this requires the arguments to be valid X-exprs, so I'm not too keen on that.

sorawee commented 2 years ago

If stuff that you are gonna import are run-time definitions, you can use dynamic-require, FWIW.

bluebear94 commented 2 years ago

I'm not getting much luck using dynamic-require from Pollen:

pollen.rkt:

#lang racket

(displayln (dynamic-require "pollen/common.rkt" 'sei))

pollen/common.rkt:

#lang racket

(provide sei)

(define sei "○")

This works fine when imported from a .pm file in the project root, but from a file in a subdirectory such as grammar/index.html.pm, the dynamic-require tries to require the module at grammar/pollen/common.rkt.

bluebear94 commented 2 years ago

And now I have a proof of concept. Perhaps the grossest thing I've done in Racket.

(begin-for-syntax
  (define (target->module-path target)
    (format "pollen/~a.rkt" (symbol->string target)))
  (define (exports-from-target target)
    (define path (target->module-path target))
    (dynamic-require path #f)
    (define-values (vars syntaxes) (module->exports path))
    (define (get-ids-from-export-list el)
      (define el0 (assoc 0 el))
      (if el0
          (map car (cdr el0))
          '()))
    (append (get-ids-from-export-list vars)
            (get-ids-from-export-list syntaxes)))
  (define (exports-from-all-targets targets)
    (parameterize ([current-load-relative-directory "/home/kozet/ncv9/src"])
      (define exports (make-hash))
      (for ([target targets])
        (define target-exports (exports-from-target target))
        (for ([item-id target-exports])
          (define item (dynamic-require (target->module-path target) item-id))
          (hash-update! exports
                        item-id
                        (lambda (l) (cons (list target item) l))
                        '())
          )
        )
      exports))
  (define EXPORTS (exports-from-all-targets '(html tex)))
  )

(define-syntax (generate-reexports obj)
  (syntax-case obj ()
    [(_)
     (with-syntax
         ([(statements ...)
           (for/list ([(item-id items) (in-hash EXPORTS)])
             (with-syntax
                 ([item-id-stx (datum->syntax #'obj item-id)]
                  [(cases ...)
                   (for/list ([target+item items])
                     (define target (car target+item))
                     (define item (cadr target+item))
                     (with-syntax ([target-stx (datum->syntax #'obj target)]
                                   [item-stx (datum->syntax #'obj item)])
                       #'[(target-stx)
                          (keyword-apply item-stx kws kw-args rest)]))])
               #'(begin
                   (provide item-id-stx)
                   (define item-id-stx
                     (make-keyword-procedure
                      (lambda (kws kw-args . rest)
                        (case (current-poly-target)
                          cases ...
                          [else (error (quote item-id-stx) "not defined for ~a" (current-poly-target))]))))))
             )])
       #'(begin statements ...))
     ]))

(generate-reexports)

current-load-relative-directory is currently parameterized to a hard-coded path; I'm still looking for a way to avoid the hard-coding.

bluebear94 commented 2 years ago

As I've mentioned earlier, we can't require pollen/setup for syntax because if we did, then loading pollen.rkt would require pollen/setup when compiling which depends on pollen.rkt for the setup parameters ad infinitum. This also prevents us from require-ing any Pollen-related modules in pollen/html.rkt or pollen/tex.rkt.

sorawee commented 2 years ago

Can you use define-runtime-path to solve the wrong directory problem?

bluebear94 commented 2 years ago

Perhaps, but I don't think it'll solve the import cycle.

jnboehm commented 2 years ago

I think this is the most promising approach that I have come across: https://github.com/otherjoel/thenotepad/tree/master/pollen-local

It does some trickery with defining the functions according to a naming scheme, so it doesn't go by file itself but allows for separating it that way. I'd suggest at polytag.rkt, where the macro is defined, then the actual tag functions are in other files.

otherjoel commented 2 years ago

I have a slightly more evolved version of that approach here: https://thelocalyarn.com/code/file?name=pollen.rkt&ci=tip