標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

自作Lispで、ようやくISLisp の検証プログラムが少し動くようになってきた

自作Lispで、ようやく ISLisp の検証プログラムが少し動くようになってきた。

このプログラム、テストケースを動かすための土台(tp.lsp)そのものがテクニックが駆使された難しいコードで、自作Lisp ではそれをまず処理できずにつまづいていた。テストケースにあるコードを直接実行する分には動作しているように見えるのに、検証プログラムでは NG というケースも多々あった。まず

  • パーサーが ',、シングルクォート(quote)の直後にカンマ(unquote)があるパターンに対応していなかった
(setq form `(eval ',form)))

という式が評価できていなかった。結果として (defglobal) など宣言系のコマンドが全てスルーされる結果になっていた。

  • ((lambda (x) (+ x x)) 4) などという式が Valid

lambda の結果は関数オブジェクトだから funcall 経由でないと呼び出せないものとばかり思っていた。実際に OK!Lisp や IRIS で試したところ、両者とも問題なく動作した。

$ ISLisp.exe
> ISLisp  Version 0.80 (1999/02/25)
>
ISLisp>((lambda (x) (+ x x)) 4)
8
ISLisp>(funcall (lambda (x) (+ x x)) 4)
8
$ iris.exe
Iris ISLisp Interpreter Commit HEAD on go1.22.3
Copyright 2017 islisp-dev All Rights Reserved.
>>> ((lambda (x) (+ x x)) 4)
8
>>> (funcall (lambda (x) (+ x x)) 4)
8

例外対応として、cons の評価部分で、car がシンボルであれば対応する関数を、関数オブジェクトならば funcall 相当の処理を呼び出すようにした。

( が、なぜか結果はあってるのに異常終了判定されてしまう問題に数時間悩まされるはめに。原因は今まで、式がconsであるかの確認/変換処理を Go の型アサーションでやってたのを、専用の型変換関数を使うようにしたせいだった。型があっていない時に自動的に Lisp の例外ハンドラーが呼び出され、異常終了フラグがセットされてしまったのだった。以下、不具合を再現させる必要最小限のコードを示す。ここで ERROR=nil と表示されなくてはいけないのが ERROR=t と表示されてしまう…。どんだけマニアックなコードやねん )

(defglobal *tp-error-flag* nil)
(defun tp-error-handler (condition)
  (setq *tp-error-flag* t)
  (format t "ERROR-HANDLE: ~S~%" condition)
  (throw 'tp-error condition))

(defmacro tp-eval-form (&rest form)
  `(catch 'tp-error
     (with-handler #'tp-error-handler ,@form)))

(defglobal form '((lambda (x) (+ x x)) 4))
(defglobal source (list 'tp-eval-form form))
(format (standard-output) "SOURCE=~S~%" source)
(format (standard-output) "RESULT=~S~%" (eval source))
;(format (standard-output) "RESULT=~S~%" (tp-eval-form form))
(format (standard-output) "ERROR=~S~%" *tp-error-flag*)
(format (standard-output) "EXPAND=~S~%" (macroexpand (list 'tp-eval-form form)))

ISLisp の検証プログラムとは

未来の自分のために書いておこう

検証プログラムは islisp.org の Downloadページ からダウンロードできる。

$ curl -O http://islisp.org/program/Verify.zip
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 76761  100 76761    0     0   657k      0 --:--:-- --:--:-- --:--:--  669k

$ unzip Verify.zip
Archive:  Verify.zip
  inflating: readme.txt
  inflating: tp-ipa.zip

$ unzip tp-ipa.zip
Archive:  tp-ipa.zip
   creating: tp-ipa/
   creating: tp-ipa/data/
  inflating: tp-ipa/data/array.lsp
   creating: __MACOSX/
   creating: __MACOSX/tp-ipa/
   creating: __MACOSX/tp-ipa/data/
  inflating: __MACOSX/tp-ipa/data/._array.lsp
  :

$ cd tp-ipa

$ gmnlisp -e "(load \"tp.lsp\") (tp-all)"

> TP File : data/formeval.lsp
> NG: (function) -> #<Correct number of arguments] [#<Wrong number of arguments>]
> NG: (function nil nil) -> #<Correct number of arguments] [#<Wrong number of arguments>]
> NG: (funcall (function -) 3) -> 3 [-3]
> NG: (funcall (function -) 3) -> 3 [-3]
> NG: (function . (+ . 1)) -> #<Error> <error> [#<Error> <error>]
    :
  • 検証プログラムは (eval OBJ)(load "FILENAME") という命令があることが前提になっている。それらってISLisp の規格にないと思うんだけど、いいんですかね (まぁ、そもそも沖電気さんが OK!Lisp のために作った検証プログラムだしな)

  • テストを動かすプログラムは tp.lsp のみで、テストケースは data/*.lsp に格納されている

  • デフォルトでは失敗したケースしか表示されないが、(tp-all) のところを (tp-all 'verbose) とかに変えると成功ケースも表示されるようになる。

  • デフォルトでは全ケースのテストを行うようになっているが、(tp-all)(tp "data/formeval.lsp" 'verbose) などにすることで、特定のテストファイルだけをテストすることができる

  • data/ 以下にあるテストケースは、そのまま動くLisp のプログラムのように見えるが、実はS式で書かれたデータにすぎない。基本は (式 期待値 equalなどの評価関数名) のリストとなっている。$eval というシンボルも散見されるが、命令ではなく、マークにすぎない(tp.lsp の方でそれを解釈して、cond で処理を分岐させている)

それにしても、またニーズがない書き物をしてしまったよ