ときどきの雑記帖 RE* (新南口)
お年玉
parse.y の森の中へ 完結編
さて、parse.y で ‘=’ が使われているところを抜き出すとこんな結果になる (参照しているバージョンによって行番号は異なるので参考程度に)。
parse.y:916:%right '=' tOP_ASGN
parse.y:1185: | mlhs '=' command_call
parse.y:1193: | lhs '=' mrhs
parse.y:1201: | mlhs '=' mrhs_arg
parse.y:1211:command_asgn : lhs '=' command_rhs
parse.y:1840:arg : lhs '=' arg_rhs
parse.y:4178:f_opt : f_arg_asgn '=' arg_value
parse.y:4188:f_block_opt : f_arg_asgn '=' primary_value
916行目は演算子の優先順位を決めている(という書き方は語弊がある?)ところですな。 1185行目、1193行目、1201行目は
stmt : keyword_alias fitem {SET_LEX_STATE(EXPR_FNAME|EXPR_FITEM);} fitem
{
/*%%%*/
$$ = NEW_ALIAS($2, $4, &@$);
/*% %*/
/*% ripper: alias!($2, $4) %*/
}
| keyword_alias tGVAR tGVAR
から始まる一連の規則の一つで、1211行目はまた別の規則の一つ。
command_asgn : lhs '=' command_rhs
{
/*%%%*/
$$ = node_assign(p, $1, $3, &@$);
/*% %*/
/*% ripper: assign!($1, $3) %*/
}
| var_lhs tOP_ASGN command_rhs
以下略
このように ‘=’ が登場する規則はいくつかあるけれども、 今回ポイントになるのは1840行目の規則(4178行目と4188行目はとりあえず脇に置く)。
arg : lhs '=' arg_rhs
{
/*%%%*/
$$ = node_assign(p, $1, $3, &@$);
/*% %*/
/*% ripper: assign!($1, $3) %*/
}
ここね。
一方’+‘を検索すると結果はこう。
parse.y:927:%left '+' '-'
parse.y:1809: | '+' { ifndef_ripper($$ = '+'); }
parse.y:1951: | arg '+' arg
parse.y:1953: $$ = call_bin_op(p, $1, '+', $3, &@2, &@$);
927行目は演算子の優先順位を決めている部分のもので、
1809行目は演算子を読み込んだ時に字句解析に関する状態を調整するためのもの。
1953行目は1951行目にくっついてる処理のものなので今回は無視してよい。
‘+’ 以外の二項演算子に関する規則でも同様にarg OP arg
という右辺になっていて、
上述の =
だけが異なっている
(まあ代入先にできるものは通常の「式」ではありえないので当然と言えば当然)。
結局のところ、例の代入絡みの文の解析結果がああなるのは演算子の順位は関係なくて、 構文規則がそうなっているから。というハナシでした。
ここで、1840行目からの一連の定義の規則に付随する処理などを削除して規則部分のみを取り出すとこんな感じになる。
arg : lhs '=' arg_rhs
| var_lhs tOP_ASGN arg_rhs
| primary_value '[' opt_call_args rbracket tOP_ASGN arg_rhs
| primary_value call_op tIDENTIFIER tOP_ASGN arg_rhs
| primary_value call_op tCONSTANT tOP_ASGN arg_rhs
| primary_value tCOLON2 tIDENTIFIER tOP_ASGN arg_rhs
| primary_value tCOLON2 tCONSTANT tOP_ASGN arg_rhs
| tCOLON3 tCONSTANT tOP_ASGN arg_rhs
| backref tOP_ASGN arg_rhs
| arg tDOT2 arg
| arg tDOT3 arg
| arg tDOT2
| arg tDOT3
| tBDOT2 arg
| tBDOT3 arg
| arg '+' arg
| arg '-' arg
| arg '*' arg
| arg '/' arg
| arg '%' arg
| arg tPOW arg
| tUMINUS_NUM simple_numeric tPOW arg
| tUPLUS arg
| tUMINUS arg
| arg '|' arg
| arg '^' arg
| arg '&' arg
| arg tCMP arg
| rel_expr %prec tCMP
| arg tEQ arg
| arg tEQQ arg
| arg tNEQ arg
| arg tMATCH arg
| arg tNMATCH arg
| '!' arg
| '~' arg
| arg tLSHFT arg
| arg tRSHFT arg
| arg tANDOP arg
| arg tOROP arg
| keyword_defined opt_nl {p->in_defined = 1;} arg
| arg '?' arg opt_nl ':' arg
| primary
;
結構たくさんあるですな。
すごーく大雑把に(省略と誇張を交えて)書くと、通常の四則演算の式の場合は
primary + primary - primary * primary
のようにつないでいけるけど、代入が絡むと
primary + lhs = arg_rhs
のように ‘=’ を含む部分を通常の式の arg
にはできない
lhs
が登場してくるので、
lhs = arg_rgs
の部分を arg に reduce しておいてから
arg + arg
という形に持っていっている(ということは結局代入が加算より優先されたということ)。
といったことを踏まえて、ruby --dump=yydebug -e 'a+b=3'
の出力を追いかけてみようと
思ったのだけど……
Starting parse
Entering state 0
Reducing stack by rule 1 (line 1177):
lex_state: NONE -> BEG at line 1178
vtable_alloc:11774: 0x0000000002979990
vtable_alloc:11775: 0x0000000002979b90
cmdarg_stack(push): 0 at line 11788
cond_stack(push): 0 at line 11789
-> $$ = nterm $@1 (1.0-1.0: )
Stack now 0
Entering state 2
Reading a token:
lex_state: BEG -> CMDARG at line 8765
Next token is token "local variable or method" (1.0-1.1: a)
Shifting token "local variable or method" (1.0-1.1: a)
Entering state 35
Reading a token:
lex_state: CMDARG -> BEG at line 9190
Next token is token '+' (1.1-1.2: )
Reducing stack by rule 629 (line 4736):
$1 = token "local variable or method" (1.0-1.1: a)
-> $$ = nterm user_variable (1.0-1.1: )
Stack now 0 2
Entering state 118
以下略
やっぱり面倒だなあ(笑)。
「お年玉」にはちょっとしょぼかったすな。