Here is the simplest trivial example that I can think of using the AST tree built from s-expressions with keyword operators:
;; functions map, can be easily extended with new functions ;; map is of keyword -> code generating function (def funcs {:if (fn [cond exp1 exp2] `(if ~cond ~exp1 ~exp2)) :neg (fn [exp1] `(- 0 ~exp1)) :plus (fn [& exps] `(+ ~@exps ))}) ;; compile directly to Clojure source code (defn my-compile [code] (cond (sequential? code) ;; if we have a list, look up the function in funcs (cons (funcs (first code)) (map compile (rest code))) :else ;; treat anything else as a constant literal code)) ;; example compilation to a Clojure expression (my-compile `(:if true (:neg 10) (:plus 10 20 30))) => (if true (clojure.core/- 0 10) (clojure.core/+ 10 20 30)) ;; evaluate compiled code (eval (my-compile `(:if true (:neg 10) (:plus 10 20 30)))) => -10
Hope this is enough to give you some ideas / get started. The obvious extensions to consider would be:
- Compile the AST tree with metadata, not directly to a Clojure source. A Clojure
defrecord may be appropriate as an AST node representation - Add other statements, loop builders, "goto", etc.
- Simple optimizations, for example. evaluating constant expressions at compile time
- It has some kind of execution context that allows you to assign dynamic search for variables, etc. The compiler output may be a function that takes the source context as input and returns the final context.
source share