かわいいRuby: 拡張可能な%リテラル宣言

Akihiko Odaki <nekomanma@pixiv.co.jp>
クリエイティブ・コモンズ・ライセンス この作品 (index.html) は クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています. 表示される画像等はそれぞれのライセンス従います.

pixivでアルバイトやってます

2017年7月から

Rubyで書かれたマイクロブログサービス, Mastodon及びその派生であるPawooとPawoo Musicを開発. (発表内容とはあまり関係ない)

pixivは前夜祭の主催のようで, 今回, 刺客として送り込まれる.

Mastodonのロゴ
https://joinmastodon.org/
Pawooのロゴ
https://pawoo.net/
Pawoo Musicのロゴ
https://music.pawoo.net/

Ruby?

発表することがない!

「1つのこと*をうまくやる」ことができている

私が開発しているのはアプリケーション

ならばこちらからRubyに近づかなければならない

Ruby Internals

Rubyはオープンソース

  • メーリングリスト
  • バグトラッカー
  • ソースコードリポジトリ

まずはバグトラッカーを見てみる

十分強力な文法を持っているように思えるRubyだが, 拡張の提案は未だ多い

機能 #11529 『拡張可能な%リテラル宣言』

%q{select foo from bar}

では%sql{select foo from bar}は?

バグトラッカーのスクリーンショット

洞察

%は演算子でもある

%Q%%%%%%

辛そう

mame (Yusuke Endoh) 氏がバグトラッカーで%Q%%%%%%をRubyの一番かわいいところと述べているスクリーンショット

parse.y

Yaccを使用.

字句解析にはflexなどは使っておらず, 直接書かれていて複雑そうである.

%が字句解析されるまで

  1. yylex
  2. parser_yylex
  3. parse_percent

yylex

例のアレ

  1. 状態の復元
  2. parser_yylexの呼び出し
  3. 状態の保存

parser_yylex

実際に字句解析が始まるのはここから

文字列が開始されているならば文字列の解析, そうでなければトークンの最初の文字によって特殊化された別の関数を呼び出す.

parse_percent

読んで字のごとく

ここにきてようやく演算子か, %記法の開始かが判定される

%記法であれば, 文字列, ワードリスト等の開始を意味するトークンを返す.

lex_state

現在状態がいい感じに定義されているビットフィールド

EXPR_BEG_bit
「(先行する) 改行が無視され, +/-が (演算子ではなく) 符号として解釈される」
→ %も演算子ではなく%記法の開始を意味する

lex_strterm

文字列の終端を表す変数

…と思わせておいて実は式展開が有効かどうかなどを表すenum string_typeというビットフィールド, 開始文字なども含む.

仕様策定: 定義

def %fooなどと定義されたメソッドに文字列を第1引数として渡す

%メソッドは演算子と被るので定義できない

仕様策定: リテラル

%のすぐあとが大文字 (例: %FOO) の場合は式展開を行う

%W相当のものは式展開内の空白とリテラル内の空白を区別できないので実装できない

クラスメソッドの場合はFoo.%barという形で使用できる

定義から見れば分かりやすいが, いざ使うとなると見た目が若干悪い

実装の検討: 字句, 状態の追加

tPERCENT_BEGという字句を追加. %記法におけるメソッド名を表す.

tPERCENT_ENDという字句を追加. 文字列を表す.

string_typestr_percentを追加. 指定されていた場合, 文字列の終端でtPERCENT_END返される.

実装の検討: 字句解析

既存の%記法は現在の実装と同様に字句解析し, これで定義されていない記法であると判別したときにtPERCENT_BEGを返す.

%演算子の定義をパースしている部分で%以降のアルファベットも識別子に含めるように.

実装の検討: 構文解析

tPERCENT_BEGで始まりtPERCENT_ENDで終わる, percentノードを追加. メソッド呼び出しとして解析される.

メッセージ送信の構文のあとにtPERCENT_BEGがあった場合のノードを追加. 同様にメソッド呼び出しとして解析される.

実装: コード例

module M
	def self.%m s
		'hello, ' + s
	end
end

p M.%m{world}

実装: パースツリー

ruby --dump=parsetree
@ NODE_CALL (line: 7. lineno: 7, column: 2)
+- nd_mid: :%m
+- nd_recv:
|   @ NODE_CONST (line: 7. lineno: 7, column: 2)
|   +- nd_vid: :M
+- nd_args:
    @ NODE_ARRAY (line: 7. lineno: 7, column: 4)
    +- nd_alen: 1
    +- nd_head:
@ NODE_STR (line: 7. lineno: 7, column: 4)
+- nd_lit: "world"
    +- nd_next:
        (null node)

実装: 結果

ruby
"hello, world"

やったぜ

…本当にこれでいいの?

%単独で定義できない
%単独での定義の利用場面が思い浮かばないが…「かわいくない」
式展開内か式展開外か判別できない問題
sprintf形式で返答すると扱いやすいが%%%にする必要があるなど面倒がある. 式展開内の範囲を配列にして渡すという方法も.

かわいいRuby

お年頃のRuby

スピードも気になるけど, おしゃれもしっかりやっていきましょう.

参照

Feature #11529: extensible % literal declarations - Ruby trunk - Ruby Issue Tracking System
https://bugs.ruby-lang.org/issues/11529

ライセンス (敬称略)

index.html
Akihiko Odakiにより, クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています.
logo_full.svg
Gargronにより, AGPL-3.0の下に提供されています.

ライセンス (敬称略)

logo_pawoo.png
pixiv株式会社により, AGPL-3.0の下に提供されています.
logo.png
pixiv株式会社により, AGPL-3.0の下に提供されています.
ruby.svg
Yukihiro Matsumotoにより, クリエイティブ・コモンズ 表示 - 継承 2.5 国際 ライセンスの下に提供されています.

ライセンス (敬称略)

@pacochiにより, Good Boy Licenseの下に提供されています. なお, このキャラクターはmstdn.maud.ioというMastodonインスタンスの管理者であるほた氏の周りでよく見かける『藍川 茜』という子で, Rubyとはかわいいことと「赤い目」をしているという以外あまり関係はないです.