Common Lisp的FORMAT有相反的操作吗?

4

我有一个问题困扰了我一段时间。Common Lisp的format函数是否是可逆的(至少在某种程度上),即格式字符串是否可以用于从format的输出中检索原始参数?我知道映射不是一对一的(一个例子是~@:(~a~),它将输入转换为大写并且不可逆),因此必然会丢失一些信息。我想到的确切是替代正则表达式进行字符串解析。例如,我希望能够编写:

(destructure-format t "[~{~a~^, ~}]" "[0, 1, 2]")

并获取响应:

=> (0 1 2)

您是否知道任何类似尝试或论文探讨了类似的方法?


1
呼。destructuring-bindread-from-string的结合怎么样?如果您需要序列化数据,那就是S表达式的用处。据我所知,在核心语言中没有任何可以允许您提到的这种字符串模式匹配的内容。当然,您可以使用正则表达式 - Dirk
是的,现在我看起来,这似乎是一个合理的事情要做。谢谢! - Wojciech Gac
1
http://jcsu.jesus.cam.ac.uk/~csr21/format-setf.lisp - 但这有点像是一个玩笑/黑客。 - Xach
谢谢,@Xach。我一定会看看的。 - Wojciech Gac
2个回答

4

标准中没有相关内容

标准中没有类似的内容。格式化表达式不提供足够的信息来使其在实际中有用。对于几乎所有不绑定 *print-readably* 的输出,都存在难以读回的情况。对于您所提供的列表格式,情况是如此。

(destructure-format t "[~{~a~^, ~}]" "[0, 1, 2]")

任何解决方案都必须检查格式指令。它可以明确观察到什么?字符串中的第一个字符必须是#\[,最后一个字符必须是#\],而且字符串中的", "某些出现必须分隔由~a生成的输出。那么可能会出现什么歧义?任何导致在输出中写入", "的情况。例如,
CL-USER> (format t "[~{~a~^, ~}]" '(|, | 2 3))
[, , 2, 3]
NIL
CL-USER> (format t "[~{~a~^, ~}]" '(|, | | ,|))
[, ,  ,]
NIL
CL-USER> (format t "[~{~a~^, ~}]" '(|, | | ,| |,|))
[, ,  ,, ,]
NIL
CL-USER> (format t "[~{~a~^, ~}]" '(|, | | ,| #\,))
[, ,  ,, ,]
NIL

第三方库

虽然在 Stack Overflow 上不建议讨论库推荐,但本问题并非如此。在看到 Rörd 的回答 建议使用 C 的 scanf 进行外部函数调用之后,我迅速搜索了 CLiki 并找到了 format-setf(重新阅读评论后,我发现 Xach 先行发现了这一点),其描述如下:

Common Lisp 中的 scanf() 等价物。

在 comp.lang.lisp 上有一个(相对较频繁的)问题是:“有什么与 scanf() 等效的函数?”。通常的答案是:“没有,因为很难确定应该发生什么”。这也是公平的。

然而,Christophe 在考试期间感到无聊,于是他写了 format-setf.lisp,可能会满足你的需求。

需要指出的是,目前该程序的行为是未指定的,不仅仅是在“CL”包中覆盖符号。很希望能够出现其行为的规范说明,这样我就没有借口回避它有 bug 的问题了。

其他选择

如果您真的想知道“这样匹配的可能方式有哪些”,那么您将本质上问的是一个正则表达式加上格式化所提供的额外功能。

我的思路确切地说是一种字符串解析的替代方案,而非正则表达式。

如果您正在寻找正则表达式,则正则表达式非常适合。如果您正在寻找不使用正则表达式的解析,那么您可能需要编写一个真正的解析器。第一次可能会让人望而生畏,但之后会变得容易得多,而且 Common Lisp 使其相对轻松。甚至还有可用的解析器生成库。另一方面,如果您正在寻找序列化和反序列化,Common Lisp 读取器和编写器使 s 表达式成为一个良好而易于选择的选项。


谢谢,@Joshua。我想我最终会倾向于编写一个解析器 :). 你知道有关这个主题的任何好的简明指南吗? - Wojciech Gac
@WojciechGac Stack Overflow并不是一个很好的库/工具推荐平台,但你可以查看CLiki解析器生成器页面获取一些实用工具。至于理论和技术方面,许多人喜欢龙书 - Joshua Taylor

1
如果您想进行基于格式字符串的解析,但不需要format的高级功能,则可以通过FFI使用C的scanf函数。以下是使用CFFI执行此操作的示例:
(with-foreign-strings ((input "[0, 1, 2]") (format "[%d, %d, %d]"))
  (with-foreign-objects ((a :int) (b :int) (c :int))
    (foreign-funcall "sscanf" :pointer input :pointer format
                     :pointer a :pointer b :pointer c)
    (loop for x in (list a b c) collect (mem-ref x :int))))

这个回答让我想到在CLiki上搜索scanf。我找到了http://cliki.net/format-setf。 - Joshua Taylor

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