続・関数形式マクロは演算子を引数に取れる - わさっきの続き.
Rubyを使った生成プログラムです.
#!/usr/bin/env ruby # opgen.rb class OperatorGenerator def initialize @filename_src = "op.c" @filename_bin = "op" if /mswin(?!ce)|mingw|cygwin|bccwin/i =~ RUBY_PLATFORM @filename_bin += ".exe" end @command_compile = "cc -o #{@filename_bin} #{@filename_src}" @ok_a = [] @qty_op = 6 end def op(num) ("%0#{@qty_op}b" % num).gsub(/0/, "-").gsub(/1/, "+") end private :op def rm_bin if test(?f, @filename_bin) File.unlink(@filename_bin) end end private :rm_bin def gen_code(num) code = "" 0.upto(num) do |j| code0 = "opp(a, #{op(j)}, b);" if @ok_a[j] == false code0 = "/* #{code0} */" end code += " #{code0}\n" end src = <<'EOS' #include <stdio.h> #define op(x, y, z) ((x) y (z)) #define p2(x, y) printf(#x " = %d, " #y " = %d\n", x, y) #define opp(x, y, z) p2(x, z),\ printf(#x " " #y " " #z " = %d\n", op(x, y, z)),\ p2(x, z), puts("") int main(void) { int a = 4, b = 2; __CODE__ return 0; } EOS src["__CODE__"] = code open(@filename_src, "w") do |g| g.print src print src if $DEBUG end end private :gen_code def p_system(command) puts command system command end private :p_system def start @ok_a = [] max = 2 ** @qty_op - 1 0.upto(max) do |i| puts "[a #{op(i)} b]" gen_code(i) rm_bin p_system @command_compile @ok_a << test(?f, @filename_bin) end puts "[result]" gen_code(max) p_system @command_compile p_system "./#{@filename_bin}" end end if __FILE__ == $0 OperatorGenerator.new.start end
要所は3か所.
- 「+と-の並び」の全組み合わせを求めるのに,文字列なり配列なりを作ってそれを変えていくというのは,ループで回しにくいものです.上のコードでは,整数値を,+と-の並びに変換します.方法は,プライベートメソッドopのとおりで,Ruby用sprintfフォーマット*1の%bを使って2進値にし,あとはgsubで0を-に,1を+に置き換えます.
- 「opp(a, ------, b);」や「opp(a, +-+-+-, b);」といったコードが正しいかを確認するため,一つ一つコンパイルをしています.最初にすべての「+と-の並び」のコードを生成し,コンパイルしてエラー行を見つけるというのは手間と考えたのです.実際には,最終結果のコード生成と共通化させるため,プライベートメソッドgen_codeは引数numをとり,0以上num以下の各整数に対応する「+と-の並び」すべてについて,コードを生成します(すでにエラーになる文はコメントしています).コンパイルして,実行ファイルが作られればOK,作られなければNGです.コンパイルの前に,実行ファイルがもしあれば,消すようにします.
- どの整数に対応する文はエラーになるかの情報は,配列のインスタンス変数@ok_aに格納させます.@ok_a[整数値]がfalseのときのみ,エラーになることを確認しているという意味です.trueのときはOK,nilのときは未検証ですが,gen_codeの中ではどちらの場合も,コメントとせずにコードを吐いています.
小さいことを2点.
- 「if /mswin(?!ce)|mingw|cygwin|bccwin/i =~ RUBY_PLATFORM」*2から始まるif文で,実行環境がWindowsであるかを判別し,もしそうなら,実行ファイルに「.exe」をつけています.Cygwin限定かもしれませんが,「.exe」をつけていないと,test関数によるファイルの存在確認ができないのです.
- 「0.upto(x) do |i|」は,「(x + 1).times do |i|」と等価*3ですが,ブロックパラメータを用いるtimesはどうもなじめません.