|
| 1 | +(ns ^{ :doc "Implementation of lynaghk/cljx's basic semantics using rewrite-clj."} |
| 2 | + rewrite-clj.cljx |
| 3 | + (:require [rewrite-clj.parser :as p] |
| 4 | + [rewrite-clj.zip :as z] |
| 5 | + [rewrite-clj.printer :as prn])) |
| 6 | + |
| 7 | +;; ## Semantics |
| 8 | +;; |
| 9 | +;; cljx allows for Clojure forms to be prefixed with reader macros of the form |
| 10 | +;; "#+<profile>" and "#-<profile>". If the given profile is active, everything with |
| 11 | +;; "+" will remain in the code (minus the prefix) and everything with '-' will be removed. |
| 12 | +;; |
| 13 | +;; (defn my-inc |
| 14 | +;; [x] |
| 15 | +;; #+debug (println "inc: x =" x) |
| 16 | +;; (+ x 1)) |
| 17 | +;; |
| 18 | +;; If the profile 'debug' is active, the following result will be created: |
| 19 | +;; |
| 20 | +;; (defn my-inc |
| 21 | +;; [x] |
| 22 | +;; (println "inc: x = " x) |
| 23 | +;; (+ x 1)) |
| 24 | +;; |
| 25 | +;; Whitespace has thus to be preserved. |
| 26 | + |
| 27 | +;; ## Implementation |
| 28 | + |
| 29 | +(defn- cljx-macro? |
| 30 | + "Check if the given zipper node contains a cljx reader macro ('#+...' or '#-...')." |
| 31 | + [loc] |
| 32 | + (when (= (z/tag loc) :reader-macro) |
| 33 | + (let [[t sym] (z/value loc)] |
| 34 | + (when (and (= t :token) (symbol? sym)) |
| 35 | + (let [^String nm (name sym)] |
| 36 | + (or (.startsWith nm "+") (.startsWith nm "-"))))))) |
| 37 | + |
| 38 | +(defn- replace-with-spaces |
| 39 | + "Replace the given reader macro node with spaces." |
| 40 | + [zloc] |
| 41 | + (let [w (count (with-out-str (prn/print-edn (z/node zloc))))] |
| 42 | + (-> zloc (z/prepend-space w) z/remove))) |
| 43 | + |
| 44 | +(defn- remove-reader-macro |
| 45 | + "Remove the macro part of the given reader macro node." |
| 46 | + [zloc] |
| 47 | + (-> zloc |
| 48 | + z/down replace-with-spaces ;; replace the '+...'/'-...' part with spaces |
| 49 | + z/up z/splice ;; remove the macro wrapper |
| 50 | + z/prepend-space)) ;; insert a space to make up for the missing '#' |
| 51 | + |
| 52 | +(defn- handle-reader-macro |
| 53 | + "Handle a reader macro node by either removing it completely or only the macro part." |
| 54 | + [active-profiles zloc] |
| 55 | + (let [profile-node (-> zloc z/down) |
| 56 | + value-node (-> profile-node z/right) |
| 57 | + ^String profile-string (-> profile-node z/sexpr name) |
| 58 | + profile-active? (contains? active-profiles (.substring profile-string 1)) |
| 59 | + print? (.startsWith profile-string "+")] |
| 60 | + (if (or (and profile-active? (not print?)) (and (not profile-active?) print?)) |
| 61 | + (replace-with-spaces zloc) |
| 62 | + (remove-reader-macro zloc)))) |
| 63 | + |
| 64 | +(defn cljx-walk |
| 65 | + "Replace all occurences of profile reader macros." |
| 66 | + [active-profiles root] |
| 67 | + (loop [loc root] |
| 68 | + (if-let [mloc (z/find loc z/next cljx-macro?)] |
| 69 | + (recur (handle-reader-macro active-profiles mloc)) |
| 70 | + loc))) |
| 71 | + |
| 72 | +;; ## Test |
| 73 | + |
| 74 | +(comment |
| 75 | + (def data |
| 76 | + (z/edn |
| 77 | + (p/parse-string |
| 78 | + "(defn my-inc\n [x]\n #+debug (println \"inc: x =\" x #-nomark \"[debug]\")\n (+ x 1))"))) |
| 79 | + |
| 80 | + (println "Original Code:") |
| 81 | + (-> data z/root prn/print-edn) |
| 82 | + (println "\n") |
| 83 | + |
| 84 | + (println "Processed Code:") |
| 85 | + (-> (cljx-walk #{"debug" "nomark"} data) z/root prn/print-edn) |
| 86 | + (println)) |
0 commit comments