2012-05-26 40 views
6

我正在Clojure中設計一個用於驅動代碼生成器的DSL(在這種情況下用於過程圖像合成 - clisk),並且在處理中間值的最佳表示時遇到問題。Lisp/Clojure DSL的中間表示形式

最初,DSL由返回一個或多個表單的功能組成,例如, (說明性的)

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

然後可以組合這些函數來構建更大的代碼塊。

這很簡單,生成的表單可以直接送入代碼生成器。不過,我現在已經確定了這種方法看起來有些弱點,例如,如果需要傳遞一些輔助數據(例如,無法像BufferedImages這樣的表單編碼的對象,對優化有用的元數據等)。

我確定這是Lisp世界中的一個已解決的問題 - 通常這種DSL的最佳中間表示是什麼?

回答

7

無論何時您需要一個將用於生成代碼的中間表示形式,我想到的最明顯的就是抽象語法樹(AST)。您的示例表示是列表,根據我的經驗,它不像表單那樣靈活。對於任何不僅僅是簡單的代碼生成而言,我都不會繞過灌木叢,只需要全面展示AST代碼即可。通過使用列表,您可以將更多工作推給生成一側,以解析出類型等信息以及第一項意味着什麼。轉移到AST表示會給你更多的靈活性和更多的系統分離,代價是從解析方面做更多的工作(或者更多的工作來自生成表單的函數)。發電方也將做更多的工作,但這些組件中的許多組件可以分離,因爲它們的輸入將更加結構化。

在又該AST的樣子,我就複製或者克里斯托夫大的enlive,在那裏他使用{:tag <tag name> :attrs <map of attrs> :content <some collection>}

還是什麼Clojure的腳本使用,{:op <some operator> :children <some collection>}條款。

這使得它相當一般,因爲你可以定義偷看到:children任意步行者,並且可以遍歷任何結構而不知道:op:tag是什麼。

那麼對於原子組件,你可以在地圖中把它包起來,並給它這是獨立於實際類型對象的某些類型的信息(相對於您的DSL的語義)。 {:atom <the object> :type :background-image}

在代碼生成方面,遇到原子時,您的代碼可以在:type上調度,然後如果需要,可以進一步調度對象的實際類型。從集合生成表單也很容易,調度上:OP /:標記,然後與孩子復發。對於使用的收集孩子,我會閱讀更多了對谷歌的羣體討論。他們的結論對我很有啓發。

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

總之,爲了孩子,如果有語義順序的重要性,如在if語句,然後用地圖{:conditional z :then y :else x}。如果它只是一個參數列表,那麼你可以使用一個向量。

+0

非常感謝 - 只是那種洞察力我一直在尋找的! – mikera

1

我想我不明白。我只是自己使用列表或結構。

在Lisp中,列表可以包含任何內容。我應該說,CONS單元可以指向任何東西,因此列表可以包含任何東西。所以幾乎可以有任何其他數據結構(結構,數組,地圖等)。

現在,這些結構不能通過PRINT或渲染到可讀的內容(通過READ),但這並不意味着它們不能被存儲和操作。

是否有某些原因需要將此表示形式外部化?

+0

最終欲編譯使用例如表格使用像'(EVAL \'(FN [XYZ]〜形式對編譯))' - 這不符合雖然嵌入的對象(見http://stackoverflow.com/questions/10735701/embedding-arbitrary-工作對象 - - Clojure的代碼) – mikera

1

不是一個真正的答案,因爲我不知道Clojure在這方面的工作原理,但是在CL中有專門爲這種情況設計的閱讀器宏:即,您將定義用於打印不可打印對象的函數+閱讀器宏從你打印它們的方式讀取它們。要定義打印對象的方式,你需要定義一個新的方法print-object,它專門用於你需要的對象類型,set-macro-character可以向知道如何讀取設計對象的可讀表中添加一個函數。

有許多事情需要注意,但有些通常像定時炸彈一樣行爲的情況下,當對象被允許遞歸引用自己,在這種情況下,打印需要考慮以前打印的對象。