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