内部ドメイン専用言語支援のため の 型に連動した字句・構文ルールの 変更機構 理学部 情報科学科 千葉研究室 07_02363 市川 和央 指導教員 千葉 滋 教授 1
内部ドメイン専用言語(内部 DSL ) ホスト言語の中で作った DSL ホスト言語: 枠組みとなる既存言語 DSL : ある問題の解決に特化した言語 生産性・保守性が向上 繰り返しを排除した可読性の高いコード ホスト言語との連携が容易 Java 中で SQL を利用 2 void printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; System.out.println(candidates.toString()); } void printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; System.out.println(candidates.toString()); } Java の中に SQL の select 文を記述 name や TOEIC_score は Java の変数ではない
表現能力が低い 同じ構文の衝突 難しい 例 1. C/C++ 言語 従来の内部 DSL の実現手法の問題点 1/3 3 #define select select_( #define from, #define where, #define end ); #define select select_( #define from, #define where, #define end ); SQLResult candidates = select “name” from employees where “TOEIC_score >= 600” end SQLResult candidates = select “name” from employees where “TOEIC_score >= 600” end void printNum(int beg, int end) { for(int i = beg; i < end; i++) printf(“%d\n”, i); } void printNum(int beg, int end) { for(int i = beg; i < end; i++) printf(“%d\n”, i); } 条件部分を 文字列で渡している end マクロに書き換えられてしま い 正しくコンパイルできない どのような文を表現 するのかわかりづら い
従来の内部 DSL の実現手法の問題点 2/3 4 例 2. Scheme (define-syntax select (syntax-rules (from where) ((select col from table where cond) (select_ ‘col table cond)))) (define-syntax >= (syntax-rules () ((>= col val) (geq ‘col val)))) (define-syntax select (syntax-rules (from where) ((select col from table where cond) (select_ ‘col table cond)))) (define-syntax >= (syntax-rules () ((>= col val) (geq ‘col val)))) (define candidates (select name from employees where (>= TOEIC_score score))) (define candidates (select name from employees where (>= TOEIC_score score))) 前置記法に変化 括弧も増えている (if (>= (row_count candidates) 10) (draw_lots candidates) candidates) (if (>= (row_count candidates) 10) (draw_lots candidates) candidates) >= マクロが適用されてしまう
従来の内部 DSL の実現手法の問題点 3/3 5 例 3. Scheme( 衝突回避版 ) (define-syntax select (syntax-case x () ((sql-syntax e...) (with-syntax ((expr (datum->syntax (syntax k) ‘(let-syntax ((select (syntax-rules (from where) ((select col from table where cond) (select_ ‘col table cond)))) (>= (syntax-rules () ((>= col val) (geq ‘col val))))),(syntax->datum (syntax (begin e...))))))) (syntax expr))))) (define-syntax select (syntax-case x () ((sql-syntax e...) (with-syntax ((expr (datum->syntax (syntax k) ‘(let-syntax ((select (syntax-rules (from where) ((select col from table where cond) (select_ ‘col table cond)))) (>= (syntax-rules () ((>= col val) (geq ‘col val))))),(syntax->datum (syntax (begin e...))))))) (syntax expr))))) 難しすぎ る! (define candidates (sql-syntax (select name from employees where (>= TOEIC_score score)))) (define candidates (sql-syntax (select name from employees where (>= TOEIC_score score))))
提案: Java に型で制限された ユーザ定義 N 項演算子を導入 Java で強力な内部 DSL を実現可能に DSL の構文を N 項演算子として定義 C++ のオペレータオーバーロードの強化版 優先順位を設定可能に N 項演算子は型情報を持つ SQLResult printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; } SQLResult printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; } 6 if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; select...from...where... 三項演算子とみなす Table 型...>=... 二項演算子とみなす SQLCond 型 Column 型 SQLResult 型
N 項演算子の定義 メソッド定義と似た形式 メソッド名にあたる部分は N 項演算のパターン 7 SQLResult select :col from :table where :cond (readas Column col, Table table, SQLCond cond) : priority = 100 { Connection con =...; Statement stmt = con.createStatement(...); return new SQLResult(stmt.executeQuery(...)); } SQLResult select :col from :table where :cond (readas Column col, Table table, SQLCond cond) : priority = 100 { Connection con =...; Statement stmt = con.createStatement(...); return new SQLResult(stmt.executeQuery(...)); } N 項演算子の オペランド 各オペランドは 引数と対応 キーワード select...from...where... の処理内容 Java で記述
型によるスコーピング SQLResult printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; } SQLResult printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; } 期待される型によって N 項演算子を制限 返り値の型が一致しないと利用されない 引数の型もチェック 8 SQLResult 型が期待されるので、 select...from...where... を利用 SQLCond 型が期待されるので、 SQL の...>=... を利用 boolean 型が期待されるので、 通常の意味の...>=... を利用
利用する N 項演算子によってルールを切り替え キーワードの変更 必要に応じた識別子のリテラル化 SQLResult printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; } SQLResult printCandidates(int score) { SQLResult candidates = select name from employees where TOEIC_score >= score; if(candidates.getRowCount() >= 10) drawLots(candidates); return candidates; } 字句・構文ルールの変更 9 select...from...where... のルールに切り替え SQL の...>=... の ルールに切り替え 通常の意味の...>=... の ルールに切り替え select,from,where はキーワード 第一引数は Column 型として読む Column 型リテラル として読む
従来の手法の問題点を解決 ホスト言語による制限が少ない ホスト言語が構文解析できなくてもよい 型をスコープとして衝突を回避 期待される型によって適切な N 項演算子を選択 ソースコードの改変を意識しなくてよい メタプログラミングを隠蔽 10 実際に本システムで作成した DSL 簡単な select 文( N 項演算子定義部分は 20 行程度) BNF(N 項演算子定義部分は 30 行程度 )
実装 Java により実装 5500 行程度 コード生成は Javassist を利用 宣言部分と本体部分を別々に解析 本体部分の解析に型や N 項演算子などの情報が必要 字句解析・構文解析・型チェックを連携 型によって N 項演算子を選択し、字句・構文ルールを変更 トップダウン構文解析 式を解析する前にその式の型がわかる必要 11
関連研究 Scala メソッドを単項・二項演算子のように見ることが可能 省略規則の応用によって実現 Smalltalk メッセージを N 項演算のように記述する Registration-Based Language Abstractions [S. Davis, et al, Onward! 2010] エディタによって言語に新たな構文を追加できる 構文木を変換するコードを書かなければならない 字句ルールは変更できない 12
まとめ Java にユーザ定義 N 項演算子を導入することで 内部 DSL を実現可能に 型によって N 項演算子を制限 字句・構文ルールの切り替え 今後の課題 表現力の強化 静的な構文スコープの導入 コンパイル時のオーバーヘッドの測定 13
BNF の規則 具体例 14 目的コード例 ::= “1” | “2” | 略 | “9”; ::= “0” | ; ::= | ; 目的コード例 ::= “1” | “2” | 略 | “9”; ::= “0” | ; ::= | ; 二項演算子 ::= オペレータにあたるも のがない二項演算子 単項演算子 <> operators BNFOperators { BNFExpr :term (String term) :priority = 250 {... } Nonterm (readas String name) :priority = 250 {... } BNFExpr (readas String name) :priority = 250 {... } BNFExpr :s1 :s2 (BNFExpr s1, BNFExpr s2) :priority = 200 {... } BNFExpr :s1 | :s2 (BNFExpr s1, BNFExpr s2) :priority = 150 {... } BNFRule :sym ::= :expr (Nonterm sym, BNFExpr expr) :priority = 100 {... } } operators BNFOperators { BNFExpr :term (String term) :priority = 250 {... } Nonterm (readas String name) :priority = 250 {... } BNFExpr (readas String name) :priority = 250 {... } BNFExpr :s1 :s2 (BNFExpr s1, BNFExpr s2) :priority = 200 {... } BNFExpr :s1 | :s2 (BNFExpr s1, BNFExpr s2) :priority = 150 {... } BNFRule :sym ::= :expr (Nonterm sym, BNFExpr expr) :priority = 100 {... } }
select 文の定義 メソッドの定義と似た形式で定義 15 operators SQLOperators { SQLResult select :col from :table where :cond (readas Column col, Table table, SQLCond cond) : priority = 100 { 普通の Java コード } SQLCond :col >= :val(readas Column col, int val) : priority = 200 { 普通の Java コード } } operators SQLOperators { SQLResult select :col from :table where :cond (readas Column col, Table table, SQLCond cond) : priority = 100 { 普通の Java コード } SQLCond :col >= :val(readas Column col, int val) : priority = 200 { 普通の Java コード } }
select 文の解析 Java の中に次のような select 文を記述できる 16 SQLResult result = select name from employees where TOEIC_score >= 600; SQLResult result = select name from employees where TOEIC_score >= 600; SQLResult 型が必要なの で、 select 文を利用 SQLCond 型が期待 されるので、 >= を利 用 Column 型リテ ラルとして読む int 型引数 Column 型として読 む Table 型の変数