嵌入式ECL Lisp错误处理获取默认错误字符串和可能的行号

时间:2012-06-06 16:03:10

标签: exception-handling lisp common-lisp conditional-statements ecl

请先查看#7755661。我正在使用ECL并且基本上想要执行一些代码,捕获可能发生的任何条件,然后继续执行,而不提示或进入调试器。使用以下handler-case宏很容易实现:

(handler-case
  (load "code.lisp") ; this may raise a condition

  (error (condition)
    (print condition))) ; this prints sth like #<a UNBOUND-VARIABLE>

我唯一的问题是我找不到为用户打印更有意义的错误的通用方法。实际上我的应用程序是HTTP服务器,输出转到网页。 code.lisp是由用户编写的,它可以引发任何类型的条件,我现在想在我的代码中列出它们。当我不使用handler-case时,我只想打印我在REPL上看到的相同错误消息,但是在HTML页面中,例如对于“未绑定变量”错误,类似“变量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是实现内部的函数和变量的包,但我不担心我的代码是否不可移植,只要它有效。

BTW在其他类型的条件对象中,还有其他明显有用的插槽用于我的目的,名为SI:FORMAT-CONTROLSI:FORMAT-ARGUMENT,但我也无法从我的代码中访问其中任何一个。

我一直在寻找类似于Lisp中Java异常对象的getMessage()方法的一些想法,但我的所有来源都没有提到类似的东西。

此外,是否有希望能够在code.lisp中获取错误发生的行号?没有它,用户很难在他的code.lisp源文件中找到问题。我真的想提供这些信息,并且在第一个错误时停止对我来说是可以接受的。

2 个答案:

答案 0 :(得分:3)

在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 Printing Conditions中描述了这一点。

另请注意,当您有一个具有插槽且该插槽的值为函数的对象时,您需要使用函数SLOT-VALUE获取插槽值,然后使用FUNCALLAPPLY并使用正确的参数调用函数。

如果您具有类型simple-condition的条件,则它具有格式控制和格式参数信息。这是通过一个示例来描述的,如何将其用于CLHS Function SIMPLE-CONDITION-FORMAT-CONTROL, SIMPLE-CONDITION-FORMAT-ARGUMENTS

中的FORMAT

答案 1 :(得分:2)

我的答案基于我在ECL邮件列表中提供的答案。实际上我会声称这不是一个嵌入问题,而是一个Lisp问题。您希望在表单的文件位置获取一些导致错误的信息。这不附加到条件,因为条件的发生与评估的表单是否被解释,编译或已经安装在Lisp图像中的函数的一部分无关。换句话说,由您来了解正在读取的文件的位置并进行一些添加信息的包装。

以下是非标准且易于更改:当在源文件上使用LOAD时,ECL通过定义变量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内部结构,这表明你将来可能面临更多问题,尝试使用非便携式解决方案(隐藏的插槽名称,报告功能等)而不是先尝试标准方式。