日本ソフトウェア科学会第 33 回大会 (2016 年度) 講演論文集

動的型付き言語上での構文拡張手段の提供
岩間 雄太

市川 和央

千葉 滋

本論文では，Ruby 言語上でモジュラーな構文拡張をおこなう言語機構を提案する．プログラマーはユーザ定義演算子を定義することで Ruby 言語の構文を拡張することができる．定義できる演算子の文法は，演算子の名前部分と引数部分が任意の順番で並んでいる文法である．提案する機構の特徴は，演算子にタグを付与することができる点，及びそのタグを利用して演算子の引数部で使用できる演算子を制限できる点である．タグは演算子に複数付与することができるので，新たに非終端記号を定義して構文拡張を実現する場合と比べより細かい演算子の制限が可能となる．

が，我々の知る限り，動的型付け言語では知られてい1 はじめにない．

内部 DSL(Internal Domain Specific Language) [4]

本論文では，Ruby 上で構文拡張を可能にする機とはホスト言語 (Ruby などの汎用言語) の内部で構

構 Erbs を提案する．Erbs を使用すると Ruby 上に築された DSL のことであり，ある特定のドメインのRuby の構文に依存しない自由な構文を持つ DSL を問題を解くためのライブラリやフレームワークのこ作成できる．Erbs は DSL 開発者が新しい演算子をとを指す．Ruby，Scala といった言語では内部 DSL定義することで構文拡張をおこなう．複数の構文拡張を作成するためにブロックや implicit parameter なによって起こる曖昧性を回避するために，DSL 開発どの有用な機構が提供されている．Ruby on Rails，者はそれぞれの演算子にタグをつけられる．また，こRspec や ScalaTest など様々なライブラリで DSL をのタグにより，Erbs は自分が行った拡張を後から第提供しており，アプリケーション開発には欠かせない三者が更に拡張しても，拡張が壊れることがないモものになっている．内部 DSL はホスト言語内に DSLジュラーな構文拡張方法を提供している．

を構築するため他の内部 DSL やホスト言語との連携が容易である．しかし同時に，内部 DSL の文法はホ2 Ruby 上での構文拡張

スト言語の文法に依存しているため表現できる文法

Ruby は高い表現力を持ち，柔軟な構文を表現可に制限がある．DSL 開発者が自由に構文規則を作成能である．例えば，シェルスクリプトのコマンドでできるような仕組みを導入すれば，ホスト言語の文法ファイルの内容を標準出力に出力する cat のようなに依存しない内部 DSL を作成することができる．しDSL を Ruby 上で表現する例を考える．cat コマン

かし，こうした場合，構文解析の結果が一意に決まら

ドは Ruby 上では Listing 1 のようにメソッドを定義なくなる曖昧性という問題が発生する．曖昧性の問

す Ruby ではメソッドの引数を囲む括弧を省略でき

題は静的型付け言語であれば解決法は知られているるからである．このメソッドにより Ruby 上でシェル

Yuta Iwama, Kazuhiro Ichikawa, Shigeru Chiba, 東京
大学大学院情報理工学系研究科創造情報学専攻, Dept.
of Creative Informatics, Graduate School of Information Science and Technology, The University ofTokyo..

スクリプトの cat と同等のことができるようになり

cat “hello.c” とすることで hello.c の中身を標準出力に出力できる．
このように Ruby は様々な DSL を作成できるが，

Listing 1 cat を表現する Ruby のプログラムの例
def cat ( filepath )puts File . read ( filepath )endcat " hello . c "# = > print contents of hello . c

作成できない DSL も存在する．例えば cat “hello.c”“world.c” > “hello world.c” のような構文を許す DSL

Listing 2 オープンクラスを使用した catclass String
def >( others )[ self , others ]endenddef cat ( filepath )source , out = filepath$stdout = File . open ( out , " w ")puts File . read ( source )$stdout = STDOUT
endは作成できない．この構文は 2 つのファイル hello.cと world.c を受け取ってその 2 つの内容を足し合わせcat " hello . c " > " hello_back . c "たものをファイル hello world.c に出力する．Rubyの構文ルール上，メソッド呼び出しの第一引数と第二ように書き換えることで解決可能ではある．しかし，引数の間はカンマで区切られている必要があるため，String クラスの > のような他の場所でも頻繁に使用

この構文はエラーとなる．構文解析を成功させるたされるメソッドを書き換える手法は，アプリケーショ

めには， cat “hello.c”, “world.c” > “hello world.c”

ンの他の部分を壊す可能性があり， 非常に危険なののようにカンマを加えなければならない．カンマなで避けるべきである．あるいは Method Seals [7] のしの構文を許す DSL を作成できるようにするには，ような既存メソッドの書き換えの有効範囲を制御でき

Ruby 言語自体がユーザによる構文拡張を許さなけれる機構を使用して，String クラスの > の書き換えをばならない．

ある範囲内でのみ有効にする手法と組み合わせなけ

ユーザに構文拡張を許す仕組みを実現するにあたっればならない．て難しい点は，拡張された構文が引き起こす構文の曖昧性をどう解決するかである．例えば上の例の場合，

3 構文拡張システム Erbs

構文拡張により cat メソッドの第一引数”hello.c” と本章では，Ruby 上の構文拡張機構 Erbs を提案す第二引数 ”world.c” の間のカンマを省略できるようにる．Erbs では DSL 開発者が新しい演算子を定義すできたとしても，> の解釈をめぐる曖昧性を解決しなることで構文拡張をおこなう．複数の構文拡張によっければならない．Ruby では > は文字列比較メソッておこりうる曖昧性を回避するために，DSL 開発者ドでもあるので，”world.c” > “hello world.c” 全体をがそれぞれの演算子にタグを付けることができる．演第二引数とみなす解釈もありうる．しかし DSL 作成

算子を定義する際に，特定のタグを持つ Ruby 式だ

者からすれば，第二引数は “world.c” だけで、cat メ

けを演算子の引数部分に受け付けるように制限する

ソッドの呼び出し式全体が > の左辺式 (あるいは >ことで，構文解析時に候補となる構文木を減らすこメソッドの呼び出し先) であると解釈されてほしいととができるので，曖昧性を回避できる．また，Erbsころである．

では同一演算子に複数のタグをつけることができる．

このような曖昧性は、Ruby の構文拡張を必要としなそのため，一つしかタグを付けられない場合と比べ，い簡単な DSL でも起こりうる．例えば cat メソッドの引数部分に書ける Ruby 式の種類を細かく制限でき引数を 1 個だけにした cat “hello.c” > “hello back.c”

る．これは複数の DSL を同時に使う場合に重要な機の場合でも，> の解釈は曖昧性をともなう．この >能である．の解釈の曖昧性は，Ruby のオープンクラス機構を用いて, String クラスの > メソッド自体を Listing 2 のListing 3 キーワード defs を使用した構文拡張
1 defs cat file1 file2 (2file1 : origin ,3
file2 : origin
4 )5puts File . read ( file1 )6puts File . read ( file2 )7 end
89 cat " hello . c " " world . c "

3. 1

12
34
56
78
9101112
ユーザ定義演算子を採用した Ruby 上での13Listing 4 曖昧性が発生する構文拡張の例
defs checkout bname ( bname : origin )" checkout " + bname
enddefs git cmd ( cmd : origin )exec (" git " + cmd )end
def checkout ( time )" checkout at " + timeend
git checkout " master "構文拡張

Erbs はユーザが演算子を新しく定義することで構文拡張を行う．演算子は 2 項演算子だけでなく 4 項る．なお，origin については次節で説明する．メソッ以上の演算子も定義することが可能である．DSL 開

ドボディ部は 5 から 6 行目の Ruby 構文のことであ

発者は新しい演算子の文法を終端記号と Ruby 式がる．ここには定義する演算子の式を実行したときに実任意の順に並んでいるものとして定義できる．Ruby

行される Ruby の構文を指定する．この例では Ruby

式とは既存の Ruby の構文の式と新しくユーザが定でファイル名を受け取り，そのファイルの内容を読み義した演算子の式のことを言う．ユーザが定義した演込んで標準出力に出力するようなコードを指定した．

算子の式を実行したときは，その演算子に結びつけられたメソッドボディが実行される．演算子のオペラン

3. 2 タグを使用した曖昧性の回避手段の提供

ド部分の Ruby 式は，評価された後にメソッドボディ

単純な方法で構文拡張をおこなうと，定義したすべに引数として渡される．ての拡張構文が任意の場所で使用可能になってしまいErbs では演算子を定義するために defs というキー

既存の構文と拡張した構文、または拡張同士が衝突しワードを使用する．Listing 3 では defs を使用し言語て曖昧性が発生する．曖昧性が発生すると構文が壊れ拡張を行い cat コマンドのような構文を実現していて意図しない動作を引き起こす危険がある．

る．Listing 3 の 9 行目の cat コマンドを実行するとcheckout という git サブコマンドを Ruby 上に表ファイル hello.c と world.c を読み込み，その内容を現する例を Listing 4 で考える．この拡張を実現する

標準出力に表示することができる．defs は 構文定義

ことで Ruby 上で git で管理されたリポジトリのブ部，引数定義部，メソッドボディ部を持っている．構

ランチを変更することができる．Erbs では Listing 4

文定義部は Listing 3 の 1 行目の cat file1 file2 の部分の 1 行目の git bname と 5 行目の checkout cmd とで終端記号 cat の後に file1，file2 という Ruby 式をいう演算子を定義すると実現できる．しかし，9 行並べたものを定義している．ここは新しく定義する演目のように Ruby のメソッドとして checkout が定義算子の文法を指定する部分で，終端記号と Ruby 式のしてある場合，曖昧性が発生し 13 行目の checkout並びを指定する．引数定義部は 1 から 4 行目の (file1:“master” を 1 行目の? checkout か 9 行目の checkout

origin, file2: origin) の部分である．ここは定義する演どちらを使用して構文解析すればよいか一意に決ま算子の文法中に含まれる Ruby 式を指定する．このらない．

部分で指定された Ruby 式は評価された後，メソッドボディ部に渡される．この例では，file1 と file2 が対応する Ruby 式の評価結果を表す引数を定義していこのような曖昧性を避けるために，曖昧性の回避手段を持つことは言語拡張をする際に重要である．
そこで，Erbs ではユーザが定義した演算子にタグListing 5

キーワード Operator を使用し曖昧性を回避

1
した構文拡張2
1 Operator ( git , subcommand )32
defs checkout bname ( bname : origin )43" checkout " + bname
54end65 end
7687 Operator ( origin , git )98defs git cmd ( cmd : subcommand )109exec (" git " + cmd )1110end1211 end
13121413 git checkout " master "1516
1718をつけることができるようにした．引数部分でタグ 19を指定し，構文解析時に候補となる演算子を減らす 20
21ことで曖昧性を回避する．タグはすべての演算子にListing 6 モジュラーな拡張の例
Operator ( command )defs cat file1 file2 (file1 : origin , file2 : origin)puts File . read ( file1 )puts File . read ( file2 )end
end
Operator ( origin )defs input > output (input : command && ! @token ( >) ,output : origin)$stdout = File . open ( output , " w ")input
$stdout = STDOUT
end
end
cat " hello . c " " world . c " > " hello_world . c "は 1 つ以上付いている．Erbs ではタグを演算子につけるために Operator キーワードを使用する．Listingて origin というタグを付与しているため，git cmd は5 は Operator を使用して演算子にタグをつけるこ

既存の Ruby 構文と同等に扱える．つまり，プログラとで Listing4 で発生した曖昧性を回避した例であム中では既存の Ruby 構文を書けるところであれば，る．Operator はタグ定義部とボディ部を持っている．

自由に git cmd を書ける．

Listing 5 の 1 行目の (git, subcommand) の部分がタグ定義部でボディ部で定義された演算子につけるタグ

3. 3 モジュラーな曖昧性の回避を指定する部分である．2 から 4 行目はボディ部で，Erbs では，構文拡張をモジュラーに実装できるよ演算子を定義する部分である．演算子の定義は 3. 1 節

うに，defs の中でタグを複数利用できる。defs の引で述べた defs を使用する．すべての演算子にタグを数定義部では 論理演算子 &&, ||, !, @token を用いて，付与するために defs は Operator の中でのみ使用可能

タグ同士あるいはタグと @token 述語とを組み合わせとした．この例では定義した演算子 checkout bnameて指定することで，タグを一つしか使えない場合と比には git と subcommand の 2 つのタグをつけ，8 行

べ，引数定義部に書ける Ruby 式を詳細に制限する

目の git cmd には origin と git というタグをつけた．

ことができる．DSL 開発者間でタグに共通の意味付タグを使用することで，演算子を定義するときに defs

けを与えることで，ある DLS 開発者が行った拡張をの構文定義部に現れる Ruby 式を特定のタグをもつ後から第三者が更に拡張しても，拡張が壊れることがものに制限できる．Listing 4 は git cmd の cmd の部

ないモジュラーな DSL を実装できる．

分に任意の Ruby 式を許していたため曖昧性が発生

Listing 6 は複数のタグと述語を使用したモジュした．そこで Listing 5 は git cmd の cmd の部分にラーな DSL の実装例である．この例は Listing 3 のは subcommand というタグを持つ Ruby 式のみを受出力先を標準出力からファイルに変更する (21 行目)．

け付けるようにした．こうすることで，cmd の部分今までの例のように単一タグだけを用いて，この例をの既存のメソッド呼び出しとの曖昧性を回避すること実現すると Ruby 構文 String#> との間の曖昧性をができる．また，Erbs では既存の Ruby 構文に対し回避できない．一方，ここでは 1 行目から 8 行目でcommand というタグを持った cat file1 file2 という演算子を定義している．defs の引数定義部では file1，Listing 7

既存の拡張を再利用した例

file2 ともに origin タグを持った Ruby 式を許してい 1 Operator ( origin )2るので，すべての組み込みの Ruby 構文が使用可能と 3なる．10 から 19 行目では タグ origin を持った input 45> output という演算子を定義している．この演算子 6の引数部分では output は origin というタグをもった 7

defs input >> output (input : command && ! @token ( > >) ,output : origin)$stdout = File . open ( output , " w +")input$stdout = STDOUT
endend89command && !@token(>) となっている．こうするこ 10とで曖昧性を回避しつつモジュラーな言語拡張を可 11
12 cat " hello . c " " world . c " >> " hello_world . c "Ruby 式を許す．一方で，input に書ける式の条件は能にしている．&& はタグ同士の AND 演算を行う．
例えば A && B という指定があった場合，受け付ける Ruby 式は A かつ B のタグを持つ全ての Ruby 式

まる演算子式の定義中に > や >> に関する制限を直である．@token(>) の ! はタグの否定演算を行い，指接記すことになるので，再利用性がなくなり，モジュ定されたタグを持ってないすべての Ruby 式を受けラーな実装でなくなる．複数タグの利用は，Listing 6付ける．@token は引数に指定した終端記号を式中にの 11 行目と Listing 7 の 2 行目のリダイレクト演算含むすべての Ruby 式を意味する．@token は推移的子の定義の再利用も可能にする．cat に加えて ls コであり，!@token(>) に該当する Ruby 式は，その部マンドを実行する演算子を定義する際も，その演算子分式の中であっても > を含まない式である．例えばに command タグを付加すれば，そのままリダイレ式 (a > b) == (c < d) は該当しない．したがってクト演算子と組み合わせて利用できる．

Listing 6 の 12 行目の input の部分に書ける Ruby式は，command というタグを持ち，かつ > という4 実装終端記号を含まないものである．このように定義する

Erbs のアイデアを実現するため Ruby サブセット

ことで input の Ruby 式中では > が使用できなくな上でソースコード変換器を実装した．この実装がサり，Ruby 組み込みの String#> メソッドとの間で曖ポートしている Ruby の構文はクラス，メソッド，変昧性が発生することを避けられる．

数の定義，メソッドの実行，制御構文，基本的な 2 項

例えばこの DSL に >> という上書きのリダイレ演算子などである．@token と タグが推移的になる部

クトを可能にする演算子を追加することを考える．

分は未実装であるが複数タグ，演算子 &&, ||, ! は実

Erbs では Listing 7 のように実装することで，既存装済みである．この変換器はユーザが定義した拡張の Listing 6 を修正することなく，そのまま再利用を含んだ Ruby プログラムを純粋な CRuby プログラして >> を実装できる．Listing 6 の 2 行目からのムに変換し，変換後のソースコードを CRuby 上でcat から始まる演算子は command タグを持つが，こ実行する．演算子定義部分は CRuby 上の関数定義にの演算子式の定義では > を式中に含んでもよいこと変換され，演算子呼び出しは CRuby の関数呼び出しになっている．> を式中に含んではいけないことは，に変換される．Listing 8 に Listing 3 で定義した演12 行目に分離して書かれている．この分離により，算子を本実装で変換した後の CRuby のプログラムを

Listing 7 では cat から始まる演算子式の定義をその示す．

まま再利用している．Listing 7 の演算子の定義で必Ruby の構文解析器は Scala のパーザコンビネータ要な，cat から始まる演算子式の式中に >> を含んライブラリを使用して実装した．既存の Ruby の構

ではいけないという制限は，Listing 7 の中に記され文解析器を使用しなかったのは Ruby の構文解析器ている．もし単一のタグしか使えないと，cat から始はパーザジェネレータで作られており，実行時に拡張静的型付け言語上で型を使用して曖昧性を回避しListing 8 変換後の Ruby のソースコード
module Operator :: Origin
def self . cat_File1_File2 ( file1 , file2 )puts File . read ( file1 )puts File . read ( file2 )end
endている研究もある．Mixfix operators [1] は Coq やIsabelle などの言語で実装されているユーザ定義可能な演算子である．Mixfix operator は非常に強力であり様々な構文を表現することができるが名前部分と引数部分が交互にこなければいけないという制約がある．
また，静的型付け言語上で実装されているため曖昧性

Operator :: Origin :: cat_File1_File2 (" hello . c " , " hello2 . c ")の解決に引数部分の型を使用している．ProteaJ [6]は protean operator という演算子を使用して強力な構文拡張を可能した Java 言語の拡張である．proteanoperators とは演算子の名前部分と引数部分によってすることは困難であっためである．Scala のパーザコ表現する演算子である．Mixfix operators と比べて名ンビネータライブラリは Packrat Parsing [3] を採用

前部分と引数部分の並びに制限も存在しない．曖昧性しており，再帰下降構文解析では最悪で指数時間かかの回避に返り値の型と引数の型の両方を使っているる解析を線形時間で行うことを可能にしている．

が，動的型付け言語だとこのような方法は取ることができない．

5 関連研究演算子を利用して内部 DSL を構成し構文拡張を行

6 まとめと今後の課題

う言語や研究はこれまでにも多く存在する．C++は本論文では，動的型付け言語上でモジュラーな構文演算子オーバーロードの機構を持っており，既存の拡張を可能にする Erbs を提案した．ユーザが自由に演算子のほとんどはオーバーロード可能になってい新しい演算子を定義し，その演算子にタグを付与できる．しかし，C++の演算子オーバーロードは新たにるようにした．Erbs は複数のタグの利用を許すので，演算子を定義することができない．Ruby はオープン

ある DSL 開発者が実装した構文拡張を，後から第三クラスの機構を使用することで演算子オーバーロー者がそれを変更せずに再利用して，モジュラーに拡張ドを実現することができ，既存の演算子であればほぼすることができる．今後の課題として，述語@tokenすべてオーバーロードすることが可能である．しかの実装を完成させること，またこの@token 舞うようし，この機構も C++と同様に新たな演算子を定義すにすることが挙げられる．

ることはできない．Smalltalk [5] のメッセージ式である 2 項メッセージとキーワードメッセージを使用する

参 考 文 献と N 項演算のような形で構文を記述できる．しかし[1] Danielsson, N. A. and Norell, U.: Parsing Mixfix Operators, Proceedings of the 20th InternationalConference on Implementation and Application of
Functional Languages, IFL’08, Berlin, Heidelberg,Springer-Verlag, 2011, pp. 80–99.
[2] Erdweg, S., Rendel, T., Kästner, C., and Ostermann, K.: SugarJ: Library-based Syntactic Language Extensibility, Proc. of OOPSLA 2011, ACM,
2011, pp. 391–406.
[3] Ford, B.: Packrat Parsing:: Simple, Powerful,Lazy, Linear Time, Functional Pearl, SIGPLAN
Not., Vol. 37, No. 9(2002), pp. 36–47.
[4] Fowler, M.: InternalDslStyle, http://martinfowler.com/bliki/InternalDslStyle.html, 2006.
[5] Goldberg, A. and Robson, D.: Smalltalk-80: The

Smalltalk の 2 項メッセージには使用することのできない記号がある．また，キーワードメッセージでは終端記号の後に必ず :(コロン) が必要となる．SugarJ[2] は言語拡張をライブラリという形で提供することのできる Java 言語の拡張である．ホスト言語が構文解析できないような新しい文法をユーザが定義しその文法をシンタックスシュガーとして追加できる．定義したシンタックスシュガーをインポートすることでホスト言語の文法を変えることができる．しかし，曖昧性がある場合にはコンパイルエラーとなる．

Language and Its Implementation, Addison-WesleyLongman Publishing Co., Inc., Boston, MA, USA,1983.
[6] Ichikawa, K. and Chiba, S.: Composable Userdefined Operators That Can Express User-definedLiterals, Proceedings of the 13th International Con-

ference on Modularity, MODULARITY ’14, NewYork, NY, USA, ACM, 2014, pp. 13–24.
[7] 嶺福室, 滋千葉: コールスタックに基づいてクラス拡張の有効範囲を制御するための言語機構の提案, 日本ソ
フトウェア科学会大会論文集, Vol. 32(2015), pp. 6p.