Top > Programming > .NetFramework > Others > ConvertCShrpToRuby
Last-modified: Sat, 05 Apr 2014 13:58:04 JST
Counter:2414 Today:1 Yesterday:1 Online:8
このエントリーをはてなブックマークに追加

C# のソースコードを Ruby に変換する

About

作業しながらのメモ書きです。

世の中には自動変換サービスがあるようですが、一応手動で行った内容をメモします。手動でテキストを置換して、アルゴリズムを確認しつつ、変換します。

基本構文(最初に変換する処理)

  • C# のコメントアウト // を # に変換する。
  • ";\n" なりで検索し、"\n" に変換する。
  • "{" を削除して "}" を "end" に変換する。
    • "{" の削除時に、必要なら空行となる改行を削除する。
  • void, string, int などの型名を削除する。
    • "string\s*" を削除する。int なども同じ。型名の後の半角スペースは削除。
    • "string[]\s" を削除する。int なども同じ。
  • null を nil に変換する。
  • "(.+?)\+\+" を "$1 += 1" に変換する。
  • "(.+?)\-\-" を "$1 -= 1" に変換する。
    • ++/--によるインクリメントデクリメントは使えない。

配列

  • サイズを固定して初期化する配列
    • "new\s*\[\(d+)\]" は、"= Array.new($1)" に変換する。
    • 配列の初期化についてはソースコードを読みながら手動で置き換えるしかない。
  • 要素が固定される配列
    • "new\s*\[\s*\]\s*\{(.*?)\}" は、"[$1]" に変換する。
  • それ以外
    • "[]" は、他に引数などのために使われていることがあるのでソースを読みながら手動で置き換えるしかない。
  • ".Skip\((.*?)\)\.Take\((.*?)\)" を "[$1..$2]" に変換する。
    • ただし、Skip/Take の中に足し引きなどの演算が含まれるとき、() で閉じる必要があるので、手動での確認も必要になる。

繰り返し文

  • while
    • "{" と "}" で閉じられない1行の while の終わりを end で閉じる
    • "while\s*\((.*)\)" を "while $1" に変換する。
    • "continue" を "next" に変換する。
  • for
    • for の実行内容については手作業で直す必要が少なからず出てくる。C#(やその他Cのようなfor構文を持つ言語)の for によって実装されるアルゴリズムを、Ruby の for では網羅することができない。
    • もしも繰り返し構文を統一したいときは while しか選択肢がないが、自動変換は難しい。
    • 例えば Ruby の for では downto に該当する処理を実装できない。変数を増やすなどすればできる。
      • ただし、downto を回避してまで繰り返しの構文を統一するときは while で統一した方が良い。
      • 下手に一部を自動変換しようとすると、手作業での修正時に、原型が分からなくなる可能性がある。
  • foreach
    • "foreach\s*\((.+?)\s*in\s(.+?)\)" を "$2.each do |$1|" に変換する。

for と times.do を変換するときは注意する。

  • "for (int i = 0; i < max; i += 1)" を変換するとき
    • "max.times do" が正しい。
    • "(max - 1).times do" は誤り。

upto, downto も注意する必要がある。

  • "0.upto(3) do" は "0,1,2,3" が実行される。
  • "3.downto(0) do" は "3,2,1,0" が実行される。

条件判定文

  • if 文
    • "if\s*\((.*)\)" を "if $1" に 変換する。
    • "else(\n*)if" を "elsif" に変換する
    • "{" と "}" を省略した、"if" および "else" を "end" で閉じる。
    • "end\s*elsif" を "elsif"に変換して end を削除する。
    • "end\s*else" の "end" を削除する。
      • 手作業でやった方が良さそう。for などから変換した end が含まれたり、1行のif/elseから変換すると失敗する恐れがある。
  • case 文
    • "case\s*(.*):" を "when $1" に変換する。
    • "switch\s*\((.*)\)" を "case $1" に変換する。
    • "}" を変換して得られた "end" を削除する。
    • "break" を削除する。
    • "default:" を "else" に変換する。
if
   if XXX

   end(複数回変換するとこのendも削除されるので注意)
end(このendだけを削除する
else

メソッド

  • 定義
    • メソッド名の頭文字を小文字にして、改行して、def を付ける。
    • "static Method" なメソッドの定義を "self.小文字メソッド" に変換する。
  • メソッド内
    • 大文字で開始するメソッドの呼び出しを小文字に変換する。
      • "(\s+)([A-Z])(.*?\n)" を "$1\l$2$3" に変換するなど。
      • ※メソッド名をピンポイントで変換する方法があるならそれを利用した方が良い。
      • ※この正規表現ではメソッド以外を拾いすぎる。\(\)を対象にした方が良い。
      • 定数が変換される恐れがあるので注意すること。
    • "const\s*" を削除する。
  • static の代わりに self となったクラスメソッドのアクセスレベルは、private_class_method によって private にすることができる。

クラス

  • クラスのアクセス修飾子 public private protected を削除する。
    • Ruby はオープンクラスで簡易に代替できる機能がない。
  • "new\s*(.*?)\((.*?)\)" を "$1.new($2)" に変換する。
    • newXXXなる変数名やクラス名に注意する必要がある。
  • 定義した型の宣言を削除する。
    • "Class instance;" のように初期化せずに書いているとき、"instance = nill" のように書く必要がある点に注意する。

変数とプロパティのアクセス修飾子

  • public なクラス変数(C#)を変換する。
    • "public\s*(.+?)\n" を "attr_accessor :$1\n@$1" に変換する。
    • private/protected はそのままアクセス修飾子を削除する。
  • プロパティ(C#)の get/set に該当するものがアクセサとして用意されている。
    • get だけなら attr_reader、set だけなら attr_writer 両方なら attr_accessor を設定する。

定数と変数

  • 定数
    • 定数フィールドからpublic/private/protectedを削除する。
      • Rubyの定数はprifvateなりprotectedにできない?
  • C# で static const などが宣言されたクラス変数を、大文字にして定数にします。
  • readonly などの修飾子を削除する。

名前空間

  • namespace を Module に変換する

正規表現

  • "new Regex" を "Regexp.new" に変換する。
  • "RegexOptions.Singleline" を "Regexp::MULTILINE" に変換する。
  • "RegexOptions.Multiline" を削除する。
    • SingleLine モードは Ruby では MULTILINE モード
    • Ruby は標準の正規表現で複数行モードになっている(C#のMultiline)。
  • "RegexOptions.Ignorecase" を "Regexp::IGNORECASE" に変換する。
  • "\.Match\(.*?\)" を ".match($1)" に変換する。
    • Ruby にも MatchData なる 正規表現の結果を保存するデータ型がある。
  • Match クラスの変数が match のとき、
    • "match.Success == false" を "match == nil" に変換する。
    • "match.Success" を "match != nill" に変換する。
    • "match.Groups\[(.*?)\].Index" を "match.begin($1)" に変換する。
    • "match.Groups\[(.*?)\].Length" を "match[$1].length" に変換する。
    • "match.Groups\[(.*?)\].Value" を "match[$1]" に変換する。
    • "match.Groups\[(.*?)\]" を "match[$1]" に変換する。
    • "Match match\s*(?!\=)" を "match = nil" に変換する。
  • "([^\s]+?).Replace\((.*?),\s*(.*?)\)" を "$2.gsub($1, $3)" に変換する。
    • string.Replace() を変換した後に変換する。

条件が複雑な場合が多いので、モードを除いて自動変換は難しい。

  • MULTILINE(Ruby) モードでは、^ と $ がうまく動作しないので、\A と \z(小文字) にする
  • 正規表現のキャプチャグループの一部がキャプチャされないとき、空文字列ではなくnilが与えられる。
    • ".length != 0" のようなアルゴリズムで非マッチングの検出を行っている場合、"!= nil" に変換する。"== 0" も同じ。

Stack

Ruby に Stack に該当するものはなく、Array に push と pop が実装される。

  • "Stack<.*?>\s*(?!\()" を削除する。(型宣言の削除)
  • "new Stack<.*?>\(\)" を "[]" に変換する。
  • ".Push" を ".unshift" に変換する。
  • ".Pop" を ".shift" に変換する。

push と pop でも Stack と同じような動作を実現させることができるが、push と pop は使ってはいけない。Ruby の push と pop はそれぞれ、末尾に追加し、末尾から取り出す。対して C# の Stack は push と pop は先頭に追加して先頭から取り出す。Ruby で C# と同じ挙動を取るのは unshift と shift である。Stack(Array)の中を走査するような処理が含まれる可能性があることを考慮すると、unshift と shift を使う必要がある。

また、Ruby には C# における List も用意されず、Array を利用する。List.Add(C#) に該当する処理は Array.push(Ruby) なので、Stack のために push を利用すると、List との区別が付かなくなる問題がある。

List

Ruby に List に該当するものはなく、Array が可変配列となっている。

  • "List<.*?>\s(?!\()" を削除する。(型宣言の削除)
  • "new\s*List<.*?>\(.*?\)" を "[]" に変換する。
  • ".Add" を ".push" に変換する。
  • "\.ToArray()" を削除する。
  • "\.Contains\(" を ".include?\(" に変換する。
  • "\.Count" を ".length" に変換する。
    • Dictionary や Stack の類も同時に変換できる。
  • "\.ElementAt<.*?>\((.*?)\)" を "[$1]" に変換する。
    • Dictionary や Stack の類も同時に変換できる。

Dictionary

Ruby には Dictionary の代わりに、Hash(連想配列)を使う。

  • "Dictionary\s*<.*?>\s" を削除する。(型宣言の削除)
  • "=\s*new\s*Dictionary<.*?>\s*\(\)" を "= Hash.new()" に変換する。
  • "ContainsKey" を "has_key?" に変換する。
  • "\.Keys" を ".keys" に変換する。
  • "\.Add" を "\.store" に変換する。
    • List や Stack などの変換時に、変換されてしまう恐れがあるので注意する。

文字列に関する操作

  • ".Replace\((.*?)\)" を ".gsub($1)" に変換する。
  • ".Remove\((.*?)\)" を ".slice!($1)" に変換する。
    • この正規表現だと、あるインデックス以降の全ての文字を削除するパターンに対応できないので注意。
      • あるインデックス以降の全ての文字を削除するメソッドは標準では用意されていない。
      • "string.slice!(index..string.length)" で対応するしかないか。
    • slice! は破壊的メソッドである点に注意する必要がある。非破壊メソッドでRemoveに該当するものは標準では用意されていない。
  • ".Trim\(\)" を ".strip()" に変換する。() は省略しても良い。
  • ".Substring\((.*?)\)" を "[$1]" に変換する。
    • あるインデックス以降の全ての文字を取得するメソッドは標準では用意されていない。
    • .sliceないし、[]で代用できる。"string[index..string.length]"
  • "\.Split" を ".split" に変換する。
  • "\.ToLower" を ".downcase" に変換する
  • "\.ToUpper" を ".upcase" に変換する

その他

  • Ruby では大文字から開始するメソッドも許容されているが、スタンダードは小文字であるので変換する方が良い。
    • Ruby の標準のモジュール(クラスライブラリ)が小文字で開始している。

ファイル操作

  • "Directory\.Exsists" を "File.exist?" に変換する。
  • "Directory\.GetFiles" の大体は "Dir.glob" であるが自動変換が難しい。
    • 引数が異なる点に注意する。
  • "Path.GetFileNameWithoutExtension\((.*?)\)" を "File.basename($1, ".*")"に変換する。