【问题标题】:Writing Common Lisp code that executes from the command line, but not inside the interpreter编写从命令行执行但不在解释器内部执行的 Common Lisp 代码
【发布时间】:2012-03-20 23:03:53
【问题描述】:

在编写 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 的有用回答和跟进。 以下是关于该答案的一些注释,由 cmets 编译为该答案。 @Vsevolod,如果您将这些添加到您的答案中,我会删除它们。

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

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

  3. 观察到关键字:noscript 恰好在#! 字符出现时添加到*features*。此外,#! 当代码在解释器中运行时出现字符,例如使用 slime 时,但显然不存在(已从程序中删除)
    运行sbcl --script 的文本。因此,:noscript 被添加到 *features* 中,当代码在 interpeter 中运行时,而不是作为 a
    脚本。

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

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

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

    因此,可以像使用#-:noscript#+:noscript 一样使用#-:swank#+:swank,只是不需要编写任何代码。 当然,如果使用命令行解释器sbcl,这将不起作用,例如, 此后:SWANK 将不会出现在*features* 中。

【问题讨论】:

    标签: command-line common-lisp slime sbcl


    【解决方案1】:

    您可以使用以下技巧:

    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。

    编辑

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

    这种方法的缺点是我们的条件是特征中没有:noscript,而不是:script。要对其进行修改,应在sbcl --script 处理本身中完成相应功能的推送。

    【讨论】:

    • Shebang 行被sbcl --script 从程序文本中删除,并且在您以任何其他方式加载文件时不会被删除。 #-/#+ 是标准读取宏,类似于 C 的 #IFDEF/#IFNDEF。他们检查*features* 中的符号。我们为#! 定义了一个读取宏来将:noscript 推送到*features*。它应该在文件loaded 之前定义。不幸的是,您不能更正确地执行此操作并在脚本调用中推送:script,因为当时该行不存在。你可以在这里阅读更多关于读取宏的使用:stackoverflow.com/questions/1351956/…
    • 使用set-dispatch-macro-characterset-macro-character 定义阅读器宏(请参阅clhs.lisp.se/Body/02_add.htm)。 #-/#+ 是内置的,我们定义#!。您不必为每一行添加前缀 - 只需为每个表单添加前缀(您可以将所有代码包装在 progn 中,例如 #-:noscript (progn <your code here>)
    • @faheem-mitha 我已经编辑了解决方案以解决首次加载的情况:(pushnew ...) 应该包含在eval-when 中,以便它始终执行,就像读取该行一样.
    • @VsevolodDyomkin:如果您愿意,请详细说明该解决方案。另外,我在您对我的问题的回答中添加了一些注释,请随时发布更正/cmets。
    • @VsevolodDyomkin 感谢您的详细回答,后续回答确实从中学到了很多。
    【解决方案2】:

    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

    【讨论】:

    • 谢谢你的回答,丹尼尔。
    • 还有较新的Roswell,除了像 cl-launch 这样的脚本启动器之外,它还是一个实现安装程序、脚本管理器/下载器、构建实用程序等。Some lispers 建议在此基础上进行构建Roswell,不再是 cl-launch。
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-18
      • 2012-07-08
      • 1970-01-01
      • 1970-01-01
      • 2012-03-20
      相关资源
      最近更新 更多