読者です 読者をやめる 読者になる 読者になる

openboxにターミナル起動キーバインドを追加しました。

ctrl+alt+tでターミナルエミュレータを起動するようにする。
~/.config/openbox/rc.xmlキーバインド関係の設定がある。

<keybord>内に以下のような感じで書き加える。
こちらの環境では、lilytermを使っている。

    <keybind key="C-A-t">
        <action name="Execute">
            <command>lilyterm</command>
        </action>
    </keybind>

修飾キーの略語は、
C:Control
A:Alt
S:Shift
W:windows key
のような感じ。

gentooにtint2をいれました。

openboxのお供にする。

インストール

ランチャー機能がある0.12以上を入れる。

emerge --ask xdotool   
emerge --ask "=x11-misc/tint2-0.12.3"   

コンフィグを修正

~/.config/tint2/tint2rcを編集する。
初期状態では、そんざいしないアプリケーションへのリンクとかがあるので、
#Launcharの#launcher_item_app から始まるものをすべてコメントアウトする。

アプリケーションをメニュに加える

.desktopファイルを追加するだけでよい。
例えば、forefox-binだと、
.desktopファイルをapplications下において、

cp /usr/portage/www-client/firefox-bin/files/firefox-bin.desktop /usr/share/applications/

コンフィグファイルに追加する。

launcher_item_app = /usr/share/applications/firefox-bin.desktop


おしまい。
f:id:niyarin-man:20160921171606j:plain

schemeのcaseについて

キーと各データをどう比較しているのか気になったので調べた。
r5rsによるとeqv?で比較しているそうだ。(4.2.1)
というわけで、
・symbol、char、symbol、空リストは見た目が同じならよい。
・数値は、正確数同士か不正確同士で数値的に等しければよい。
・文字列、リスト、ベクタはアドレスが同じならばよい。
ということですな。

3impの3章を読みました。

3impの3章は、schemeのheap型VMとそのコンパイラの実装について書かれている。
コードを見てだいたい分かったので文のほうはさらっとしか読んでないけど。

vm用コードもs式
レジスタは、a,x,e,r,sの5つで、それぞれ(アキュムレータ(返り値とか次のコードで使うための一時保存領域?)、次に実行するコード、引数の一時置き場、コールスタック)
・aとrを統合してもいいのではないか?
・構文は、if、lambda、set!,quoteをサポートしている(いくつか機能は省かれている)
・call/ccもある。
・ただしVM上に継続の補足と呼び出しを実装しているので、手続きではない。
レジスタと次に実行するコードをまとめてオブジェクトにするという形で実現している。

ちょっと拡張

pythonで実装して、lambdaのbodyが2つ以上の式でも動くように拡張した。
コンパイラに手を加える。
FRAME命令をつなげてできそう。

・FRAMEについて

(FRAME A B)

・B実行後Aを実行する。
・aレジスタはB実行時のものを、それ以外はFRAME実行時のを
・BからAにつなげるためBにはRETURNが含まれている必要あり。

以上をふまえて

            c = ["RETURN"]
            for b in  bodies:
                bi = compiler(b,["RETURN"])
                if (c[0] == "RETURN"):
                    c = bi
                else:
                    c = ["FRAME",bi,c]
            compiled_body = c

lispの自作VMへのコンパイラ。

この記事はKobeUniv Advent Calendar 2015の20日めの記事です。
今回は、僕が書いているlisp処理系をどう実装したかについて書こうと思います。
完成が間に合わなかったので、できたら後日公開します。

実装したこと、してないこと

搭載機能

  • vm(スタックマシン)上で動作
  • 継続を第一級オブジェクトとして使える。call/cc

実装中

  • 健全なマクロ

未実装

  • 末尾再帰最適化
  • その他最適化

とりあえず後述するcps変換を入れないで階乗をコンパイル するとこんなかんじになりました。

00:  FUNCTION 3
01:  SETG fact
02:  STOP
03:  LAMBDA0:
04:  LOADL 0,0
05:  ARGS 1
06:  LOADG zero?
07:  CALL
08:  IF 2
09:  PUSH 1
10:  FORWORD 13
11:  LOADL 0,0
12:  LOADL 0,0
13:  PUSH 1
14:  ARGS 2
15:  LOADG -
16:  CALL
17:  ARGS 1
18:  LOADG fact
19:  CALL
20:  ARGS 2
21:  LOADG *
22:  CALL
23:  RET

cps変換いれて足し算をコンパイルするとこんなかんじ。

(+ (+ a b ) (+ c d ) )
00:  LOADG a
01:  LOADG b
02:  FUNCTION 21
03:  ARGS 3
04:  LOADG +
05:  CALL
06:  STOP
07:  LAMBDA0:
08:  LOADL 0,0
09:  ARGS 1
10:  LOADG return
11:  CALL
12:  RET
13:  LAMBDA1:
14:  LOADL 1,0
15:  LOADL 0,0
16:  FUNCTION 7
17:  ARGS 3
18:  LOADG +
19:  CALL
20:  RET
21:  LAMBDA2:
22:  LOADG c
23:  LOADG d
24:  FUNCTION 13
25:  ARGS 3
26:  LOADG +
27:  CALL
28:  RET
ほとんど最適化してないので括弧が少しあるだけでだいぶ大きくなりますね。
ちなみに、cps変換いれなかったら14行のコードに成りました。倍以上です。

やったこと

スタックマシンへのコンパイルは、コードがリスト構造なので
いくつかの法則にもとづいて変換するだけです。

例えば、関数適用だと、

( F 1 2 3 )
PUSH 1 
PUSH 2
PUSH 3 
ARG 3 
LOADG F 
CALL

ifはこんなかんじ

(if #t 2 3 )
PUSH #t
if 2
PUSH 2
FORWORD 2
PUSH 3

(※)ifの右についている数字は、falseだった場合コードのアドレス進ませる量で、 FORWORDも右の数だけコードを進ませる命令です。

cps変換

今回は、継続の実装のためにCPS変換を使いました。
以前作ったのは、VMの情報をまるごと保存しておいて,
継続呼び出し時に現在の内容を上書きするものでした。
VMに手を入れなくてすむのはありがたい。
 ( + PROC1 (+ PROC2 PROC3 ) )
これの評価順序は、 PROC1を評価する -) PROC2を評価する -) PROC3を評価する -)
PROC2の結果とPROC3の結果を足す -) その結果とPROC1とを足す となっています。
これの"-)"ごとにlambdaでくるんでしまいます。 
すると以下のようなコードになります
 (PROC1 (lambda (z1) (PROC2 (lambda (z2) (PROC3 (lambda (z3) ( + z2 z3 (lambda (z4)
(+ z1 z4 ...) ) ) ) ) ) ) ) )
この変換をするとすべての関数呼び出しで継続が引数が最後にくるので
組み込み関数も変換する必要があります。
 (define (k-add a b k ) (k (+ a b ) ) )
lambda式も継続とります。
 (lambda (ARGS k ) (k BODY ) )
VMが太らなくてすみましたが、コードがかなり太ってしまいました。
関数呼び出しも増えました。

僕は、継続実装のために使いましたが、
この形式は様々な最適化しやすい形ですし、β 簡約で少しは短くできるそうなので
うまくやればいいことがあるかもしれませんね。

おわり

やったことをはしょりまくって説明しただけでしたorz
マクロ実装間に合いませんでしたorz
最適化できませんでしたorz