コンパイラ作成(44) コンパイラオプション

optparse

rubyのgetoptみたいなのを調べたらoptparseってのがそれみたいなんで使ってみるよ。optparseライブラリを使う(in Ruby) | 雑記帳を参考にさせてもらったよ。
今回合わせてrspcheck.oみたいなのを指定できるようにする。それとprint.oを自動的にリンクする機能は削除する。もう必要ないからね。以前作ったテストプログラムをコンパイルするときはprint.oをコマンドライン引数で指定しないとダメ。

メイン部

それじゃコーディングいくよ。

$ver = "0.04r"

require 'optparse'

これをソースの頭の方に追加。

$opt_c  = false
$opt_d  = false
$opt_l  = false
$opt_O  = 3
$opt_v  = false
$opt_cc = "clang-5.0"

OptionParser.new do |opt|
  opt.on('-c',      'Compile only')      {|v| $opt_c = v}
  opt.on('-d',      'Debug mode')        {|v| $opt_d = v}
  opt.on('-l',      'Lexer module test') {|v| $opt_l = v}
  opt.on('-O val',  'Optimize level')    {|v| $opt_O = v.to_i}
  opt.on('-v',      'Version')           {|v| $opt_v = v}
  opt.on('--cc val','Assemble & Link')   {|v| $opt_cc = v}

  begin
    opt.parse!(ARGV)
  rescue
    puts "Invalid option. \nsee #{opt}"
    exit
  end 
end

# versionの表示
if $opt_v then
  puts "myc version #{$ver}"
  exit 0
end

# 字句解析単体テスト
if $opt_l then
  fname = ARGV[0]
  lex = Lexer.new(fname)
  lex.moduletest
  lex.close
  exit 0
end

f = []
execfname = nil
ARGV.each do |fname|
  if fname.match(/\.myc$/) then
    asmfname = fname.sub(/\.myc$/,'.s')  # アセンブリコードのファイル名
#   puts "compile #{fname}"
    if !execfname then execfname = fname.sub(/\.myc$/,'') end
    parser = Parser.new(fname)
    parser.program
    f << asmfname
  else
#   puts fname
    f << fname
  end
end
if !$opt_c then
  system("#{$opt_cc} #{f.join(' ')} -o #{execfname}")
end
exit 0

optparseはらくちんだね。使用方法も分かり易いし。自動的に-hに対応してusageも表示してくれるしね。今回、調子に乗ってオプションをいくつか増やしたよ。

不要コード

print.o関連の修正で不要になったコードをコメントアウトした。

  # コンストラクタ
  def initialize(fname)
    @fname = fname                        # ソースファイルのファイル名
    @asmfname = fname.sub(/\.myc$/,'.s')  # アセンブリコードのファイル名
=begin
    @exefname = fname.sub(/\.myc$/,'')    # 実行ファイル名
=end
    @lex = Lexer.new(@fname)              # 字句解析
    @funcname = nil                       # 現在処理している関数名
    @labelcnt = nil                       # 自動生成するラベルの個数(関数単位)
    @literalcnt = 0                       # 文字列リテラルの数
    @literaltable = []                    # 文字列リテラルのリスト
=begin
    @needprint = false                    # print.oのリンクが必要か
=end
    @functions = Hash.new                 # 関数
    @lvars = nil                          # ローカル変数
    @lvarsize = nil                       # スタックに確保する領域のサイズ
  end

=begin
  # アセンブル、リンクコマンド
  def asmcmd()
     if @needprint then
       "clang-5.0 -s "+@asmfname+" print.o -o "+@exefname
     else
       "clang-5.0 -s "+@asmfname+" -o "+@exefname
     end
  end
=end
    elsif kind == TK::RESERVE && str == "print" then
      # 組み込み関数printの処理
=begin
      @needprint = true
=end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      codegen "  mov edi, eax"
      codegen "  call print"
      if kind != TK::SYMBOL || str != ")" then perror end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after builtin function"
      end

コメントアウトじゃなくて削除しちゃっても良かったかな。

動作テスト

いってみるよ。

~/myc$ myc -v
myc version 0.04r
~/myc$ myc -h
Usage: myc [options]
    -c                               Compile only
    -d                               Debug mode
    -l                               Lexer module test
    -O val                           Optimize level
    -v                               Version
        --cc val                     Assemble & Link
~/myc$

versionの表示とか単なる恰好付け。

~/myc$ myc m23.myc rspcheck.o
~/myc$ ./m23
rsp = 00000000fe515510
rsp = 00000000fe515500
~/myc$

この前やりたくてできなかったことができたよ。コマンドラインで複数のファイルを指定されたときは.mycならコンパイル、それ以外はそのままclangに渡されるようにしたよ。

~/myc$ myc c.myc print.o
~/myc$ ./c
42
~/myc$

print.oの場合の例。

~/myc$ myc c.myc print.o --cc 'gcc -no-pie'
~/myc$ ./c
42
~/myc$

ccを使うとアセンブル、リンクに使うコマンドを指定できる。上の例ではgccを指定。-no-pieは付けないとgccに怒られるんで付けてるよ。assembly - Assembling with GCC causes weird relocation error with regards to .data - Stack Overflow
Optimize levelとか勢い余って入れちゃったけど、当然付けても何も起こらないよ。このオプションがちゃんと役立つ日は果たして来るのだろうか。
今回のはmycの機能追加ってよりは、Rubyコマンドラインオプションの解析の仕方を理解するってのがメインだね。ここまでmycを作ってきて大分Rubyにも慣れてきたよ。でも、Rubyの深いところまではそんなに立ち入ってないかもなあ。ちなみにmycの開発目的はRubyに慣れる、暇つぶしの二つだよ。さて、次回は何やろうかな。