Anytime you need an intermediate representation that will be used to generate code, the most obvious thing that comes to my mind is the Abstract Syntax Tree (AST). The presentation of your example is lists that in my experience are not as flexible in form. For any thing other than trivial code generation, I wouldn't beat around the bush and just went with a full-blown AST view. Using lists, you push more work to the generation side to parse information such as types and what the first element means. Moving to the AST view will give you more flexibility and unleash most of the system with more work from the parsing (or more work with the functions that generate the forms). The generation side will do more work, but many of these components can be separated, as their inputs will be more structured.
In terms of what AST should look like, I would either copy Christophe Grand enlive where it used {:tag <tag name> :attrs <map of attrs> :content <some collection>}
or that clojure script uses {:op <some operator> :children <some collection>}
.
This makes it pretty general, as you can define arbitrary walkers that look in :children
and can cross any structure without knowing exactly what :op
or :tag
.
Then for atomic components, you can wrap it on a map and provide it with some type information (regarding the semantics of your DSL), which does not depend on the actual type of the object. {:atom <the object> :type :background-image}
.
On the code generation side, when you encounter an atom, your code can then send to :type
, and then, if you want, further send by the actual type of the object. A generation of collection forms is also easy, send to: op /: tag, and then repeat with the children. For which collection to use for children, I would be more familiar with the discussion of google groups. Their findings were useful to me.
https://groups.google.com/forum/#!topic/clojure-dev/vZLVKmKX0oc/discussion
To summarize, for children, if the meaning of semantic ordering is, for example, in an if statement, then use the map {:conditional z :then y :else x}
. If this is just a list of arguments, you can use a vector.