@pbaille Here’s an example:
;; [name doc-string? attr-map? [params*] prepost-map? body]
;; [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?]
(defn parse-defn [form]
(m/rewrite form
(m/with [%doc-and-rest (m/or ((m/pred string? ?doc-string) & %attr-map-and-rest)
(m/let [?doc-string nil] %attr-map-and-rest))
%attr-map-and-rest (m/or ((m/pred map? ?attr-map) & %fn-tail)
(m/let [?attr-map nil] (& %fn-tail)))
%fn-tail (m/or %fn-tail-1 %fn-tail-n)
%fn-tail-1 (m/and ([& !params] & _) (m/let [?tail-attr-map nil]))
%fn-tail-n (%fn-tail-1 ... & %tail-attr-map)
%tail-attr-map (m/or {:as ?tail-attr-map} (m/let [?tail-attr-map nil] ()))]
(`defn ?name & %doc-and-rest))
{:name ?name
:doc-string ?doc-string
:attr-map ~(merge ?attr-map ?tail-attr-map)
:fn-specs [{:params !params} ...]}))
["no doc, no attr map, 1 arity"
(parse-defn '(clojure.core/defn foo [x y] 42))
"doc, no attr map, 1 arity"
(parse-defn '(clojure.core/defn foo "doc" [x y] 42))
"no doc, attr map, 1 arity"
(parse-defn '(clojure.core/defn foo {:doc "doc"} [x y] 42))
"doc, attr map, 1 arity"
(parse-defn '(clojure.core/defn foo "foo" {:doc "doc"} [x y] 42))
"no doc, no attr map, n arity"
(parse-defn '(clojure.core/defn foo ([x] 41) ([x y] 42)))
"doc, no attr map, n arity"
(parse-defn '(clojure.core/defn foo "doc" ([x] 41) ([x y] 42)))
"no doc, attr map, n arity"
(parse-defn '(clojure.core/defn foo {:doc "doc"} ([x] 41) ([x y] 42)))
"doc, attr map, n arity"
(parse-defn '(clojure.core/defn foo "foo" {:doc "doc"} ([x] 41) ([x y] 42)))]
;; =>
["no doc, no attr map, 1 arity"
{:name foo,
:doc-string nil,
:attr-map nil,
:fn-specs [{:params [x y]}]}
"doc, no attr map, 1 arity"
{:name foo,
:doc-string "doc",
:attr-map nil,
:fn-specs [{:params [x y]}]}
"no doc, attr map, 1 arity"
{:name foo,
:doc-string nil,
:attr-map {:doc "doc"},
:fn-specs [{:params [x y]}]}
"doc, attr map, 1 arity"
{:name foo,
:doc-string "foo",
:attr-map {:doc "doc"},
:fn-specs [{:params [x y]}]}
"no doc, no attr map, n arity"
{:name foo,
:doc-string nil,
:attr-map nil,
:fn-specs [{:params [x]} {:params [x y]}]}
"doc, no attr map, n arity"
{:name foo,
:doc-string "doc",
:attr-map nil,
:fn-specs [{:params [x]} {:params [x y]}]}
"no doc, attr map, n arity"
{:name foo,
:doc-string nil,
:attr-map {:doc "doc"},
:fn-specs [{:params [x]} {:params [x y]}]}
"doc, attr map, n arity"
{:name foo,
:doc-string "foo",
:attr-map {:doc "doc"},
:fn-specs [{:params [x]} {:params [x y]}]}]
The other way to write it is without with
where you specify each case.
(defn parse-defn [form]
(m/rewrite form
(`defn ?name [& ?params] & ?body)
{:name ?name
:doc-string nil
:attr-map nil
:fn-specs [{:params ?params}]}
,,,))
TBH — and I know no one here is soliciting my opinion — this is why I’m not a big fan of optionality: it breeds a lot of complexity.
There are actually 16 possible ways to write defn
.
16.
sure but not all complexity is incidental 😛
Just like all things, its probably worthwhile to use the word when e.g. When is complexity incidental? When you’re coding. When is it not? When you’re parsing. 🙂
:thumbsup: