Compile Time Grass to D CompilerでGrassを実行可能バイナリにコンパイル

みどりの日恒例 Grass Hackathon (2年ぶり2回目、そして記事は2日遅れ)
D言語版のGrassインタプリタと、コンパイル時にGrassコードをD言語に変換し、結果をmixinするという流れでGrassを実行可能バイナリにコンパイルするモノを作ってみました。

https://github.com/youz/grasses/tree/master/D

grass.dが普通のインタプリタ、grassctc.d がコンパイル時Grass to Dコンパイラです。

grassctc.d コンパイル方法

grassのコードを"source.grass"というファイル名でgrassctc.dと同じ場所に保存して以下のコマンドでコンパイルします。

$ dmd -J. -of実行ファイル名 grassctc.d

あるいはwindowsならgrassc.bat, linux&Mac(or msys)ならgrassc(シェルスクリプト)にgrassファイルを渡せば良きに計らってくれます。
また、-pオプションを付けて呼び出すとstderrに変換結果を表示しつつコンパイルします。

$ cat w.grass
wWWwwww
$ path/to/grassc -p w.grass
(new F(delegate V(V f1) {
  return f1(f1);
}))(new F(delegate V(V f0) {
  return writer(w);
}));

$ ./w.exe
w

変換サンプル

256回'w'を出力するやつ

wwWWwWWWwvWwWwWwwwwWwwwwwww

(new F(delegate V(V f3) {
  return (new F(delegate V(V f4) {
    return (new F(delegate V(V f5) {
      return (new F(delegate V(V f6) {
        return (new F(delegate V(V f7) {
          return f7(f7);
        }))(f6(w));
      }))(f5(writer));
    }))(f4(f4));
  }))(f3(f3));
}))(new F(delegate V(V f0) {
  return new F(delegate V(V f1) {
    return (new F(delegate V(V f2) {
      return f0(f2);
    }))(f0(f1));
  });
}));

hello world

wwWWWWwWwwwWwwwWwwwvwwWWwWWWwvWwWwvwWWWWWwWWWwWwwwvwvWwwwwwWwwwWWwWWWwWWWWWWWWwWWWWWWwwwwwwwwwwwwwwWWWWWWWWwWWWWWwWWWWWwwwWWWWWWwwwWWWWWWWWWwWWWWWWWWWWwwwWWWWWWWWWwWWWWWWWWWWwwwWWWWWWWWWWwwwwWWWWWWWWWWWWWwWWWWWWWWWWWWwwWWWWWWWWWWWWWWWwWWWWWWWWWWWWWWWwWWWWWWWWWWWWWWWWWWWWWWWWWWWwvwWWWWWWWWWWWWWWWWWWWWWWWWWWWWwvWwwwwwwWWwwwwwwwwwwWWWwwwwwwwwwwwwwWWWWwWWWWWwwwwwwwwwwwwwwwwwWWWWWWwwwwwwwwwwWWWWWWWwwwwwwwwwvwWWWWWWWWWwvWwwwwwwwwwwwwwwwwwwwwwwwwWWwwwwwwwwwwwwwwwwwwwwwwWWWwwwwwwwwwwwwwwwwwwwwwwwwWWWWwwwwwwwwWWWWWwwwwwwwwwwwwwwwwwwwwWWWWWWwwwwwwwwwwwwwww

(new F(delegate V(V f5) {
  return (new F(delegate V(V f9) {
    return (new F(delegate V(V f10) {
      return (new F(delegate V(V f11) {
        return (new F(delegate V(V f15) {
          return (new F(delegate V(V f17) {
            return (new F(delegate V(V f18) {
              return (new F(delegate V(V f19) {
                return (new F(delegate V(V f20) {
                  return (new F(delegate V(V f21) {
                    return (new F(delegate V(V f22) {
                      return (new F(delegate V(V f23) {
                        return (new F(delegate V(V f24) {
                          return (new F(delegate V(V f25) {
                            return (new F(delegate V(V f26) {
                              return (new F(delegate V(V f27) {
                                return (new F(delegate V(V f28) {
                                  return (new F(delegate V(V f29) {
                                    return (new F(delegate V(V f30) {
                                      return (new F(delegate V(V f31) {
                                        return (new F(delegate V(V f32) {
                                          return (new F(delegate V(V f33) {
                                            return (new F(delegate V(V f34) {
                                              return (new F(delegate V(V f35) {
                                                return (new F(delegate V(V f36) {
                                                  return (new F(delegate V(V f37) {
                                                    return (new F(delegate V(V f39) {
                                                      return (new F(delegate V(V f40) {
                                                        return (new F(delegate V(V f41) {
                                                          return (new F(delegate V(V f42) {
                                                            return (new F(delegate V(V f43) {
                                                              return (new F(delegate V(V f44) {
                                                                return (new F(delegate V(V f45) {
                                                                  return (new F(delegate V(V f46) {
                                                                    return (new F(delegate V(V f48) {
                                                                      return (new F(delegate V(V f49) {
                                                                        return (new F(delegate V(V f50) {
                                                                          return (new F(delegate V(V f51) {
                                                                            return (new F(delegate V(V f52) {
                                                                              return (new F(delegate V(V f53) {
                                                                                return (new F(delegate V(V f54) {
                                                                                  return f54(f54);
                                                                                }))(f48(f37));
                                                                              }))(f48(f31));
                                                                            }))(f48(f43));
                                                                          }))(f48(f25));
                                                                        }))(f48(f26));
                                                                      }))(f48(f23));
                                                                    }))(new F(delegate V(V f47) {
                                                                      return f39(f47);
                                                                    }));
                                                                  }))(f39(f36));
                                                                }))(f39(f34));
                                                              }))(f39(f26));
                                                            }))(f39(f42));
                                                          }))(f39(f28));
                                                        }))(f39(f30));
                                                      }))(f39(f33));
                                                    }))(new F(delegate V(V f38) {
                                                      return writer(f38);
                                                    }));
                                                  }))(succ(f36));
                                                }))(f21(f35));
                                              }))(f20(f34));
                                            }))(f22(f32));
                                          }))(f20(f32));
                                        }))(f22(f28));
                                      }))(f21(f28));
                                    }))(f21(f29));
                                  }))(f19(f26));
                                }))(f19(f27));
                              }))(f21(f24));
                            }))(f21(f23));
                          }))(f20(f24));
                        }))(f15(f23));
                      }))(f17(w));
                    }))(f10(f21));
                  }))(f18(f20));
                }))(f18(f19));
              }))(f18(f15));
            }))(f17(f9));
          }))(new F(delegate V(V f16) {
            return f16;
          }));
        }))(new F(delegate V(V f12) {
          return (new F(delegate V(V f13) {
            return (new F(delegate V(V f14) {
              return f14(f12);
            }))(f11(f13));
          }))(f5(f12));
        }));
      }))(f10(f10));
    }))(f9(f9));
  }))(new F(delegate V(V f6) {
    return new F(delegate V(V f7) {
      return (new F(delegate V(V f8) {
        return f6(f8);
      }))(f6(f7));
    });
  }));
}))(new F(delegate V(V f0) {
  return new F(delegate V(V f1) {
    return (new F(delegate V(V f2) {
      return (new F(delegate V(V f3) {
        return (new F(delegate V(V f4) {
          return f4(f2);
        }))(f3(f1));
      }))(f2(f0));
    }))(succ(f1));
  });
}));

ベンチマーク

wを (2^2)^(3^2) = 262144回succした結果を印字するコードでインタプリタコンパイルした奴の速度を比較してみる。

$ cat 262144.grass
wwWWwWWWwvwwWWwWWWwWWWWwvWWwwWWWwwWwwWwwwwwwwWwwwwwwwwwWWWWWWWWw

$ time grass.exe 262144.grass
w
real    0m0.520s
user    0m0.015s
sys     0m0.000s

$ grassc 262144.grass

$ time ./262144.exe
w
real    0m0.265s
user    0m0.015s
sys     0m0.046s

もうちょっと重い処理をさせてみたいけど簡単に書ける重いコードってのがなかなか難しい…

Conclusion

D言語はろくにプログラミング言語を知らない頃にABAさんのSTGでその名前を知り、超絶クールなゲームが作れるプログラミング言語というイメージを持っていましたが、実際使ってみると普通なコードは簡潔に書けて良い感じですし、またCTFE&MIXINで魔界への扉が半分以上開いてる感じもあってとても良いですね。
コンパイルコンパイラコンパイラのライブラリとかいくつもあるみたいですし、escodegen的なライブラリも出てくれば(すでにある?)楽しいだろうなーと思いました。

(宣伝) Willow Wind Orchestra 第6回定期演奏会

1週間前告知。
来週2月17日(日)長野県千曲市のあんずホールにて私の所属してる吹奏楽団「Willow Wind Orchestra」の第6回定期演奏会があります。

定期演奏会情報

平成25年2月17日(日) 千曲市更埴文化会館 あんずホール
開場13:00 開演13:30   入場料:500円(未就学児無料)
指揮:藤井貴宏

[曲目]
♪13管楽器のためのセレナード/R.シュトラウス
 *オーボエ:藤井貴宏
♪ハンガリー民謡「くじゃく」による変奏曲/Z.コダーイ
♪エル・カミーノ・レアル/A.リード
♪オリエント急行/P.スパーク
♪「サウンド・オブ・ミュージック」メドレー    他

メインは「くじゃく」を全曲やります。
興味のある方はお気軽にお越しくださいませ。

Schemeベースのcyber-physical programming環境 "Extempore"

Lisp Advent Calendar 2012の記事です。

あまり知られてなさそうなScheme処理系 "Extempore" で遊んでみます。

Extemporeとは

Andrew Sorensen氏によって開発されているOpenSourceのScheme処理系です。
https://github.com/digego/extempore

クロスプラットフォームで動作するよう開発が進められていて、現在はLinux, MacOS X, Windows(いずれも64bitのみ)がサポートされています。

cyber-physical programming って何?

Andrew氏らの提唱する概念で論文がこちら
LiveCoding+αみたいなモンでしょうか。(読んでない

あ、ここでいうLiveCodingっていうのは10日目のnitroさんの記事のソレではなく、英語版Wikipediaにある方の意味。

a performance practice centred upon the use of improvised interactive programming in creating sound and image based digital media.

http://en.wikipedia.org/wiki/Livecoding

要するにプログラミングで行うVJみたいなモンですね。



ExtemporeにはImpromptuという前身があり、Macで動作する無料のソフトでした。(ソースコードは非公開)
3年ほど前にRadium SoftwareのKZRさんが紹介されていたのでご存じの方もいるかもしれません。

Impromptuのデモ(Vimeo)
https://vimeo.com/2735394

動画を見るとわかりますがImpromptuの時点で相当クールな処理系でした。より強力で柔軟な言語機能を得る為に0から処理系を作りなおしたようです。

Extemporeの言語仕様

インタープリタで実行されるSchemeと、LLVMを通してネイティブコードにコンパイルされて実行される見た目Scheme風の静的型付き言語 "xtlang" を組み合わせて処理するハイブリッドなシステムになっています。

;; Schemeのfib
(define (fib n)
  (if (< n 2) 1
      (+ (fib (- n 1))
	 (fib (- n 2)))))

;; xtlangのfib
(bind-func fibx
  (lambda (n:i64)
    (if (< n 2) 1
	(+ (fibx (- n 1))
	   (fibx (- n 2))))))

SchemeはR5RSのサブセットっぽい感じで、継続なんかも使えますがdefine-syntaxがない等いくつか足りない物があるようです。
bind-funcフォームの中身がxtlangです。64bit intを受け取って64bit intを返すコードがllvmコンパイルされ、fibxに束縛されます。

文法他詳しい説明はこちらにあります。

言語機能以外ではDSP(Digital Sound Processing)機能とOpenGLを使ったグラフィクス描画の機能が用意されていますが、FFI機構があるのでほぼなんでもできる感じです。
例. Kinectから入力を読み取るライブラリ

Extemporeのビルド

LinuxMacについてはgithubレポジトリのドキュメントの通りに作業すればOK。
Windowsの場合はVS2010製品版でビルドする場合はドキュメントの通りでOKですが2012の場合はLinuxMac共通のLLVMパッチの他にいくつか修正しないといけない作業があります。
http://benswift.me/2012-11-05-building-extempore-on-windows.html
VS2012の場合↑このページの説明の通りに作業すればOK。
ちなみにVS2010expressだとCMAKEがうまく動かなくてLLVMのビルドでハマります…2012ならexpressでも大丈夫。

また、BoostとかLLVMにパッチ合っててフルビルドとかめんどくせーって人用にLinuxとMac OSX用のコンパイル済みバイナリが用意されています。
https://github.com/digego/extempore/downloads
Githubのdownloadsページはつい先日廃止が決まったので欲しい人はお早めに。
Windows用バイナリは公式で配布されてませんが、私がビルドした物をDropboxに置いておきました。

32bit環境はサポートされてないんですが、音鳴らすだけならとりあえず大丈夫なんで置いておきます。
FFI関係の機能が全滅なのでOpenGLのサンプルとか動かそうとすると死にます。

Extemporeの起動

Extemporeはファイルを渡して実行するようなインタプリタではなく、SWANKの様なS式サーバーとして動作します。
引数なしで起動すると、ポート7099でTCP接続を待ちます。

##########################################
##                                      ##
##               EXTEMPORE              ##
##                                      ##
##           andrew@moso.com.au         ##
##                                      ##
##            (c) 2010-2012             ##
##                                      ##
##########################################
     ################################
          ######################
               ############
                    ##

// 中略 //

Starting primary process
Trying to connect to 'localhost' on port 7099
New client connection
Successfully connected to remote process

Starting utility process
Trying to connect to 'localhost' on port 7098
New client connection
Successfully connected to remote process

データの送受信は評価したいS式をそのまま送って最後にCRLFを付ければ良いようです。
レポジトリにEmacs用とVim用のプラグインがありますが、今回はEmacsを使って行きます。

extempore-modeの使い方

extempore-modeの設定方法は省略。(elファイルを見て下さい)
操作は

  • C-x C-j … Extemporeに接続
  • C-x C-x … カーソル位置のフォーム(トップレベルまで)をExtemporeに送信
  • C-x C-r … リージョン内のフォームをExtemporeに送信
  • C-x C-b … バッファ内のすべてのフォームをExtemporeに送信

http://gyazo.com/c31ba4179eb6d316c31039c70e7fb687.png
左がソースバッファ(extempore-mode)で右がshell-modeから起動したextemporeの出力
左側のカーソル位置(緑の□)で C-x C-x を押すと、 (define (fib ... )) が評価されます。
print, display関数等の出力は標準出力へ、フォームの評価結果の値はminibufferに表示されます。
http://gyazo.com/b3fb6df5f58e16d5c4ba734c8dab6a7e.png

先ほどの2種のフィボナッチ関数を走らせてみて、どのくらい差があるか見てみましょう。

(define-macro (time form)
  `(let ((t (clock:clock))
	 (result ,form))
     (print (- (clock:clock) t) "sec.\n")
     result))

(time (println (fib 30)))
(time (println (fibx 30)))
(time (println (fibx 40)))

結果

1346269
20.868164 sec.

1346269
0.072266 sec.

165580141
9.282226 sec.

私のマシンでは(fib 30)はGaucheで0.5秒くらいですからxtlangはかなり速いですね。sbclの最適化ナシの速度と大体同じくらいになっています。
しかし実際に触ってみるとわかるのですがコンパイルがとても重いです。
ですので重い処理はあらかじめロード(コンパイル)しておいて、Schemeで各々の処理を組み合わせてLiveCodingしていく、というスタイルになります。

音を鳴らしてみる

前述のとおり実行速度はとても速いので、MIDI等使って外部のシンセサイザーを鳴らすまでもなくxtlangで波形生成からエフェクト処理まで書けてしまいます。
このサンプルを実行してみると、こんな音がします、と紹介しようと思ったんですがUSB音源がダメすぎてループバック録音ができない事に気づいた午後11時。両オスのコードも見つからないぞ。ぎゃー


(12/28 追記) 録音できた

竹内関数音楽をExtemporeで

昨年話題になった竹内関数音楽をExtemporeで演奏するコードを書いたのですがこれも録音できず。
折角なのでコードだけ載せておきます。録音は後日…


(12/28 追記) こっちも録音できた

;; 音色はさっきのサンプルから拝借
(load "libs/core/instruments.xtm")

(define-instrument synth synth_note_c synth_fx)

(bind-func dsp:[double,double,double,double,double*]*
  (lambda (in time chan dat)
    (cond ((< chan 2.0)
           (synth in time chan dat))
          (else 0.0))))

(dsp:set! dsp)


;; 音階
(define (scale n base)
  (let ((m (modulo n 7)))
    (+ base
       (* 12 (/ (- n m) 7))
       (list-ref '(0 2 4 5 7 9 11) m))))

;; tak関数
(define (tak x y z next)
  (let* ((len (* 2 44100))   ; 2秒ずつ鳴らす
         (t (+ (now) len)))  ; 次の音へ移る時刻
    (print "[" x y z "]\n")
    ; 和音発声
    (map (lambda (n) (play-note (now) synth (scale n 60) 70 len))
         (list x y z))
    ; 次の音へ
    (if (<= x y)
        (callback t next y)
      (callback t tak (- x 1) y z
                (lambda (nx)
                  (tak (- y 1) z x
                       (lambda (ny)
                         (tak (- z 1) x y
                              (lambda (nz)
                                (tak nx ny nz next))))))))))

;; start
(tak 10 5 1 println)

;; stop
(define (tak x y z next) )

tak関数がCPS変換されている理由ですが、普通の形だと発音の後に発音時間分待ってから(※)次の再帰呼び出しへ行かないといけないので、途中で演奏を止めたくなったらC-c C-cで強制終了するしかありません。
というかそもそも処理をブロックしてしまったらLiveCodingどころではないので、再帰的な演奏はcallback機構を使って "Temporal Recursion" というスタイルで書くのがExtempore流です。

http://benswift.me/2012-10-15-time-in-extempore.html
↑この辺参照

(tak 10 5 1 println)を評価するとcallbackで演奏を継続しつつ入力も受け付ける状態になります。
stopコメントの式を評価すると、takが何もしない関数に上書きされて大体1フレーズ後に演奏が止まります。

※ play-noteによる発音自体は非同期処理

Extempore動画

Andrew氏がVimeoに投稿しているモノをいくつかご紹介。とても面白いモノばかりですので是非一度ご覧あれ。

チュートリアル。ちょっとxtlangの書き方が今と違いますがExtemporeとはどんな物なのかがよくわかります。

YAMAHAの自動演奏ピアノ"disklavier"をExtemporeからコントロールして演奏。

3Dモデルを動かすサンプル。

Extemporeでモーションキャプチャーからの入力を読み取って音を鳴らす実験。これはKinectではない模様。

複数の巨大タッチパネルの入力&物理演算。

最新作。楽しそー。

Impromptuの動画ですが、未来のIDEっぽくてステキ。

映像をリアルタイム解析してBGMを自動生成するシステム。(ImpromptuかExtemporeかどっちか分からず)

Impromptuでのクリスマス風音楽の即興演奏。



以上Extemporeの紹介でした。
音鳴らすだけならそんなに難しくないのでみなさんも是非遊んでみて下さい。

xyzzyのole-reader

この記事はLisp Reader Macro Advent Calendar 2012の記事です。

lisp方言のリーダーマクロの紹介という事で、Windowsテキストエディタ xyzzy のマクロ言語 xyzzy lisp よりole-readerをご紹介。

xyzzy lispのリーダーマクロ

まずはxyzzyをあまり知らない人向けにxyzzy lispのリーダー機能について簡単にご説明。
xyzzyはいわゆる1つのEmacsenってやつですが、マクロ言語はCommon LispのサブセットになっているためEmacs Lispとは結構違う所があります。

マクロ言語として採用されているxyzzy LispCommon Lispに近く6割程度の仕様が実装されている。
Emacs Lispとの互換性はあまり無い。その一方で、Windows APIにアクセスできるなど、Windowsネイティブのソフトウェアである利点をいかした作りになっている。

http://ja.wikipedia.org/wiki/Xyzzy

リーダー機能については、CommonLispの全ての標準マクロ文字と #* (bit-vector), #P (pathname) 以外の全ての#ディスパッチマクロに対応しています。

;;; xyzzy lisp REPL
user> #b01010101
85
user> #xffffffff
4294967295
user> #36r123456789abcdefghijklmnopqrstuvwxyz
86846823611197163108337531226495015298096208677436155
user> (exp (* pi #C(0 1)))
#C(-1.0d0 1.224646799147353d-16)
user> (aref #(1 2 3) 1)
2
user> (apply #'+ 1 2 3 '(4 5 6))
21
user> (list #+xyzzy 1 #-xyzzy 2 #+cl 3 #|comment|# )
(1)
user> #1=(format t "~S" '#1#)
#1=(format t "~S" '#1#)
nil

これらに加え、標準添付されているole拡張をロードするとole-reader #{ } が使えるようになります。

xyzzyのole-reader

xyzzy lispにはOLEオートメーションサーバーを操作する為の関数がいくつか用意されています。

  • ole-create-object
  • ole-get-object
  • ole-getprop
  • ole-putprop
  • ole-method
  • etc..

これらを使ってコードを書くと、

;;; http://xyzzy.s53.xrea.com/reference/wiki.cgi?p=OLE%A5%AA%A1%BC%A5%C8%A5%E1%A1%BC%A5%B7%A5%E7%A5%F3%A4%CE%BB%C8%CD%D1%CE%E3
; 意味もなく全部のシートに「東西南北」を書き込む
(let* ((xl (ole-create-object "Excel.Application"))
       (book (ole-method (ole-getprop xl 'Workbooks) 'Add))
       (numsh (ole-getprop
               (ole-getprop book 'Worksheets)
               'count)))
  (dotimes (i numsh)
    (let ((sheet (ole-getprop book 'Worksheets (1+ i))))
      (ole-putprop (ole-method sheet 'Range "A1:D1")
                   'Value #("東" "西" "南" "北"))))
  (ole-putprop xl 'Visible t))

ole-getprop, ole-methodがネストしまくってとても書きづらいし読みづらい。
そこでole-readerを使うと

(require 'ole)
(let* ((xl (ole-create-object "Excel.Application"))
       (book #{xl.Workbooks.Add}))
  (dotimes (i #{book.Worksheets.Count})
    (setf #{book.Worksheets[(1+ i)].Range["A1:D1"].Value}
          #("東" "西" "南" "北")))
  (setf #{xl.Visible} t))

VBに近い形で書けるようになり、とてもスッキリします。

動作は単純で、

user> '#{book.Worksheets[(1+ i)].Range["A1:D1"].Value}
(ole-method (ole-method (ole-method book ':Worksheets (1+ i)) ':Range "A1:D1") ':Value)

こんな風に展開しています。
実装を見ると分かるんですが、ドットがマクロ文字になっていて1つのトークンになっているので、ドットの前後にスペースや改行が入れられます。

user> '#{book.foo
             .bar
             .baz}
(ole-method (ole-method (ole-method book ':foo) ':bar) ':baz)

この辺はVBよりもちょっと柔軟ですね。

このリーダーが使われているコードはあまり見かけないのですが、突如大量のExcelに関する作業が発生した時等にとても便利ですので、なんか変な方眼紙とか投げ付けられてVBAで片そうとしてブチ切れる前にカカッとxyzzyを起動してサクっとやっつけてみましょう。

oleメソッドの名前付き引数呼び出し

以下半分余談。
xyzzyのole-method関数には名前付き引数呼び出し機能がなく、

'VBA
Worksheets(1).Copy After:=Workbook(2).Worksheets(1)

このような呼び出し方が出来ません。
Rubyのwin32oleだとHashを使って

# Ruby
xl = WIN32OLE.new("Excel.Application")
book = xl.Workbooks.Add
book.Worksheets(1).Copy(:After => book.Worksheets(1))

こんな風に書けます。

しかしつい先日、この名前付き引数呼び出しに対応した関数が追加されました。
OLE メソッドの名前付き引数に対応 (#361) · c628b3d · xyzzy-022/xyzzy · GitHub

ole-readerの方の対応はまだなのですが、中の人からは以下の様な書き方が提案されてます。

#{excel.Copy[worksheet1 {:Count 3}]} ;; できるかな?

https://github.com/xyzzy-022/xyzzy/issues/361#issuecomment-10626273

他に良い記法のアイディアがありましたらこのissue #361までコメントをお寄せ下さい。

windowsでsbcl+lispbuilderのセットアップ

chatonのCL部屋で困ってる人がいたみたいなので。
ここsbclwindows forkを使えば最速(全プラットフォーム中)でサンプルの動作まで持っていけるはず。
配布のこととかも考えたらライブラリの配置、ロード方法をしっかり理解しといた方が良いとは思うけどとりあえず動かすまでを最優先で。

1. sbcl windows forkのダウンロード

ここからStand-alone executableってのをダウンロード。

zipファイルを展開して出てくるsbcl-with-contrib-win32-うんたらかんたら.exeをパスの通ってる場所に置き、名前が長いのでsbcl.exeにリネームしておく。

2. sdl.dllのダウンロード

公式サイトからwindows用バイナリ(dll)をダウンロード

zipファイルを展開して出てくるsdl.dllをパスの通ってる場所に置く。

※ 今回スクリーンショットを撮るにあたって、新規環境でのセットアップをシミュレートするためwindowsにGuestログインし、C:\Users\Guest\bin というフォルダを作ってここにパスを通してsbcl.exeとsdl.dllを置いている。

3. sbcl起動 & lispbuilder-sdlインストール

コマンドプロンプトからsbclを起動し、おもむろに ":ql lispbuilder-sdl-examples" とタイプしてリターン

するとまずquicklispのインストールが始まります。
このsbclwindows forkにはquicklisp用のREPLコマンドが組み込んであってとっても便利。
ちなみにquicklispはexeに埋め込まれてるわけではなく、qlコマンドの初回使用時に最新版を取得してインストールするようになってるっぽいです。
quicklispのインストールが完了すると絶え間なくlispbuilder-sdl-examplesのロードが始まり、依存するライブラリがインストール&ロードされて行きます。

4. サンプルの起動

ロードが完了したらlispbuilder-sdl-examplesパッケージに移動してサンプルの関数を確認。

適当に実行してみる。

できたできた。

SLIMEのセットアップ

折角なのでEmacsとSlimeのセットアップもしてみよう。

1. quicklisp-slime-helperのインストール

SLIMEのインストールもquicklispでやっちゃえば簡単。
:ql quicklisp-slime-helper と打ち込んでインストール。


インストール完了後、.emacsの設定方法が表示されるのでコピペしておく。

(load (expand-file-name "~/AppData/Local/common-lisp/quicklisp/slime-helper.el"))
;; Replace "sbcl" with the path to your implementation
(setq inferior-lisp-program "sbcl")

Win7なら↑これ。XPだとslime-helper.elのパスが変わるはず。

2. .sbclrcの設定

quicklispは:qlコマンドを使うまでロードされないので、REPLコマンドが使えないSLIMEからquicklispを使うにはsbclの初期化ファイル .sbclrc でロードするよう設定が必要。
コマンドプロンプトで起動してるsbclのREPLから (ql:add-to-init-file) を実行すれば .sbclrc に必要な設定が書き込まれます。

3. Emacsのインストール

適当にググって適当な場所にインストールして下さい。 (ここIMEパッチ適用済のバイナリがオススメ?)
環境変数HOMEの設定(.emacsの置き場所)を忘れずに。

4. .emacsにSLIMEの設定を書き込む

1. の最後に表示されてる設定を ~/.emacs に書き込んで保存

5. SLIME起動

emacs起動しなおして M-x slime 実行

起動できなかったら.emacsが正しく読み込めてない(環境変数の設定か.emacsの置き場所がおかしい)か、.emacsに書き込んだ内容がおかしいか。

6. lispbuilder-sdlをquickload

(ql:quickload 'lispbuilder-sdl-examples) を実行

無事ロードできたらサンプルを実行してみる。 パッケージ名、関数名補間してくれるから探すの簡単。


できたできた。

逆FizzBuzz

http://www.jasq.org/2/post/2012/05/inverse-fizzbuzz.html
http://d.hatena.ne.jp/matarillo/20120515/p1
より。

入力を正規表現に変換してしまえばとても簡単になる。
https://gist.github.com/2708823

けれど、cl-ppcreだと上記のコードのままじゃちょっと長めの入力を渡すと死んでしまう。
バックトラックが爆発してるんだろうけど、正しい書き方あるのかな。

CL-USER(10): (defun test (n) (time (inverse-fizzbuzz (loop repeat n append '(fizz buzz fizz fizz buzz fizz fizzbuzz)))))

TEST
CL-USER(11): (test 5)

Evaluation took:
  0.091 seconds of real time
  0.093601 seconds of total run time (0.093601 user, 0.000000 system)
  103.30% CPU
  225,231,803 processor cycles
  65,520 bytes consed
  
(3 75)
CL-USER(12): (test 6)

Evaluation took:
  8.923 seconds of real time
  8.907657 seconds of total run time (8.907657 user, 0.000000 system)
  99.83% CPU
  22,234,078,381 processor cycles
  98,296 bytes consed
  
(3 90)
CL-USER(13): (test 7)
;; 返って来ない…

7個以上渡された時は最初の7個の繰り返しになってるか確認してから最初の7個をチェック、で終点は入力の長さから計算って形にしないといけなさそう。

ちなみにxyzzy正規表現検索だと全く問題なし。

user> (defun scan-all (re str &optional (start 0))
	(let ((s (string-match re str start)))
	  (when s (cons (list (1+ s) (match-end 0)) (scan-all re str (1+ s))))))

scan-all
user> (defun inverse-fizzbuzz (fb)
	(let ((re (format nil "~{~A~^_*?~}" (mapcar #'(lambda (s) (case s (fizz "f") (buzz "b") (t "z"))) fb)))
	      (pattern (format nil "~V@{__f_bf__fb_f__z~}" (+ 2 (floor (length fb) 7)) t)))
	  (car (sort (scan-all re pattern) #'> :key (lambda (p) (apply #'- p))))))
inverse-fizzbuzz
user> (defun test (n)
	(inverse-fizzbuzz
	 (loop repeat n append '(fizz buzz fizz fizz buzz fizz fizzbuzz))))

test
user> :time (test 5)
(3 75)
----------
0.000 sec.
user> :time (test 10)
(3 150)
----------
0.000 sec.
user> :time (test 1000)
(3 15000)
----------
0.484 sec.

7000個渡しても大丈夫。

(宣伝) WWO第5回定期演奏会

明後日26日に長野県千曲市のあんずホールにて吹奏楽の演奏会があります。
http://dl.dropbox.com/u/215714/willow/2012concert/wwo-5th-concert.png
1段目のはじっこの方で吹いてます。

曲目

北信にお住まいの方、90年代課題曲マニアの方は是非お越しくださいませ。
入場料は\500です。(未就学児無料)