ACL チュートリアル

はじめに
ACL 6.2 Trial インストール
ライセンスファイルの入手
インストール
アップデート
ライセンスファイルの更新
ACL 6.2 Enterprise Edition インストール
ライセンスファイルの入手
インストール
アップデート
環境設定
PATH の追加
~/.emacs の設定
起動
終了
Emacs 開発環境
S 式単位の移動 (C-M-f, C-M-b)
コマンドヒストリ (C-c C-p, C-c C-n)
補完 (C-c TAB または M-TAB)
割り込み (C-c C-c)
引数リスト (C-c a)
マクロ展開 (C-c m)
定義にジャンプ (C-c .)
評価とコンパイル (C-c C-s, C-c C-r, C-c C-b, C-M-x)
コメントアウト (C-c ;)
トップレベルコマンド
ディレクトリ移動 (:pwd :cd :pushd :popd :dirs)
ロードとコンパイル (:ld :cf :cl)
コンティニューとリセット (:cont :res)
デバッグ
トレース
関数のトレース
labels, flet のトレース
メソッドのトレース
ステップ
デバッガ
ズーム (:zo)
バックトレース (:bt)
ローカル環境の表示 (:loc)
ブレイクポイント (break)
逆アセンブル (disassemble)
ソケット
http-client.cl
ソケットのオープン (make-socket)
SSL ソケットのオープン (make-ssl-client-stream)
ソケットのクローズ (close)
インライン関数
スレッド
プロファイル
C との連携
Java との連携
バイナリ作成
小ネタ
バージョン依存のコードを書く
コメントアウトあれこれ
実行可能スクリプト

はじめに

このページでは Franz 社Allegro Common Lisp (ACL) の使い方について解説します。

Common Lisp 自体のチュートリアルは Web 上に沢山あると思うので、ここでは ACL 固有の話題(開発環境、デバッグ、プロファイル、スレッド、ソケット、C や Java との連携など)をとりあげます。

遠い昔に書いた Allegro Common Lisp で並行プログラミング の内容もこの文書にとりこみたいと考えています。

ACL 6.2 Trial インストール

まずはじめにインストールしましょう 1。ここでは Mac OS X 10.2.8 に ACL Trial 6.2 をインストールする方法を書きます。他の環境の場合は適当に読み換えてください。詳細については http://www.franz.com/support/documentation/6.2/doc/installation.htm を参照してください。

ライセンスファイルの入手

まず http://www.franz.com/downloads/#acl に行ってユーザ登録します。登録するとメールでライセンスファイルが送られてくるので devel.lic という名前で保存しておきます。

インストール

その後以下のようなコマンドで、インストールを行います。
% curl -O http://ftp.franz.com/ftp/pub/acl62trial/macosx/acl62_trial.bz2
% sudo ./bunzip2 < acl62_trial.bz2 | (cd /usr/local; tar xf -)
% cp -p devel.lic /usr/local/acl62_trial/

アップデート

以下のようなコマンドで、最新版へのアップデートを行います。アップデートは定期的に行なっておいたほうがよいです。パッチの内容については http://www.franz.com/support/patches/log/ で確認できます。
% cd /usr/local/acl62_trial
% ./alisp
CL-USER(1): (sys:update-allegro)
CL-USER(2): (exit)
% ./update.sh

ライセンスファイルの更新

Trial のライセンスの期限は 60 日間です。ライセンスの期限が切れたら
% cd /usr/local/acl62_trial
% ./newlicense
を実行することで期限を延長できます。

[1] インストールすることすら面倒な人は telnet prompt.franz.com でお手軽にいじくれます。

ACL 6.2 Enterprise Edition インストール

ここでは Mac OS X 10.2.8 に ACL 6.2 Enterprise Edition をインストールする方法を書きます。他の環境の場合は適当に読み換えてください。詳細については http://www.franz.com/support/documentation/6.2/doc/installation.htm を参照してください。

ライセンスファイルの入手

ACL を購入すると、ライセンスファイルを取得する URL が書かれたメールが送られてきます。その URL にアクセスしてライセンスファイルを取得して devel.lic という名前で保存しておきます。

インストール

ACL 6.2 の CD-ROM をドライブに入れ、
% cd /Volumes/ACL62Disc2/macosx
% sudo ./bunzip2 < acl62.bz2 | (cd /usr/local; tar xf -)
% cp -p devel.lic /usr/local/acl62/

アップデート

以下のようなコマンドで、最新版へのアップデートを行います ...と思ったのですがいきなりエラーになりました。
% cd /usr/local/acl62
% ./alisp
CL-USER(1): (sys:update-allegro)
; Fast loading /usr/local/acl62/code/update.fasl
;   Fast loading /usr/local/acl62/code/ASERVE.fasl
;     Fast loading from bundle code/acldns.fasl.
;   Fast loading /usr/local/acl62/code/CRC.fasl
;   Fast loading /usr/local/acl62/code/INFLATE.fasl
;; Connecting to http://www.franz.com/ftp/pub/patches/...
downloading: code/update.fasl
; Fast loading /usr/local/acl62/code/update.fasl
;;; Installing update patch, version 17
Error: Package "PRODUCTNAME" not found.
  [condition type: READER-ERROR]

Restart actions (select using :continue):
 0: retry the load of /usr/local/acl62/code/update.fasl
 1: skip loading /usr/local/acl62/code/update.fasl
 2: Return to Top Level (an "abort" restart).
 3: Abort entirely from this process.

[changing package from "COMMON-LISP-USER" to "SYSTEM.UPDATE"]
[1] SYSTEM.UPDATE(2): :res

[changing package from "SYSTEM.UPDATE" to "COMMON-LISP-USER"]
CL-USER(3): (exit)
何か原因がよくわかりませんが http://www.franz.com/support/patches/log/#update_fasl_17 が関係しているような気がする(Mac OS X 10.2.8 だけど)ので、パッチサイトから直接ダウンロードします。
% cd /usr/local/acl62/code
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/code/update.fasl
% cd ..
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/alisp
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/alisp8
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/mlisp
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/mlisp8
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/libacli623.dylib 
% curl -O ftp://ftp.franz.com/pub/patches/6.2/macosx102/libacl623.dylib 
で、あらためて最新版へのアップデートを行います。
% cd /usr/local/acl62
% ./alisp
CL-USER(1): (sys:update-allegro)
CL-USER(2): (exit)
% ./update.sh
アップデート作業は定期的に行っておいたほうがよいです。パッチの内容については http://www.franz.com/support/patches/log/ で確認できます。

環境設定

インストールをすませたら次は環境設定です。ここでは Emacs で ACL 6.2 Trial を利用する場合について解説します(正規版ユーザは acl62_trial の部分を acl62 に置きかえてください)。

PATH の追加

~/.bashrc に以下のように PATH を追加します 2
export PATH="/usr/local/acl62_trial:$PATH"

~/.emacs の設定

次に Emacs から起動できるように ~/.emacs に以下のように書いておきます。
;; Allegro Common Lisp 6.2 trial
(add-to-list 'load-path "/usr/local/acl62_trial/eli")
;;(load "/usr/local/acl62_trial/eli/fi-site-init")
(autoload 'fi:common-lisp "fi-site-init" "Allegro Common Lisp" t)
(setq fi:common-lisp-image-file "/usr/local/acl62_trial/alisp.dxl")
(setq fi:common-lisp-image-name "/usr/local/acl62_trial/alisp")
(defun run-common-lisp ()
  (interactive)
  (fi:common-lisp fi:common-lisp-buffer-name
                  fi:common-lisp-directory
                  fi:common-lisp-image-name
                  fi:common-lisp-image-arguments
                  fi:common-lisp-host
                  fi:common-lisp-image-file)
  (set-buffer-process-coding-system 'emacs-mule 'emacs-mule))
(set-buffer-process-coding-system 'emacs-mule 'emacs-mule) の部分は日本語を正しく扱うために必要となります。

上記のように設定をおこなったら Emacs を再起動しましょう。

起動

ACL を起動するには M-x run-common-lisp です 3。ごにょごにょとスタートアップメッセージが表示された後、以下のプロンプトが表示されます。
CL-USER(1): 

終了

ACL を終了するにはリスナから (exit) と入力します。
CL-USER(2): (exit)

[2] csh 系の場合は ~/.cshrc に

set path = (/usr/local/acl62_trial $path)

[3] M-x fi:common-lisp で対話的に起動することもできます。

Emacs 開発環境

まず、よく使うキーバインディングを紹介します。詳細については http://www.franz.com/support/documentation/6.2/doc/eli.htm を参照してください。

S 式単位の移動 (C-M-f, C-M-b)

まず、基本。C-M-f で S 式単位の前進、C-M-b で後退です。よく使うので確実にマスターしておきましょう。これ以外にも C-M-k, C-M-@, C-M-t 等があります。Emacs の info を参照してください。

コマンドヒストリ (C-c C-p, C-c C-n)

リスナモードではシェルと同じようにヒストリが使えます 4

CL-USER(1): (/ 1 3)
1/3
CL-USER(2): (cons 1 2)
(1 . 2)
CL-USER(3): 
この状態で C-c C-p を入力すると、1 個前のコマンド (cons 1 2) が挿入されます。
CL-USER(1): (/ 1 3)
1/3
CL-USER(2): (cons 1 2)
(1 . 2)
CL-USER(3): (cons 1 2)
もう一度 C-c C-p を入力すると、その前のコマンド (/ 1 3) が挿入されます。また C-c C-n で次のコマンドに戻すことができます。

補完 (C-c TAB または M-TAB)

Emacs の lisp-interaction-mode での M-TAB と同様に、Common-Lisp のシンボルを補完することができます。

CL-USER(5): (array
例えば上のように入力して C-c TAB をすると以下のように別バッファに候補が表示されます。
Click <mouse-2> on a completion to select it.
In this buffer, type RET to select the completion near point.

Possible completions are:
inspect::array-element-inspect     excl::array-typep
excl::array-initialize-rec         excl::array-base
excl::array-initialize             array-dimension
array-row-major-index              array-in-bounds-p
array-rank                         array-has-fill-pointer-p
arrayp                             array-displacement
array-total-size                   array-element-type
array-dimensions

割り込み (C-c C-c)

無限ループ等に陥いってしまった場合、C-c C-c で割り込みをかけることができます。
CL-USER(8): (loop)
Error: Received signal number 2 (Keyboard interrupt)
  [condition type: INTERRUPT-SIGNAL]

Restart actions (select using :continue):
 0: continue computation
 1: Return to Top Level (an "abort" restart).
 2: Abort entirely from this process.
[1c] CL-USER(9): 

引数リスト (C-c a)

この関数/マクロの引数って何だったっけ? というときに、C-c a でミニバッファに引数リストを表示することができます。

CL-USER(5): (push
例えば上記のようなときに push にカーソルを会わせて C-c a と入力し、関数/マクロ名を入力すると、ミニバッファに以下のように表示されます。(筆者はよく push の引数の順番を忘れます...)
PUSH's arglist: (VALUE PLACE)

マクロ展開 (C-c m)

カーソルの直前の S 式をマクロ展開します。

CL-USER(3): (dolist (item '(1 2 3 4 5))
              (print item))
上の例の場合、一番最後の閉じ括弧の後にカーソルをもっていって C-c m と入力すると 、
(DO* ((#:G1000 '(1 2 3 4 5) (CDR #:G1000)) (ITEM (CAR #:G1000) (CAR #:G1000)))
     ((NULL #:G1000))
  (PRINT ITEM))
とテンポラリバッファに表示されます。 macroexpand-1 相当のようです。さらにテンポラリバッファに移って、カーソルを一番最後の閉じ括弧の後にもっていって C-c m すると、
(LET* ((#:G1000 '(1 2 3 4 5)) (ITEM (CAR #:G1000)))
  (DECLARE)
  (BLOCK NIL
    (TAGBODY
     #:|Tag13| (COND ((NULL #:G1000) (RETURN-FROM NIL (PROGN))))
             (TAGBODY (PRINT ITEM))
             (SETQ #:G1000 (CDR #:G1000) ITEM (CAR #:G1000))
             (GO #:|Tag13|))))
さらにマクロ展開することができます(dolist は結局 goto に展開されるんですね)。ちなみに、このバッファは C-c SPC で消すことができます。

定義にジャンプ (C-c .)

C-c . の後に参照したいシンボルを入力すると、そのシンボルの定義にジャンプします。

評価とコンパイル (C-c C-s, C-c C-r, C-c C-b, C-M-x)

評価を行う各種コマンドです 5。C-c C-s は直前の S 式を評価します。C-c C-r はリージョン、C-c C-b はバッファを評価します。C-M-x はカーソルのある defun フォームを評価します。それぞれ fi:lisp-evals-always-compilet だとコンパイルも行います(デフォルトは t です)。

コメントアウト (C-c ;)

リージョンをコメントアウトします。C-u C-c ; で元に戻します。

[4] リスナのみのコマンド。

[5] common-lisp モード(ソースコードを編集する時のモード)のみのコマンド。

トップレベルコマンド

ここからはトップレベルコマンドです。トップレベルコマンドとは Lisp リスナに入力する特別なコマンドです。コロン ':' が先頭についていることに注意してください。トップレベルコマンドは :help と入力すると一覧(と短縮形)が参照できます。 詳細については http://www.franz.com/support/documentation/6.2/doc/top-level.htm を参照してください。

ディレクトリ移動 (:pwd :cd :pushd :popd :dirs)

CL-USER(3): :pwd
Lisp's current working directory is "/Users/ore/"
*default-pathname-defaults* is #p"/Users/ore/"
CL-USER(4): :cd lisp
/Users/ore/lisp/
CL-USER(5): :cd ~
/Users/ore/
CL-USER(6): :pushd lisp
/Users/ore/lisp/ /Users/ore/
CL-USER(7): :dirs
/Users/ore/lisp/ /Users/ore/
CL-USER(8): :popd
/Users/ore/
CL-USER(9): :pwd
Lisp's current working directory is "/Users/ore/"
*default-pathname-defaults* is #p"/Users/ore/"
基本的にはシェルと同じです。

ロードとコンパイル (:ld :cf :cl)

ファイルのロードは :ld です。おそらく (load "foo.cl") と同じだと思います。

CL-USER(17): :ld foo.cl
; Loading /Users/ore/foo.cl
ファイルをコンパイルするには :cf です。
CL-USER(18): :cf foo.cl
;;; Compiling file foo.cl
;;; Writing fasl file foo.fasl
;;; Fasl write complete
ファイルをコンパイルしてロードするには :cl です。
CL-USER(19): :cl foo.cl
;;; Compiling file foo.cl
;;; Writing fasl file foo.fasl
;;; Fasl write complete
; Fast loading /Users/ore/foo.fasl

コンティニューとリセット (:cont :res)

エラーが起きるとデバッガが起動して選択肢を表示してくれるので、 :cont で選択肢の番号を指定します。

CL-USER(36): (defun foo () x)
FOO
CL-USER(37): (foo)
Error: Attempt to take the value of the unbound variable `X'.
  [condition type: UNBOUND-VARIABLE]

Restart actions (select using :continue):
 0: Try evaluating X again.
 1: Use :X instead.
 2: Set the symbol-value of X and use its value.
 3: Use a value without setting X.
 4: Return to Top Level (an "abort" restart).
 5: Abort entirely from this process.
[1] CL-USER(38): :cont 3
enter an expression which will evaluate to a new value: 100
100
とにかくトップレベルに戻って欲しいときは :res でリセットします。
CL-USER(39): (foo)
Error: Attempt to take the value of the unbound variable `X'.
  [condition type: UNBOUND-VARIABLE]

Restart actions (select using :continue):
 0: Try evaluating X again.
 1: Use :X instead.
 2: Set the symbol-value of X and use its value.
 3: Use a value without setting X.
 4: Return to Top Level (an "abort" restart).
 5: Abort entirely from this process.
[1] CL-USER(40): :res
CL-USER(41): 

デバッグ

トレース

関数のトレース

関数をトレースするには trace で関数名を指定します。
CL-USER(4): (defun fact (n)
              (if (<= n 1) 1
                (* n (fact (1- n)))))
FACT
CL-USER(5): (trace fact)
(FACT)
CL-USER(6): (fact 5)
 0[1]: (FACT 5)
   1[1]: (FACT 4)
     2[1]: (FACT 3)
       3[1]: (FACT 2)
         4[1]: (FACT 1)
         4[1]: returned 1
       3[1]: returned 2
     2[1]: returned 6
   1[1]: returned 24
 0[1]: returned 120
120
C-c t にキーバインディングされています。

トレースを解除するには untrace で関数名を指定します。

CL-USER(7): (untrace fact)
(FACT)
untrace を引数なして指定すると全ての関数の trace を解除します。
CL-USER(9): (untrace)
C-c t にキーバインディングされています。

labels, flet のトレース

labels, flet をトレースすることもできます。 compile は必須です。
CL-USER(24): (defun fact (n)
               (labels ((f (i r)
                          (if (<= i 1) r
                            (f (1- i) (* i r)))))
                 (f n 1)))
FACT
CL-USER(25): (compile 'fact)
FACT
NIL
NIL
CL-USER(26): (trace ((labels fact f)))
((LABELS FACT F))
CL-USER(27): (fact 5)
 0[1]: ((LABELS FACT F) 5 1)
   1[1]: ((LABELS FACT F) 4 5)
     2[1]: ((LABELS FACT F) 3 20)
       3[1]: ((LABELS FACT F) 2 60)
         4[1]: ((LABELS FACT F) 1 120)
         4[1]: returned 120
       3[1]: returned 120
     2[1]: returned 120
   1[1]: returned 120
 0[1]: returned 120
120

メソッドのトレース

メソッドのトレースはこんな感じです。
CL-USER(52): (defmethod foo ((n number))
               (* 2 n))
#<STANDARD-METHOD FOO (NUMBER)>
CL-USER(53): (defmethod foo ((s string))
               (concatenate 'string s s))
#<STANDARD-METHOD FOO (STRING)>
CL-USER(54): (trace ((method foo (string))))
((METHOD FOO (STRING)))
CL-USER(55): (foo 1)
2
CL-USER(56): (foo "bar")
 0[1]: ((METHOD FOO (STRING)) "bar")
 0[1]: returned "barbar"
"barbar"

ステップ

任意のフォームをステップ実行することができます。

CL-USER(6): (defun fact (n)
              (if (<= n 1) 1
                (* n (fact (1- n)))))
FACT
CL-USER(7): (compile 'fact)
FACT
NIL
NIL
CL-USER(8): (step (fact 5))
; Fast loading from bundle code/step.fasl.
 1: (SYSTEM::FUNCTION-INFORMATION FACT NIL)
[STEP] CL-USER(9): 
 result 1: NIL
 1: (FBOUNDP FACT)
[STEP] CL-USER(9): 
 result 1: #<Function FACT>
 1: (EXCL::%INVOKES FACT (5))
[STEP] CL-USER(9): 
  2: (EXCL::%EVAL 5)
[STEP] CL-USER(9): 
  result 2: 5
  2: (FACT 5)
[STEP] CL-USER(9): 
   3: (EXCL::*_2OP 2 1)
[STEP] CL-USER(9): 
   result 3: 2
   3: (EXCL::*_2OP 3 2)
[STEP] CL-USER(9): 
   result 3: 6
   3: (EXCL::*_2OP 4 6)
[STEP] CL-USER(9): 
   result 3: 24
   3: (EXCL::*_2OP 5 24)
[STEP] CL-USER(9): 
   result 3: 120
  result 2: 120
 result 1: 120
120
compile しないでステップ実行した場合と比べるとおもしろいです。

デバッガ

実行時にエラーが起きた場合やブレイクポイントを設定した場合にデバッガが起動します。マルチスレッドでプログラムを実行している場合でも、スレッド毎にデバッガが起動してくれます(エラーの起きていない他のスレッドはそのまま実行されます)。

ズーム (:zo)

:zo でエラーが起きた箇所を特定することができます(スタックを表示)。
CL-USER(1): (defun fact (n)
               (labels ((f (i r)
                          (if (<= i 1) r
                            (f (1- i) (* i r)))))
                 (f n 1)))
FACT
CL-USER(2): (fact "10")
Error: `"10"' is not of the expected type `REAL'
  [condition type: TYPE-ERROR]

Restart actions (select using :continue):
 0: Return to Top Level (an "abort" restart).
 1: Abort entirely from this process.
[1] CL-USER(3): :zo
Evaluation stack:

   (ERROR TYPE-ERROR :DATUM ...)
 ->(<= "10" 1)
   [... EXCL::%EVAL ]
   (IF (<= I 1) R ...)
   ((LABELS FACT F) "10" 1)
   (LET NIL (F N 1))
   [... EXCL::%EVAL ]
   (LABELS (#) (F N 1))
   (FACT "10")
   (EVAL (FACT "10"))
   (TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP)

... more older frames ...
この場合は <= で文字列と整数を比較したことがエラーの原因だとわかります。

:help :zo でヘルプを表示できます。

CL-USER(33): :help :zo

    Zoom prints a portion of the evaluation stack, and takes the following
keyword arguments:

:top      start at the newest frame
:bottom   start at the oldest frame
:brief    print just the frame's name. This option sticks.
:moderate print the frame's name and arguments (the default).  This option
          sticks.
:verbose  print lots of info about a frame.  This option sticks.
:specials this is used to set (not bind) the variable
          top-level:*zoom-print-special-bindings* before doing the :zoom.
          This option sticks.
:count    print this many frames (default = 7). This option sticks.
:all      print all frames (regardless of :hide and :unhide).  This option
          sticks.
:function print frames so that the function object is given rather than
          its name.  This option sticks.
:relative print the relative offsets of each frame. This option sticks.

バックトレース (:bt)

:bt:zo より簡略にスタックを表示してくれます。
[1] CL-USER(4): :bt
Evaluation stack:

<= <-
  [... EXCL::%EVAL ] <- IF <- (LABELS FACT F) <- LET <- [... EXCL::%EVAL ] <-
  LABELS <- FACT <- EVAL <- TPL:TOP-LEVEL-READ-EVAL-PRINT-LOOP <-
  TPL:START-INTERACTIVE-TOP-LEVEL

ローカル環境の表示 (:loc)

:loc でその時点におけるローカル環境の内容を表示できます。
[1] CL-USER(8): :loc
Interpreted lexical environment:
N: "10"
I: "10"
R: 1
Compiled lexical environment:
0(REQUIRED): NUMBER: "10"
1(REST): EXCL::MORE-NUMBERS: (1)
2(LOCAL): N: "10"
3(LOCAL): I: "10"
4(LOCAL): R: 1
5(LOCAL): EXCL::|local-0|: (1)
6(LOCAL): EXCL::|local-1|: "10"
7(LOCAL): EXCL::|local-2|: (1)
:help :loc でヘルプを表示できます。
CL-USER(34): :help :loc

    Print lexical environment (ie, local variables) for the current
frame.  The different forms of this command are:

  :local                - for frames associated with compiled
                          functions, print the locals of that
                          function and the interpreted lexical
                          environment
                        - for frames associated with interpreted
                          forms, print the interpreted lexical
                          environment of only the current frame
  :local :i             - print only interpreted lexical environment
  :local :c             - print only compiled locals
  :local :x             - print numeric values in hexadecimal
  :local [:c][:i][:x] name - return value of local `name' from
                             interpreted or compiled code (default
                             is to look for both instances)
  :local [:x] index     - return compiled local at index

ブレイクポイント (break)

(break) を任意の場所に埋めこむことによってブレイクポイントを設定できます。ブレイクポイントではデバッガが起動します。
CL-USER(26): (defun fact (n)
               (labels ((f (i r)
                          (break)
                          (if (<= i 1) r
                            (f (1- i) (* i r)))))
                 (f n 1)))
FACT
CL-USER(27): (fact 10)
Break: call to the `break' function.

Restart actions (select using :continue):
 0: return from break.
 1: Return to Top Level (an "abort" restart).
 2: Abort entirely from this process.
[1c] CL-USER(28): :loc
Interpreted lexical environment:
N: 10
I: 10
R: 1
[1c] CL-USER(29): :cont 0
Break: call to the `break' function.

Restart actions (select using :continue):
 0: return from break.
 1: Return to Top Level (an "abort" restart).
 2: Abort entirely from this process.
[1c] CL-USER(30): :loc
Interpreted lexical environment:
N: 10
I: 9
R: 10
[1c] CL-USER(31): :cont 0
Break: call to the `break' function.

Restart actions (select using :continue):
 0: return from break.
 1: Return to Top Level (an "abort" restart).
 2: Abort entirely from this process.
[1c] CL-USER(32): :cont 1

逆アセンブル (disassemble)

disassemble で関数を逆アセンブルすることができます。
CL-USER(24): (disassemble 'fact)
;; disassembly of #<Function FACT>
;; formals: N
;; constant vector:
0: #<Closure Template Function (LABELS FACT F)>

;; code start: #x3076f3ec:
   0: 7c0802a6     [mfspr] mflr r0  
   4: 9421ffa0             stwu r1,-96(r1)
   8: 90010068             stw r0,104(r1)
(略)

ソケット

ソケットの使い方は基本的にファイル等のストリームと同じです。詳細については http://www.franz.com/support/documentation/6.2/doc/socket.htm を参照してください。

http-client.cl

まず、クライアントソケットの使い方を説明するために簡単な HTTP クライアントを取り上げてみます。
;;; -*- mode: common-lisp; package: http-client -*-
;;;
;;; $Id: ACLTutorial,v 1.2 2004/02/14 18:28:53 ota Exp $

(defpackage :http-client
  (:nicknames :http-c)
  (:use :common-lisp
        :socket
        :net.uri)
  (:export #:http-get
           #:http-get-url))

(in-package :http-client)

(declaim (inline concat))
(declaim (inline make-client-socket))
(declaim (inline make-ssl-client-socket))
(declaim (inline close-socket))

;;;
;;; utility
;;;
(defun concat (&rest strings)
  (apply #'concatenate 'string strings))

(defvar *crlf* (concat (string #\return) (string #\newline)))


;;;
;;; socket
;;;
(defun make-client-socket (hostname port)
  (socket:make-socket :remote-host hostname
                      :remote-port port
                      :format :bivalent
                      :type :hiper))

(defun make-ssl-client-socket (hostname port)
  (socket:make-ssl-client-stream
   (make-client-socket hostname port)))

(defun close-socket (socket)
  (close socket))


;;;
;;; HTTP client
;;;
(defun send-request (stream host path)
  (write-sequence (concat "GET " path " HTTP/1.0" *crlf*
                          "Host: " host *crlf*
                          "Connection: " "close" *crlf*
                          *crlf*)
                  stream))

(defun receive-headers (stream)
  (do ((line (delete #\return (read-line stream nil))
             (delete #\return (read-line stream nil)))
       (headers nil (cons line headers)))
      ((or (null line) (zerop (length line)))
       (nreverse headers))))

(defun receive-body (stream array-size element-type)
  (do* ((buffer (make-array array-size :element-type element-type)
                (make-array array-size :element-type element-type))
        (pos (read-sequence buffer stream)
             (read-sequence buffer stream))
        (bodys (if (zerop pos) nil
                 (cons (if (< pos array-size) (subseq buffer 0 pos) buffer)
                       nil))
               (if (zerop pos) bodys
                 (cons (if (< pos array-size) (subseq buffer 0 pos) buffer)
                       bodys))))
      ((zerop pos)
       (nreverse bodys))))


;;;
;;; public functions
;;;
(defun http-get (host port path &key (array-size 4096)
                                     (element-type 'character)
                                     (ssl-p nil))
  (let ((socket (if ssl-p (make-ssl-client-socket host port)
                  (make-client-socket host port))))
    (unwind-protect
        (progn
          (send-request socket host path)
          (force-output socket)
          (let* ((headers (receive-headers socket))
                 (body (receive-body socket array-size element-type)))
            (list headers body)))
      (ignore-errors (close-socket socket)))))

(defun http-get-url (url &key (array-size 4096)
                              (element-type 'character)
                              (ssl-p nil s-ssl-p))
  (let ((uri (net.uri:parse-uri url)))
    (http-get (net.uri:uri-host uri)
              (or (net.uri:uri-port uri)
                  (case (net.uri:uri-scheme uri)
                    (:http      80)
                    (:https    443)
                    (otherwise  80)))
              (net.uri:render-uri uri nil)
              :array-size array-size
              :element-type element-type
              :ssl-p (if s-ssl-p ssl-p (eq :https (net.uri:uri-scheme uri))))))
上のコードをファイルに保存し、Emacs で開いて C-c C-b でバッファを評価します。C-c C-j でリスナに移って、実行は以下のように入力します。
CL-USER(34): (http-client:http-get "www.google.co.jp" 80 "/")
CL-USER(35): (http-client:http-get-url "http://www.google.co.jp/")

ソケットのオープン (make-socket)

まず、 make-socket でソケットをオープンします。クライアントソケットの場合、基本的に :remote-host:remote-port を指定するだけで大丈夫です。
(defun make-client-socket (hostname port)
  (socket:make-socket :remote-host hostname
                      :remote-port port))
ですが、 http://www.franz.com/support/documentation/6.2/doc/socket.htm#socket-characteristics-1 によると :format :bivalent を付けるとバイナリもテキストも同一のソケットで扱うことができます。これは HTTP のようなプロトコルを扱うのに便利です。このオプションは ACL 5.0.1 頃から追加されたもので、きっと AllegroServe のために追加したんだと思います(chunking protocol も)。

もうひとつ、 :type :hiper というドキュメントに載っていないオプションがあります。これもおそらく AllegroServe ために追加されたもので、AllegroServe のソース(main.cl)にはいっています。何か特別にパフォーマンスチューニングされたソケットだと思われます(というか早くドキュメントに書いてください > Franz 様)。

サーバソケットについてはスレッドの章で説明します。

SSL ソケットのオープン (make-ssl-client-stream)

SSL 6 のクライアントソケットは簡単で make-ssl-client-stream でソケットをラップするだけです。
(defun make-ssl-client-socket (hostname port)
  (socket:make-ssl-client-stream
   (make-client-socket hostname port)))

SSL のサーバソケットについてはスレッドの章で説明します。

ソケットのクローズ (close)

ソケットのクローズはファイルと同様に close で総称関数になっています。ここでは一応、処理系依存を考慮して close-socket という関数でラップしています。
(defun close-socket (socket)
  (close socket))

インライン関数

ソケットとは関係ありませんが、 close-socket のような小さな関数は以下のようにしてインライン関数にすることができます。
(declaim (inline close-socket))

[6] SSL は ACL 正規版の Enterprise Edition 以上でサポートされています。

スレッド

Allegro Common Lisp で並行プログラミングmp:process-add-run-reason 関係の話を追加。

プロファイル

(time (fact 10))
(prof:start-profiler :type :time
                     :count-list (list #'fact #'fact-aux))
(prof:start-profiler :type :space
                     :count-list (list #'fact #'fact-aux))
(fact 10)
(prof:stop-profiler)
(prof:show-flat-profile)
(prof:show-call-graph)
(prof:show-call-counts)

C との連携

ff:...

Java との連携

jlinker

バイナリ作成

generate-application (Enterprise Edition Only)

小ネタ

バージョン依存のコードを書く

#+(version>= major minor) と #+(version< major minor) を使ってバージョン毎にコードを変えることができます。
(setq socket
  #+(version>= 6 0)
  (socket:make-ssl-server-stream socket ...)
  #+(version< 6 0)
  socket)

コメントアウトあれこれ

ACL と関係ないかもしれませんが...

セミコロン。

;;;(defun fact (n)
;;;  (if (<= n 1) 1
;;;    (* n (fact (- n 1)))))
#| ... |#
#|
(defun fact (n)
  (if (<= n 1) 1
    (* n (fact (- n 1)))))
|#
#+... を使ったこういう方法もあります。くれぐれも *features*ignore を入れないようにしましょう。
#+ignore
(defun fact (n)
  (if (<= n 1) 1
    (* n (fact (- n 1)))))

実行可能スクリプト

Unix のシェル等から実行可能なスクリプトを書くことができます。詳細については http://www.franz.com/support/documentation/6.2/doc/startup.htm#starting-unix-script-3 を参照してください。

以下のように書いて fact.cl として保存します。

#! /usr/local/acl62_trial/alisp -#!

(format t "(sys:command-line-arguments) => ~S~%"
        (sys:command-line-arguments))

(format t "(sys:command-line-arguments :application nil) => ~S~%"
        (sys:command-line-arguments :application nil))

(defun fact (n)
  (if (<= n 1) 1
    (* n (fact (- n 1)))))

(let ((n (parse-integer (car (last (sys:command-line-arguments))))))
  (format t "(fact ~S) => ~S~%" n (fact n)))

後はファイルに実行属性を付けるだけです。

% chmod 755 fact.cl
% ./fact.cl 10
(sys:command-line-arguments) => ("alisp" "10")
(sys:command-line-arguments :application nil) => ("alisp"
                                                  "-I"
                                                  "/usr/local/acl62_trial/alisp.dxl"
                                                  "-#!"
                                                  "./foo.cl"
                                                  "10")
(fact 10) => 3628800

Susumu Ota (ccbcc55@hotmail.com)