スクリプト言語作ってみたよ
2週間でできる! スクリプト言語の作り方 (Software Design plus)と言語実装パターン ―コンパイラ技術によるテキスト処理から言語実装までにとても感銘を受けて、自分でも作ってみました。みました、といっても完成したわけでなくそれなりに動くようになった程度ですけどね。
ANTLRを使って生成したJavaのLexer/Parserを使って、インタプリターとして動きます。
シンタックスはCoffeeScript、F#、Rubyが混ざったような感じで、セマンティック的にはプロトタイプベースでJavaScriptをまねたものになっています。
基本的な文法
最初は、Python風のインデントでブロックを表していたんですけど、パースが難しくなってきて、endとか{}でブロックを明示するスタイルに落ち着きました。
CoffeeScript、少ない字句規則しか使っていないように見えるんですが、よくインデントでブロックできてるなーと尊敬します。
# 四則演算 b = 10 + 2 * 3 - 6 / 2 print(b) # 13 # 条件分岐 i = 5 if i < 2 print('if') elif i > 10 print('elif') else print('else') end # 繰り返し i = 0 while i < 5 print(i) i += 1 end
関数
スクリプト言語を作ってみようと思ったのは、この辺りの実装に興味があったから。
実装していて一番楽しかった。
# 関数 add = { x, y -> x + y } print(add(1, 2)) # 3 # 関数(パラメータの省略) double = { 2 * it } print(double(5)) # 10 # パイプライン 2 |> { x -> 2 * x } |> print # 4 # 部分適用 mul = { x, y -> x * y } mul2 = mul <| 2 print(mul2(3)) # 6 # 関数合成 f = add >> { x -> 2 * x } print(f(2, 3)) # 10 # クロージャ x = 10 f = { y -> x + y } print(f(1)) # 11
配列やオブジェクト
JavaScriptっぽいかんじ。
# 配列 array = [1, 2, 3, 4, 5] array.map({ it * 2 }).filter({ it > 5 }) |> print # [6, 8, 10] # オブジェクト person = { name: 'hoge', age: 20 } print(person) # { name: 'hoge', age: 20 }
プロトタイプ
配列には、まだconcatメソッドつくっていないんですが、簡単に足せます。
# プロトタイプ Array.prototype.concat = { ret = [] push = { ret.push(it) } self.each(push) it.each(push) ret } [1,2,3].concat([4, 5]) |> print # [1, 2, 3, 4, 5]
クラス
クラスはCoffeeScriptっぽいかんじ。
コンストラクタのパラメータの宣言でインスタンス変数の代入を示す書き方まねてみました。クラスつくってみたもの、なくてもいいかなー(ちょっとバグってるし、superの扱いとか面倒なんだもの)。いずれ削除するかも。
# クラス class Parent name: 'foo' hello: { -> 'hello ' + @name } end class Child extends Parent constructor: { @name, @age -> } hi: { -> 'hi ' + super.name + ' ' + @age } end child = Child('hoge', 30) print(child.hello()) # hello hoge print(child.hi()) # hi foo 30
今後
いまのところ、エラー処理があまりに適当なので、もう少しなんとかしようかなあ思っています。
あとは、リスト内包表記とかパラメータでのパターンマッチングとかが実装候補ですね。
とりあえず作ってみましたが、思っていた以上に楽しいです。実用的かと聞かれれば全然そんなことはないですが、得られた知識を使って仕事で使えるような外部DSLを作るのはどうだろうなあと考えたりしています。