请先看#7755661。我正在使用ECL,基本上想执行一些代码,捕获可能发生的任何类型的条件,然后继续执行,而无需提示或进入调试器。使用以下处理程序大小写宏可以轻松实现这一点:
(handler-case
(load "code.lisp") ; this may raise a condition
(error (condition)
(print condition))) ; this prints sth like #<a UNBOUND-VARIABLE>
我唯一的问题是我找不到一种通用方法来为用户打印更有意义的错误。事实上,我的应用程序是一个HTTP服务器,输出到一个网页。code.lisp 是由用户编写的,它可以引发任何类型的条件,我现在想在我的代码中将它们全部列出。当我不使用处理程序大小写但在 HTML 页面中时,我只想打印我在 REPL 上看到的相同错误消息,例如对于"未绑定变量"错误,像"变量 VAR 未绑定"这样的字符串。
通过检查类型为 UNBOUND-VARIABLE
的条件对象,我看到它有两个插槽:SI:REPORT-FUNCTION
,这是一个编译的函数和 SI:NAME
,在这种情况下设置为变量的名称。我想SI:REPORT-FUNCTION
可能是我需要调用的,但我该怎么称呼它呢?如果我尝试:
(handler-case foo (error (condition) (SI::REPORT-FUNCTION condition)))
它告诉我 SI:REPORT-FUNCTION 是未定义的。ECL 中的 SI 或 SYS 是实现内部函数和变量的包,但我不担心我的代码是否不可移植,只要它有效。
顺便说一句,在其他类型的条件对象中,还有其他明显有用的插槽,名为 SI:FORMAT-CONTROL
和 SI:FORMAT-ARGUMENT
,但我也无法从我的代码中访问它们中的任何一个。
我一直在寻找一些类似于 Lisp 中 Java 异常对象的getMessage()
方法的想法,但我的资料中没有一个提到过这样的东西。
此外,是否有希望能够在code.lisp中获取发生错误的行号?否则,用户将很难在他的 code.lisp 源文件中找到问题所在。我真的很想提供这些信息,并且在第一个错误处停止对我来说是可以接受的。
在 Common Lisp 中,当禁用打印转义时,将打印错误消息。
CL-USER > (handler-case
a
(error (condition)
(write condition :escape nil)))
The variable A is unbound.
#<UNBOUND-VARIABLE 4020059743>
请注意,PRINT
将*print-escape*
绑定到T
。
使用PRINC
有效 - 它将*print-escape*
绑定到NIL
.
CL-USER > (handler-case
a
(error (condition)
(princ condition)))
The variable A is unbound.
#<UNBOUND-VARIABLE 4020175C0B>
CLHS 9.1.3 打印条件中对此进行了描述。
另请注意,当您有一个对象,该对象具有插槽并且该插槽的值是一个函数时,则需要使用函数SLOT-VALUE
获取插槽值,然后使用FUNCALL
或APPLY
并使用正确的参数调用该函数。
如果您有类型 simple-condition
的条件,则它具有格式控件和格式参数信息。这是通过一个示例描述的,如何在CLHS函数中将其用于FORMAT
简单条件格式控制,简单条件格式参数
我下面的答案是基于我已经在ECL邮件列表中给出的答案。实际上,我会声称这不是一个嵌入问题,而是一个Lisp问题。您希望在导致错误的表单的文件位置获取一些信息。这不会附加到条件上,因为条件的发生与所评估的表单是被解释、编译还是已经安装在 Lisp 映像中的函数的一部分无关。换句话说,由您知道正在读取的文件的位置并进行一些包装以添加信息。
以下内容是非标准的,容易更改:ECL 通过在源文件上使用 LOAD 时定义变量 ext::source-location 来帮助您。此变量包含一个用户永远不应更改或存储的 CONS,但您可以获取文件作为(CAR EXT:*SOURCE-LOCATION*)
,文件位置作为(CDR EXT:*SOURCE-LOCATION*)
。然后,计划是将您的 LOAD 表单嵌入到 HANDLER-BIND 中。
(defparameter *error-message* nil)
(defparameter *error-tag* (cons))
(defun capture-error (condition)
(setf *error*
(format nil "At character ~S in file ~S an error was found:~%~A"
(cdr ext:*source-location*)
(car ext:*source-location*)
condition)))
(throw *error-tag* *error-message*))
(defun safely-load (file)
(handler-bind ((serious-condition #'capture-error))
(catch *error-tag*
(load file)
nil)))
(SAFELY-LOAD "myfile.lisp")
将返回 NIL 或格式化错误。
无论如何,我坚信依靠 LOAD 注定要失败。您应该创建自己的 LOAD 版本,从这个开始
(defun my-load (userfile)
(with-open-file (stream userfile :direction :input :external-format ....whateverformat...)
(loop for form = (read stream nil nil nil)
while form
do (eval-form-with-error-catching form))))
其中EVAL-FORM-....实现类似于上面的代码。此功能可以变得更加复杂,您可以跟踪文件位置、行号等。这样,您的代码也将更具可移植性。
因此,请阅读ANSI规范并学习该语言。事实上,您不知道如何打印可读的条件,而是尝试使用ECL内部结构,这表明您将来可能会面临更多问题,尝试使用非可移植解决方案(隐藏的插槽名称,报告功能等),而不是首先尝试标准方式。