C言語をコンパイルするSELECT文(SQLite3用)を作った

@shinhさん作のコンパイラインフラストラクチャELVMに SQLite3シェル用のSQLを吐き出すバックエンドを足して@rui314さん作のCコンパイラ8ccSQLコンパイルし、29万行のSELECT文から成るCコンパイラが出来上がりました。

https://github.com/youz/8cc-sqlite3

こちらからCコンパイラ&ELVM IRアセンブラが ビューの形で入っているSQLite3のデータベースファイルを入手できます。

使い方

JSON1拡張が有効になっているバージョンのSQLite3シェルを使用します。 SQLite公式サイトで配布しているバイナリやDebianパッケージのsqlite3 ver.3.11 とかでOK。

$ sqlite3 elvm.db3

1. Cソース読み込み

elvm.db3を読み込んだら、テーブルsrc(b BLOB)コンパイルしたいCプログラムを入れます。

(注意) #includeを含むCソースはコンパイルできません

DELETE FROM src;
INSERT INTO src(b) VALUES('
int putchar(int c);
int main() {
  const char* p = "Hello, world!\n";
  for (; *p; p++)
    putchar(*p);
  return 0;
}');

あるいはreadfile関数を使ってファイルから読み込むと簡単です。

INSERT INTO src(b) VALUES(readfile('sample/hello.c'));

2. C → ELVM IR コンパイル

8ccをSQL化したビューelvm_8ccからstdout列をSELECTし、テーブルeir(b BLOB)に入れます。

DELETE FROM eir;
INSERT INTO eir(b) SELECT stdout FROM elvm_8cc;

(注意) 先のHelloWorldのコンパイルにはCore i5 2.5GHzのマシンで30分ほどかかります

3. ELVM IRのアセンブル

最後にELVM IRをELVMが対応している各種バックエンド言語にアセンブルするわけですが、 出力先言語は設定テーブル option(target TEXT) のtarget列に書き込んで指定します。 (optionテーブルには1行だけレコードが入っています)

UPDATE option SET target = 'rb';

出力先言語とそれに対応する文字列はREADMEに書いてあります。 もしくはDB内のsupported_targetsテーブルを見てください。

アセンブル処理はビュー elvm_elc からSELECT。

SELECT writefile('hello.rb', stdout) FROM elvm_elc;

(注意) HelloWorldのELVM IR→Rubyへのアセンブルには大体20分ほどかかります

Rubyアセンブルした結果はこんな感じ

$ cat hello.rb
@a = 0
@b = 0
@c = 0
@d = 0
@bp = 0
@sp = 0
@pc = 0
@mem = [0] * (1 << 24)
@mem[0] = 72
@mem[1] = 101
@mem[2] = 108
@mem[3] = 108
@mem[4] = 111
@mem[5] = 44
@mem[6] = 32
@mem[7] = 119
@mem[8] = 111
@mem[9] = 114
@mem[10] = 108
@mem[11] = 100
@mem[12] = 33
@mem[13] = 10
@mem[15] = 16


def func0
 while 0 <= @pc && @pc < 512
  case @pc

  when 0
   true && @pc = 1 - 1

  when 1
   @d = @sp
   @d = (@d + 16777215) & 16777215
   @mem[@d] = @bp
   @sp = @d
   @bp = @sp
   @sp = (@sp - 1) & 16777215
   @a = 0
   @b = @sp
   @a = 0
   @b = @bp
   @b = (@b + 16777215) & 16777215
   @mem[@b] = @a

  when 2
   @b = @bp
   @b = (@b + 16777215) & 16777215
   @a = @mem[@b]
   @b = @a
   @a = @mem[@b]
   @a == 0 && @pc = 4 - 1

  when 3
   true && @pc = 5 - 1

  when 4
   true && @pc = 7 - 1

  when 5
   @b = @bp
   @b = (@b + 16777215) & 16777215
   @a = @mem[@b]
   @b = @a
   @a = @mem[@b]
   @d = @sp
   @d = (@d + 16777215) & 16777215
   @mem[@d] = @a
   @sp = @d
   putc @a
   @sp = (@sp + 1) & 16777215

  when 6
   @b = @bp
   @b = (@b + 16777215) & 16777215
   @a = @mem[@b]
   @d = @sp
   @d = (@d + 16777215) & 16777215
   @mem[@d] = @a
   @sp = @d
   @a = (@a + 1) & 16777215
   @b = @bp
   @b = (@b + 16777215) & 16777215
   @mem[@b] = @a
   @a = @mem[@sp]
   @sp = (@sp + 1) & 16777215
   true && @pc = 2 - 1

  when 7
   @a = 0
   @b = @a
   exit
   exit
  end
  @pc += 1
 end
end

while true
 case @pc / 512
 when 0
  func0
 end
end

$ ruby hello.rb
Hello, world!

コンパイル&アセンブルを1度に済ませたい時はelvm_8cc_elcからSELECTしてください。最初から書くと

DELETE FROM src;
INSERT INTO src(b) VALUES(readfile('source.c'));
UPDATE option SET target = 'js';
SELECT writefile('output.js', stdout) FROM elvm_8cc_elc;

samplesフォルダにELVM本家のテストから3つほど#includeのない(あるいは処理済みの)物を 選んで入れてありますので、暇だったら遊んでみてください。

(注意) sample/fizzbuzz.cのコンパイルはめっちゃ時間かかります(計測放棄)

バックエンドの解説

SQL文中でループ等の制御構文を使えるDBMSならRubyやJS等とほぼ変わらない仕組みで バックエンドが書けそうですが、制御構文もストアドプロシージャもないSQLite3で どうやってやるかっていうと、ver 3.8.3でサポートされたRecursive Common Table Expression(以下再帰CTE)で実現できます。

再帰CTE

再帰CTEっていうのは、知らない人向けに説明すると関数型言語に良くある unfold関数と同じような再帰的展開処理をSELECT文で実現できる仕組みです。

Scheme(SRFI-1)のunfoldの定義は以下の通り

(unfold p f g seed [tail-gen]) =
  (if (p seed)
      (tail-gen seed)
      (cons (f seed)
            (unfold p f g (g seed))))
;; Gaucheでの使用例
(use srfi-1)
(unfold (lambda (e) (> e 10))    ; 終了判定をする関数 p
        (lambda (e) (* e e))     ; seedをリストの要素に変換する関数 f
        (lambda (e) (+ e 1))     ; seedから次のseedを生成する関数 g
        0)                       ; seed
        ; tail-genは省略可。デフォルトは (lambda (x) '())
;-> (0 1 4 9 16 25 36 49 64 81 100)
-- 再帰CTEの例
WITH RECURSIVE
  rec(e) AS (
    SELECT 0                      -- seed
    UNION SELECT e + 1 FROM rec   -- g
    WHERE e < 10                  -- p
  )
SELECT e * e FROM rec;            -- f

0
1
4
9
16
25
36
49
64
81
100

見比べるとコードのコメントにp, f, g, seedと書いてある行がそれぞれ 対応している事が分かるかと思います。

ちなみに再帰CTEでのseedは単一レコードではなくても良いので、再帰する毎に レコード数が変化するような処理も書けます。 (UNION SELECTが返すレコードセットが0件になった時点で再帰が止まる、 という仕組みになっています。)

-- 7回再帰して255レコードを生成する例
WITH
  d(i) AS (SELECT 0 UNION SELECT 1),
  r(i, v) AS (
    SELECT 1, 1
    UNION SELECT r.i + 1, r.v*2 + d.i
    FROM r CROSS JOIN d
    WHERE r.i < 8
  )
SELECT i, v FROM r;

1|1
2|2
2|3
3|4
3|5
3|6
3|7
4|8
4|9
4|10
...
8|253
8|254
8|255

その他のサンプル

ELVMの実装

さてこの再帰CTEでELVMバックエンドをどうやって作るかというと、

  • VMの状態(レジスタ、プログラムカウンタ(pc)、メモリ、IO等)を1レコードで表現する
  • インストラクション列を状態遷移としてUNION SELECTの部分で表現する

これが出来ればOK。

レジスタ、pc、フラグ等はINT、stdin/stdoutはTEXTとして単純に持てば良いのですが メモリがちょっと面倒。テーブルで表現できれば楽なんですがSELECT文中からテーブルを 更新するような事は出来ないので値として持つ必要があります。

BLOBでメモリを表してみたら書き換え時にメモリ食いまくって一瞬でメモリ不足エラーになってダメだったので、 遅そうだけれども省メモリで着実に動作してくれるJSON1拡張のjson_object形式でメモリを持つことにしました。

状態遷移ですが、1つのpcが指すコードブロック中の命令列に連番(コード中ではstepと表現)を振って、 pcとstepの組で状態を表す事にします。

で、あとはCASE式を機械的に組んでいけばOKなんですが、CASE式が表現できるのは あくまで1つの値なので、VMを表すレコードの各列ごとに状態遷移を書く必要があり、 単純な処理でもコンパイル結果は大分悲惨な事になります。

例えばELVMのテストにある02mov.eirは

mov A, 43    # pc=1 step=0
putc A       # pc=1 step=1
exit         # pc=1 step=2
  • (pc, step) = (0, 1)だったらレジスタAに43をセット
  • (pc, step) = (0, 2)だったらstdoutにstdout+char(A)をセット
  • (pc, step) = (0, 3)だったらrunningに0をセット

という状態遷移になるのですが、SQLアセンブルした結果がこちら↓

DROP TABLE IF EXISTS stdin;
CREATE TABLE stdin(i BLOB);
INSERT INTO stdin(i) VALUES(readfile('input.txt'));
DROP TABLE IF EXISTS data;
CREATE TABLE data(i INT PRIMARY KEY, v INT);
INSERT INTO data VALUES
  (0,1)
;
-- .stats on
WITH
 elvm AS (
  SELECT
   0 a,
   0 b,
   0 c,
   0 d,
   0 bp,
   0 sp,
   0 pc,
   0 step,
   1 running,
   (SELECT json_group_object(i, v) FROM data) mem,
   (SELECT i FROM stdin) stdin,
   '' stdout,
   0 cycle
  UNION ALL SELECT
   CASE pc / 512
   WHEN 0 THEN
    CASE pc
    WHEN 1 THEN
     CASE step
     WHEN 0 THEN 43
     ELSE a END
    ELSE a END
   ELSE a END
   ,
   b
   ,
   c
   ,
   d
   ,
   bp
   ,
   sp
   ,
   CASE pc / 512
   WHEN 0 THEN
    CASE pc
    WHEN 0 THEN
     CASE step
     WHEN 0 THEN 1
     WHEN 1 THEN pc+1
     ELSE pc END
    WHEN 1 THEN
     CASE step
     WHEN 2 THEN pc+1
     ELSE pc END
    END
   END
   ,
   CASE pc / 512
   WHEN 0 THEN
    CASE pc
    WHEN 0 THEN
     CASE step
     WHEN 0 THEN 0
     WHEN 1 THEN 0
     ELSE step + 1 END
    WHEN 1 THEN
     CASE step
     WHEN 2 THEN 0
     ELSE step+1 END
    END
   END
   ,
   CASE pc / 512
   WHEN 0 THEN
    CASE pc
    WHEN 1 THEN
     CASE step
     WHEN 2 THEN 0
     ELSE running END
    ELSE running END
   ELSE running END
   ,
   mem
   ,
   stdin
   ,
   CASE pc / 512
   WHEN 0 THEN
    CASE pc
    WHEN 1 THEN
     CASE step
     WHEN 1 THEN stdout||char(a)
     ELSE stdout END
    ELSE stdout END
   ELSE stdout END
   ,
   cycle + 1
  FROM elvm WHERE running = 1
 )
SELECT writefile('output.txt', stdout) FROM elvm WHERE running = 0;

恐ろしく見通しが悪いですね!

コンパイラSQLともなると、JMPするわけでもない連続した処理なのに次は10万行後、 その次はそこから15万行前とか、そういう酷いコードになります。 printfデバッグがしやすいのがせめてもの救い。(最後のSELECT文をいじって stdout以外の列も持ってきたり途中経過を持ってきたりするようにするだけ)

つい先日SQLite3に導入されたRow Valuesが使えたら 大分マシになるのかなと思いますが、残念ながら現時点ではCASE式の値としては使えません。

というわけでちょっとしたコードでも悪夢のようなSQLになってしまいますが、 無事ELVMの全テストをパスするバックエンドが出来上がりました。

おまけ:処理速度について

メモリの書き換えが

  1. JSON形式の文字列データをパースする
  2. 指定されたキーの値を書き換える
  3. データをJSON形式で文字列化する

という処理になっている(JSON1拡張はDBにJSON型を導入するのではなく、 文字列型データをJSONとしてパースした上で色々する機能を導入する拡張)ので、 メモリアクセスがボトルネックになってるだろうなと。 (そもそもテーブルでなく値で持ってるので、書き換えの度にコピーする前提)

という訳で実験として、単純で邪悪なSQLite3拡張を作ってみました。

garray.c

gainitでメモリ上に1つ配列を作り、garefで参照、gasetで書き換えます。 評価順が一意に決まるような所でしか使えない、DBMSには入れるべきでないアレな代物です。

JSONの代わりにこの配列拡張を使ってみた結果、30分かかっていたHelloWorldの コンパイル時間が2分ちょうど位になりました。

ドメイン特化言語で2分なら割と良い性能なのではないでしょうか(要出典)

CodeIQの言語総選挙の予選でcodegolf

先月開催されたCodeIQの言語総選挙の問題の予選ルールがショートコーディング入門によさ気で面白かったのです。 出題者がショートーコーディング本の著者のOzyさんという事もあり、これは解説でゴルフコードの紹介なんかもあるんでないかなーと期待して気合を入れて短くしたコードを投稿したのですが、全く触れられていなくて無念…

というわけで、ここで短くして行った過程を解説してみようと思います。 私の回答はCommonLispで書いて提出したのですが、折角なので1位を取ったRubyで書きなおしてみました。


問題

問題も解説もCodeIQにログインしないと見れないので簡単に説明。

教室に席が格子状に並んでおり、出席者をO、欠席者をXで表した表が標準入力から与えられる。 生徒たちが前後or左右の隣りの生徒と2人1組のグループを作る時、余りが出ない配置の場合はyes、どう組んでも余る生徒が出てしまう場合はnoと出力せよ、という問題。

(例)

OOOOO
OOOXO
OOOOO

上図の場合、ペアを数字で表すと

11223
445X3
66577

のように組めるのでyesと出力する。

OOO
OOX
OOO

だと

122
13X
O3O

のようにペアを作れない生徒が出てしまうのでno

入力データの条件として以下の制限があります。

席の列数をW, 行数をH, 欠席者の数をXとした時
予選 : 2≦W*H≦30, 0≦X≦1
本選 : 1≦W≦10, 1≦H≦10, 0≦X≦W*H

素朴な深さ優先探索で予選も本選も用意されたテストケースは全て通ってしまうようですが、予選の条件では後に解説するように簡単に判別する方法がありました。

以下予選の条件で解法を考えていきます。

方針1. 塗り分け&数え上げ

まずCodeIQの解説にあったやり方。 下図のように各席を市松模様のように塗り分けて考えます。

f:id:youz:20141013174136p:plain

ペアは必ずA席とB席の生徒で組まれるので、出席者のうちA席の生徒とB席の生徒をそれぞれ数え上げて同数なら余りが出ない、という考え方です。 欠席者が複数になるとAB同数でも余りが出る場合がありますが、欠席者が最大1名の予選ルールならこの数え上げだけで解が出ます。

というわけで最初のコード

def solve(input)
  map = input.split(/\n/)
  w = map[0].length
  h = map.length
  counts = [0, 0]   # A席,B席の生徒数を記録する配列
  h.times do |y|
    w.times do |x|
      if map[y][x] == "O"
        counts[(x+y)%2] += 1
      end
    end
  end
  counts[0]==counts[1]
end

puts solve($<.read) ? "yes" : "no"

一番左上の席の座標を(x,y) = (0,0)として、x+yが偶数だったらA席、奇数だったらB席と判定しています。

変数名を短くするだとか空白・改行を削るだとかの作業は一番最後にやるとして、まずアルゴリズムの簡素化をしていきます。

1重ループ化

とりあえず2重ループを1重にできないかなと考えます。 幅が奇数の場合は、下図のように改行を取っ払って1行にしてしまっても数え上げはうまく行きます。

ABA
BAB → ABABABABA
ABA

左端を0番目として、偶数番目がA席で奇数番目がB席。よって2重ループの部分は

map = input.delete("\n")
map.length.times do |i|
  if map[i] == "O"
    counts[i%2] += 1
  end
end

と短く出来ます。

幅が偶数の場合は改行を取っ払ってしまうと

ABAB
BABA → ABABBABAABAB
ABAB

のようになってしまってABの判別が面倒な事になります。 しかし幅が偶数ということは欠席者のいない行は必ずAの数=Bの数なので、欠席者のいる行だけ考えればよろしい。 そうすると先の奇数幅用のコードでも結果的には問題ないことがわかります。ちょっと気持ち悪いけど。

というわけで全体としてはこんな感じに。

def solve(input)
  map = input.delete("\n")
  counts = [0, 0]
  map.length.times do |i|
    if map[i] == "O"
      counts[i%2] += 1
    end
  end
  counts[0] == counts[1]
end

puts solve($<.read) ? "yes" : "no"

数え上げの工夫

配列を使って数え上げてるのがイケてない気がするのでなんとかしたいと考えた所、数え上げ用変数を1つだけ用意して、偶数番目だったらデクリメント・奇数番目だったらインクリメントして最終的に0になっていればOkとする方法がありました。

def solve(input)
  map = input.delete("\n")
  c = 0
  map.length.times do |i|
    if map[i] == "O"
      c += i%2*2-1
    end
  end
  c == 0
end

数え上げの所はc += i%2-0.5として0.5ずつ増減させる方法もアリ。

この時点でアルゴリズム上の工夫はこれ以上思いつかなくなってしまったので、一旦畳んでスコア(コード長)を測ってみます。

c=0
(m=$<.read.delete("\n")).length.times{|i|m[i]=="O"&&c+=i%2*2-1}
puts c==0?"yes":"no"

89バイトになりました。 とりあえずこの方針はここで一旦保留。

方針2. 欠席者の位置から判断

W*Hが偶数の場合は欠席者の有無、奇数の時はA席の生徒が欠席しているかどうかで判断できるのだから数え上げるまでもないですよね、という考え。

要するにこう。

  • W*H が偶数
    • 欠席者ナシ → yes
    • 欠席者アリ → no
  • W*H が奇数
    • 欠席者ナシ → no
    • 欠席者はA席 → yes
    • 欠席者はB席 → no

という訳で最初のコードがこちら

def solve(input)
  map = input.split("\n")
  w = map[0].length
  h = map.length
  if (w*h).even?
    # 偶数なら欠席者がいなければ"yes"
    input !~ /X/
  else
    # 奇数の場合
    h.times do |y|
      w.times do |x|
        if map[y][x] == "X"
          # 欠席者が見つかったらA席かどうかを返して終了
          return (x+y).even? 
        end
      end
    end
    # 欠席者が見つからなかったら"no"
    false
  end
end

puts solve($<.read) ? "yes" : "no"

方針1に比べて大分長い所からスタートする感じですが頑張って短くして行きましょう。

まず奇数の場合は方針1で使った1重ループ化の考え方が問題なく使える & 改行取っ払ってしまえば偶奇判定も楽という事で

def solve(input)
  map = input.delete("\n")
  l = map.length
  if l.even?
    input !~ /X/
  else
    l.times do |i|
      if m[i] == "X"
        return i.even?
      end
    end
    false
  end
end

っと、ループの所、文字列の探索ならindexメソッドで十分ですね。

def solve(input)
  map = input.delete("\n")
  if map.length.even?
    input !~ /X/
  else
    pos = map.index("X")
    !pos.nil? && pos.even?
  end
end

大分スッキリしましたが、偶数の場合奇数の場合それぞれでXを検索してるのが気になる所。 とりあえずposの取得を前に持ってきてみる。

def solve(input)
  map = input.delete("\n")
  pos = map.index("X")
  if map.length.even?
    pos.nil?
  else
    !pos.nil? && pos.even?
  end
end

この時点では畳んでもなんとか100を切れる程度。 全体長の偶奇で処理分けてるのコスト高いよなー何とかしたい、と風呂で考えてたら良いアイデアが降ってきました。

def solve(input)
  input.delete("\n").split("X").all?{|l|l.length.even?}
end

入力から改行を取っ払った上でXの位置で分割してしまえ、というアイデア。どうなるかいうと

  • W*H が偶数
    • 欠席者アリ(no) → 分割すると["OOOO", "O"]のように、長さが奇数の文字列と長さが偶数の文字列を要素に持つ配列になる
    • 欠席者ナシ(yes) → 分割されないので、偶数長の文字列1つを要素に持つ配列になる
  • W*H が奇数
    • 欠席者ナシ(no) → 奇数長の文字列1つを要素に持つ配列になる
    • 欠席者はA席(yes) → ["OO", "OOOOOO"]のように、偶数長の文字列2つを要素に持つ配列になる
    • 欠席者はB席(no) → ["OOO", "OOOOO"]のように、奇数長の文字列2つを要素に持つ配列になる

つまり、分割した結果、偶数長の文字列だけを持つ配列になる場合にyesとすれば良い訳です。

分割して、断片の長さの偶奇を測る、たったこれだけの作業ならアレで一発ですよねーという訳で

def solve(input)
  input.delete("\n") =~ /^(OO)*X?(OO)*$/
end

正規表現のマッチング1回で済んでしまいました。 畳んで全体を書くと

puts $<.read.delete("\n")=~/^(OO)*X?(OO)*$/?"yes":"no"

54バイトに。これだけ単純になると文法上のハックはほとんど必要ないですね。

保留にしておいた方針1でこれを超えるのは無理でしょう(多分)、という事でこれが私の回答の最終形。

二次元的な情報を全部捨ててしまっていて、一見するとこれは(用意されているテストケースだけ通る)cheatコードでしょーと 思ってしまいそうな回答ですが、ちゃんとどんなパターンにも対応できているっていうのが面白いです。


おまけで、予選落ちとなってしまったCommonLispのコードの供養。

予選用

(princ(if(regexp:match"^\\(OO\\)*X\\?\\(OO\\)*$"(format()"~{~A~}"(loop
while(listen)collect(read))))"yes""no"))

本選用

探索の前に

  • AB塗り分けによる枝刈り
  • 組む相手が1人しかいない箇所を予め埋めておく
  • 探索が早く収束するように、全体を4分割して欠席者が最も多いブロックから探索を開始する

といった工夫を入れた所、extracaseにも全て対応できました。

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) を実行

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


できたできた。