Racket Macro to auto-define functions given a list

前端 未结 1 1257
执念已碎
执念已碎 2021-01-15 10:41

I want to auto-generate a bunch of test functions from a list. The advantage being I can change the list (e.g. by reading in a CSV data table) and the program will auto-gen

1条回答
  •  被撕碎了的回忆
    2021-01-15 11:24

    I see a couple of errors in your code:

    1. Your *oxyanion-tests* is a runtime value, but you need its values to use as function name identifiers, so it must be available at compile time.
    2. The syntax around the result of syntax-rules is implicit. So with syntax-rules, you only get the macro template language (see the docs for syntax for more info). Thus you can't do the datum->syntax that you are trying to do. You have to use syntax-case instead, which allows you to use all of Racket to compute the syntax objects you want.

    Here's what I came up with:

    #lang racket
    (require (for-syntax racket/syntax)) ; for format-id
    
    (define-for-syntax *oxyanion-tests*
      ;           name         cation
      (list (list "aluminate"  "Al")
            (list "borate"     "B")
            (list "gallate"    "Ga")
            (list "germanate"  "Ge")
            (list "phosphate"  "P")
            (list "sulfate"    "S")
            (list "silicate"   "Si")
            (list "titanate"   "Ti")
            (list "vanadate"   "V")
            (list "stannate"   "Sn")
            (list "carbonate"  "C")
            (list "molybdate"  "Mo")
            (list "tungstate"  "W")))
    
    (define ((*ate? elem) s-formula)
      (or (regexp-match? 
           (regexp (string-append "\\(" elem "[0-9.]* O[0-9.]*\\)")) 
           s-formula)
          (regexp-match?
           (regexp (string-append "(^| )" elem "[0-9.]* O[2-9][0-9.]*")) 
           s-formula)))
    
    (define-syntax (define-all/ate? stx)
      (syntax-case stx ()
        [(_)
         (let ([elem->fn-id 
                (λ (elem-str)
                  (format-id 
                   stx "~a?" 
                   (datum->syntax stx (string->symbol elem-str))))])
           (with-syntax 
             ([((ate? cation) ...)
               (map 
                (λ (elem+cation)
                  (define elem (car elem+cation))
                  (define cation (cadr elem+cation))
                  (list (elem->fn-id elem) cation))
                *oxyanion-tests*)])
             #`(begin
                 (define (ate? sform) ((*ate? cation) sform))
                 ...)))]))
    
    (define-all/ate?)
    (module+ test
      (require rackunit)
      (check-true (borate? "B O3"))
      (check-true (carbonate? "C O3"))
      (check-true (silicate? "Si O4")))
    

    The key is the elem->fn-id function, which turns a string into a function identifier. It uses datum->syntax with stx as the context, meaning the defined function will be available in the context where the macro is invoked.

    0 讨论(0)
提交回复
热议问题