elispはプログラミング言語として(勿論emacs環境上ではあるが)十分実用に耐え、 ちょっとしたアプリケーションなどはすぐ実現できる。筆者もこのページの日記書き用にelisp でアプリケーションを組んで定型文の入力などを行っていたが、どうせなら、ネットワークを介してサーバーにアップロード までできればより便利だと思った。elispならばelispのみの実装でこれは可能である。 例えば秀丸に付属のマクロ言語も相当な能力があるが、このような機能を実現するためには、外部にDLLを用意するなど マクロ外での補助的な機構が必要となる。この点、elispを用いるならば"emacs"さえあれば、当該elispプログラムを インストールするだけで、そのプログラムが動かすことができ非常に便利だ。
本文章は、日記エディタにネットワーク更新機能を付加した際に調べた、elispにおけるネットワーク の取扱いについて纏めておく。なにぶん素人なもので、間違いなど含まれていることもあると思われることを あらかじめ断っておく。
Emacs Lispプログラムで、同一マシンや別のマシン上の他のプロセスに対して TCPネットワーク接続を開く場合、ネットワーク接続を、非同期サブプロセスとして扱う。 一般的なサブプロセスは、Emacs内部では『プロセス』と呼ばれるLispオブジェクトで 表現される。Lispプログラムはこのオブジェクトを用いてサブプロセスと通信したりそれ を制御する。一方、ネットワークでの通信相手のプロセスはEmacsプロセスの子プロセスでは ないため、データの送受信のみが可能で、キルしたりシグナルを送 ることはできないという大きな違いがある。
elisp を用いてTCPネットワーク接続を開くには`open-network-stream' 関数を用いる。引数は、 `NAME'、`BUFFER-OR-NAME'、`HOST'、`SERVICE'の4つである。
open-network-stream NAME BUFFER-OR-NAME HOST SERVICE
| NAME | プロセスオブジェクトに付ける名前を指定する。必要に応じて一意にするために修正される。 |
| BUFFER-OR-NAME | 接続に対応付けるバッファである。出力を扱うフィルタ関数を指定しない限り、接続からの出力はそのバッファに挿入される。 BUFFER-OR-NAMEが`nil'であると、接続にはバッファを対応付けないことを意味する。 |
| HOST | 接続先のホスト名(文字列)を指定する。 |
| SERVICE | 接続先のポート番号(整数)を指定する。ポート番号は、定義済みのネットワークサービス(文字列)で代用できる。 |
`process-status' 関数は、引数 `PROCESS-NAME' にプロセス、バッファ、 プロセス名(文字列)、バッファ名(文字列)のいずれかを与えることで、PROCESS-NAMEの状態をシンボルとして返す。 一般のサブプロセスで得られるシンボルは `run'、`stop'、`exit'、`signal'、`nil'であるが、 ネットワーク接続に対しては`open'か`closed'をつねに返し、一般のサブプロセスに対してはこれらの いずれの値もけっして返さない。この性質を利用して、プロセスが一般のサブプロセスかネットワーク接続かを区別するのに用いる こともできる。
process-status 関数
process-status PROCESS-NAME
| open | ネットワーク接続を開いている。 |
| closed | ネットワーク接続は閉じている。接続をいったん閉じるとそれを再度開 くことはできないが、同じ接続先へ新たな接続を開くことはできる。 |
プロセスへの入力、プロセスからの出力に対するコーディングシステムは、変数 `coding-system-for-write'、`coding-system-for-read' が`nil'以外であればこれを用いられ、それ以外ではデフォルトの機構で決まるものが用いられる。また、 関数 `set-process-coding-system'を用いて、プロセスPROCESSからの出力および入力 に用いるコーディングシステムを指定することも出きる。
set-process-coding-system PROCESS DECODING-SYSTEM ENCODING-SYSTEM
| PROCESS | コーディングシステムを指定するプロセス |
| DECODING-SYSTEM | サブプロセスからの出力に対する コーディングシステム |
| ENCODING-SYSTEM | サブプロセスからの入力に対する コーディングシステム |
ネットワーク接続を確立した後は、以下の関数を用いてEmacsから入力を送ることができる。 サブプロセスへの入力は、サブプロセスがそれを受け取るまえにコーディングシステム を用いて普通は符号化される。以下の関数における、引数 PROCESS-NAME は、プロセスかプロセス名であること。
この関数は、文字列STRINGの内容を標準入力としてプロセス PROCESS-NAME に送る。
process-send-string PROCESS-NAME STRING
| PROCESS-NAME | 入力を送るプロセス。プロセスオブジェクトか、プロセスの名前を指定する。 これが`nil'であると、カレントバッファのプロセスを用いる。 |
| STRING | 入力する文字列 |
process-send-region 関数
この関数は、STARTとENDで定義される領域内のテキストを標準入力としてプロセスPROCESS-NAMEへ送る。 STARTとENDのどちらかがカレントバッファ内の位置を表す整数でもマーカでもないと、エラーを通知する。 (どちらが大きな数であるかは重要ではない。)
process-send-region PROCESS-NAME START END
| PROCESS-NAME | 入力を送るプロセス。プロセスオブジェクトか、プロセスの名前 を指定する。これが`nil'であると、カレントバッファのプロセスを用いる。 |
| START | カレントバッファ内の位置を表す整数あるいはマーカ |
| END | カレントバッファ内の位置を表す整数あるいはマーカ |
process-send-eof 関数
この関数は、プロセスPROCESS-NAMEが入力で「ファイルの終りEOF」を見る ようにする。EOFはそれまでに送ったテキストのあとにある。
process-send-eof &optional PROCESS-NAME
| PROCESS-NAME(オプション) | 入力を送るプロセス。プロセスオブジェクトか、プロセスの名前を指定する。 これを指定しなかったり、`nil'であると、カレントバッファのプロセスにEOFを送る。 |
サブプロセスが標準出力に書く出力を受け取る方法としては、以下の2通りがある。
プロセスにバッファもフィルタ関数もなければ、その出力は破棄される。
サブプロセスからの出力は、端末入力を読んでいるとき、`sit-for'や`sleep-for'等の 待機関数を実行中のとき、或いは`accept-process-output'を実行中のとき等、 Emacsが待機している時にだけ到着する。 これにより、並行プログラムで一般に悩まされるタイミングエラーの問題が減少する。 たとえば、安全にプロセスを作成してから、バッファかフィルタ関数を指定する。この処理の途中で、 待機を行うような基本関数を呼び出さなければ、出力は到着しない。
ファイルから読むテキストと同様に、サブプロセスの出力は、バッファやフィル タ関数が受け取るまえにコーディングシステムを用いて普通は復号化する。
プロセスにフィルタ関数がなければ、その出力はプロセスに対応付けられたバッファ(プロセスバッファ) に挿入される。プロセスバッファはプロセスがキルされたことを判定することにも用いられる。
process-buffer 関数
この関数は、プロセスPROCESSに対応付けられているバッファを返す。
process-buffer PROCESS
| PROCESS | 戻り値のバッファに対応するプロセス |
set-process-buffer 関数
この関数は、プロセスPROCESSにバッファBUFFERを対応付ける。
set-process-buffer PROCESS BUFFER
| PROCESS | バッファを対応つけるプロセス |
| BUFFER | プロセスに対応つけられるバッファ |
get-buffer-process 関数
この関数はBUFFER-OR-NAMEに対応付けられたプロセスを返す。バッファに 複数のプロセスが対応付けられている場合には、それらの1つを選ぶ。(現 状では、もっとも最近に作られたプロセスを選ぶ。)同じバッファに複数 のプロセスを対応付けることは一般にはよくない。
get-buffer-process BUFFER-OR-NAME
| BUFFER-OR-NAME | 得たいプロセスに対応付けられたプロセスバッファ |
accept-process-output 関数
この関数は、Lispプログラムから特定の場面で出力の到着を明示的に許す為に用いられる。 その出力は、対応付けられたバッファに挿入されるか、フィルタ関数に与えられる。PROCESSが `nil'以外であると、この関数は、PROCESSからなんらかの出力を得るまで戻らない。
accept-process-output &optional PROCESS SECONDS MILLISEC
| PROCESS(オプション) | 出力を受け取るプロセス。 |
| SECONDS(オプション) | 秒単位のタイムアウト時間を指定する。 |
| MILLISEC(オプション) | ミリ秒単位のタイムアウト時間を指定する。 |
指定された2つの時間は合計され、任意のサブプロセスの出力を受け取ったどうかに関わらず、その時間だけ経過 すると`accept-process-output'は処理をemacsに戻す。この関数は、出力を得ると`nil'以外を返す。あるい は、出力が到着するまえに時間切れすると`nil'を返す。
elispを用いたネットワークプログラミングの例として、httpクライアントを作成した。 これは、httpサーバに問い合わせて、ファイルを読み込むだけで、レンダリングもしない単純なものである。
1: (defun test-web-client () 2: "get file from http server" 3: (interactive) 4: (let ((buf (get-buffer-create "web conection")) 5: (proc nil)) 6: (setq proc (open-network-stream 7: "web-connection" 8: buf 9: "www.asahi-net.or.jp" 10: 80)) 11: (set-process-coding-system proc 'binary 'binary) 12: (display-buffer buf) 13: (process-send-string 14: proc 15: (format (concat 16: "GET /~pw9s-szk/ HTTP/1.0\r\n" 17: "MIME-Version: 1.0\r\n" 18: "\r\n")))))
動作は以下の様になっている。