vimperatorのヒント文字のレイアウトが崩れてしまう問題を解消する

vimperatorはfirefoxの操作をvimというテキストエディタ風のキーバインドで行うためのツールで、vimを使うことが多い自分としては欠かせない。

vimperatorの特徴的な機能としてヒントモードと呼ばれるものがあって、これがとても便利な機能となっている。デフォルトでは ' f ' を押すことで下のようにヒント文字列が現れて、文字列を入力するとリンクに飛べるというものだ。

ただしこの文字列、デフォルトではとても見づらい。二文字以上のヒント文字列でレイアウトが崩れてしまうことがある。

f:id:atdxfe:20131124022128p:plain

vimperatorrcのHintに関する設定項目に以下のパラメータを追加しよう*1。ここではヒント文字列のZ座標を調節し、ヒント文字列の要素の改行を禁止している。

hi Hint z-index: 10000; white-space: nowrap;

こんな風にいい感じになります。

f:id:atdxfe:20131124023611p:plain

基本的に設定ファイルなんてお好きな感じでどうぞというようなものだと思うけど、この設定で随分と便利になったので共有。

vimperatorを使ったことが無いvimユーザは是非vimperatorを試してみよう。

*1:カラースキームに関する項目は省略した

Node.js v0.10.x系のREPLで f)( のように括弧が逆でも関数が呼べてしまう

Node.js v0.10.x系のREPLでは次のように関数呼び出しの括弧の順序が逆でもキチンと関数が呼べてしまう。

> function f() {return 1;}
undefined
> f)( // 明らかにおかしいが、Node.jsのREPLでは動作する
1

もちろん上記のコードを.jsに保存して実行させても動作しない。単にNode.jsのREPLの仕様としてそうなっているだけのようだ。

プログラミング言語処理系のREPL(Read-Eval-Print loop)というのは案外複雑に出来ている。単にスクリプトをEvalするだけではなく、色々なケースを考慮しなければいけなくて、例えばfor文やif文、関数定義は複数の行にまたがって書かれるので、即Evalをするのではなくユーザの入力を待つ必要がある。

> function f() {

この関数定義は明らかに入力途中なので、node.jsでは次のようにプロンプトを出している。

> function f() {
... // 次の入力を促すプロンプト

ではどのようにしてユーザの入力が複数行になるのかを判定すればよいのか。大抵の場合、とりあえずEvalをしてみて上手くいくかどうかで判定すればよい。
実際Node.jsのREPLもそうしている。

ただし、この場合も入力が式(Expression)か文(Statement)かを判定しなければいけない。なぜなら、式の場合はEvalの結果として値が返るが文の場合は値が返らないなど、処理が異なるからだ。

{a: 1} // オブジェクトリテラル
{var a = 1;} // ブロック構文

ではどうやってこの違いを判定するか?
入力を構文解析した結果の抽象構文木の構造を見れば良いのだけれども、いかんせんNode.jsのREPLはJavaScriptで書かれているため、Node.jsで使われているJavaScript処理系であるV8の生成する抽象構文木の構造を見るのは骨が折れる作業になってしまう。

そのため、この例でもNode.jsでは「Eval出来るかどうかで判定する」というアプローチを使っている。
具体的には以下のように括弧でくくることで、Evalが可能かどうかで判定を行っている。

({a: 1}) // オブジェクトリテラルは式なので、valid
({var a = 1;}) // 明らかにinvalid

このため、最初に示したコードが以下のように変形され、validな入力として扱われてしまっていた。

> function f() {return 1;}
undefined
> (f)() // 普通の関数呼び出し
1

その点LispのREPLはあれこれ考える必要もなくシンプルに実装出来るので素晴らしいと思う。

(loop (print (eval (read))))

ちなみにこの仕様、バグと言っても差し支えないレベルなのでnode.js v0.11.x系では無くなってしまっているようだ。
'{}'で括られたインプットのみ'()'で括ってEvalを行い、式か文かを判定している。
javascript - Why does calling a function in the Node.js REPL with )( work? - Stack Overflow

組み込みボードSTM32-P407のデバイスドライバについて

ARM CortexM3を載したSTM32-P407(STM32-P407)上で外部SRAMを利用するにあたってデバイスドライバを触った時の経験について纏めてみる。
STM32-P407はLCD、イーサネット、USB、I2Cといった組み込みボードが持つ一般的な入出力機器を備えている評価ボードのようだったので、過去PICマイコンしか触ったことのない組み込み初心者には丁度いいんじゃないかと思い使ってみた。

どれだけのメモリが利用出来るのか

結論から言うと、STM32-P407には外部RAMが搭載されているのだが、これが無いとメモリが不足してプログラムが全く動かないような状態だった。

ベンダのHP(STM32-P407)にあるユーザマニュアルを参照してこの評価ボードで利用可能な記憶領域を確認。

上記の画像はユーザマニュアル中のメモリマップを表す図の一部。
どうやらSTM32-P407ではアドレスが0x10000000から始まる64KBと、0x20000000から始まる128KB(メインRAM)が利用出来るようだ。
またこの他にもアドレスが0x60000000から始まる512KBが外部SRAMとして利用出来るよう。
コンパイル後のバイナリはROMに配置されたCode Segmentに置かれるので、バイナリのサイズはあまり問題にはならなかった。

どうやって使用するのか

メインRAMと0x10000000から始まる64KBは普通に触ることが出来るが、512KBの外部SRAMを使用するためにはいくつかのレジスタの値を変更する必要があるようだった。
そのレジスタを触るためのコードがデバイスドライバにあたる。
STM32-P407のデバイスドライバは以下のページのサンプルに含まれていた。
https://www.olimex.com/Products/ARM/ST/STM32-P407/
STM32-P407 demo packageの
/DemoSoft_STM32-P207_IAR_6.20/modules/ExternalSRAM.h
というファイルがそれだ。

#define EXT_SRAM_BASE_ADDRESS 0x60000000
#define EXT_SRAM_SIZE 0x80000

int ExtSRAM_Initialize(void);
void ExtSRAM_Deinitialize(void);

外部SRAMがメモリにマッピングされている開始アドレスと、そのサイズが書かれていることからも明らかだ。
ExtSRAM_Initializeを呼べば外部SRAMが使用出来るようになる。

このコードの中身がどうなっているかというと、GPIO(General Purpose Input/Output)とFSMC(Flexible Static Memory Controller)のピンを叩いているようだ。
メモリの種類やメモリのバンド幅、書き込み許可などを設定している。

ほんの触り程度だが、デバイスドライバとは何か、どういった動作をしているのか、知ることが出来た気がする。

JavaScriptで動作するパーサジェネレータPEG.jsを使ってみる

Markdownっぽいデータ記述言語を作成した際に、PEG.jsと呼ばれるJavaScript向けパーサジェネレータを使用した。
PEG.jsは文法定義から実際にパースを行うコードを生成してくれるため、何かと便利なツール。
公式ドキュメント以上の情報が載っている感じではないが、まとめてみる。

インストール

npmを使用。

npm install pegjs

以下のURLにPlaygroundも用意されているためブラウザ上でPEG.jsの動作を確認することも出来るようになっている。
PEG.js – Parser Generator for JavaScript

PEG.jsを用いてパーサを生成する

作成したファイルの拡張子は.pegjsが推奨されている模様。npmでPEG.jsをインストールするとpegjsコマンドが利用出来るようになり、これを用いてパーサを生成する。文法を定義したsample.pegjsに対して以下のような操作を行う。

#パーサの生成 アウトプットはsample.js
pegjs sample.pegjs
#パーサの生成(+エクスポート形式の指定)
pegjs -e PEG sample.pegjs

通常はNode.jsのモジュール(module.export)として生成されるが、-eオプションを用いてエクスポート形式を指定することが出来る。二つ目の例ではsample.js内でPEGというオブジェクトがグローバルスコープに定義される。

PEG.jsで生成されたパーサを使う

Node.jsのモジュールとして使用する場合を例にとる。非常に簡単。

var PEG = require('sample.js');
PEG.parse('1 + 1');

PEG.jsの記述例

Documentation » PEG.js – Parser Generator for JavaScript
上記のドキュメントよりそのまま抜粋。整数の加算と乗算を行う。
非終端記号である加算(additive)や乗算(multiplicative)、終端記号integerなどを一つ一つ定義していく。
非終端記号は=の右辺に生成規則を記述する。右辺の終端記号/非終端記号には名前を付けることが出来、これを{}ブロック内のJavaScriptコードから変数として参照することが出来る。

start
  = additive

additive
  = left:multiplicative "+" right:additive { return left + right; }
  / multiplicative

multiplicative
  = left:primary "*" right:multiplicative { return left * right; }
  / primary

primary
  = integer
  / "(" additive:additive ")" { return additive; }

integer "integer"
  = digits:[0-9]+ { return parseInt(digits.join(""), 10); }
より詳細に記述する

上の例では整数リテラルであるintegerは整数であれば全て受理してしまうが、3桁までの整数に限定したい場合の一例。
${}のブロック内で真偽値を返す任意のJavaScriptコードを記述し、返り値がtrueと評価される場合のみ以降のパースに進めさせる処理が記述出来る。ブロックの左側で定義された変数はブロック内で利用可能なため、digits(1文字づつ配列に格納されている)の長さが3以下か判定するだけでよい。

integer "integer"
  = digits:[0-9]+ &{return digits.length <= 3} { return parseInt(digits.join(""), 10); }

ブロックのネストを始めとする入れ子構造を処理する際に、自分はこのブロックを使って無理やりカウンタを増減させていた。

block "block"
  = &{counter++; return true;} symbol:nonterminal_symbol &{counter--; return true;}

まとめ

自分が求めていた、Markdown記法+αレベルの文法定義は十分に行えた。
任意のJavaScriptコードを埋め込んで強引な処理を記述出来るため、複雑なコードを生み出してしまう可能性も無くはない。
ただしパーサジェネレータが機械的に処理可能なレベルのシンタックスは、手書きでも容易にパーサが作れてしまうかもしれない。

Mac OS Xでログイン画面のスクリーンショットを撮る

以外と簡単にログイン画面(スクリーンセーバ)のスクリーンショットを撮れたので、メモ

1. [システム環境設定] -> [デスクトップとスクリーンセーバ] -> [ホットコーナー]を選択
2. 右下辺りを[スクリーンセーバを開始する]に設定
3. 以下のシェルスクリプトを保存し、実行。

sleep 10
screencapture -ttiff screenshot.tiff

4. マウスカーソルを右下に持っていき、スクリーンセーバ起動。適当な時間でシャッター音が鳴ってスクリーンショットが撮られる。

ログイン画面はスクリーンセーバ起動時に何らかの操作を行うと表示されます。

Lisp処理系の実装 考察

こちらで開発を行っているLispの処理系ですが、目標だったaobenchの動作まで行えるようになりました。上の画像はaobenchを実行することで作成される画像になります。*1

文法

100個程度の関数・マクロを提供しています。これらの関数についてはCで実装を行っています。
Common Lispと同様の記述が行えるように頑張ったのですが、Common Lispの仕様を完全に再現する事は難しく、省略している部分がいくつかあります。

  • 逆クォート
  • キーワード引数
  • 多くの組み込み関数・マクロ

型・データ構造

整数・浮動小数点・リスト・文字列・配列をサポートしています。
データについては、Lispの特徴的なデータ構造であるコンスセルを用いて表現しているのですが、整数と浮動小数点についてはUnboxingされた状態で値を保持しています。

メモリ管理

mark and sweep GCを用いた動的メモリ管理を行っています。プログラム中のBoxingされているオブジェクトの基底型としてcons_tを用意し、型毎の初期化関数をnewメソッドとして提供しています。コンスセルのnewはnew_cons_cell関数を用いて行い、メモリが足りなくなった場合は内部でGCが走ります。

cons_t *new_string(const char *str) {
    cons_t *cons = new_cons_cell();
    cons->type = STRING;
    cons->api = &cons_string_api;
    cons->str = str;
    return cons;
}

パフォーマンス

下記のような処理速度になりました。大体clispと同程度のパフォーマンスを得られているようです。*2

処理系 fibonacci aobench
clisp 0.86 14.67
自作処理系 1.22 12.56

関数呼び出しの際のオーバーヘッドがかなり大きく、パフォーマンスが伸び悩んだ原因となっていると思われます。
関数を返す関数を定義する事が出来るプログラミング言語では、クロージャーと呼ばれる関数を定義する事が出来ます。クロージャーは自身が定義された箇所から参照する事の出来る変数に触る事が出来るため、関数内で定義されたローカル変数の生存期間が、関数の呼び出しから終了までの期間と一致しない場合があります。

;関数fは、ローカル変数xの値をインクリメントする関数を返す
(defun f () (let ((x 0)) (lambda () (setq x (+ x 1)))))

(setq g (f))

;関数fの呼び出しは終了しているが、ローカル変数xは生存している
(funcall g) ;1
(funcall g) ;2

クロージャーから参照される可能性の無いローカル変数は生存期間が関数のスコープと同一なため、これらの変数についてはコールスタックをもちいてやりとりする事で高速な実行を行う事が出来るのですが、私の処理系ではローカル変数がクロージャーから参照されるかどうかの判定を行っていません。
クロージャーの定義・呼び出しの際に行う処理をクロージャーでない関数の際にも行ってしまっているので、余計な処理を行っている分パフォーマンスが低下しています。

Common Lispの挙動を出来る範囲で再現し、いくつかのベンチマークを動作させる事が出来ました。満足しきれていない部分もあるのですが、今回の処理系の実装を通してLispに対する理解もかなり進んだため、それなりに満足しています。

*1:ベンチマークを走らせて作成したppm形式の画像をjpgに変換したもの

*2:同一のプログラムを動作させた場合です。clispはdeclareを用いる事でここから更に処理速度が向上します

Lisp処理系の実装

昨年行っていたLisp処理系の実装を踏まえて、大学院に進学し、2年目となった今年も挑戦しています。

去年は

  • 型は数値のみ
  • GC無し
  • if文と関数定義を実装し、フィボナッチ関数の実行速度向上のみに取り組む

という内容で、最終的にLLVMによるJITを行っていたため、今年はLispの仕様に忠実にいこうと考えました。

開発はshidasan/lisp_2012 · GitHubで行っており、C言語を用いています。


Lispのプログラムはバイトコードにコンパイルし、vmで実行するようにしているのですが、
文法の拡張を容易に行えるように工夫しました。

Lispのif文

(if (< n 0) (- n) n)

は、僕の処理系では以下のように実装されています。

static cons_t *_if(cons_t **stack, array_t *insts) {
    cons_t *cond = vm_exec((opline_t*)array_get(insts, 0), stack);
    if (!IS_nil(cond)) {
        return vm_exec((opline_t*)array_get(insts, 1), stack);
    } else {
        return vm_exec((opline_t*)array_get(insts, 2), stack);
    }
}

去年は全ての処理をvmで行おうとしていたのですが、今年はCによるランタイムの実装を行っており、例えばif文の実装は上記の関数と、引数の数などの情報を追加するだけで行えるようにしています。

3週間程実装を行い、eval、ラムダ式、マクロ、クロージャーのような機能が動作しています。
aobenchが動作する事を目標に、現在も開発を行っています。