npt特有の機能

nptのドキュメントです。
参照元:ANSI Common Lisp npt
前へ:npt amalgamation
次へ:入力モジュール

5.1 nptの機能

本章はnpt特有の機能について、代表的なものを紹介します。
nptで使用するコマンドと変数は、npt-systemパッケージに存在します。

下記の内容について説明します。

5.2 コマンド引数

nptコマンドの引数は下記の変数に格納します。

npt-system::*arguments*

値は配列であり、第一要素がnptのコマンド、 第二引数以降がコマンドの引数です。

例として下記のコマンドを実行したことを考えます。

$ npt -- 10 20 30
*

引数の--は、これ以降の引数がnptコマンドの引数になるという指定です。
値は次のようになります。

* npt-system::*arguments*
#("npt" "10" "20" "30")

5.3 環境変数

環境変数は下記の変数に格納しています。

npt-system::*environment*

値はハッシュテーブルです。
例えば、ホームディレクトリを表す$HOMEを取得するには 次のように実行します。

$ npt
* (gethash "HOME" npt-system::*environment*)
"/home/username"
T
*

5.4 ガベージコレクタの実行

下記のコマンドでガベージコレクタを起動します。

(npt-system:gc)

ガベージコレクタはheap領域が圧迫されていれば自動的に実行されますが、 タイミングによっては判定が間に合わずにメモリ不足に陥る可能性があります。
上記のコマンドを定期的に実行することで回避できるかもしれません。

もしメモリ不足が生じた場合はLISP ABORTします。

関数仕様を作成しました。
Lisp関数仕様 - システム関数

5.5 コアファイルの保存

下記のコマンドでコアファイルを作成できます。

(npt-system:savecore file)

本命令はserious-conditionsavecoreconditionを実行してnptを終了させます。
正しくsavecoreconditionで終了したら、コアファイルの保存が始まります。

作成したコアファイルを読み込むためには、nptコマンドの--core引数を使用します。

関数仕様を作成しました。
Lisp関数仕様 - システム関数

5.6 プロセスの終了

下記のコマンドでnptを終了します。

(npt-system:exit code)
(npt-system:quit code)

上記2つは全く同じものです。
nptのプロンプトで(exit)(quit)を実行できるように、 標準でcommon-lisp-userパッケージにimportされています。

本命令はserious-conditionexitconditionを実行してnptを終了させます。
正しくexitconditionで終了したら、引数の値を終了コードにしてプロセスを終了させます。

関数仕様を作成しました。
Lisp関数仕様 - システム関数

5.7 ed関数によるエディタ起動

Common Lispの関数edはエディタを起動します。
この機能に対応しているのはFreeBSD, Linux, Windowsです。

起動するエディタは設定可能であり、 下記の順番でエディタのコマンド名を取得します。

  1. npt-system::*ed-program*から文字列を取得
  2. 環境変数EDITORから文字列を取得
  3. npt標準のエディタを起動

npt標準のエディタとは、 FreeBSD/Linuxならvi、 Windowsならnotepad.exe、 それ以外ならedです。

5.8 require関数の動作

Common Lispの関数requireは引数の名前からモジュールを読み込む機能です。
nptでの実装はsbclのものとほぼ同じにしてあります。

requireは実行されると、変数npt-system::*module-provider-functions*を 関数のリストとして読み込み、 最初の関数から順にrequireの引数を指定して実行していきます。
もしどれかの関数がNIL以外の値を返却したら、 ロードが成功したとみなして処理を終了します。

例を示します。

(defun require-tmp (var)
  (load (merge-pathnames
          (format nil "~(~A~).lisp" var)
          #p"/tmp/")))

(push #'require-tmp npt-system::*module-provider-functions*)

(require 'aaa)

実行した結果を示します。(ただし失敗例)

ERROR: NPT-SYSTEM::SIMPLE-FILE-ERROR
Cannot open file #P"/tmp/aaa.lisp".

 0. ABORT            Return to eval-loop.
[1]* 0

ファイル/tmp/aaa.lispを読み込もうとしているのがわかります。

5.9 EastAsianWidthの操作

EastAsianWidthとは、Unicodeの全角・半角を表すものです。
nptでは全角が2文字、半角が1文字として、 formatやPretty Printingの出力を整形しています。

EastAsianWidthに関する関数は下記の3つです。

(defun eastasian-width (var) ...)
(defun eastasian-get (var) ...)
(defun eastasian-set (var size &optional errorp) ...)

関数仕様を作成しました。
Lisp関数仕様 - システム関数

関数eastasian-width

オブジェクトからEastAsianWidthのサイズを返却します。

引数varが整数の場合は、Unicodeの文字としてサイズを返却します。
引数varが文字の場合は、その文字のサイズを返却します。
引数varが文字列の場合は、全ての文字のサイズの合計を返却します。

第1返却値はEastAsianWidthのサイズです。
第2返却値はサイズが正しく求められたかどうかのboolean値です。

関数eastasian-get

EastAsianWidthは全角と半角の判定と書きましたが、 正確には文字に対して、N, A, H, W, F, NAの 6つのカテゴリに分類されます。
本関数はそれぞれのカテゴリが、何文字分に相当するかを取得します。

引数varstring-designatorであり、カテゴリの名前を表します。

第1返却値はEastAsianWidthのサイズです。
第2返却値はカテゴリを表すsymbolであり、エラーは‘NIL`です。

例を示します。

* (npt-system:eastasian-get :na)
1
NPT-SYSTEM::NA

* (npt-system:eastasian-get :hello)
0
NIL
*

関数eastasian-set

EastAsianWidthのカテゴリのサイズを設定します。

引数varはカテゴリをあらわすsymbolです。
引数sizeはカテゴリに対するサイズです。
引数&optional errorperror condition発生の有無です。

5.10 load-logical-pathname-translationsの読み込み

load-logical-pathname-translationsとは、 論理パス名の設定をファイルから読み込む機能です。
ファイルの読み込む場所は処理系依存です。
nptでは下記のspecial変数をもとにファイルを探索します。

npt-system::*load-logical-pathname-translations*

load-logical-pathname-translations関数は 文字列を引数として受け取り、対応する設定ファイルを読み込みます。
例えば次のように実行されたことを考えます。

(load-logical-pathname-translations "hello")

設定ファイルが次の場所に存在するとします。

/tmp/host/hello.txt

この場合は、special変数の値を次のように設定します。

(setq npt-system::*load-logical-pathname-translations* #p"/tmp/host/*.txt")

動作確認をしてみます。
まずは適当な設定ファイルを配置します。
ファイルの内容を下記に示します。

("*.*" "/home/lisp/")
("path;to;*.*" "/home/path/")

設定ファイルを配置します。

$ cd /tmp
$ mkdir host
$ cd host
$ vi hello.txt
上記の内容を入力
$

nptにより次の命令を実行します。

(load-logical-pathname-translations "hello")
-> t

(translate-logical-pathname "hello:name.txt")
-> #P"/home/lisp/name.txt"


(translate-logical-pathname "hello:path;to;name.txt")
-> #P"/home/path/name.txt"

正しく変換されているのがわかります。

5.11 pathnameの仕様

pathnameとはファイル名を表すオブジェクトです。

pathname-hostは、ファイル名がどの環境に属しているかを表します。
nptでは次の値を使用します。

pathname-host 環境
npt-system::unix Unix環境
npt-system::windows Windows環境
文字列 論理パス

初期値は*default-pathname-defaults*pathnameオブジェクトに 設定されているhostの値です。

parse-namestring関数では、hostの値によって文字列を分析する方法が決まります。
もし実行している環境がUnix上であっても、hostの値を変更することで Windows用のファイル名を認識することができます。
例えば下記の通り。

* (parse-namestring "C:\\Windows\\" 'npt-system::windows)
#P"C:\\Windows\\"
11
* (pathname-directory *)
(:ABSOLUTE "Windows")

equal関数はpathnameが同一かどうかを調べることができます。
もしnamestringが同じでもhostが違う場合は同一ではありません。

* (parse-namestring "notepad.exe" 'npt-system::windows)
#P"notepad.exe"
11
* (parse-namestring "notepad.exe" 'npt-system::unix)
#P"notepad.exe"
11
* (equal ** *)
NIL

equal関数は、hostの値を見て文字列の大文字小文字の判定を行います。
Unix環境では大文字小文字を別とみなし、 Windows環境では同一とみなします。

* (equal
    (parse-namestring "Hello.TXT" 'npt-system::unix)
    (parse-namestring "hello.txt" 'npt-system::unix))
NIL
* (equal
    (parse-namestring "Hello.TXT" 'npt-system::windows)
    (parse-namestring "hello.txt" 'npt-system::windows))
T

pathname-deviceは、Unixと論理パスでは無視されます。
Windowsでは、ファイルの種別か、あるいはドライブレターが設定されます。

* (pathname-device (parse-namestring "C:\\Windows\\" 'npt-system::windows))
"C"
* (pathname-device (parse-namestring "notepad.exe" 'npt-system::windows))
NIL
* (pathname-device (parse-namestring "\\\\.\\COM1" 'npt-system::windows))
NPT-SYSTEM::DEVICE

pathname-versionは、UnixとWindowsでは無視されます。
論理パスでは規則に従って使用されます。

* (pathname-version (logical-pathname "test:hello.txt"))
:NEWEST
* (pathname-version (logical-pathname "test:hello.txt.999"))
999

5.12 random-stateの初期値

random-stateとは乱数を生成するときに使用するオブジェクトです。
nptではxorshiftという乱数の生成器を使用しており、 random-stateには128bitの内部状態を保有しています。

初期値は関数make-random-stateに引数tを渡すことで、 可能な限りデタラメな値を設定できます。
例えば下記の通り。

* (make-random-state t)
#<RANDOM-STATE #x85A5B416389D9716A81B4F43F76E922A>
* (make-random-state t)
#<RANDOM-STATE #xE51C94EB01856FEAC5B1EC8C90E3107E>

乱数の初期値を決めることは簡単ではないので、 基本的にはOSに初期値を設定してもらいます。
本章では、乱数の初期値をどのように取得しているか説明します。

FreeBSD / Linux

コンパイルモードがFreeBSDかLinuxの場合は、 ファイル/dev/urandomから乱数の初期値を取得します。

読み込むデバイスは/dev/randomではないことに注意してください。
FreeBSDでは両者に違いはありませんが、Linuxでは/dev/randomの方が安全です。
nptでは安全性より利便性を優先し、/dev/urandomを使用することにしました。

/dev/urandomから、256byteのデータを受け取り、 それをMD5でハッシュに送り、 MD5の内部状態をそのままxorshiftの内部状態に設定します。
もし/dev/urandomの読み込みが失敗した場合はエラーです。

Windows

コンパイルモードがWindowsの場合は、 Advapi32.dllの関数SystemFunction036、 通称RtlGenRandom関数から初期値を取得します。

RtlGenRandomから、256byteのデータを受け取り、 それをMD5でハッシュに送り、 MD5の内部状態をそのままxorshiftの内部状態に設定します。
もしRtlGenRandomの読み込みが失敗した場合はエラーです。

ANSI-C

ファイル/dev/urandomを読み込もうとします。
もし値が取得できた場合は初期値に使用しますが、 値が取得できなかった場合はあきらめて、 時刻などを乱数の初期値にします。

FreeBSD, Linux, Windowsとは違い、 初期値用のデバイスが読み込めなかった場合でも エラーが生じることはなく続行します。

5.13 load関数の引数

load関数は、Lisp式が記述されたテキストファイルか、 compile-file関数により生成されたバイナリファイル(通称faslファイル)を 読み込むことができます。

どちらを読み込むかはpathname-type"FASL"であるかどうかで判定されますが、 load関数の引数:typeにより種別を指定することもできます。

テキストファイルを読み込む場合

(load file :type :lisp)

faslファイルを読み込む場合

(load file :type :fasl)

load関数の第一引数はpathnameだけではなく、memory-streamを指定することができます。
ただしmemory-streampathnameを持たないため、 :type引数を指定する必要があります。

実行例を示します。

(let ((input (npt-system:make-memory-io-stream))
      (output (npt-system:make-memory-io-stream)))
  (with-open-file (stream input :direction :output)
    (format stream "(format t \"Hello~~%\")"))
  (with-open-file (stream input :direction :input)
    (compile-file stream :output-file output))
  (file-position output :start)
  (load output :type :fasl))

実行結果

Hello
T

この例では、変数inputにテキストファイルを書き込み、 compile-file関数によって変数outputにfaslファイルを出力します。
出力されたoutputのファイルポインタを先頭に戻してから、 load関数により生成されたfaslファイルを実行します。