オレオレ言語実装に役立つプル型astウォーカーapi
TRANSCRIPT
オレオレ言語実装に役立つプル型 AST ウォーカー API神戸隆行(椎路ちひろ) Twitter: @ChihiroShiiji / FB: takayuki.kando
2016/07/03 (第 14 回福岡市西区プログラム勉強会資料)
XGMTK & Lore
自己紹介 神戸 隆行(かんど たかゆき)、 PN. 椎路ちひろ(しいじ ちひろ) Twitter: @ChihiroShiiji / FB: takayuki.kando 出身は名古屋、趣味イラストとコスプレ 九大の社会人博士課程で博士を取ろうとしながら研究開発に従事する失業者 専門は流転中:
数値解析(のプログラミング・インターフェースの改良、修論@名大)→数式処理(のプログラミング・インターフェースの改良@某 F 研) →プログラム最適化(博士課程一回目の失敗@東工大)( 2005 年に仕事を紹介され福岡へ来た)→コンパイラ開発( Redefis 、動的再構成可能プロセッサ向けコンパイラ)→ SW/HW開発環境( IDE 上からクラウド上の開発ツールを利用できるミドルウェアPTaaS の開発) http://www.qualiarc.com/?post_type=seihin&p=202
使用プログラミング言語: 最初は FORTRAN 、大学以降は C++ と C 、今は主に Java 、他は必要に応じてボチボチ
本日は趣味で開発している Lore 言語の実装の副産物 幾つかのソースとサンプルを GitHub[ https://github.com/TakayukiKando/LoreAST ] に置いてます
2016/07/032 第 14 回福岡市西区プログラム勉強会
本日の内容「プル型 AST ウォーカーAPI 」
2016/07/03第 14 回福岡市西区プログラム勉強会3
LoreAST TRPG ルールシナリオ記述言語 Lore の実装に使うつもりで作成している AST ライブラリ
オンライン / オフライン・セッション支援ツール XGMTK で利用する XGMTK はルールやシナリオを実行可能な形で記述することでルール主導のセッション支援ツール
本日は主にその AST にアクセスする API の設計について 構文解析結果を保存するデータ構造としてツリーは良いけれど、意外とアクセスが面倒くさい
先行事例を調べていないのでもしかしたら「あるある」事例かも …… まぁ小ネタですし
具象構文木( CST…Concrete Syntax Tree ) と抽象構文木( AST…Abstract Syntax Tree )
2016/07/03第 14 回福岡市西区プログラム勉強会4
例(代入文): d=(a+b)*c;
代入文=d 式
* c項
+ ba
項 )(
CST:構文規則の適用を記録し、ソースの記号列の解析結果を忠実に記録したデータ構造
非終端記号
終端記号
代入文id: d 式 : *
id: c式 : +
id: bid: a
AST:構文が表現していたプログラムの構造を抽象的に表現したデータ構造
構文解析のデバッグに便利 プログラムの解析に便利
AST の実装 - Node.java
2016/07/03第 14 回福岡市西区プログラム勉強会5
public final class Node { private final Map<Attribute<?>, Object> attrs; /* アトリビュート(後述) */ private final List<Node> children; /* 子ノード */ /* コンストラクタ */ public Node(List<Node> children){/* 省略 */} public Node(Node...children){/* 省略 */} /* アトリビュート(後述)の取得と設定、存在の確認 */ public final <T> void setAttribute(Attribute<T> attribute, T value){/* 省略 */} public final <T> T getAttribute(Attribute<T> attribute){/* 省略 */} public Map<Attribute<?>, Object> attributes() {/* 省略 */} public final <T> boolean hasAttribute(Attribute<T> attribute){/* 省略 */} /* 子ノードの追加と取得 */ public final void addChildren(Node...children){/* 省略 */} public final void addChildren(List<Node> children) {/* 省略 */} public List<Node> children(){/* 省略 */} /* 等値演算とハッシュコード(ツリー比較、テスト用) */ @Override public boolean equals(Object obj){/* 省略 */} @Override public int hashCode() {/* 省略 */} /* ビジター受け入れメソッド(後述) */ public final void startVisit(Visitor visitor) {/* 省略 */}}
追加可能で静的型検査可能なアトリビュート・マップ
2016/07/03第 14 回福岡市西区プログラム勉強会6
public interface Attribute<T> {}
• アトリビュートのキーとなる enum 型群のためのインターフェース• 型パラメータ T がミソ
public class Attributes{ public enum LocationAttr implements Attribute<Location>{ LOCATION; } public static final LocationAttr LOCATION = LocationAttr.LOCATION; public enum StringAttr implements Attribute<String>{ SYMBOL; } public static final StringAttr SYMBOL = StringAttr.SYMBOL;}
public final <T> T getAttribute(Attribute<T> attribute){/* 省略 */}
• Node の getAttributeメソッド…キーの型パラメータの型と同じ型が返る• enum にしてるのはキーをお手軽にシングルトンにするため• Location はノードの由来となったコードの位置を示すデータ構造(エラーメッセージ等に利用)• ここでは記号文字列を格納する SYMBOL とソースコード上の位置を示す LOCATIONだけだが、 Attribute<T> を継承すれば追加できる。
ツリーを辿る(1)…ビジタ( Visitor )
2016/07/03第 14 回福岡市西区プログラム勉強会7
アルゴリズムによって定まる順番でツリーのノードを訪問するビジター 深さ優先探索や幅優先探索など ツリーを辿る定番デザイン・パターン
長所: enter と leaveメソッドの呼び出しによりサブツリーの開始と終了を認識できる 短所 : コールバックによるイベント駆動的なコードになり構造をツリーの意識した記述がしにくい
AST ではサブツリーのパターンによって処理を分けて記述するようなことがしたい ノードの種類を増やしてビジタのメソッドを増やして実装を Fat にすればある程度できるが、メンテが面倒に
短所: 継承が必須であり、用途毎にビジタ・クラスが増える
代入文id: d 式 : *
id: c式 : +
id: bid: a
enter #0
leave #6enter
#1leave
#0
enter #2leave #5
enter #3leave #3
enter #4leave #1
enter #5leave #2
enter #6leave #4
順番の例、深さ優先探索の場合• v をビジタ・オブジェクトとして以下の様に動作:• ノードに初回訪問時に v.enter() が呼ばれる• ノードへの最後の訪問時( leafノードでは
v.enter() の直後)に v.leave() が呼ばれる
ツリーを辿る(2)…イテレータ( Iterator )
2016/07/03第 14 回福岡市西区プログラム勉強会8
アルゴリズムによって定まる順番でツリーのノードを訪問することをイテレータ化 深さ優先探索や幅優先探索など ツリーに限らずグラフ探索全般で利用できる簡単で優れた抽象化
長所: 制御をプログラム主導で行えるのでアルゴリズムが記述し易い 短所 : AST 処理で良くある、サブツリーを辿り終わった後で何か処理をするようなコードが書きにくい
サブツリーのルートノードを再訪するアルゴリズムにしたとしても初回と再訪が区別できない 区別する述語は追加できるがあまり使いやすくはない
代入文id: d 式 : *
id: c式 : +
id: bid: a
#0
#1 #2
#3 #6
#4 #5順番の例、深さ優先探索の場合
• it をイテレータ・オブジェクトとして以下の様に動作:•it.next() すると次のノードが取れる•it.hasNext() が真を返している間は次がある
ツリーを辿る(3)…プル型ウォーカ( Pull Walker )
2016/07/03第 14 回福岡市西区プログラム勉強会9
アルゴリズムによって定まる順番でツリーのノードを訪問することをイベント・ストリームに変換する 深さ優先探索や幅優先探索など
長所: enter と leave イベントによりサブツリーの開始と終了を認識できる 長所: イテレータ同様にプログラム主導で行えるのでアルゴリズムが記述し易い
AST ではサブツリーのパターンによって処理を分けて記述するようなことがしたい
代入文id: d 式 : *
id: c式 : +
id: bid: a
enter #0
leave #6enter
#1leave
#0
enter #2leave #5
enter #3leave #3
enter #4leave #1
enter #5leave #2
enter #6leave #4
順番の例、深さ優先探索の場合• sをイベント・ストリーム・オブジェクトとして以下の様に動作:• s.next() で次のイベントへ遷移• getCurrentEvent() でイベント取得• イベントは START (初期状態)、 ENTER (ノード訪問)、 LEAVE (サブツリー終了)、 FINISH (ツリー全体の終了)の 4 タイプで、現在のノードが格納されている
アイディア元: XML Pull Parser http://www.xmlpull.org/ 実装例
kXML Web: http://www.kxml.org/ SourceForge(JAR ファイル):
https://sourceforge.net/projects/kxml/ MXP1 ( Smack が使っているが、ダウンロード URL がリンク切れ)
http://www.extreme.indiana.edu/xgws/xsoap/xpp/mxp1/ XML のパース結果をイベントストリームに変換
SAX パーサ(イベント駆動)と DOM ツリーの間を取った XML パーサインターフェース2016/07/0310 第 14 回福岡市西区プログラム勉強会
イベント・ストリームVisitor スレッド
プル型ツリーウォーカの構造
2016/07/03第 14 回福岡市西区プログラム勉強会11
代入文id: d 式 : *
id: c式 : +
id: bid: a
enter #0
leave #6enter
#1leave
#0
enter #2leave #5
enter #3leave #3
enter #4leave #1
enter #5leave #2
enter #6leave #4
ビジタ
固定長ブロッキング・キュー
利用側スレッド
ひたすら辿ってイベントを作成して投入
利用側の好きなタイミングで順にイベントを取り出す• イベント駆動の挙動を変換するのに使っているだけなのでキューの容量は 0 でも良い• 利用側の処理速度によってはツリーの全ノード分のイベントがキューに溜まる可能性があるので可変長は止めておいた方が良い
ASTStream.java (1) - フィールド
2016/07/03第 14 回福岡市西区プログラム勉強会12
public class ASTStream { /* プライベート定数省略 */
/* フィールド */ private ASTEvent current; // 現在のイベント private final BlockingQueue<ASTEvent> queue; // イベント・キュー private final Node root; // 現在のノード private final Visitor visitor; // ツリーを辿るビジター private final ExecutorService executor; // スレッド実行サービス private boolean finished; // 終了状態を示すフラグ /* 内部クラスとプライベートメソッド省略 */
ASTStream.java (2) - メソッド
2016/07/03第 14 回福岡市西区プログラム勉強会13
/* コンストラクタ */ public ASTStream(Node root, int queueLength) {/* 省略 */} public ASTStream(Node root){/* 省略 */} /* ストリームの開始 */ public Future<Boolean> start(){/* 省略 */} /* 現在のイベント */ public ASTEvent getEvent(){/* 省略 */} /* 現在のイベントのノード */ public Node getNode(){/* 省略 */} /* 次のイベントへ遷移 */ public void next() throws IllegalStateException {/* 省略 */} /* 判定と検証(後述) */ public boolean is(Predicate<ASTEvent> predicate) {/* 省略 */} public void require(Predicate<ASTEvent> predicate) throws ASTStreamInvalidException {/* 省略 */} /* スキップ(後述) */ public void skipTo(Predicate<ASTEvent> predicate) {/* 省略 */}}
イベントの判定と検証
2016/07/03第 14 回福岡市西区プログラム勉強会14
is(predicate) 現在のイベントに応じて場合分けするための述語
require(predicate) 現在のイベントが想定通りかどうか検証し、不正な場合は例外を投げる
predicate は Predicate<ASTEvent> 型の1引数関数オブジェクト EventMatcher に良く使う述語を返すメソッドを用意してある
EventMatcher
2016/07/03第 14 回福岡市西区プログラム勉強会15
match(type) イベント型が type であることを判定する述語を返す 主に他の述語との組み合わせ用だが START と FINISH の判定にも使う
match(attribute, value) attribute で指定されるアトリビュートが value に等しいことを判定する述語を返す 主に他の述語との組み合わせ用
match(type, attribute, value) イベント型が type で attribute で指定されるアトリビュートが value に等しいことを判定する述語を返す
enter(attribute, value) イベント型が ENTER で attribute で指定されるアトリビュートが value に等しいことを判定する述語を返す
same(node) 指定されたノード同じノードのイベントであることを判定する述語を返す 主に他の述語との組み合わせ用
leave(node) 指定されたノードの LEAVE イベントであることを判定する述語を返す
イベントのスキップ
2016/07/03第 14 回福岡市西区プログラム勉強会16
AST の全てのノードを処理しない応用も多いので処理しないノードをスキップできると便利 skipTo(predicate)
条件に合うイベントまでスキップ predicate は is()及び require() と同じ 実装は is(predicate) が真になるまで next() が繰り返し呼ばれるだけ
計測していないが、マルチプロセッサ環境ではイベントキューの容量を 0より程良く大きくしておくと、スキップしない処理の間にキューにイベントが貯まって skipTo() が早くなるかも?
今後
2016/07/03第 14 回福岡市西区プログラム勉強会17
特定の条件とその条件に合致した場合の処理をまとめて書く「パターンマッチ」の実装 現状: if 文に is() と skipTo() との組み合わせでは処理中のサブツリーにパターンがあった場合(入れ子)の処理ができない
AST の書き換え処理の実装 現状: AST を辿ることはできるが書き換えをサポートしていない
まとめプル型 AST ウォーカー API
2016/07/03第 14 回福岡市西区プログラム勉強会18
言語処理系の基本データ構造である AST ( Abstract Syntax Tree )の復習
Lore 言語実装の副産物として汎用 AST ライブラリを作成したので紹介した シンプルな単一種類ノードの AST に型チェック付きで様々な型のアトリビュートを格納する AST 実装
総称型キーを利用した汎用 getter enum による簡易なシングルトン・オブジェクトをキーに利用
AST を辿る過程をイベント・ストリームに変換してアクセスすることでイベント駆動でないプル型のアクセスを可能にするAST ウォーカーの実装 マルチスレッド&ブロッキングキューの応用でイベント駆動から変換 関数型を利用したシンプルな場合分け、検証、スキップ機能