LoginSignup
11
11

More than 5 years have passed since last update.

Ruby: 色付き pp

Posted at

はじめに

pp は オブジェクトを人間に見やすく出力してくれます。
しかし、色付けはされません。
(拙作 prolog_parser.rb (参考)はオブジェクトを pp で出力しています)

$ ruby prolog_parser.rb 'f(x,y) :- a(b(c(d,e,f),!),[1,2,X|Y])'
{:":-"=>
  [{:f=>[:x, :y]},
   {:a=>[{:b=>[{:c=>[:d, :e, :f]}, :!]}, {[1, 2, {nil=>:X}]=>[{nil=>:Y}]}]}]}

複雑なオブジェクト出力だと、ちょっとツラいです。
そこで、色付き pp が欲しくなりました。

Ripper

Ripper は Ruby プログラムを解析するためのライブラリです。
字句解析を行うメソッド Ripper::Lexer#lex を試してみます。

t.rb
require 'pp'
require 'ripper'

pp Ripper.lex $<.read
: お試し(その1)
$ ruby t.rb < t.rb
[[[1, 0], :on_ident, "require"],          # 出力情報は[位置情報,シンボル,値]
 [[1, 7], :on_sp, " "],
 [[1, 8], :on_tstring_beg, "'"],
 [[1, 9], :on_tstring_content, "pp"],
 [[1, 11], :on_tstring_end, "'"],
 [[1, 12], :on_nl, "\n"],
 [[2, 0], :on_ident, "require"],
 [[2, 7], :on_sp, " "],
 [[2, 8], :on_tstring_beg, "'"],
 [[2, 9], :on_tstring_content, "ripper"],
 [[2, 15], :on_tstring_end, "'"],
 [[2, 16], :on_nl, "\n"],
 [[3, 0], :on_ignored_nl, "\n"],
 [[4, 0], :on_ident, "pp"],
 [[4, 2], :on_sp, " "],
 [[4, 3], :on_const, "Ripper"],
 [[4, 9], :on_period, "."],
 [[4, 10], :on_ident, "lex"],
 [[4, 13], :on_sp, " "],
 [[4, 14], :on_gvar, "$<"],
 [[4, 16], :on_period, "."],
 [[4, 17], :on_ident, "read"],
 [[4, 21], :on_nl, "\n"]]
: お試し(その2)
$ echo ':a' | ruby t.rb 
[[[1, 0], :on_symbeg, ":"], [[1, 1], :on_ident, "a"], [[1, 2], :on_nl, "\n"]]
: お試し(その3)
$ echo ':A' | ruby t.rb
[[[1, 0], :on_symbeg, ":"], [[1, 1], :on_const, "A"], [[1, 2], :on_nl, "\n"]]
: お試し(その4)
$ echo ':":-"' | ruby t.rb
[[[1, 0], :on_symbeg, ":\""],
 [[1, 2], :on_tstring_content, ":-"],
 [[1, 4], :on_tstring_end, "\""],
 [[1, 5], :on_nl, "\n"]]

Racc

Ruby コードのシンタクスハイライトをするコマンドを作って、コマンドラインでフィルタする戦略にしようと思います。
Racc で簡単なパーサを作ってみます。
ここでは、シンボルリテラルを緑色に色付けするパーサにします。
シンボルリテラルは上の「お試し(その2〜4)」のパターンです。

try.ry
class HighlightParser
rule
  program :                         { result = ""      }
          | program token           { result << val[1] }

  token   : ANY
          | symbol                  { result = color(val, GREEN) }
          | ON_IDENT
          | ON_CONST
          | ON_TSTRING_CONTENT
          | ON_TSTRING_END
          | ON_SYMBEG

  symbol  : ON_SYMBEG ON_IDENT                          { result = val.join }
          | ON_SYMBEG ON_CONST                          { result = val.join }
          | ON_SYMBEG ON_TSTRING_CONTENT ON_TSTRING_END { result = val.join }
end

---- header
require 'pp'
require 'ripper'

---- inner
GREEN = 32

def parse(src)
  selection = %i(on_symbeg on_ident on_const on_tstring_content on_tstring_end)
  conv = -> sym { selection.include?(sym) ? sym.upcase : :ANY }

  @q = Ripper.lex(src).map {|token| token.drop(1) }       # 位置情報は捨てる
                      .map {|sym,val| [conv.(sym), val] } # 興味あるトークン以外は ANY に変換する
                      .reverse                            # pop で取れるように反転
  #pp @q

  do_parse
end

private
def next_token
  @q.pop
end

def color(val, code)            # 色付けは、ANSIエスケープシーケンスで画面制御
  "\e[#{code}m#{val.join}\e[m"
end

---- footer
if __FILE__ == $0
  puts HighlightParser.new.parse ARGF.read
end
: Racc ソースのコンパイル
$ racc -o try.rb try.ry           # try.rb ができあがる
3 shift/reduce conflicts          # これは「シフト/還元衝突」の警告です。ここでは無視...

実行。

tty-mini.png

いろいろ作りが粗いですが、これはこれで OK。
...しかし、ふと気付いたことが。。。

Pry::ColorPrinter

ところで、pry では pp が色付けされます。

tty-mini2.png

もしや、と思い調べたら Pry::ColorPrinter というのがありました。
早速、トライ。

try-color-printer.rb
require 'pry'

obj = [:foo,1,{true=>(10..20)},"hello"]

Pry::ColorPrinter.pp obj

tty-mini3.png

pry で、こんなことができたとは知りませんでした。
自作パーサのように自分でプログラミングする必要なくていいです。

おわりに

結論。
Pry::ColorPrinter が便利そうなんで、これからは使ってみようと思います。
(パーサ作る前に気づきたかった...)


本稿内容の動作確認は以下の環境で行っています。

  • Ruby 2.1.5 p273
11
11
2

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
11
11