nanopass-compiler-frameworkを使ってみたので、それがどういうものかを紹介した。
Nanopass compiler frameworkを使ってみましたNiyarin
View Slide
Nanopass compiler frameworkとは- コンパイラを書くためのフレームワーク- コンパイラの中間表現の記述とその変換方法を楽に書ける枠組みを提供してくれる。- ただし、S式言語 → S式言語正確には、Schemeの外部表現で記述できる言語?- Chez Scheme の実装に使われている?- Indiana大(Chezの開発元)のAndy Keepさんのプロダクト
前提知識:Multi-pass compiler- いくつかのフェーズからなるコンパイラ- ↔ one-pass compiler- 部品として切り出せるのが利点- 入力言語が複数ある場合、Parserの次だけ共通にするとか一例
前提知識:nano-pass compiler- 1フェーズを最小単位にしたmulti-pass compiler- 利点: 手を入れやすくなる バグ発見が容易になる- 欠点: 時間、空間計算量の増加
前提知識:nano-pass compilerの各パスに必要なもの入出力言語とその変換方法
nano-pass compiler framework各言語定義と変換の方法を容易にする枠組みの提供- 各パスでの入出力言語の定義を容易にする枠組み- 各パスの変換を容易にする枠組み入出力はS式- 字句解析や構文解析は別でやる必要がある(非S式言語の場合)
例 説明に使うコンパイルパスの例- Piyo言語と名付けたSchemeからほとんどの機能を削った言語次のページで詳細を書く- Piyo言語lambda式のbodyに複数の式が含まれる形式を消すbeginでbody包む(lambda (x) (display x) (newline) (+ x 1))→ (lambda (x) (begin (display x) (newline) (+ x 1)))
拡張BNFでPiyo言語の文法を記述する :: = | | | (lambda ( ...) ...) | (begin ...) | ( ...)- 拡張BNF: 0回以上の繰り返しを “...” で表記したBNFプリミティブ手続き(cons, car, cdr, display) シンボル、ブーリアンlambda式 begin式 手続き適用左辺に出てこないやつ ( 、 、 ) を終端記号と呼ぶ左辺に出てくるやつ ()を非終端記号と呼ぶ
Nanopass compiler frameworkでPiyo言語を表現する1言語の文法をS式の拡張BNFで記述する(define-language piyo-lang(terminals(primitive-procedure (proc))(boolean (b))(symbol (x)))(Expression (e)procbx(lambda (x ...) e e* ...)(begin e e* ... )(e e* ...)))
Nanopass compiler frameworkでPiyo言語を表現する1言語の文法をS式の拡張BNFで記述する(define-language piyo-lang(terminals(primitive-procedure (proc))(boolean (b))(symbol (x)))(Expression (e)procbx(lambda (x ...) e e* ...)(begin e e* ... )(e e* ...)))・終端記号の定義 非終端記号名とメタ変数のリストを並べる メタ変数とは、非終端記号の定義で使われる 変数のようなもの例えば、symbolのメタ変数はxとして定義している
Nanopass compiler frameworkでPiyo言語を表現する1言語の文法をS式の拡張BNFで記述する(define-language piyo-lang(terminals(primitive-procedure (proc))(boolean (b))(symbol (x)))(Expression (e)procbx(lambda (x ...) e e* ...)(begin e e* ... )(e e* ...)))非終端記号の定義 非終端記号とメタ変数のリストと定義を書く 拡張BNFをS式にしただけ あるメタ変数に”*”や数字を付けても同じように使える
Nanopass compiler frameworkでPiyo言語を表現する2終端記号の定義も必要・ primitive-procedureやsymbol、booleanについて・終端記号名に”?”を付けた名前の述語を終端記号の定義としているsymbol?とboolean?はSchemeがすでに提供している(define (primitive-procedure? x)(or (eq? x 'cons)(eq? x 'car)(eq? x 'cdr)(eq? x 'display)))
lambdaから複数bodyが消えたPiyo言語を表現する(define-language single-body-lambda-piyo-lang(extends piyo-lang)(Expression (e)(- (lambda (x ...) e e* ...))(+ (lambda (x ...) e))))Piyo言語との差分を書くだけで済む
lambdaから複数bodyが消えたPiyo言語を表現する(define-language single-body-lambda-piyo-lang(extends piyo-lang)(Expression (e)(- (lambda (x ...) e e* ...))(+ (lambda (x ...) e))))Piyo言語との差分を書くだけで済む拡張したい言語名を指定する
lambdaから複数bodyが消えたPiyo言語を表現する(define-language single-body-lambda-piyo-lang(extends piyo-lang)(Expression (e)(- (lambda (x ...) e e* ...))(+ (lambda (x ...) e))))Piyo言語との差分を書くだけで済む“-”を頭に付けると削除できる“+”を頭に付けると追加できる
beginでbodyを包む部分を書くsyntax-case風の構文で変換規則を書くパターンに当てはまった構文を指定した方法で展開する(define-pass remove-multi-body-lambda :piyo-lang (e) -> single-body-lambda-piyo-lang ()(Expression : Expression (ir) -> Expression ()((lambda (,x ...) ,(e) ,(e*) ...) `(lambda (,x ...) (begin ,e ,e* ...)))))
beginでbodyを包む部分を書くsyntax-case風の構文で変換規則を書く(define-pass remove-multi-body-lambda :piyo-lang (e) -> single-body-lambda-piyo-lang ()(Expression : Expression (ir) -> Expression ()((lambda (,x ...) ,(e) ,(e*) ...) `(lambda (,x ...) (begin ,e ,e* ...)))))本体パターン・ 与えた式の外側から見ていき、パターンにマッチしたものを展開部で置き換える・パターン内のメタ変数にマッチしたものは、本体部で参照できる・この展開を再帰的に行うには、パターンのその部分をリストで囲う
beginでbodyを包む部分を書くsyntax-case風の構文で変換規則を書く(define-pass remove-multi-body-lambda :piyo-lang (e) -> single-body-lambda-piyo-lang ()(Expression : Expression (ir) -> Expression ()((lambda (,x ...) ,(e) ,(e*) ...) `(lambda (,x ...) (begin ,e ,e* ...)))))この辺のごたっとしたのは自信がないのでまた今度。途中まで書いているので、ブログ等でそれを説明するつもり
不正な入力に対して怒ってくれる(その他の良い点)入出力言語の文法を定めているため、間違いは指摘してくれるコンパイラの変換規則の記述の間違いコンパイラへの入力の間違いとても親切というほどではないが。例えば、Piyo言語は数値を受理しないが、それを入れたら怒ってくれるException in parse-piyo-lang: invalid syntax 3
コンパイルする変換器に渡すにはパーサーを使って内部表現に変換する必要がある(※ここでのパーサーは、構文解析器のことではない)言語名とパーサ名を与えれば、パーサとアンパーサができるアンパーサは、パーサ名の頭にunがついたもの(例: un-parse-piyo-lang)(define-parser parse-piyo-lang piyo-lang)(define input '(cons (lambda (x y) (display x) (lambda (z) (cons x y))) foo )))(unparse-single-body-lambda-piyo-lang(remove-multi-body-lambda(parse-piyo-lang input)); → (cons (lambda (x y) (begin (display x) (lambda (z) (begin (cons x y))))) foo)
おわり・Nanopass compiler frameworkはすごくよいソフトウェアでした構文を楽に書ける記法+とか-とかで拡張できる点syntax-case風の変換方法コンパイラの記述やあるパスへの入力が間違っていれば怒ってくれる入出力言語の文法を与えているため