はじめに
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 # これは「シフト/還元衝突」の警告です。ここでは無視...
実行。
いろいろ作りが粗いですが、これはこれで OK。
...しかし、ふと気付いたことが。。。
Pry::ColorPrinter
ところで、pry では pp が色付けされます。
もしや、と思い調べたら Pry::ColorPrinter というのがありました。
早速、トライ。
try-color-printer.rb
require 'pry'
obj = [:foo,1,{true=>(10..20)},"hello"]
Pry::ColorPrinter.pp obj
pry で、こんなことができたとは知りませんでした。
自作パーサのように自分でプログラミングする必要なくていいです。
おわりに
結論。
Pry::ColorPrinter が便利そうなんで、これからは使ってみようと思います。
(パーサ作る前に気づきたかった...)
本稿内容の動作確認は以下の環境で行っています。
- Ruby 2.1.5 p273