LispにS式があってマクロがあるように。

Rubyの配列を使ってプログラミング言語を書いてみました。Ruby本体を使ってプログラム(= 配列)を操作することで、Lispのマクロのようなことができます。言語の名前は「ざぼん」です。「ざ」をいただきました。

とりあえずフィボナッチを求めるプログラムを書くための最低限を実装しています。プログラムは関数定義のみで、main関数がエントリポイントになっています。で、main関数の返り値を表示します。細かい文法はLispから直感を働かせてください。

ファイル: zbn.rb

#!/usr/bin/env ruby class Env def initialize (parent) @parent = parent @env = {} end attr_reader :parent , :env protected :parent , :env def []= (sym, val) @env [sym] = val end def [] (sym) env = self while env val = env.env[sym] return val if val env = env.parent end nil end end class RootEnv < Env def initialize super ( nil ) self [ :+ ] = [[ :a , :b ], lambda {| env | env[ :a ] + env[ :b ]}] self [ :- ] = [[ :a , :b ], lambda {| env | env[ :a ] - env[ :b ]}] self [ :* ] = [[ :a , :b ], lambda {| env | env[ :a ] * env[ :b ]}] self [ :/ ] = [[ :a , :b ], lambda {| env | env[ :a ] / env[ :b ]}] self [ :eq ] = [[ :a , :b ], lambda {| env | env[ :a ] == env[ :b ]}] end end def ebal (env, body) case body when Proc body[env] when Array case body.first when :if ebal(env, body[ 1 ]) ? ebal(env, body[ 2 ]) : ebal(env, body[ 3 ]) else func, *args = body.map {| a | ebal(env, a)} newenv = Env .new(env) func[ 0 ].each_with_index do | sym , i | newenv[sym] = args[i] end ebal(newenv, func[ 1 ]) end when Symbol env[body] else body end end def parse (env, program) program.each do | expr | func, *arg_and_body = expr env[func] = arg_and_body end end if $0 == __FILE__ env = RootEnv .new parse(env, eval ( ARGF .read)) p ebal(env, env[ :main ][ 1 ]) end

実行例そのいち。階乗。

ファイル: fact.zbn

[ [ :fact , [ :n ], [ :if , [ :eq , :n , 0 ], 1 , [ :* , :n , [ :fact , [ :- , :n , 1 ]]]]], [ :main , [], [ :fact , 3 ]] ]

$ ruby zbn.rb fact.zbn 6

実行例そのに。フィボナッチ。

ファイル: fib1.zbn

[ [ :fib , [ :n ], [ :if , [ :eq , :n , 0 ], 1 , [ :if , [ :eq , :n , 1 ], 1 , [ :+ , [ :fib , [ :- , :n , 1 ]], [ :fib , [ :- , :n , 2 ]]]]]], [ :main , [], [ :fib , 10 ]] ]

$ ruby zbn.rb fib1.zbn 89

実行例そのさん。フィボナッチ。condマクロ版。

ファイル: fib2.zbn

def cond (*exprs) root = list = nil exprs.each do | expr | ifexpr = [ :if , *expr] list << ifexpr if list list = ifexpr root = list unless root end list << nil if list root end [ [ :fib , [ :n ], cond( [[ :eq , :n , 0 ], 1 ], [[ :eq , :n , 1 ], 1 ], [ true , [ :+ , [ :fib , [ :- , :n , 1 ]], [ :fib , [ :- , :n , 2 ]]]])], [ :main , [], [ :fib , 10 ]] ]

$ ruby zbn.rb fib2.zbn 89

いかがでしょうか？ Rubyで配列操作してプログラムをいじれるってのがポイントです。いろいろ失った代わりに自由を得ました。

参照: Route 477