我该如何发现微妙的Lisp语法错误?

14

我是一个对Lisp(实际上是Emacs Lisp)感兴趣的新手。这很有趣,但每次遇到相同的语法错误时就会感到头疼。

例如,我遇到过多次的情况是,我有一些cond表达式,就像这样:

(cond
 ((foo bar)
  (qux quux))
 ((or corge
      (grault warg))
  (fred)
  (t
   xyzzy)))

而默认子句返回的xyzzy永远不会被执行,因为它实际上是嵌套在前一个子句中:

(cond
 ((foo bar)
  (qux quux))
 ((or corge
      (grault warg))
  (fred))
 (t
  xyzzy))

当缩进只相差一个空格时,我很难发现这样的错误。这种情况随着时间的推移会变得更容易吗?

当 (错误的) 缩进行与应该缩进的行之间有很大的距离时,我也会遇到问题。比如带有许多复杂绑定的 let 表达式,或者一个带有长条件的 unless 表达式:

(defun test ()
  (unless (foo bar
               (qux quux)
               (or corge
                   (grault warg)
                   (fred))))
  xyzzy)

事实证明,xyzzy 根本没有在 unless 结构内:

(defun test ()
  (unless (foo bar
               (qux quux)
               (or corge
                   (grault warg)
                   (fred)))
    xyzzy))

我习惯自动缩进,并使用括号高亮功能来避免数括号。大部分时候都很顺利,但偶尔会发现语法错误只能通过调试才能发现。我该怎么办?

7个回答

28

突出S表达式

首先,如果还没有打开内置的括号匹配突出显示 (show-paren-mode),请先打开。它总是能让你知道当前所在的缩进级别。

还有一些更复杂的软件包。例如,请参阅 TJ的关于 mic-paren 的回答。或者虽然我觉得不适合我,但是有 Schumacher的highlight-parentheses mode 可以用不同颜色突出显示每个表达式块。甚至 Edward O'Connor也有一个模式 可以突出显示当前的S表达式。

Paredit

使用 paredit-mode 帮助你编写S表达式。你可以轻松地在S表达式之间导航和重新组织代码。此外,它确保括号始终平衡。当我第一次尝试时,我非常讨厌Paredit约束编码方式,但自那以后,我已经习惯了使用它,我更加高效,从不混淆开放和关闭括号。

自动缩进

使用Emacs Starter Kit,默认情况下启用了许多有用的编程助手,例如在Elisp中换行时重新缩进。

ElDoc

emacs-lisp-mode有几个有用的扩展。例如,我总是使用eldoc-mode,它在回显区域显示当前输入函数的参数列表或变量的文档字符串。它还可以帮助您轻松识别是否在错误的块中。

Edebug

我差点忘记提到Edebug,作为最后的机会,它总是帮助您找出代码中发生了什么。


1
哇,eldoc-mode 真是太酷了——在我阅读别人的代码时,它为我节省了大量的 C-h v 和 C-h f。 - polyglot

7
以下是三个具体的方法,可以帮助您发现Lisp语法问题。随着时间的推移,这将成为第二天性。但在那之前:

括号匹配

括号匹配是检查分组的最简单方法。我最喜欢的软件包是mic-paren。我喜欢这个特定的配置:
(setq paren-dont-touch-blink t)
(require 'mic-paren)
(paren-activate)
(setq paren-match-face 'highlight)
(setq paren-sexp-mode t)

当光标位于sexp的开头/结尾时,会使sexp(匹配括号)高亮显示。如果括号不匹配,则高亮颜色不同 - 更亮。当匹配的括号在屏幕外时,它会在minibuffer中显示该结尾的样子(并告诉您距离它有多少行)。

编译

对于稍微复杂一些的方法,您可以使用M-x compile-defun运行Elisp编译器。例如,当我编译这个简单的例子时:

(defun mytestfun ()
  (let ((cur (current-buffer)))
    )
  (set-buffer cur))

我有一个名为*Compile-Log*的缓冲区,上面写着:

警告:引用了自由变量“cur”

这提示我,在定义curlet语句之外使用了它。

更改缩进级别

如果你想要更明显的缩进,可以自定义变量listp-body-indent

(setq lisp-body-indent 4) ;# default is 2

您还可以自定义各种结构的缩进方式,但我不建议这样做,因为它会非标准化,并且在查看大多数Lisp代码时可能会导致混淆。


2

有一个简单的方法是将光标移动到每个cond条件的开头,并查看闭合括号的位置。


2
最佳的编写Lisp的方式是使用Emacs + Slime组合。它提供括号高亮、制表符自动完成、您可以直接跳转到Lisp超文本规范文档,它为函数提供变量名称(见下文)等等。
(defun foo (bar) ...)
当您开始键入(foo)时,它会显示foo需要一个名为bar的参数。这样,您可以轻松“猜测”函数需要哪些参数。这对于不遵循Lisp约定的函数特别有用。

1
当缩进的差异只有一个空格时,我很难看到这样的错误。这种情况会随着时间的推移而变得更容易吗?
至少对我来说不是这样的。它在某种程度上取决于您使用的特定xterm字体,但我发现我需要四个空格的缩进才能有效地工作(是的,我使用那个旧的原始xterm字体——怪罪SunOS 4),即使两个空格也有问题。
我也使用括号高亮显示和vi中的“%”键。
不幸的是,这不是一个非常有用的答案。

1

我看到你可以采取两种方法,这两种方法都可以帮助你解决这两个例子的问题。

第一种方法是先画出一个大局,然后再填写细节。针对你的第一个例子,你可以首先编写以下代码:

(cond
 (--
  --)
 (--
  --)
 (t
  --))

接下来您可以开始填写细节。 -- 就像是待办事项的占位符。

第二个例子中,您可以从以下内容开始:

(defun test ()
  --)

然后填写更多类似于这样的内容:
(defun test ()
  (unless (--)
    --))

然后填写更多内容。
另一种方法是自定义彩虹括号,使其将偶数级括号突出显示为一种颜色,奇数级括号突出显示为另一种颜色。这将有助于处理cond表单。它不能帮助您的第二个示例,但如果它是(xyzzy)而不是xyzzy,那就可以了。

0
除了其他人提供的有价值的指导意见之外,我想说的是“使用基于结构的移动命令”,如backward-sexpforward-sexp等。这样可以让您在代码中进行导航。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接