マクロ展開時に計算処理を行なう


Tags: R6RS, マクロ

syntax-case を使うとマクロ展開時に計算処理を行なうことができる。

ここでは、 スター閉包・連接・和だけに構文を簡略化した SRE のサブセットを PCRE を表す文字列に変換するマクロを考える。

(import (rnrs))

(define-syntax rx
  (lambda (x)
    (define (regexp-quote s)
      (call-with-string-output-port
       (lambda (p)
         (string-for-each
          (lambda (c)
            (when (memv c '(#\( #\) #\* #\|))
              (write-char #\\ p))
            (write-char c p))
          s))))
    (define (intersperse x ys)
      (fold-right (lambda (y knil)
                    (if (null? knil)
                        (cons y knil)
                        (cons* y x knil)))
                  '() ys))
    (define (parse re)
      (syntax-case re (submatch : * or)
        ((submatch re ...)
         (string-append "("
                        (apply string-append (map parse #'(re ...)))
                        ")"))
        ((: re ...)
         (string-append "(?:"
                        (apply string-append (map parse #'(re ...)))
                        ")"))
        ((* re ...)
         (string-append "(?:"
                        (apply string-append (map parse #'(re ...)))
                        ")*"))
        ((or re ...)
         (string-append "(?:"
                        (apply string-append
                               (intersperse "|" (map parse #'(re ...))))
                        ")"))
        (s
         (string? (syntax->datum #'s))
         (regexp-quote (syntax->datum #'s)))
        (pat
         (syntax-violation 'rx:parse "invalid syntax" x #'pat))))
    (syntax-case x ()
      ((k re ...)
       (datum->syntax #'k (parse #'(: re ...)))))))
(rx (or "a" "b")) ; => "(?:(?:a|b))"
(rx (* (or "|" "*"))) ; => "(?:(?:(?:\\||\\*))*)"
(rx (: (or "(" ")") "a")) ; => "(?:(?:(?:\\(|\\))a))"

これらの計算は実行時ではなくマクロ展開時に行なわれる。コンパイルを行なう処理系ではこれらの計算は実行時に毎回行われるのではなく、コンパイル時にマクロを展開するときに一度だけ行なわれる。このように計算処理を実行時からマクロ展開時にずらすことで効率を上げることができることもある。