270 likes | 412 Views
ユーザ定義演算子による内部 DSL の構成法. 市川 和央 千葉 滋 東京工業大学大学院. Domain Specific Language (DSL). 用途に応じたミニ言語. SQL. select name from register where age < 30. Make. hello : hello.c cc –c hello.c. 内部 DSL. 一つの汎用的な言語の中で DSL を実現. SQL. ResultSet rs = select name from register where age < 30 ;. Make.
E N D
ユーザ定義演算子による内部DSLの構成法 市川和央 千葉滋 東京工業大学大学院
Domain Specific Language (DSL) • 用途に応じたミニ言語 SQL select name from register where age < 30 Make hello : hello.c cc –c hello.c
内部DSL • 一つの汎用的な言語の中でDSLを実現 SQL ResultSetrs = select name from register where age < 30; Make MakeRule hello = “hello.c” cc –c “hello.c”; タブ
比較 if ( 0 <= a < 10 ) { ... } Map HashMap<String, Integer> modifiers = { “public” -> 1 “private” -> 2 “protected” -> 4 ... };
従来の手法 • (ホスト言語に組み込む) • ソースコード変換 • マクロ • 柔軟な記法を用意 • シンタックスシュガーを利用
柔軟な記法を用意 ☓ DSLの構文がホスト言語に制限される ◯ 複数のDSLやホスト言語と同時に利用できる 例: Scala valrs = sql select “name”from register where (“age”< 30) if (a < 30) { ... } 構文の制限 valrs = sql.select(”name”).from(register).where(“age”.<(30)) if(a.<(30)) { ... } 等価 他のDSLやホスト言語を壊さない
ソースコード変換 ◯ DSLの構文がホスト言語に殆ど制限されない ☓ 複数のDSLやホスト言語と同時に利用できない ResultSetrs = selectnamefromregisterwhereage<30; if (a<30) { ... } 自由な構文 ResultSetrs = sql_select(name,register,sql_lt(age,30)); if (sql_lt(a,30)) { ... } 変換 もとの意味を破壊
ユーザ定義演算子の利用 • DSLの構文をN項演算子として表現 • N項演算子の形式は自由 ResultSetrs = select name from register where age < 30; 三項演算子select from where 二項演算子<
期待される型により演算子を制限 • 返り値の型が期待される型である場合のみ有効に • オペランドも再帰的に制限される ResultSetrs = select name from register where age < 30;
期待される型により演算子を制限 • 返り値の型が期待される型である場合のみ有効に • オペランドも再帰的に制限される ResultSetrs = select name from register where age < 30; ResultSet型が期待されるので、select...from...where...が有効に
期待される型により演算子を制限 • 返り値の型が期待される型である場合のみ有効に • オペランドも再帰的に制限される ResultSetrs = select name from register where age < 30; ResultSet型が期待されるので、select...from...where...が有効に 条件節が期待されるので、専用の<演算子が有効に
従来の手法の問題点を解決 ◯ DSLの構文がホスト言語に殆ど制限されない • 演算子によって文法が切り替わる ◯ 複数のDSLを組み合わせて利用できる • 期待される型により有効な演算子が制限される
制限 • 型推論との相性が最悪 • クラス定義等の式より大きいものは表現できない • 左辺値等の期待される型がない所では利用できない • 動的型付け言語では不可能
LasticJ • Javaのサブセットに前述のアイデアを導入 • ジェネリクス・インターフェース等はなし • コンパイラはJavaにより実装 • 名前は変わるかも
三項演算子 select from where の定義 • メソッド定義と似た形式 • 処理内容はホスト言語で記述 返り値の型 キーワード オペランド ResultSet “select” col “from” table “where” cond (readasSQLColumn col, SQLTable table, SQLCondcond) : priority = 200 { Connection conn = ...; Statement stmt = conn.prepareStatement(...); return stmt.executeQuery(); } オペランドの型 処理内容
演算子モジュール • 同一の機能に関する演算子を集めたもの • 演算子定義はここに記述 三項演算子select from where operators SQLOperators { ResultSet “select” col “from” table “where” cond (readasSQLColumn col, SQLTable table, SQLCondcond) : priority = 200 { ... } SQLCond col “<“ val (readasSQLColumn col, intval) : priority = 100 { ... } ... } 二項演算子<
演算子の利用 • using節により利用する演算子モジュールを指定 using lasticj.test.sql.SQLOperators; class Test { public void run() { SQLTableregister = ...; ResultSetrs = select name from register where age < 30; ... } } SQLOperatorsを利用 SQLOperatorsの演算子を利用して解釈
readas修飾子 • オペランドの位置の単語をリテラルとして読む ResultSet “select” col “from” ... (String col, ...)... このようなダブルクォートは書きたくない ResultSetrs = select “name” from register where “age” < 30; ResultSet “select” col “from” ... (readasSQLColumn col, ...)... SQLColumn型のリテラルとして解釈 ResultSetrs = select name from register where age < 30;
本体部分の構文解析法 • Packrat parsingのアルゴリズムを利用 • 字句解析器・構文解析器・型チェッカーが一体化 • 式の解析は次のようにして行う • 期待される型を返す演算子を優先度順に試す • 最初に成功したものを結果として返す • 全て失敗した場合、通常のJavaのルールで解析する
関連研究 • SugarJ [ S. Erdwegら’11 ] • ユーザがシンタックスシュガーをライブラリとして定義 • 文法が衝突すると一方が破壊される • Template Haskell [T. Sheardら‘02 ] • 型安全なコンパイル時メタプログラミング • ユーザが直接構文木を操作しなければならない • Scala • メソッド呼び出しを単項・二項演算のように記述できる • 実現可能な内部DSLの文法に制約がある
まとめ • ユーザ定義演算子による内部DSLの構成法を提案 • このアイデアを導入した言語LasticJを作成 今後の課題 • 記述力の強化 • 様々な内部DSLを実際に構築する
ResultSetrs = select name from register where age < 30; operators SQLOperators { ResultSet “select” col “from” table “where” cond (readasSQLColumn col, SQLTable table, SQLCondcond) : priority = 200 { ... } SQLCond col “<“ val (readasSQLColumn col, intval) : priority = 100 { ... } }
MakeRule hello = “hello.c” cc –c “hello.c”; operators MakeOperators { MakeRulefile “\t“ script (String file, Script script) : priority = 100 { ... } Script command option file (readas Command command, Option option, String file) : priority = 100 { ... } Option “-” op (readas String op) : priority = 100 { ... } }
if ( 0 <= a < 10) { ... } operators BoolOperators { boolean a “<=“ b “<“ c (int a, int b, int c) : priority = 1000 { ... } }
HashMap<String, Integer> modifiers = { “public” -> 1 “private” -> 2 “protected” -> 4 ... }; operators MapOperators { Map<K, V> “{“ entries “}” (MapEntry<K, V>... entries) : priority = 100 { ... } MapEntry<K, V> key “->” val (K key, V val) : priority = 100 { ... } } 注)現在の実装ではジェネリクス及び0回以上の繰り返しを示す...には対応していないため、実際には余分なクラスと演算子をいくつか作る必要がある。