vm2gol v2 (52) リファクタリング: 字句解析処理を別ファイルに分離



vgparser.rb で行っていた字句解析処理を vglexer.rb に分離します。

そうしなければいけない強い理由はあまりなくて、その方が他言語への移植がスムーズだった経験から、Ruby版にもフィードバックしておくか……というくらいのノリです。 あとは、個々のモジュールが小さくなっている方が威圧感がなくていいかなとか。


ファイル分割にあわせて、字句解析の結果であるトークン列をシリアライズして一度ファイルに出力する形にします。

整数値については、これまで字句解析の際に文字列から整数に変換して Token.new に渡していましたが、最終的に文字列にシリアライズするのならわざわざ 文字列 → 整数 → 文字列 のように変換しなくても、文字列のまま受け渡してしまえばいいかなと。 *1

というわけで、まずはメインの修正の準備として文字列のまま渡すように修正。

--- a/vgparser.rb
+++ b/vgparser.rb
@@ -49,7 +49,7 @@ def tokenize(src)
 
     when /\A(-?[0-9]+)/
       str = $1
-      tokens << Token.new(:int, str.to_i)
+      tokens << Token.new(:int, str)
       pos += str.size
 
     when /\A(==|!=|[(){}=;+*,])/

これにより、Token#value の型が String に統一され、静的型の言語への移植も(ちょっとだけ)やりやすくなります。

文字列から整数への変換はパーサで行うことになります。 微妙に DRY じゃないですが、まあいいか。

--- a/vgparser.rb
+++ b/vgparser.rb
@@ -131,7 +131,7 @@ class Parser
       t.value
     elsif t.type == :int
       @pos += 1
-      t.value
+      t.value.to_i
     else
       raise ParseError
     end
@@ -289,7 +289,14 @@ class Parser
     if t_left.type == :int || t_left.type == :ident
       @pos += 1
 
-      expr_l = t_left.value
+      expr_l =
+        case t_left.type
+        when :int
+          t_left.value.to_i
+        else
+          t_left.value
+        end
+
       parse_expr_right(expr_l)
 
     else

これで準備ができました。字句解析処理を vglexer.rb に移動します……が、diff は大きいので略。

vglexer.rb だけを動かして出力(シリアライズされたトークン列)を見るとこんな感じ:

$ ruby vglexer.rb gol.vg.txt | head
kw:func
ident:to_vi
sym:(
ident:w
sym:,
ident:x
sym:,
ident:y
sym:,
ident:offset

行ごとに記号と値をコロンで区切って並べるだけ。


あとは、 Parser クラスを解消して、トップレベルにメソッドが並ぶだけのスタイルにしました。簡単で分かりやすくする方向で考えるとクラスなくてもいいかなと。

テストコードの方もあわせて修正。すでに他のテストでやっているように ruby 〜.rb の形でコマンドを実行して結果を読む方式に。

# test_vgparser.rb

  def _parse(src)
    File.open(VG_FILE, "wb") { |f| f.print src }
    _system %( ruby #{PROJECT_DIR}/vglexer.rb  #{VG_FILE} > #{TOKENS_FILE} )
    _system %( ruby #{PROJECT_DIR}/vgparser.rb #{TOKENS_FILE} > #{TREE_FILE} )
    json = File.read(TREE_FILE)
    JSON.parse(json)
  end

その他の修正

  • 変数名をより適切なものに変更
    • words => insn, insns
    • op, operator => opcode
  • typo の修正

あと master から main にブランチ名を変えました。



*1:ドメインロジックではドメインに即したオブジェクトに変換して使うべき、という観点からはその都度変換した方がよいと思いますが、ここは難しく考えなくていいんじゃないかな……。