自从1986年首次将autolisp引入,autolisp在autocad中已经效力快40年了。 这几十年间,autocad经历了几十个大版本的升级,然而,其lisp解释器却并没有一同升级增强。 目前最新的autocad2022,其lisp解释器vllib仍然停留在1999年,只是在autocad2022发布的时候, 重新编译了一下而已。

看看下面这个截图,发布时间相隔10年的两个autocad,其lisp解释器的差异只有一个编译号。 pic1

长时间的冷落,自然也导致有些问题迟迟得不到解决,多少有点鸵鸟策略的味道。就个人使用经历来看, autocad目前的lisp解释器、编译器存在的问题可能难以发现,有些可能和语言特性有关,有些则真的像是bug。 对我来讲,最头疼也是耗时最久才解决的,主要有两个问题。

形参冲突

一般来说,形参是最不应该出问题的地方,然而,事实就是这么残酷,它恰恰就出问题了。

发现这个问题纯属偶然。去年年初,尝试着自己实现一遍vl-*系列函数,在尝试的过程中,就发现了这个问题。 出问题的,就是下面这段代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
(defun xg-remove-if (func lst) 
  ;; 自行实现的vl-remove-if函数
  ;; 作者:徐工, 微博:@徐工徐工2020,头条:@徐工徐工
  (cond 
    ((null lst) nil)
    ((apply func (list (car lst))) (xg-remove-if func (cdr lst)))
    (t (cons (car lst) (xg-remove-if func (cdr lst))))))
		   
(defun xg-remove-if-not (func lst) 
  ;; 自行实现的vl-remove-if-not函数
  ;; 作者:徐工, 微博:@徐工徐工2020,头条:@徐工徐工
  (xg-remove-if '(lambda (x) (not (apply func (list x)))) lst))

(xg-remove-if '(lambda (x) (= 0 x)) '(0 1 2 3 0 4 0 5 0 6))
(xg-remove-if-not '(lambda (x) (= 0 x)) '(0 1 2 3 0 4 0 5 0 6))

上述代码,语法上没有问题,一般认为不应该会有什么问题。最后两行,期望的返回值分别是'(1 2 3 4 5 6)'(0 0 0 0)。 事实上,上述代码在common lisp 和racket/scheme环境下,返回值是正常的,就是期望的结果。

common lisp的运行结果如下: piccl

racket的运行结果如下: picracket

但是,上述代码的最后一行,在autolisp环境下会陷入死循环进而出错终止。思考了很久,尝试了很多办法, 都没能解决问题。后来,在autodesk官方的论坛里边,发帖求助,终于,得到了网友的帮助,说是形参冲突, 因为xg-remove-if-not里边的形参与xg-remove-if同名,修改形参名之后就能得到期望的结果了。大概可以改成下面这样子。

1
2
3
4
(defun xg-remove-if-not (func1 lst) 
  ;; 第2版自行实现的vl-remove-if-not函数
  ;; 作者:徐工, 微博:@徐工徐工2020,头条:@徐工徐工
  (xg-remove-if '(lambda (x) (not (apply func1 (list x)))) lst))

特定情况下出现编译过程卡死

曾经长时间使用autocad 2008,至今仍然认为2008版是最为流畅的版本。自从用了win 10之后,就换成了autocad 2012了, 因为2012版在当时是最流畅的,另外,网上流传的64位autocad 2008缺乏第三方插件的支持。

编译卡死的问题,就出现在2012版,因为手头上最老的就是这个版本。

这个问题也是花了很久才想清楚,前前后后差不多两三年时间。具体问题就是,在lisp源文件编译时, 链接模式选择为“链接”或者“内部链接”,假如源文件中有很多局部化的函数(位于顶层函数内部, 并且将其函数名设置为局部变量的函数),那么,就有很大概率在编译时出现卡死。

对于这个问题,当时的第一反应是,也许源文件列表中的顺序有问题?花了很多时间检查,调整了很多次, 最终还是无功而返。由于在更高版本的autocad上,比如2016 、2022版,可以正常编译,索性就懒得去管它了。

最近几个月花了不少时间重构,经常使用visual lisp ide的代码检查功能,在检查之后的回显信息中, 函数被局部化了就是作为警告信息回显的。在看了无数次的警告信息之后,突然有一天开悟了,把解决编译卡死的 思路转到警告信息上了。于是,花时间把局部函数移到顶层了,再次进行编译时,一切又恢复正常了。困扰多年的老问题, 就这样解决掉了。

结语

前面说的第一个问题,算起来可能并不是bug,毕竟,autolisp作为老派的lisp代表,动态作用域是它的特色。 动态作用域虽然历史悠久,有其大批拥趸,也还是容易导致形参命名冲突这一类问题的出现,这样的问题一旦出现了, 即使定位准了可能也难以找到解决方案。对autolisp来说,这种情况更加地棘手,因为没有命名空间, 没有隔离机制,在代码量达到一定程度之后,命名冲突似乎难以避免。

第二个问题,可能真的算是编译器的bug了。因为,在“标准”编译模式下,不对函数进行链接的时候,问题就不存在了。 所以,问题就出在编译优化上,强行对某些不该链接的函数进行链接,可能就导致了问题。

另外,需要特别注意的是,autolisp提供了名为function的函数,用这个函数包裹lambda函数之后, 在编译阶段也可以对lambda这种匿名函数进行优化。这种做法,也可能导致某些莫名其妙的问题, 使用function还是要小心为好。