(defn analyze-fn-method [[params & body :as form] {:keys [locals local] :as env}]
(when-not (vector? params)
(throw (ex-info "Parameter declaration should be a vector"
(merge {:params params
:form form}
(-source-info form env)
(-source-info params env)))))
(when (not-every? valid-binding-symbol? params)
(throw (ex-info (str "Params must be valid binding symbols, had: "
(mapv class params))
(merge {:params params
:form form}
(-source-info form env)
(-source-info params env))))) ;; more specific
(let [variadic? (boolean (some '#{&} params))
params-names (if variadic? (conj (pop (pop params)) (peek params)) params)
env (dissoc env :local)
arity (count params-names)
params-expr (mapv (fn [name id]
{:env env
:form name
:name name
:variadic? (and variadic?
(= id (dec arity)))
:op :binding
:arg-id id
:local :arg})
params-names (range))
fixed-arity (if variadic?
(dec arity)
arity)
loop-id (gensym "loop_")
body-env (into (update-in env [:locals]
merge (zipmap params-names (map dissoc-env params-expr)))
{:context :ctx/return
:loop-id loop-id
:loop-locals (count params-expr)})
body (analyze-body body body-env)]
(when variadic?
(let [x (drop-while #(not= % '&) params)]
(when (contains? #{nil '&} (second x))
(throw (ex-info "Invalid parameter list"
(merge {:params params
:form form}
(-source-info form env)
(-source-info params env)))))
(when (not= 2 (count x))
(throw (ex-info (str "Unexpected parameter: " (first (drop 2 x))
" after variadic parameter: " (second x))
(merge {:params params
:form form}
(-source-info form env)
(-source-info params env)))))))
(merge
{:op :fn-method
:form form
:loop-id loop-id
:env env
:variadic? variadic?
:params params-expr
:fixed-arity fixed-arity
:body body
:children [:params :body]}
(when local
{:local (dissoc-env local)}))))