kiyokaのブログアーカイブ

Archive of old blog posts

syntax-rulesの実装中。難航中。

オレ処理系の[Nendo]についての開発メモ。 img chibi-scheme 0.3のer-macro-expanderとsyntax-rulesをそのままNendoに移植中。 今は、make-syntactic-closureをどう実装していいかわからないので、正解を探しているところ。

syntactic-closures Macros offer a way to extend the Scheme language by introducing new forms of syntax that are transformed into the simpler base language. Since Scheme’s syntax is defined in terms of Scheme objects – lists & symbols, mainly –, one would imagine that procedures that perform such transformations, known as macro transformers, should be easy: they just operate on lists & symbols, especially in the presence of quasiquote. However, this is not quite the case: macros need to also be aware of the underlying lexical scoping of the programs they transform. This article will not go into the need for that; see my article hygiene-versus-gensym for discussion of that matter. Macros need to work with more than S-expressions: they need S-expressions annotated with lexical scoping information. (以下略)

この文章を読んでも、具体的にどう実装すればいいのかわからん。 MIT Schemeとchibi-scheme 0.3のソースをじっくり読めばわかるのだろうか。

多分、クロージャを作ってそのレキシカル環境でバインドした変数名を返せばいいのだろう。 で、それは具体的にRubyでどう実装すればいいのかという問題。 受け取ったシンボルを、グローバル環境で(gensym)した変数名に代入してから、その(gensym)したシンボルを返してもいけるかも。

ちなみに、今の嘘実装はこれ。 (er-macro-expanederとsyntax-rulesはchibi-scheme 0.3のコードそのまま動いたので、本ブログへの引用は省略)

(define (make-syntactic-closure mac-env use-env identifier)
  (let* (*id-str     (symbol->string identifier)*
         *alias-data (assq-ref id-str mac-env)*)
    (if alias-data
        (string->symbol (car alias-data))
        identifier)))

グローバルバインドされたシンボル(ifなどの予約語)は別名 /nendo/macroenve/if などを返し、 それ以外の未定義のシンボルは、そのままシンボルを返している。

簡単な例では一見動いてように見える。

(define-syntax nil!
  (syntax-rules ()
    ((_ x)
     (set! x '()))))
nendo> (define a 1)

nendo> (macroexpand '(nil! a))
(/nendo/macroenv/set! a (/nendo/macroenv/quote ()))
nendo> (nil! a)
()
nendo> a
()

試しに、以下のchibi-schemeのcutの定義はそのまま通るので、動かしてみた。

(define-syntax %cut
  (syntax-rules (<> <...>)
    ((%cut e? params args)
     (lambda params args))
    ((%cut e? (params ...) (args ...) <> . rest)
     (%cut e? (params ... tmp) (args ... tmp) . rest))
    ((%cut e? (params ...) (args ...) <...>)
     (%cut e? (params ... . tmp) (apply args ... tmp)))
    ((%cut e? (params ...) (args ...) <...> . rest)
     (error "cut: non-terminal <...>"))
    ((%cut #t (params ...) (args ...) x . rest)
     (let ((tmp x)) (%cut #t (params ...) (args ... tmp) . rest)))
    ((%cut #f (params ...) (args ...) x . rest)
     (%cut #t (params ...) (args ... x) . rest))))
(define-syntax cut
  (syntax-rules () ((cut args ...) (%cut #f () () args ...))))
(define-syntax cute
  (syntax-rules () ((cute args ...) (%cut #t () () args ...))))

expandしてみる

(pretty-print
 (macroexpand
  '(map (cut + 1 <>) '(1 2 3 4))))

結果

(map
 (let
  ((tmp 1))
  (lambda
   (tmp)
   (+ tmp tmp)))
 ('
  (1 2 3 4)))

ヒドい。なんでもかんでもtmpになる

勿論、計算結果もでたらめ。

nendo> (map (cut + 1 <>) '(1 2 3 4))
(2 4 6 8)

まだまだ挑戦は続くのであった。