unofficial mirror of guile-user@gnu.org 
 help / color / mirror / Atom feed
* Dynamically add bindings to the module currently being compiled
@ 2024-11-09 12:27 Vivien Kraus
  2024-11-09 13:06 ` Maxime Devos via General Guile related discussions
  0 siblings, 1 reply; 4+ messages in thread
From: Vivien Kraus @ 2024-11-09 12:27 UTC (permalink / raw)
  To: guile-user

Dear guile users,

I would like to define a macro G_ which accepts 1 string literal
argument. Whenever it is expanded during the compilation of a module, I
would like to dynamically add a binding to that module (named 'marked-
strings for instance) to be exported. marked-strings would contain the
list of string literals that were used as arguments to the G_ macro
anywhere in the module.

I tried to use the module reflection API in my macro definition to add
bindings to (current-module), but I can’t get it to work, and I have a
feeling the compiler is not compiling what I dynamically added there.

Am I correct that the compiler does not integrate my dynamically added
bindings? Can I have hope to achieve something similar to what I want
to do?

Best regards,

Vivien



^ permalink raw reply	[flat|nested] 4+ messages in thread

* RE: Dynamically add bindings to the module currently being compiled
  2024-11-09 12:27 Dynamically add bindings to the module currently being compiled Vivien Kraus
@ 2024-11-09 13:06 ` Maxime Devos via General Guile related discussions
  2024-11-09 21:57   ` Vivien Kraus
  0 siblings, 1 reply; 4+ messages in thread
From: Maxime Devos via General Guile related discussions @ 2024-11-09 13:06 UTC (permalink / raw)
  To: Vivien Kraus, guile-user@gnu.org

If you dynamically add bindings during _expansion_ (instead of expanding to code that _defines_ and exports something), then naturally the compiler never sees it, so it doesn’t end up in .go and when the .go is loaded, that binding isn’t there anymore.

Proposed alternative:

• 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.

Another option is to forego a macro implementation and instead implement it as a compilation pass. There doesn’t seem to be good support for plugging in custom extra compilation passes, though.

Best regards,
Maxime Devos



^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: Dynamically add bindings to the module currently being compiled
  2024-11-09 13:06 ` Maxime Devos via General Guile related discussions
@ 2024-11-09 21:57   ` Vivien Kraus
  2024-11-09 22:05     ` Vivien Kraus
  0 siblings, 1 reply; 4+ messages in thread
From: Vivien Kraus @ 2024-11-09 21:57 UTC (permalink / raw)
  To: Maxime Devos, guile-user@gnu.org

[-- Attachment #1: Type: text/plain, Size: 990 bytes --]

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

[-- Attachment #2: exported-string-literals.scm --]
[-- Type: text/x-scheme, Size: 4881 bytes --]

;; 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))))))

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: Dynamically add bindings to the module currently being compiled
  2024-11-09 21:57   ` Vivien Kraus
@ 2024-11-09 22:05     ` Vivien Kraus
  0 siblings, 0 replies; 4+ messages in thread
From: Vivien Kraus @ 2024-11-09 22:05 UTC (permalink / raw)
  To: Maxime Devos, guile-user@gnu.org

Le samedi 09 novembre 2024 à 22:57 +0100, Vivien Kraus a écrit :
> With your precious help and some digging around, I produced the
> attached code. Thank you!

P.S. I am a bit perplexed by syntax-parameterize introducing (let () …)
forms in the expansion but I think I can work around it by doing
define-public instead of define.



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2024-11-09 22:05 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-09 12:27 Dynamically add bindings to the module currently being compiled Vivien Kraus
2024-11-09 13:06 ` Maxime Devos via General Guile related discussions
2024-11-09 21:57   ` Vivien Kraus
2024-11-09 22:05     ` Vivien Kraus

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).