この記事ではRuby 2.4.1に同梱されているERBについて書く。読んだ過程をメモしているだけなので多分あまり要約されてないので、読みにくかったらるびまの記事を参照して欲しい。
ERBのオプション
ERB#initialize
の引数は以下のように定義されている。
def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
eoutvar
は単に内部の変数名が変えられるだけなので普通は変える必要ないし、safe_level
で調整できる$SAFE
も普通は使わない機能だと思うが、trim_mode
は出力結果に改行を入れるかどうかを細かくコントロールできるのでまあ使う機能なのではないだろうか。
というわけでこの記事では最も重要なERBのコンパイルオプションであろうtrim_mode
について書く。
trim_mode オプション
ERB#initialize
に渡したtrim_mode
なる引数は、ERB::Compiler#prepare_trim_mode
というメソッドで内部的なオプション@percent
,@trim_mode
に変換される。元の引数はそれ以降使われないので、@percent
と@trim_mode
が事実上のコンパイルオプションとなる。
@trim_mode
に関しては同じ名前なのに中身が違うので少々紛らわしいが、この記事ではtrim_mode
をERB#initialize
に与えられる引数、@trim_mode
をERB::Compiler
で使われるインスタンス変数の値ということにする。
内部的にはtrim_mode
は1
,2
,0
,String
およびそれ以外というように分岐しているが、最終的にセットされうる値から逆算してまとめると以下のようになる。
@percent |
@trim_mode |
trim_mode の代表的な値 |
ソースコード上のコメント |
---|---|---|---|
false |
nil |
0 , "" , nil
|
|
false |
">" |
1 , ">"
|
%> で終わる行の改行を削除する |
false |
"<>" |
2 , "<>"
|
<% で始まり%> で終わる行の改行を削除する |
false |
"-" |
"-" |
-%> で終わる空行を削除する |
true |
nil |
"%" |
% で始まる行でRubyのコード実行を有効にする |
true |
">" |
"%>" |
% で始まる行でRubyのコード実行を有効にする,%> で終わる行の改行を削除する |
true |
"<>" |
"%<>" |
% で始まる行でRubyのコード実行を有効にする,<% で始まり%> で終わる行の改行を削除する |
true |
"-" |
"%-" |
% で始まる行でRubyのコード実行を有効にする,-%> で終わる空行を削除する |
ERBのScannerの決定
ではそれらのコンパイルオプションは何に使われるのか。実はこれらは一部@percent
がマジックコメントの判定に使われているくらいで、コンパイラ本体では使われていない。
では何に使われているかというと、コンパイラが使用するScannerの決定とその内部で使われる。なので、ほとんどスキャナオプションといっても良い。
ERBでは4つのスキャナ(うち1つは使われる条件が重複しており使われなさそう)が定義されており、これは@percent
と@trim_mode
に応じて使われるものが以下のように決定される。
@percent |
@trim_mode |
trim_mode の代表的な値 |
Scanner | ソースコード上のコメント |
---|---|---|---|---|
false |
nil |
0 , "" , nil
|
SimpleScanner2 |
|
false |
">" |
1 , ">"
|
TrimScanner |
%> で終わる行の改行を削除する |
false |
"<>" |
2 , "<>"
|
TrimScanner |
<% で始まり%> で終わる行の改行を削除する |
false |
"-" |
"-" |
ExplicitScanner |
-%> で終わる空行を削除する |
true |
nil |
"%" |
TrimScanner |
% で始まる行でRubyのコード実行を有効にする |
true |
">" |
"%>" |
TrimScanner |
% で始まる行でRubyのコード実行を有効にする,%> で終わる行の改行を削除する |
true |
"<>" |
"%<>" |
TrimScanner |
% で始まる行でRubyのコード実行を有効にする,<% で始まり%> で終わる行の改行を削除する |
true |
"-" |
"%-" |
TrimScanner |
% で始まる行でRubyのコード実行を有効にする,-%> で終わる空行を削除する |
Scannerごとの挙動の違い
選択されうるこれらのScannerのそれぞれがどのような挙動をするか見ていく。
SimpleScanner2
デフォルトの挙動。「<%
, <%=
, <%#
といったトークンを読んだあとそれにマッチする%>
を既に読んでいるか」でスキャンに使う正規表現を2パターン切り替える。TrimScannerは両方にマッチする正規表現を使うので、SimpleScanner2
の方がスキャンが速そうに見える(試してない)。
開始タグとしてスキャンされるのは<%%
,<%=
,<%#
,<%
であり、終了タグとしてスキャンされるのは%%>
, %>
である。<%%
や%%>
はテキストとして<%
や%>
を打ちこむためのエスケープ用で、それらは通常のテキストとしてスキャンされる。
なおコンパイラはそれらの特別なタグがスキャンされる際は、そのタグの部分のみをスキャンされることを想定しており、改行とかがついた状態でスキャンされると壊れるようになっているため("%>"
じゃなくて"%>\n"
だとダメ)、正規表現もそれに対応して作られている。
改行を無視するとかそういうことはやらないので、行末に%>
とかがある場合でも、その後の改行が生成コードに付加される。
ExplicitScanner
%
開始行をRubyとして評価する機能を使わず、-%>
で終わる行の空行を削除する場合に使われるスキャナ。
これもSimpleScanner2
と同じく、スキャナがERBタグの途中かに応じて正規表現をスイッチするので多分TrimScanner
より速い。機能自体はTrimScanner
のサブセットに過ぎないと思う。
(全然関係ないけど、Itamaeとかだとこれが採用されているので-%>
が使える)
TrimScanner
ここまで書いてたら僕はもう理解できてしまったのでもう解説しなくていい気がしてきた。
唯一%
開始行をRubyとして評価するのに対応しているスキャナであり、その分毎回スキャンが遅くなるはず。また"<>"
や">"
といったコンパイルオプションもこのスキャナでしか動作しない。
このスキャナを読む上で難しいところは@trim_mode
に応じて行を読み込むためのメソッドが変わるところで、以下のように分岐している。
@trim_mode |
@scan_line 用メソッド |
---|---|
">" |
:trim_line1 |
"<>" |
:trim_line2 |
"-" |
:explicit_trim_line |
nil | :scan_line |
まとめ
全ての情報をまとめると以下のようになる。
@percent |
@trim_mode |
trim_mode の代表的な値 |
Scanner | 行スキャンメソッド | ソースコード上のコメント |
---|---|---|---|---|---|
false |
nil |
0 , "" , nil
|
SimpleScanner2 |
#scan |
|
false |
">" |
1 , ">"
|
TrimScanner |
#trim_line1 |
%> で終わる行の改行を削除する |
false |
"<>" |
2 , "<>"
|
TrimScanner |
#trim_line2 |
<% で始まり%> で終わる行の改行を削除する |
false |
"-" |
"-" |
ExplicitScanner |
#explicit_trim_line |
-%> で終わる空行を削除する |
true |
nil |
"%" |
TrimScanner |
#scan_line |
% で始まる行でRubyのコード実行を有効にする |
true |
">" |
"%>" |
TrimScanner |
#trim_line1 |
% で始まる行でRubyのコード実行を有効にする,%> で終わる行の改行を削除する |
true |
"<>" |
"%<>" |
TrimScanner |
#trim_line2 |
% で始まる行でRubyのコード実行を有効にする,<% で始まり%> で終わる行の改行を削除する |
true |
"-" |
"%-" |
TrimScanner |
#explicit_trim_line |
% で始まる行でRubyのコード実行を有効にする,-%> で終わる空行を削除する |