自作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で処理を分岐させている)
それにしても、またニーズがない書き物をしてしまったよ