Arantium Maestum

プログラミング、囲碁、読書の話題

LispKit Lisp処理系の実装 6:SECD抽象機械の状態遷移〜条件分岐、関数関連など

前回に続いてstep関数内の命令コードに対するパターンマッチを見ていく。 今回見ていくのは以下の命令コード: 条件分岐のSEL、JOIN スタックに値を乗せるLD、LDC、LDF 関数適用のAP、RTN 再帰関数のためのDUM、RAP プログラム停止のSTOP 条件分岐のSEL、JOI…

LispKit Lisp処理系の実装 5:SECD抽象機械の状態遷移〜リスト操作と比較・算術演算

前回の記事ではSECD抽象機械のメモリとレジスタを定義していった。今回(と次回)はSECD抽象機械が実際にどうやってプログラムを実行するかを見ていく。 SECDマシン上でプログラムを実行するということは、Controlレジスタcが指し示すコンパイルされたプログ…

LispKit Lisp処理系の実装 4:SECD抽象機械のメモリとレジスタ

前回はLispKit Lispの挙動を確認するためもあってASTを直接実行するインタプリタを実装した。これからはLispKit LispのプログラムをSECD抽象機械上で実行するために必要なものを揃えていく。 今回と次回はSECDマシン自体の実装の話。 SECDマシン SECD抽象機…

LispKit Lisp処理系の実装 3:インタプリタ〜条件分岐から関数適用まで

前回に続いてインタプリタの実装。今回はeval_の後半、IFによる条件分岐以降の項目を見ていく。 条件分岐のパターンマッチ 条件式を評価してその結果に対してパターンマッチ、分岐部分の式は片方しか評価されない。 case Cons(Alpha("IF"), Cons(e1, Cons(e2…

LispKit Lisp処理系の実装 2:インタプリタ〜変数から算術演算子まで

前回のパーサの出力するASTをそのまま実行するインタプリタを実装する。 インタプリタを実装する意義 「SECD抽象機械へのコンパイラ実装の前にインタプリタを作る」というのはHendersonのFunctional Programming Application & Implementationでの流れに従っ…

LispKit Lisp処理系の実装 1:パーサ

前回書いたとおりまずはパーサを実装する。 最近散々パーサ実装について書いてきてなんだが、Lispなのでかなりの手抜きパーサ。 字句解析 まずはトークンの定義: from dataclasses import dataclass @dataclass class Lparen: pass @dataclass class Rparen…

LispKit Lisp処理系の実装 序

純粋関数型Lisp方言であるLispKit LispのインタプリタとSECD抽象機械の機械語へのコンパイラを作成する。 タネ本はHendersonの「Functional Programming - Application & Interpretation」で、この本に載っているISWIMやPASCALのコードを大体なぞるようにPyt…

PythonでLanguage Implementation Patternsのパーサと戯れる Packratパーサのメモリ効率化

前回まで見てきたBacktrackingパーサ、Packratパーサは「これまで見たすべてのトークン」をメモリ上のバッファに保持し続け、それらに対してのパース結果もすべてキャッシュに残し続ける実装になっていて、パース終了直前にはソースファイルのすべてのトーク…

PythonでLanguage Implementation Patternsのパーサと戯れる Packratパーサのキャッシュクリア

前回のコードだとparse関数を複数回呼び出すと前回のキャッシュが残っていてパースがうまくいかない問題があった。parse呼び出しごとにキャッシュをクリアするようにしたい。 キャッシュ xparser.pyモジュールのトップレベルにキャッシュのリストとそのリス…

PythonでLanguage Implementation Patternsのパーサと戯れる Packratパーサ

前回の記事は同じパースを何回も繰り返すので非効率だと書いたが、まずはどの程度非効率なのかを計測してからPackratパーサで効率化を図る。 計測 まずは問題の程度を計測したい。 こんなデコレータを定義する: def _print_call(func): @functools.wraps(fu…

PythonでLanguage Implementation Patternsのパーサと戯れる Backtrack可能なパーサ

前回実装したBacktrackableストリームを使って「任意のトークンを消費してパースを試した上でバックトラックできる」パーサを実装していく。 言語 前回同様の言語。再掲するとこのようなもの: list : '[' elements ']'; elements : element (',' element)*;…

PythonでLanguage Implementation Patternsのパーサと戯れる Backtrackableストリームオブジェクト

前回のような固定数のトークン先読みではなく、任意のトークンを消費しつつパースし続け、失敗したらバックトラックするようなパーサを作りたい。 今回の記事はまずバックトラック機能を提供するトークンストリームのクラスを作成する。(パーサ本体は次回)…

PythonでLanguage Implementation Patternsのパーサと戯れる LL(2)再帰的パーサ

先読み要素を増やすことで前回のLL(1)パーサより複雑な構文をパースできるようになる。 言語 ここまで見てきたリスト言語に変数間の代入を追加する: list : '[' elements ']'; elements : element (',' element)*; element : NAME | assign | list; assign …

PythonでLanguage Implementation Patternsのパーサと戯れる ネストしたリストのLL(1)再帰的パーサ

前回の記事の言語とパーサを拡張して、ネストありリスト言語の再帰下降パーサを作る。 言語 文法はこの通り: list : '[' elements ']'; elements : element (',' element)*; element : NAME | list; # elementが再帰的にlistになり得る NAME : ('a'..'z' | …

PythonでLanguage Implementation Patternsのパーサと戯れる 簡単なリスト言語の非再帰的なパーサ

(注:この記事を出した当初はパースが成功した場合Trueを返す実装にしていたが、Language Implementation Patternsと同じように成功した場合は何も返さないように修正した。実装がスッキリするということと、失敗は例外で表現しているので返り値には意味が…

PythonでLanguage Implementation Patternsのパーサと戯れる lexerとPeekableストリームオブジェクト

パーサを作るにあたってまずはlexerから。 lexerと簡単なパーサで共通しているのは「消費可能なストリームの先頭を『消費することなく』先読みする必要がある」という点。lexerだと入力の文字のストリーム、パーサだとlexerの返すトークンストリーム、と対象…

PythonでLanguage Implementation Patternsのパーサと戯れる 序

前2回の記事でも書いたとおり、Language Implementation Patternsのはじめの方をPythonで実装している。 具体的にはPackrat Parsingまで、本に出てくるクラスベースな実装ではなく、lookaheadやbacktrackの機能はトークンストリームの方に持たせた上で相互…

Language Implementation PatternsのLL(1) ParserをPythonで実装してみる

前回に続いてLanguage Implementation PatternsのJava実装例をPythonでやってみる。 今回はLL(1) Recursive Descent Parser。 パース対象 パースする言語は前回と同様: list : '[' elements ']'; elements : element (',' element)*; element : NAME | list…

Language Implementation PatternsのLL(1) LexerをPythonで実装してみる

Language Implementation Patternsという本がある。翻訳もされていて、邦題は「言語実装パターン―コンパイラ技術によるテキスト処理から言語実装まで」。作者はTerrence Parrで、ANTLRというJavaベースのパーサ・ジェネレータの開発者。 パーサ作成から始め…

Pythonで参照渡し

最近こういうツイートを見た。 なんか参照渡しでツイッター検索すると、Pythonに参照渡しがあるという一大宗教があるらしい誰が教祖だ?— (call me #'knjname) (@knjname) August 22, 2021 実際「値渡し」「参照渡し」という用語はCやC++などの「変数が多く…

OCamlのlambda IRをいじる(代数的データ型)

代数型データ型とそれに対するパターンマッチのlambda IRへのコンパイルを見ていく タグのみ直和型 まずは簡単なタグのみ直和型を見てみる: type t = A | B | C let () = let x = C in match x with | A -> print_int 1 | B -> print_int 2 | C -> print_in…

OCamlのlambda IRをいじる(レコード)

レコード型を定義して使うコードがどのようにlambda IRにコンパイルされるかを見ていく。 定義して使う 簡単なレコード型を定義した上でa.xなどとアクセスして使ってみる: type t = { x : int; y : string } let () = let a = {x=1; y="hello"} in print_en…

OCamlのlambda IRをいじる(リスト)

リストとパターンマッチ、Listモジュール使用のコンパイルを見ていく。 簡単なパターンマッチ 簡単なリストを作り、リストのコンストラクタ2つに対しての素直なパターンマッチをする: let () = let xs = [1;2] in match xs with | [] -> print_endline "he…

OCamlのlambda IRをいじる(タプル)

タプルとそれに対するパターンマッチがどうlambda IRにコンパイルされるかを見ていく。 fst/snd まずは非常に簡単なタプルと、その値をfst/sndでアクセスするコード: let () = let x = (1,2) in print_int @@ (fst x) + (snd x) -dlambdaでコンパイル: (se…

OCamlのlambda IRをいじる(再帰関数)

再帰関数のことを忘れていた! というわけで幕間的な記事。 Factorial 再帰で階乗なコード: let () = let rec f x = if x = 0 then 1 else x * (f (x-1)) in print_int @@ f 5 -dlambdaでコンパイル: (seq (let (*match*/83 = (letrec (f/80 (function x/8…

OCamlのlambda IRをいじる(関数)

前回に続いて、ローカルとモジュールレベルでの関数定義がどうlambda IRにコンパイルされるかを見ていく。 ローカル関数 まずはこんな感じの簡単な関数: let () = let f x = x + 1 in print_int @@ f 5 ocamlopt -c -dlambda localfunc.mlなどとすると: (s…

OCamlのlambda IRをいじる(変数)

前回に続いて、モジュール内で変数を定義して使うプログラムがどのようなlambda IRにコンパイルされるかを見ていく。 ローカル変数1つ OCamlで変数を定義する場合、モジュールのトップレベルでの定義と、ある式の一部としてlet x = y in zのxのような定義の…

OCamlのlambda IRをいじる(はじめに)

OCamlのコンパイラの話題でよく聞くflambdaという最適化ステップについて資料を読んでいた: https://ocaml.org/meetings/ocaml/2013/slides/chambart.pdf スライド5にコンパイラの中間表現のステップと説明が書いてあったのが面白かった。 parse tree: AST…

PythonでリテラルなしIfなし変数束縛なしFizzBuzzワンライナー〜ラムダの力を讃えよ〜

前回こう書いた通り: 追記:前回の記事に書き忘れたが、walrus operatorことAssignment Expressionsを多用しているのでPython3.8以降が必要。 これまで見てきたワンライナーはPython3.8から導入された変数束縛式:=を使っていて、そのせいで最近のPythonバー…

PythonでリテラルなしIfなしFizzBuzzワンライナー

前回の記事のFizzBuzzコードをちょこっといじってこんな感じで満足していた: リテラルを使わず簡潔な高可読性FizzBuzzが書けるプログラマです(f:=lambda *a,A=id,Fizz=id,Buzz=id:len(a) or (k:=sorted(f.__kwdefaults__)) and print(A if (x:=A%f(f,f,f))*…