Rubyに雑に演算子を足す

:hibiscus: [Okinawa.rbのAdvent Calendar 2016](http://qiita.com/advent-calendar/2016/okinawarb) :pig2: 10日目です。

RubyでMethodオブジェクトを取り出すsyntaxが欲しいときってありますよね。ruby-coreで`o.:method`や`o[.method]`や`o->method`などが提案されていますがしっくりこないみたいです。
https://bugs.ruby-lang.org/issues/12125#change-57209


Rubyのsyntaxをいじる練習に提案されているsyntaxとは別のsyntaxを雑に実装してみようと思います。

:bulb: どういうsyntaxを実装するか
----

ここにオブジェクトがあります。

```rb
o = Object.new
```

オブジェクトのmethodメソッドの取り出し方を考えます。

とりだすメソッドの名前を指定しないと取り出せないので、とりあえずmethodと書く必要はあるでしょう。

```rb
o /*何かしらのsyntax*/ method
```

るりまの[Rubyで使われる記号の意味](https://docs.ruby-lang.org/ja/latest/doc/symref.html)や[リテラル](https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html)のページを見ると記号はだいたい使われていそうです。

RubyではProcをつくるときに`-> { }`、ブロックを作るときに`foo { }`のように`{}`を使うことが多いですね。methodメソッドもProcやブロックと同じようにcallできるので同じように`{}`を使って書きたいです。
なので、今回は`receiver.{method}`でMethodオブジェクトを取り出せるようにしてみましょう。

以降`.{}`演算子と呼びます。

:wrench: 準備
----

```console
% git clone git://github.com/ruby/ruby.git
% cd ruby
% git fetch --tags
% git checkout -b method_method_syntax v2_4_0_preview3
```

今回はsyntax雑に足してみるのが目的なのでビルドは各自READMEを読んでやっていき :muscle:
https://github.com/ruby/ruby#how-to-compile-and-install

:apple::pen_fountain: テスト
----

雑に足すだけなので以下でapplepenと表示されればいいことにします。

```rb
㌰太郎 = ""
uh = ㌰太郎.{concat}

🍎 = "apple"
✒️ = "pen"

[🍎, ✒️].each(&uh)

puts ㌰太郎.to_s
# applepen
```

:eyes: parse.yを読む
----

Rubyのシンタックスはparse.yで定義されています。

parse.yを読んで以下の表現がわかれば`.{}`演算子を定義できそうです。

- レシーバー
- `.`
- メソッド名
- `{` と `}`


parse.yはだいたい1万行ちょっとあるうち半分ぐらいまでsyntaxが定義されててその下にトークン切り出す処理があります。
色々定義されてるとこにはRipper向けのコードが挟まっているので雑にsyntax足すなら1万行のうちの前半分、それぞれの定義の上の方だけ読めばよさそうです :ok_hand: 

```parse.y
なまえ : トークン1 トークン2
         {
           /*%%%*/
             $$ = じっこうされるやつ
           /*%
             $$ = Ripperのためのもろもろ
           %*/
        }
    ;
```

### メソッド名の表し方

とりあえずメソッド名の定義のあたりを探します。メソッド名を定義するときはdefと書くのでdefでgrepします。

```
% git grep -n '"def"' parse.y
parse.y:3129:                   token_info_push("def");
parse.y:11176:    {keyword_def, "def"},
```


`keyword_def`がでてきました。それっぽいのでプロジェクト内で検索すると`k_def`が出てきたので更に`k_def`で検索します。

```parse.y
k_def		: keyword_def
		    {
			token_info_push("def");
		    /*%%%*/
			$<num>$ = ruby_sourceline;
		    /*%
		    %*/
		    }
		;
```

`k_def`の次に来るのは`fname`のようです。`fname`をしらべれば、メソッド名を表す何かがわかりそうです。

```parse.y
primary		: literal

/* 中略 */

		| k_def fname
		    {
			local_push(0);
			$<id>$ = current_arg;
			current_arg = 0;
		    }
		    {
			$<num>$ = in_def;
			in_def = 1;
		    }
		  f_arglist
		  bodystmt
		  k_end
		    {
		    /*%%%*/
			NODE *body = remove_begin($6);
			reduce_nodes(&body);
			$$ = NEW_DEFN($2, $5, body, METHOD_VISI_PRIVATE);
			nd_set_line($$, $<num>1);
		    /*%
			$$ = dispatch3(def, $2, $5, $6);
		    %*/
			local_pop();
			in_def = $<num>4 & 1;
			current_arg = $<id>3;
		    }
```

おっ、`fname`がメソッド名を表してるっぽい。Rubyの場合、再定義できる演算子があるのでメソッド名っぽい`tIDENTIFIER`の他に`op`や予約語なども引数に取れるようですね。

methodメソッドでは引数に`:+`など演算子のメソッド名や大文字のメソッド名`:Integer`も取れるので`tIDENTIFIER`や`op`や`tCONSTANT`はつかえそうですが予約語の`reswords`は必要なさそうですね。

```parse.y
fname		: tIDENTIFIER
		| tCONSTANT
		| tFID
		| op
		    {
			SET_LEX_STATE(EXPR_ENDFN);
			$$ = $1;
		    }
		| reswords
		    {
			SET_LEX_STATE(EXPR_ENDFN);
		    /*%%%*/
			$$ = $<id>1;
		    /*%
			$$ = $1;
		    %*/
		    }
		;

/* 中略 */

reswords	: keyword__LINE__ | keyword__FILE__ | keyword__ENCODING__
		| keyword_BEGIN | keyword_END
		| keyword_alias | keyword_and | keyword_begin
		| keyword_break | keyword_case | keyword_class | keyword_def
		| keyword_defined | keyword_do | keyword_else | keyword_elsif
		| keyword_end | keyword_ensure | keyword_false
		| keyword_for | keyword_in | keyword_module | keyword_next
		| keyword_nil | keyword_not | keyword_or | keyword_redo
		| keyword_rescue | keyword_retry | keyword_return | keyword_self
		| keyword_super | keyword_then | keyword_true | keyword_undef
		| keyword_when | keyword_yield | keyword_if | keyword_unless
		| keyword_while | keyword_until
		;
```

`tIDENTIFIER`で探すと今回の目的に合ってそうな定義がでてきました。

```parse.y
operation2	: tIDENTIFIER
		| tCONSTANT
		| tFID
		| op
		;
```


----

捕捉: 書いてる時点では`reswords`いらないと思ってたけどこういうプログラムも書けるからいるっぽい

```rb
def nil
  puts 'hi'
end

self.send(:nil)
```

### メソッドよびだし

メソッド呼び出し時のレシーバーや`.`やメソッド名の表し方を確認するため定義を探します。メソッド呼び出しは`.`が間にあるので`.`で探してみます。

```sh
# git grep -n '"."' parse.y`ではうまくgrepできなかった
% git grep -n "'\.'" parse.y
parse.y:4976:dot_or_colon       : '.'
parse.y:4988:call_op    : '.'
parse.y:4991:                   $$ = '.';
parse.y:4993:                   $$ = ripper_id2sym('.');
parse.y:6064:      case '$': case '*': case '+': case '.':
parse.y:6275:   BIT(';', idx) | BIT(',', idx) | BIT('.', idx) | BIT('=', idx) | \
parse.y:6606:   if (c == '.') {
parse.y:7363:   else if (c == '.' || c == 'e' || c == 'E') {
parse.y:7381:     case '.':
parse.y:7395:       tokadd('.');
parse.y:7722:      case '.':            /* $.: last read line number */
parse.y:8051:         case '.': {
parse.y:8053:           if (peek('.') == (c == '&')) {
parse.y:8286:   else if (c == '.') {
parse.y:8386:      case '.':
parse.y:8388:   if ((c = nextc()) == '.') {
parse.y:8389:       if ((c = nextc()) == '.') {
parse.y:8400:   return '.';
```

`call_op`が怪しいのでそれで検索すると、それっぽいのがでました。レシーバーは`primary_value`と書き、メソッド呼び出しの`.`は`call_op`と呼ばれているようです。

```parse.y
method_call	: fcall paren_args

/* 中略 */

		| primary_value call_op operation2
		    {
		    /*%%%*/
			$<num>$ = ruby_sourceline;
		    /*% %*/
		    }
		  opt_paren_args
		    {
		    /*%%%*/
			$$ = NEW_QCALL($2, $1, $3, $5);
			nd_set_line($$, $<num>4);
		    /*%
			$$ = dispatch3(call, $1, $2, $3);
			$$ = method_optarg($$, $5);
		    %*/
		    }

```


### `{` と `}`の表現

grepしたらすぐみつかった。

```
% git grep -n "'{'" parse.y
parse.y:1068:             '{' top_compstmt '}'
parse.y:1168:             '{' top_compstmt '}'
parse.y:1280:           | keyword_END '{' compstmt '}'
parse.y:3720:brace_block        : '{'
parse.y:5771:    if (peek('{')) {  /* handle \u{...} form */
parse.y:6144:       if (c2 == '$' || c2 == '@' || c2 == '{') {
parse.y:6312:      case '{':
parse.y:7582:   else if (term == '{') term = '}';
parse.y:8541:      case '{':
parse.y:8554:       c = '{';          /* block (primary) */
```

普通に`'{'`と`'}'`を書けばよさそうですね!

```parse.y
top_stmt	: stmt
		| keyword_BEGIN
		    {
		    /*%%%*/
			/* local_push(0); */
		    /*%
		    %*/
		    }
		  '{' top_compstmt '}'
		    {
		    /*%%%*/
			ruby_eval_tree_begin = block_append(ruby_eval_tree_begin,
							    $4);
			/* NEW_PREEXE($4)); */
			/* local_pop(); */
			$$ = NEW_BEGIN(0);
		    /*%
			$$ = dispatch1(BEGIN, $4);
		    %*/
		    }
		;

/* 中略 */

stmt_or_begin	: stmt
                    {
			$$ = $1;
		    }
                | keyword_BEGIN
		    {
			yyerror("BEGIN is permitted only at toplevel");
		    /*%%%*/
			/* local_push(0); */
		    /*%
		    %*/
		    }
		  '{' top_compstmt '}'
		    {
		    /*%%%*/
			ruby_eval_tree_begin = block_append(ruby_eval_tree_begin,
							    $4);
			/* NEW_PREEXE($4)); */
			/* local_pop(); */
			$$ = NEW_BEGIN(0);
		    /*%
			$$ = dispatch1(BEGIN, $4);
		    %*/
		    }

/* 中略 */

brace_block	: '{'
		    {
		    /*%%%*/
			$<num>$ = ruby_sourceline;
		    /*% %*/
		    }
		  brace_body '}'
		    {
			$$ = $3;
		    /*%%%*/
			nd_set_line($$, $<num>2);
		    /*% %*/
		    }
		| keyword_do
		    {
		    /*%%%*/
			$<num>$ = ruby_sourceline;
		    /*% %*/
		    }
		  do_body keyword_end
		    {
			$$ = $3;
		    /*%%%*/
			nd_set_line($$, $<num>2);
		    /*% %*/
		    }
		;
```

:memo: parse.yを書く
----

parse.yをみると`primary_value`と`call_op`と`'{'`と`operation2`と`'}'`を組み合わせればそれっぽいsyntaxが定義できそうなので書いてみましょう。


### 書く場所
どこに書くか悩むんですがメソッド呼び出しやλぽい定義あったんでそのあたりに足します

```parse.y
primary		: literal

/* 中略 */

		| method_call
		| method_call brace_block
		    {
		    /*%%%*/
			block_dup_check($1->nd_args, $2);
			$2->nd_iter = $1;
			$$ = $2;
		    /*%
			$$ = method_add_block($1, $2);
		    %*/
		    }

/* このへん? */

		| tLAMBDA lambda
		    {
			$$ = $2;
		    }
```

他の部分の真似をして付け足します。

```parse.y
		| primary_value call_op '{' operation2 '}'
			{
				ここに実装を書く
			}
```

### メソッド呼び出しの実装を読む

`object.{method}`と書いたとき、Objectに対してmethodメソッドを呼び出したいので参考のためにmethod_callの実装を読みます。

```parse.y
method_call	: fcall paren_args
		    {
/* 略 */
		    }
		| primary_value call_op operation2
		    {
		    /*%%%*/
			$<num>$ = ruby_sourceline;
		    /*% %*/
		    }
		  opt_paren_args
		    {
		    /*%%%*/
                        /* 
			$$ = NEW_QCALL($2, $1, $3, $5);
			nd_set_line($$, $<num>4);
		    /*%
/* ここからしたはRipper向け定義のよう */
			$$ = dispatch3(call, $1, $2, $3);
			$$ = method_optarg($$, $5);
		    %*/
		    }
```

定義してる部分の最初のトークンから順番よく`$数字`と入っていくようです。
method_callの定義の場合は`primary_value`が`$1`に、`call_op`が`$2`に、`operation2`が`$3`に、`ruby_source_line`が`$4`に、`opt_paren_args`が`$5`でしょうか。
実装部分では`NEW_QCALL(call_op, primary_value, operation2, opt_paren_args)`の結果を`$$`に代入していますね。

`NEW_QCALL`の定義を読むと、`NEW_QCALL`ではぼっち演算子(`&.`)でメソッドが呼ばれたときに対応するための処理が入り、最終的に`NODE_CALL`が呼ばれているようです。

```c:parse.y
#define CALL_Q_P(q) ((q) == tANDDOT)
#define NODE_CALL_Q(q) (CALL_Q_P(q) ? NODE_QCALL : NODE_CALL)
#define NEW_QCALL(q,r,m,a) NEW_NODE(NODE_CALL_Q(q),r,m,a)
```

`NODE_CALL`の使い方を調べてみましょう。以下のような関数がありました。

```c:parse.y
static NODE *
call_bin_op_gen(struct parser_params *parser, NODE *recv, ID id, NODE *arg1)
{
    value_expr(recv);
    value_expr(arg1);
    return NEW_CALL(recv, id, NEW_LIST(arg1));
}
```

methodメソッドを呼ぶ場合、`NODE_CALL`に第一引数にレシーバー、第二引数のidにmethodメソッドのID、第三引数に`NEW_LIST(methodメソッドに渡す引数のNODE)`を渡すとよさそうです。

それでは実装してみましょう。

### 書く

まずはNEW_QCALLを呼び出しましょう。第1引数にはcall_opを渡し、第2引数にreceiverを渡します。

```parse.y
		| primary_value call_op '{' operation2 '}'
			{
				$$ = NEW_QCALL($2, $1, methodメソッドのID, NEW_LIST(methodメソッドの引数のNODE);
			}
```

methodメソッドをのIDを取る方法を調べます。「IDを返す」で検索すると以下の文書が引っかかりました。`rb_intern`を使うとよさそうです :smiley: 

```doc/extension.ja.rdoc
ID rb_intern(const char *name) ::

  文字列に対応するIDを返す．
```

```parse.y
		| primary_value call_op '{' operation2 '}'
			{
				$$ = NEW_QCALL($2, $1, rb_intern("method"), NEW_LIST(methodメソッドの引数のNODE);
			}
```

次はoperation2からmethodメソッドの引数のNODEを作ります。methodメソッドの引数はシンボルなのでシンボルの定義を読むと`NEW_LIT(ID2SYM(〜))`でシンボルが出来るようです。

```parse.y
literal		: numeric
		| symbol
		    {
		    /*%%%*/
			$$ = NEW_LIT(ID2SYM($1));
		    /*%
			$$ = dispatch1(symbol_literal, $1);
		    %*/
		    }
		| dsym
		;
```

念のため`NEW_LIT`がNODEを返すか定義を確認

```c:node.h
#define NEW_LIT(l) NEW_NODE(NODE_LIT,l,0,0)
```

あとは`.{}`演算子の定義のoperation2、`$4`を引数わたしてシンボル化するとよさそうですね!

```parse.y
		| primary_value call_op '{' operation2 '}'
			{
				$$ = NEW_QCALL($2, $1, rb_intern("method"), NEW_LIST(NEW_LIT(ID2SYM($4))));
			}
```

さっそくmakeしてみます。parse.yからparse.cが生成されコンパイルされます。
Ripperの定義を省いたのでripperのビルドはコケましたが、parse.yはうまくコンパイル出来たようです!

```
% make
(略)
generating parse.c
compiling parse.c
linking miniruby
(略)
compiling ripper.c
ripper.y: In function 'ripper_yyparse':
ripper.y:2798:17: warning: assignment makes integer from pointer without a cast [-Wint-conversion]
     $$ = NEW_QCALL($2, $1, rb_intern("method"), NEW_LIST(NEW_LIT(ID2SYM($4))));
                 ^
linking shared-object ripper.bundle
checking ../.././parse.y and ../.././ext/ripper/eventids2.c
*** Following extensions failed to configure:
../.././ext/openssl/extconf.rb:0: Failed to configure openssl. It will not be installed.
*** Fix the problems, then remove these directories and try again if you want.
(略)
```

早速確認用のプログラムを動かしてみましょう。

```
% cat method_method_syntax.rb
㌰太郎 = ""
uh = ㌰太郎.{concat}

🍎 = "apple"
✒️ = "pen"

[🍎, ✒️].each(&uh)

puts ㌰太郎.to_s

% ./ruby --disable=gems method_method_syntax.rb
method_method_syntax.rb:2: syntax error, unexpected {, expecting '('
uh = ㌰太郎.{concat}
                ^
```

あれ、エラーが出ました。`unexpected {`と言われて閉まっています。`{`がうまく認識されてなさそうです。
他の`{`を使うsyntaxを確認するとHashの記法の定義では`tLBRACE`を使っているようです。

```parse.y
		| tLBRACE assoc_list '}'
		    {
		    /*%%%*/
			$$ = new_hash($2);
		    /*%
			$$ = dispatch1(hash, escape_Qundef($2));
		    %*/
		    }
```

Hashと同じようにtLBRACEで置き換えてみます。

```parse.y
		| primary_value call_op tLBRACE operation2 '}'
			{
				$$ = NEW_QCALL($2, $1, rb_intern("method"), NEW_LIST(NEW_LIT(ID2SYM($4))));
			}
```

コンパイルして再実行

```
% make
% % ./ruby --disable=gems method_method_syntax.rb
applepen
```

動きました :green_apple: :pen_fountain: 

https://github.com/hanachin/ruby/tree/method_method_syntax

:sparkles::gem::sparkles: まとめ
----

parse.yを読み書きして`.{}`演算子を追加してみました:sparkles:
parse.yいじると普段変更できないsyntaxを変更出来るので楽しいですね :leopard: 

:hibiscus: Okinawa.rbについて :pig2: 
----

[Okinawa.rbのAdvent Calendar 2016](http://qiita.com/advent-calendar/2016/okinawarb) は記事を読む人・書く人を募集しています。

Okinawa.rbは参加者も募集しています :airplane::pineapple: 
開催情報はDoorkeeperに載っています。毎月どこかの週の水曜日の夜に開催しています :watermelon:  :calendar_spiral:
https://okinawarb.doorkeeper.jp

流れ的には雑談、初対面の人がいるときは自己紹介、もくもく、解散です。(僕はもくもくとRubyのソースを読んだりしています)

次回は2016-12-14（水）19:00 - 22:00です。

参考
----

- [Rubyのしくみ　Ruby Under a Microscope](http://amzn.to/2huecOL)
- - [『Rubyソースコード完全解説』サポートページ](http://i.loveruby.net/ja/rhg/)
- [Rubyist Magazine - parse.y の歩き方 - ワシの Ruby は 4 式まであるぞ -](http://magazine.rubyist.net/?0031-parsey)