Ruby | StringScanner で字句解析
概要
StringScanner で字句解析を行います
StringScanner って?
Ruby の標準ライブラリ。
スキャンする文字列と「スキャンポインタ」をセットで管理して、
正規表現でテキストをスキャンできます。
サンプル
仕様
RuboCop の警告出力を字句解析します。
RuboCopのエラーフォーマットは simple を指定します。
※RuboCopのエラーフォーマットについては下記にまとめてあります。
=>RuboCop | 警告の出力フォーマット指定
各警告内容をモデル「 RubocopWarning 」に設定します。
RubocopWarning は以下のインスタンス変数を持ちます。
- file
- type
- row
- column
- error
RubocopWarning に設定した各警告を配列に格納し、
最後の pp で標準出力します。
※RuboCopの simple フォーマットは以下のようになります。
== lib/hige.rb ==
C: 1: 1: Missing top-level class documentation comment.
# :
# : 略
# :
C: 6: 4: Final newline missing.
2 files inspected, 21 offenses detected
※わざわざ StringScanner を使うような処理ではないですが、説明のためということで黙認していただければ。
そもそも、 RuboCop は出力フォーマットとして JSON をサポートしてますし。
RubyCop警告出力用の対象プログラム
lib/hoge.rb
class Hoge
def h(a)
b
[].inject(&:+)
end
end
lib/hige.rb
class Hige
def h(a)
b
[].collect(&:+)
[].map do|e| e;end
end
end
StringScanner利用プログラム
ruby_string_scanner.rb
require 'pp'
require 'strscan'
class RubocopWarningParseError < StandardError; end
class RubocopWarning
attr_accessor :file, :type, :row, :column, :error
end
rubocop_messag = ARGV.first
s = StringScanner.new(rubocop_messag)
file = ""
rubocop_warnings = []
rw = RubocopWarning.new
row_line_count = 0
until s.eos?
case
when s.scan(%r{== (?<file>.*) ==\n})
file = s[:file]
when s.scan(%r{(?<type>[CEFRW]): +})
rw.type = s[:type]
when s.scan(%r{(?<row_or_column>\d+):( )+})
if row_line_count == 0
rw.row = s[:row_or_column]
row_line_count = 1
else
rw.column = s[:row_or_column]
row_line_count = 0
end
when s.scan(%r{(?<error>.*)\n})
rw.error = s[:error]
rw.file = file
rubocop_warnings << rw if rw.type
rw = RubocopWarning.new
else
fail RubocopWarningParseError, 'parse error'
end
end
pp rubocop_warnings
出力
$ rubocop lib --format simple | xargs -0 ruby ruby_string_scanner.rb
[#<RubocopWarning:0x0000060038a1c0
@column="1",
@error="Missing top-level class documentation comment.",
@file="lib/hige.rb",
@row="1",
@type="C">,
#<RubocopWarning:0x00000600389ef0
@column="1",
@error="Carriage return character detected.",
@file="lib/hige.rb",
@row="1",
@type="C">,
#<RubocopWarning:0x00000600389d88
@column="1",
@error="Use 2 (not 1) spaces for indentation.",
@file="lib/hige.rb",
@row="2",
@type="C">,
#<RubocopWarning:0x00000600389bf8
@column="8",
@error=
"Unused method argument - a. If it's necessary, use _ or _a as an argument name to indicate that it won't be used. You can also write as h(*) if you want the method to accept any arguments but don't care about them.",
@file="lib/hige.rb",
@row="2",
@type="W">,
#<RubocopWarning:0x00000600389a68
@column="2",
@error="Use 2 (not 1) spaces for indentation.",
@file="lib/hige.rb",
@row="3",
@type="C">,
#<RubocopWarning:0x00000600389900
@column="4",
@error="Inconsistent indentation detected.",
@file="lib/hige.rb",
@row="4",
@type="C">,
#<RubocopWarning:0x00000600389798
@column="7",
@error="Prefer map over collect.",
@file="lib/hige.rb",
@row="4",
@type="C">,
#<RubocopWarning:0x00000600389630
@column="4",
@error="Inconsistent indentation detected.",
@file="lib/hige.rb",
@row="5",
@type="C">,
#<RubocopWarning:0x000006003894c8
@column="11",
@error="Prefer {...} over do...end for single-line blocks.",
@file="lib/hige.rb",
@row="5",
@type="C">,
#<RubocopWarning:0x00000600389338
@column="18",
@error="Space missing after semicolon.",
@file="lib/hige.rb",
@row="5",
@type="C">,
#<RubocopWarning:0x000006003891d0
@column="3",
@error="end at 6, 2 is not aligned with def at 2, 1",
@file="lib/hige.rb",
@row="6",
@type="W">,
#<RubocopWarning:0x00000600389040
@column="4",
@error="Final newline missing.",
@file="lib/hige.rb",
@row="7",
@type="C">,
#<RubocopWarning:0x00000600388ed8
@column="1",
@error="Missing top-level class documentation comment.",
@file="lib/hoge.rb",
@row="1",
@type="C">,
#<RubocopWarning:0x00000600388cd0
@column="1",
@error="Carriage return character detected.",
@file="lib/hoge.rb",
@row="1",
@type="C">,
#<RubocopWarning:0x00000600388b68
@column="1",
@error="Use 2 (not 1) spaces for indentation.",
@file="lib/hoge.rb",
@row="2",
@type="C">,
#<RubocopWarning:0x00000600388a00
@column="8",
@error=
"Unused method argument - a. If it's necessary, use _ or _a as an argument name to indicate that it won't be used. You can also write as h(*) if you want the method to accept any arguments but don't care about them.",
@file="lib/hoge.rb",
@row="2",
@type="W">,
#<RubocopWarning:0x00000600388870
@column="2",
@error="Use 2 (not 1) spaces for indentation.",
@file="lib/hoge.rb",
@row="3",
@type="C">,
#<RubocopWarning:0x00000600388708
@column="4",
@error="Inconsistent indentation detected.",
@file="lib/hoge.rb",
@row="4",
@type="C">,
#<RubocopWarning:0x000006003885a0
@column="7",
@error="Prefer reduce over inject.",
@file="lib/hoge.rb",
@row="4",
@type="C">,
#<RubocopWarning:0x00000600388438
@column="3",
@error="end at 5, 2 is not aligned with def at 2, 1",
@file="lib/hoge.rb",
@row="5",
@type="W">,
#<RubocopWarning:0x000006003882a8
@column="4",
@error="Final newline missing.",
@file="lib/hoge.rb",
@row="6",
@type="C">]
参照