LoginSignup
15
13

More than 5 years have passed since last update.

Ruby | StringScanner で字句解析

Posted at

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">]

参照

15
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
13