5

我正在 Clojure 中设计一个 DSL,它用于驱动代码生成器(在这种情况下用于程序图像合成 - clisk),并且在为中间值制定最佳表示时遇到了麻烦。

最初,DSL 由返回一种或多种形式的函数组成,例如(说明性的)

(v+ 1.0 [1.0 'y])
=> ['(+ 1.0 1.0) '(+ 1.0 y)]

然后可以组合这些函数来构建更大的代码块。

这很简单,生成的表格可以直接输入代码生成器。然而,我现在已经确定了这种方法的一些弱点,例如,如果需要传递一些辅助数据(例如,无法以 BufferedImages 等形式编码的对象、对优化有用的元数据等)。

我确信这是 Lisp 世界中一个已解决的问题 - 这种 DSL 的最佳中间表示通常是什么?

4

3 回答 3

9

每当您需要用于生成代码的中间表示时,我想到的最明显的事情就是抽象语法树(AST)。您的示例表示是列表,根据我的经验,它的形式不那么灵活。除了琐碎的代码生成之外,我不会拐弯抹角,而只是使用完整的 AST 表示。通过使用列表,您将更多的工作推到生成端来解析信息,例如类型和第一项的含义。转向 AST 表示将为您提供更大的灵活性并解耦更多的系统,但代价是解析方面的工作更多(或生成表单的函数的工作更多)。一代人也将做更多的工作,

就 AST 应该是什么样子而言,我会复制 Christophe Grand 的 enlive,他使用的地方{:tag <tag name> :attrs <map of attrs> :content <some collection>}

或者 clojure 脚本使用什么,{:op <some operator> :children <some collection>}.

这使得它非常通用,因为您可以定义任意步行者,这些步行者可以窥视:children并可以遍历任何结构,而无需确切知道:op's 或:tag's 是什么。

然后对于原子组件,您可以将其包装在映射中并为其提供一些类型信息(关于您的 DSL 的语义),这些信息与对象的实际类型无关。{:atom <the object> :type :background-image}.

在代码生成方面,当遇到原子时,您的代码可以在 上分派:type,然后,如果您愿意,可以进一步分派对象的实际类型。从集合表单生成也很容易,在 :op/:tag 上调度,然后与孩子一起重复。对于儿童使用什么集合,我会阅读更多关于谷歌群组的讨论。他们的结论对我很有启发。

https://groups.google.com/forum/#!topic/clojure-dev/vZLVKmKX0oc/discussion

总而言之,对于儿童来说,如果在 if 语句中存在语义排序重要性,则使用 map {:conditional z :then y :else x}。如果它只是一个参数列表,那么你可以使用一个向量。

于 2012-05-26T13:48:47.097 回答
1

我想我不明白。我自己会使用列表或结构。

在 Lisp 中,列表可以包含任何东西。我应该说,一个 CONS 单元格可以指向任何东西,因此列表可以包含任何东西。几乎任何其他数据结构(结构、数组、映射等)也可以。

现在,这些结构不能通过 PRINT 渲染或渲染成可读的东西(通过 READ),但这并不意味着它们不能被存储和操作。

是否有某些原因需要将这种表示外部化?

于 2012-05-26T05:07:05.980 回答
1

不是一个真正的答案,因为我不知道 Clojure 在这方面是如何工作的,但是在 CL 中有专门为这种情况设计的阅读器宏:即你可以定义你的函数来打印不可打印的对象 + 一个读取它们的阅读器宏您打印它们的方式。要定义打印对象的方式,您需要定义一个print-object专门处理您需要的对象类型的新方法,set-macro-character并向可读表添加一个函数,该函数知道如何读取您的设计对象。

有很多事情需要注意,但有些情况通常就像定时炸弹一样,当对象被允许递归引用自己时,在这种情况下打印需要考虑以前打印的对象。

于 2012-05-26T07:03:02.173 回答