编写从命令行执行但不在解释器内执行的Common Lisp代码

时间:2012-03-20 23:03:54

标签: command-line common-lisp slime sbcl

编写Common Lisp代码时,我使用SLIME。特别是,我使用C-C C-k编译包含函数定义的缓冲区,然后切换到REPL以运行这些函数。将可执行代码放在缓冲区中运行这些函数似乎不能很好地工作。如果代码有错误,它可能会弄得一团糟。

有一种方法可以包含不在缓冲区中编译的代码,但是可以从命令行运行,例如,做什么时

sbcl --script foo.lisp

如果是这种情况,每次我想从命令行运行代码时,我都不必继续添加和删除代码。是否存在这样的情况?

这类似于Python条件。

if __name__=='__main__':

如果将Python文件作为模块导入,则为false;如果将其作为脚本运行,则为true。

此博客文章名为"Using SBCL for Common Lisp shell scripting",由随机谷歌搜索找到,

;; If run from command line, the following if-test will succeed

(if (> (length sb-ext:*posix-argv*) 1)

    ;; Code that is executed from the command line only goes here

)

包含的代码确实不会被SLIME中的编译器运行,但它也不会由sbcl --script运行。

更新:感谢Vsevolod Dyomkin的有用回答和后续跟进。 关于该答案的一些注释如下,从对该答案的评论中汇编而成。 @Vsevolod,如果你将这些添加到你的答案中,我会删除它们。

  1. 首先,我问了一种从命令行运行代码的方法,但不是从解释器运行代码。 提供的解决方案做得更多;它还提供了一种从解释器而不是命令行运行代码的方法。

  2. 第一步是为宏字符#!定义reader macro function。 如链接“在遇到宏字符时,Lisp读者调用其读取器宏函数”中所述。 阅读器功能由对set-dispatch-macro-character的调用定义。 因此,当看到#!字符时,set-dispatch-macro-character会导致调用正文中定义的lambda函数。 然后,此函数会将关键字:noscript添加到*features*变量中。 另请参阅讨论在SO问题中哪些读取器宏有用 Read macros: what do you use them for?

  3. 观察到:noscript字符存在时,关键字*features*会被添加到#!。此外,#! 当代码在解释器内运行时存在字符,例如使用slime时,但显然没有(删除)程序的 正在运行sbcl --script的文字。因此,当代码在互操作程序中运行时,:noscript会添加到*features*,但在运行时不会{...}} 脚本。

  4. 我们现在使用内置读取器宏#-/#+,正如Vsevolod所说,它的行为类似于C的#IFDEF/#IFNDEF。他们检查符号
    *features*。在这种情况下,#-:noscript会检查是否缺少:noscript,并#+:noscript检查是否存在:noscript。 如果满足这些条件,则运行相应的代码。要包装一段代码,可以使用 progn是这样的:#-:noscript (progn <your code here>)

  5. 最后,在运行使用此功能的代码之前,需要调用set-dispatch-macro-character。在sbcl的情况下,可以放 它在初始化文件~/.sbclrc中。注意这种方法不依赖于Common Lisp实现是SBCL。

  6. 如sbcl-devel列表中所提到的,更简单的替代方法是使用在:SWANK中键入*features*时出现关键字#-:swank的事实 使用SLIME在emacs中的REPL。 SWANK是SLIME的服务器端。 SLIME应该更准确地称为SLIME / SWANK,就像这两个一样 客户端 - 服务器体系结构的客户端/服务器组件。我发现这篇名为Understanding SLIME的博客文章很有帮助。

    因此,可以像#+:swank#-:noscript一样使用#+:noscriptsbcl,但不需要编写任何代码。 当然,如果使用命令行解释器:SWANK,这将无法工作,例如, 从那时起,*features*将不会出现在{{1}}。

3 个答案:

答案 0 :(得分:13)

您可以使用以下技巧:

  1. 为shebang定义一个调度函数:

    (set-dispatch-macro-character #\# #\!
        (lambda (stream c n)
          (declare (ignore c n))
          (read-line stream)
          `(eval-when (:compile-toplevel :load-toplevel :execute)
             (pushnew :noscript *features*))))
    
  2. 在您的脚本文件中使用#-:noscript

    #!/usr/local/bin/sbcl --script
    
    (defun test (a) (print a))
    
    (test 1)
    #-:noscript (test 2)
    #+:noscript (test 3)
    

    执行./test.lisp将打印1和2,而 C-c C-k 将输出1和3.

  3. <强> EDITS

    这个技巧应该有效,因为当通过SLIME或其他机制加载文件时,sbcl --script完全删除了shebang行,但是没有删除。

    这种方法的缺点是我们在特征中缺少:noscript而不是:script的存在。要修改它,应在sbcl --script处理本身中推送相应的功能。

答案 1 :(得分:4)

Fare Rideau编写了一个漂亮的unix实用程序CL-Launch,它可以从命令行执行lisp软件。它集成了Quicklisp支持,适用于大多数Common Lisp实现。

示例脚本可能如下所示:

#!/usr/bin/cl -sp "hunchentoot" -Q -E main

(defun main (argv)
  (format t "~A: ~{~A ~}~%" (truename ".") argv)
  (hunchentoot:start
   (make-instance 'hunchentoot:acceptor
                  :document-root (truename ".")
                  :port 8080))
  (do ((x #\s (read-char)))
      ((char-equal x #\q) nil)))

向脚本添加+ x权限并调用它后,它将在当前目录中启动http服务器。 “-sp”标志表示要加载的包,因此从包中抽象shell脚本是相当简洁的方法。

有关详细信息,请参阅: http://cliki.net/CL-Launch

答案 2 :(得分:1)

我有同样的问题,我偶然发现了这个问题。至少对于sbcl,似乎我可以使用(sb-ext:posix-getenv "_")。当在slime中运行时,它返回/usr/bin/emacs(或者emacs的路径),否则返回我用来调用脚本的命令。因此,总是可以区分粘液和脚本调用,直到您成为emacs的贡献者:)

如果要获取要调用的脚本的完整路径名,可以使用(truename (sb-ext:posix-getenv "_"))。但是,当从slime运行时,它将返回有效的emacs路径名,例如, /usr/bin/emacs-24.5,所以这可能不太方便。