言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

pepsi & coke

大島さんの記事 http://d.hatena.ne.jp/squeaker/20061031#p2 を読みながら、pepsi & coke の勉強をしています。

環境の準備

idst-5.7 に付属する jolt を使って勉強します。必要なファイルは http://piumarta.com/pepsi/ にあります。ここでは、ソースとバイナリ両方使います。ソースだけ落としてコンパイルしても勿論大丈夫です。まずバイナリを / 以下にコピーします。windows の場合は cygwin が必要です。ソースで使うのは example/jolt ディレクトリだけなので、それを適当な場所にコピーして make しておきます。以後このディレクトリで作業を行います。なお、linux で使うためには以下を実行する必要があります。

$ execstack -s main

シェルを起動して coke 式を評価

coke の実装である jolt のシェルを起動して式を入力すると答えが返ります。基本的な計算のテストをします。ここではカーソルキー等、一般的な readline のキー操作が使えます。終了するには ctrl + D です。

$ ./main.exe boot.k -
.(+ 3 4)
 => 7
.(+ 3 (* 2 3))
 => 9

coke では lisp のような形式 (関数名 引数 ...) でリストを評価します。引数を全て評価してから関数が呼ばれます。すると => の後に答えが表示されます。大島さんのテキストでは、ここから [] を使ったメッセージ送信について解説がありますが、難しいのでもう少し () だけを使った実験をします。関数定義の define を実験してみます。scheme とよく似ています。

.(define plus (lambda (a b) (+ a b)))
 => 10797800
.(plus 3 4)
 => 7
.(define fib (lambda (n)
.              (if (== n 0) 1
.                (if (== n 1) 1
.                  (+ (fib (- n 1)) (fib (- n 2)))))))
 => 10823464
.(fib 10)
 => 89

関数定義の時にも数字が出てきて鬱陶しいですが、これは関数のアドレスです。アドレスで直接呼び出しても同じです。

.(define minus (lambda (a b) (- a b)))
 => 10857176
.(10857176 100 10)
 => 90

という事は結局 coke のプログラムは整数を括弧で包んだ物と言えます。

C の関数を呼ぶ

boot.k を見ると、printf が定義されています。

.(printf "hello world\n")
hello world
 => 12
.(printf "3 + 4 = %i\n" (+ 3 4))
3 + 4 = 7
 => 10

(_dlsym _RTLD_DEFAULT 関数名) で任意の C の関数を coke の関数に出来るようです。芸が無いですが、putc を定義してみます。

.(define puts (_dlsym _RTLD_DEFAULT "puts"))
 => -519487552
.(puts "hage")
hage
 => 10

メッセージ送信

book.k では、C の関数以外に pepsi の StdOut オブジェクトもインポートしています。pepsi とは、coke の為に用意された別の言語です。ややこしいですが、coke のソースは pepsi で書かれています。インポートされた StdOut オブジェクトを使うには send 関数を使います。send 関数を使うと、(send メッセージ オブジェクト 引数 ...) の形で pepsi オブジェクトにメッセージを送信出来ます。??? なんだこれは??? boot.k を見ると、StdOut がオブジェクトとして定義されているのでこれを使ってみます。test.k がにサンプルがあります。

.(send 'nextPutAll: StdOut '"hello world\n")
hello world
 => 25

' をつける奴とつけない奴に注意が必要です。StdOut 以外には ' を付ける必要があります。 を使うとこの send 式をもう少し読みやすく表現する事が出来ます。 式では、要素の順番が変わってメッセージの ' を省略します。引数の ' は省略出来ないので注意が必要です。

.[StdOut nextPutAll: '"hello world\n"]
hello world
 => 25

ここで ' についてもうちょっと調べます。なにも付けない 3 は機械語の世界の 3 ですが、' を付けると pepsi オブジェクトになります。pepsi オブジェクトの整数の内部表現は、Squeak VM に習って 数字 * 2 + 1 になっているので coke シェルは 7 と答えます。

.3
 => 3
.(quote 3)
 => 7
.'3
 => 7
.[StdOut nextPutAll: ['3 printString]]
3 => 3

StdOut も printf と同じく define 文で定義された物ですが、C の関数ではなく pepsi のオブジェクトです。両者の定義方法を比較してみます。

  • (define printf (_dlsym _RTLD_DEFAULT "printf"))
    • C の関数
  • (define StdOut (import "StdOut"))
    • pepsi のオブジェクト

coke (jolt) と pepsi (idc) と C 言語の関係

ややこしいですが、依存関係は次のようになっています。

  • pepsi 処理系 (idc) は Smalltalk に似た言語で、pepsi 自体によって書かれています。
  • pepsi (idc) をコンパイルするには C 言語を一旦経由するため、C コンパイラが必要です。
  • coke (jolt) は scheme に似た言語で、pepsi によって書かれています。coke はインタプリタなので C コンパイラは不要ですが、C プログラム並みに早く動作します。
  • 大島さんに聞いた話では、coke によって pepsi を実装しなおすのがとりあえず目標だそうです。

文法を定義する。

さていよいよ coke の本来の目的である文法を定義します。大島さんの例は難しいのでもっと簡単に、単に数字の一だけ返す構文と、数字を二倍にする構文を作ります。今度は長いので、mySyntax.k という名前でファイルを保存します。

; mySyntax.k
(syntax one
  (lambda (node compiler)
    (let ()
      '1)))
(syntax double
  (lambda (node compiler)
    (let ()
      `(* ,[node at: '1] 2))))

(define Compiler (import "Compiler"))
[Compiler parseOption: '"-vs"]

syntax 文は文法を定義するのに使います。たとえば最初のやつは (one) と言う単純な文です。文法と関数がどう違うかと言うと、関数は引数を実行してから呼ばれますが、文法は引数を実行する前に呼ばれます。lambda に使われる node とcompiler は、この文が呼ばれる時の構文木コンパイラの状態を示します。どちらも pepsi オブジェクトなので、操作する際には [ ] を使います。最後のリストを返すのに使う妙なクォートは、クアジクオートと言って、全体ではクォートするけど途中一部 , のところだけクォートから外す機能です。

syntax の動作を決めるには三つの方法があります。

  • 最初の例のように値を返す。lambda で与えられた compile オブジェクトを使って、引数をこの時点で評価する事も出来ます。
  • 二番目の例のようにリストを作り直して返す。リストはさらに他の部分で評価されます。
  • VPU を直接操作する。この例では無いですが、バーチャルマシンを手動で操作する事も出来ます。

最後の二行は無くても大丈夫ですが、コンパイルしてる様子を表示するオプションです。これをテストしてみます。

$ ./main.exe boot.k mySyntax.k -
.(one)
  SYNTAX  -> (#one)
  REWRITE => 1
 => 1
.(one 123 muchakucha)
  SYNTAX  -> (#one 123 #muchakucha)
  REWRITE => 1
 => 1
.(double 100)
  SYNTAX  -> (#double 100)
  REWRITE => (#* 100 2)
 => 200

one の引数としてめちゃくちゃ書いてもとにかく 1 が返るというのが味噌です。今回は行き着きませんでしたが、さらにパーサーの部分をいじると、こんな括弧コッカだらけじゃなくて javascript が書けたり python が書けたりするみたいです。