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