Introduction

We can parse with clojure.spec!

Zen

Require


    (ns my.spec
        (:require [clojure.spec :as s]
                  clojure.test.check.generators))

balanced parenthesis

(s/def ::balanced-parenthesis 
  (s/* (s/cat :open #{"("}
                   :p (s/? ::balanced-parenthesis)
                   :close #{")"})))
(s/explain-str ::balanced-parenthesis (seq "(()())"))
(s/explain-str ::balanced-parenthesis (seq "())())"))
(s/exercise ::balanced-parenthesis)

Palindromes

(s/def ::palindrome 
  (s/* (s/alt :a (s/cat :a1 #{"a"}
                        :rest ::palindrome
                        :a2 #{"a"})
              :b (s/cat :b1 #{"b"}
                        :rest ::palindrome
                        :b2 #{"b"}))))
(s/explain-str ::palindrome (seq "abba"))

Another implementation for palindromes

Thanks Alex Miller!

(s/def ::pal 
  (s/alt :0 (s/cat)
         :1 int?
         :n (s/& (s/cat :a int? :b ::pal :c int?)
                 (fn [{:keys [a c]}] (= a c)))))
(s/explain-str ::pal [1 2 2 1])

Algebraic expressions

(s/def ::my-int (s/* #{\0 \1 \2 \3 \4 \5 \6 \7 \8 \9}))
(s/def ::ar (s/alt :operation (s/cat :a ::my-int      
                                     :r (s/? (s/cat :op #{"+"}
                                                    :b (s/alt :i ::my-int :e ::ar))))
                   :parentheses (s/cat :o #{"("}
                                       :b (s/alt :i ::my-int :e ::ar)
                                       :c #{")"})))

(s/explain-str ::ar (seq "(2+3)"))

args of defn macro

;;;; destructure

(s/def ::local-name (s/and simple-symbol? #(not= '& %)))

(s/def ::binding-form
  (s/or :sym ::local-name
        :seq ::seq-binding-form
        :map ::map-binding-form))

;; sequential destructuring

(s/def ::seq-binding-form
  (s/and vector?
         (s/conformer vec vec)
         (s/cat :elems (s/* ::binding-form)
                :rest (s/? (s/cat :amp #{'&} :form ::binding-form))
                :as (s/? (s/cat :as #{:as} :sym ::local-name)))))

;; map destructuring

(s/def ::keys (s/coll-of ident? :kind vector?))
(s/def ::syms (s/coll-of symbol? :kind vector?))
(s/def ::strs (s/coll-of simple-symbol? :kind vector?))
(s/def ::or (s/map-of simple-symbol? any?))
(s/def ::as ::local-name)

(s/def ::map-special-binding
  (s/keys :opt-un [::as ::or ::keys ::syms ::strs]))

(s/def ::map-binding (s/tuple ::binding-form any?))

(s/def ::ns-keys
  (s/tuple
    (s/and qualified-keyword? #(-> % name #{"keys" "syms"}))
    (s/coll-of simple-symbol? :kind vector?)))

(s/def ::map-bindings
  (s/every (s/or :mb ::map-binding
                 :nsk ::ns-keys
                 :msb (s/tuple #{:as :or :keys :syms :strs} any?)) :into {}))

(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding))

;; bindings

(s/def ::binding (s/cat :binding ::binding-form :init-expr any?))
(s/def ::bindings (s/and vector? (s/* ::binding)))

;; defn, defn-, fn

(s/def ::arg-list
  (s/and
    vector?
    (s/conformer vec vec)
    (s/cat :args (s/* ::binding-form)
           :varargs (s/? (s/cat :amp #{'&} :form ::binding-form)))))

(s/def ::args+body
  (s/cat :args ::arg-list
         :prepost (s/? map?)
         :body (s/* any?)))

(s/def ::defn-args
  (s/cat :name simple-symbol?
         :docstring (s/? string?)
         :meta (s/? map?)
         :bs (s/alt :arity-1 ::args+body
                    :arity-n (s/cat :bodies (s/+ (s/spec ::args+body))
                                    :attr (s/? map?)))))


(s/def ::arg-list
  (s/and
    vector?
    (s/conformer vec vec)
    (s/cat :args (s/* ::binding-form)
           :varargs (s/? (s/cat :amp #{'&} :form ::binding-form)))))

(s/def ::args+body
  (s/cat :args ::arg-list
         :prepost (s/? map?)
         :body (s/* any?)))

(s/def ::defn-args
  (s/cat :name simple-symbol?
         :docstring (s/? string?)
         :meta (s/? map?)
         :bs (s/alt :arity-1 ::args+body
                    :arity-n (s/cat :bodies (s/+ (s/spec
 ::args+body))
                                    :attr (s/? map?)))))

(s/conform ::defn-args '(foo "foo is a multi-arity function" {:private true} ([a b] (+ a b)) ([] (foo 1 1))))
(s/conform ::arg-list '[a b & c])
(s/conform ::binding-form '{:keys [a b c] :or {aa 2} :as pp})
(s/conform ::defn-args '(foo "foo multiplies a and b" [[a b]] (+ a b)))
(s/unform ::defn-args (s/conform ::defn-args '(foo "aa" [[a b]] (+ a b (first c)))))

custom defn

(ns my.m$macros
  (:require [clojure.spec :as s]))
(defmacro defprint [& args]
  (let [conf (s/conform :my.spec/defn-args args)
        name (:name conf)
        new-conf (update-in conf [:bs 1 :body] #(cons `(print '~name "has been called") %))
        new-args (s/unform :my.spec/defn-args new-conf)]
    (print "conf: " conf)
    (print "name:" name)
    (print "new-conf: " new-conf)
     (print "new-args: " new-args)
    (cons 'cljs.core/defn new-args)))
(my.m/defprint foo "aa" [[a b]] (+ a b (first c)))
(foo [55 200 10 200])
(with-out-str (foo [55 200]))

multi-arity


(defn update-conf [arity conf body-update]
  (case arity
    :arity-1 (update-in conf [:bs 1 :body] body-update)
    :arity-n
    (let [bodies (:bodies (second (:bs conf)))
          new-bodies (mapv (fn [body] (update body :body body-update)) bodies)]
      (assoc-in conf [:bs 1 :bodies] new-bodies))))

(defmacro defprint-multi [& args]
  (let [conf (s/conform :my.spec/defn-args args)
        name (:name conf)
        arity (first (:bs conf))
        my-conf (update-conf arity conf #(cons `(print '~name " is  called")))
        new-args (s/unform :my.spec/defn-args my-conf)]
    (print "old-conf:" conf)
    (print "new-conf:" my-conf)
    (cons 'cljs.core/defn new-args)))
(defn new-conf [arity conf name]
  (case arity
    :arity-1 (update-in conf [:bs 1 :body] #(cons `(print '~name "has been called") %))
    :arity-n
    (let [bodies (:bodies (second (:bs conf)))
          new-bodies (mapv (fn [body] (update body :body #(cons `(print '~name "has been called") %))) bodies)]
      (assoc-in conf [:bs 1 :bodies] new-bodies))))

(defmacro defprint-multi [& args]
  (let [conf (s/conform :my.spec/defn-args args)
        name (:name conf)
        arity (first (:bs conf))
        my-conf (new-conf arity conf name)
        new-args (s/unform :my.spec/defn-args my-conf)]
    (print "old-conf:" conf)
    (print "new-conf:" my-conf)
    (cons 'cljs.core/defn new-args)))
(my.m/defprint-multi foo "aa" [{:keys [a b]}] (+ a b (first c)))
(foo {:a 55 :b 200})
(my.m/defprint-multi bar 
  ([] (* 10 12))
  ([a b] (* a b)))

(bar)
(bar 12 3)
(keys my-spec)