Re: Dynamically add bindings to the module currently being compiled

Vivien Kraus <[email protected]>
Newsgroups gmane.lisp.guile.user
Message-ID <[email protected]>
Hello!

Le samedi 09 novembre 2024 à 14:06 +0100, Maxime Devos a écrit :
>  * Define a mapping of module->collected-strings somewhere.
>  * Let G_ add things to this mapping, during _expansion_ (syntax-case
> instead of syntax-rules will be needed).
>  * Define another macro ‘define-marked-strings’ that, during
> expansion, looks into this mapping, and from its contents constructs
> code that defines the variable.
>  
> For hygiene, to avoid state and to make it independent of the module
> system, you could try to make it in the form:
>  
> (collect-G-literals marked-strings
>   [various definitions])
> -> (begin [various definitions] (defined marked-strings '("bla"
> ...)))
>  
> There is something similar to ‘parameterize’ but for macros and
> syntax that may be useful for this, but I’m not sure if it has the
> required semantics.

With your precious help and some digging around, I produced the
attached code. Thank you!

Vivien
exported-string-literals.scm (text/x-scheme, 4.8 KB)
;; This solution uses syntax-parameters.

(define-syntax-parameter G_
  ;; G_ is replaced in the body by a call to a function named
  ;; “translate”, which is not defined here.
  (lambda (sintax)
    (syntax-violation 'G_
                      "G_ used outside of with-exported-string-literals"
                      sintax)))

(define-syntax-parameter all-string-literals
  ;; all-string-literals is replaced by the data in the form of a list
  ;; of calls to cons: (cons context-1 message-1) (cons context-2
  ;; message-2). So, the result evaluates to a list of pairs.
  (lambda (sintax)
    (syntax-violation 'all-string-literals
                      "all-string-literals used outside of with-exported-string-literals"
                      sintax)))

(define-syntax with-exported-string-literals
  ;; This big macro will evaluate its guts by replacing calls to G_
  ;; and all-string-literals.
  (lambda (sintax)
    ;; Whenever G_ expands, its arguments are saved in
    ;; collected-strings.
    (define collected-strings '())
    ;; This is a convenience function to push a new collected string
    ;; to the list.
    (define (push-collected-string! context message)
      (unless (string? message)
        (error "only literal string messages are accepted"))
      (unless (or (not context)
                  (string? context))
        (error "only optional literal string contexts are accepted"))
      (set! collected-strings (cons (cons context message)
                                    collected-strings))
      (datum->syntax #f #t))
    ;; The *push-collected-string!* transformer converts calls to a
    ;; G_-like macro by first recording the arguments, and then
    ;; replacing the call with a call to translate.
    (define *push-collected-string!*
      (lambda (sintax)
        (syntax-case sintax ()
          ((_ context-argument message-argument)
           #`(begin
               #,(push-collected-string! (syntax->datum #'context-argument)
                                         (syntax->datum #'message-argument))
               (translate message-argument #:context context-argument)))
          ((_ message-argument)
           #`(begin
               #,(push-collected-string! #f (syntax->datum #'message-argument))
               (translate message-argument))))))
    ;; The *all-collected-string* transformer converts the
    ;; all-string-literals placeholder with code evaluating to the
    ;; data: (list (cons "context-1" "message-1") (cons "context-2"
    ;; "message-2")), because I can’t get the transformer to do (quote
    ;; ("context-1" . "message-1") ...)
    (define *all-collected-strings*
      (lambda (sintax)
        (syntax-case sintax ()
          (_
           #`(list
              #,@(map
                  (lambda (item)
                    #`(cons #,(datum->syntax #f (car item))
                            #,(datum->syntax #f (cdr item))))
                  (reverse collected-strings)))))))
    ;; And now the transformation of the guts to
    ;; with-exported-string-literals.
    (syntax-case sintax ()
      ((_ body ...)
       (with-syntax ((push *push-collected-string!*)
                     (all *all-collected-strings*))
         #'(begin
             ;; It uses syntax-parameterize.
             (syntax-parameterize ((G_ push))
               ;; I can’t end the syntax-parameterize body with the
               ;; dots (wtf???) so I end it with a #t. The body does
               ;; not have access to the all-string-literals form.
               body ... #t)
             (syntax-parameterize ((all-string-literals all))
               (define _i18n:strings
                 all-string-literals)
               (export _i18n:strings))))))))

,expand (with-exported-string-literals
         (define (generic-hello)
           (display (G_ "greeting" "Hello, world!\\n"))
           (newline))
         (define (hello-guile-user)
           (display (G_ "greeting" "Hello, Guile user!\\n"))
           (newline)))

;; Expands to:
#;(begin
  (let ()
    (define (generic-hello)
      (display
        (begin
          #t ;; This is unfortunate but not a problem
          (translate
            "Hello, world!\\n"
            #:context
            "greeting")))
      (newline))
    (define (hello-guile-user)
      (display
        (begin
          #t
          (translate
            "Hello, Guile user!\\n"
            #:context
            "greeting")))
      (newline))
    #t)
  (let ()
    (define _i18n:strings ;; It is unfortunate that I can’t produce a quoted S-expr
      (list (cons "greeting" "Hello, world!\\n")
            (cons "greeting" "Hello, Guile user!\\n")))
    ;; The rest is to export _i18n:strings:
    ((@@ (guile) call-with-deferred-observers)
     (lambda ()
       ((@@ (guile) module-export!)
        ((@@ (guile) current-module))
        '(_i18n:strings))))))
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.