Ruby: 色付き pp

# はじめに
[pp][Ruby-PP] は オブジェクトを人間に見やすく出力してくれます。
しかし、色付けはされません。
(拙作 `prolog_parser.rb` ([参考](http://qiita.com/lnznt/items/72abba6b045ef4ffafae))はオブジェクトを pp で出力しています)

```shell-session
$ 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] は Ruby プログラムを解析するためのライブラリです。
字句解析を行うメソッド [Ripper::Lexer#lex][Ruby-Ripper-Lexer-lex] を試してみます。

```rb:t.rb
require 'pp'
require 'ripper'

pp Ripper.lex $<.read
```
```shell-session
: お試し(その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"]]
```
```shell-session
: お試し(その2)
$ echo ':a' | ruby t.rb 
[[[1, 0], :on_symbeg, ":"], [[1, 1], :on_ident, "a"], [[1, 2], :on_nl, "\n"]]
```
```shell-session
: お試し(その3)
$ echo ':A' | ruby t.rb
[[[1, 0], :on_symbeg, ":"], [[1, 1], :on_const, "A"], [[1, 2], :on_nl, "\n"]]
```
```shell-session
: お試し(その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][Ruby-Racc] で簡単なパーサを作ってみます。
ここでは、シンボルリテラルを緑色に色付けするパーサにします。
シンボルリテラルは上の「お試し(その2〜4)」のパターンです。

```rb: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
```
```shell-session
: Racc ソースのコンパイル
$ racc -o try.rb try.ry           # try.rb ができあがる
3 shift/reduce conflicts          # これは「シフト/還元衝突」の警告です。ここでは無視...
```

実行。

![tty-mini.png](https://qiita-image-store.s3.amazonaws.com/0/60624/2cc6d2aa-2ff5-eb1d-cbf8-1b78fa9491a3.png)

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

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

![tty-mini2.png](https://qiita-image-store.s3.amazonaws.com/0/60624/f4781295-5d7a-2317-1c4c-c8bb35f6606b.png)

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

```rb:try-color-printer.rb
require 'pry'

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

Pry::ColorPrinter.pp obj
```

![tty-mini3.png](https://qiita-image-store.s3.amazonaws.com/0/60624/d93774ff-0297-e298-8c5c-a93822a94006.png)

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

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


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

- Ruby 2.1.5 p273


[Ruby-PP]:http://docs.ruby-lang.org/ja/2.1.0/library/pp.html
[Ruby-Ripper]:http://docs.ruby-lang.org/ja/2.2.0/library/ripper.html
[Ruby-Ripper-Lexer-lex]:http://docs.ruby-lang.org/ja/2.1.0/method/Ripper=3a=3aLexer/i/lex.html
[Ruby-Racc]:http://docs.ruby-lang.org/ja/2.1.0/library/racc=2fparser.html
[Pry-ColorPrinter]:http://www.rubydoc.info/github/pry/pry/master/Pry/ColorPrinter