;; Erlang module parser — reads top-level forms and builds a module AST.
;;
;; Depends on parser-core.sx, parser.sx, parser-expr.sx.

(define
  er-parse-module
  (fn
    (src)
    (let
      ((st (er-state-make (er-tokenize src)))
        (mod-ref (list nil))
        (attrs (list))
        (functions (list)))
      (er-parse-module-loop st mod-ref attrs functions)
      {:functions functions :type "module" :attrs attrs :name (nth mod-ref 0)})))

(define
  er-parse-module-loop
  (fn
    (st mod-ref attrs functions)
    (when
      (not (er-at-eof? st))
      (er-parse-top-form st mod-ref attrs functions)
      (er-parse-module-loop st mod-ref attrs functions))))

(define
  er-parse-top-form
  (fn
    (st mod-ref attrs functions)
    (cond
      (er-is? st "op" "-")
      (do
        (er-advance! st)
        (let
          ((attr-name (er-cur-value st)))
          (er-advance! st)
          (let
            ((args (er-parse-attr-args st)))
            (er-expect! st "punct" ".")
            (cond
              (= attr-name "module")
              (set-nth! mod-ref 0 (get (nth args 0) :value))
              :else (append! attrs {:args args :name attr-name})))))
      (= (er-cur-type st) "atom")
      (append! functions (er-parse-function st))
      :else (error
        (str
          "Erlang parse (top): unexpected "
          (er-cur-type st)
          " '"
          (er-cur-value st)
          "' at pos "
          (get (er-cur st) :pos))))))

(define
  er-parse-attr-args
  (fn
    (st)
    (er-expect! st "punct" "(")
    (if
      (er-is? st "punct" ")")
      (do (er-advance! st) (list))
      (let
        ((args (list (er-parse-attr-arg st))))
        (er-parse-attr-args-tail st args)))))

(define
  er-parse-attr-args-tail
  (fn
    (st args)
    (cond
      (er-is? st "punct" ",")
      (do
        (er-advance! st)
        (append! args (er-parse-attr-arg st))
        (er-parse-attr-args-tail st args))
      (er-is? st "punct" ")")
      (do (er-advance! st) args)
      :else (error (str "Erlang parse attr: got '" (er-cur-value st) "'")))))

;; Attribute args often contain `Name/Arity` pairs — parse as a
;; general expression so the caller can interpret the shape.
(define er-parse-attr-arg (fn (st) (er-parse-expr-prec st 0)))

(define
  er-parse-function
  (fn
    (st)
    (let
      ((name (er-cur-value st)))
      (er-advance! st)
      (let
        ((clauses (list (er-parse-fun-clause st name))))
        (er-parse-function-tail st name clauses)
        (er-expect! st "punct" ".")
        (let ((arity (len (get (nth clauses 0) :patterns)))) {:arity arity :clauses clauses :type "function" :name name})))))

(define
  er-parse-function-tail
  (fn
    (st name clauses)
    (when
      (er-is? st "punct" ";")
      (let
        ((save (get st :idx)))
        (er-advance! st)
        (if
          (and (= (er-cur-type st) "atom") (= (er-cur-value st) name))
          (do
            (er-advance! st)
            (append! clauses (er-parse-fun-clause st name))
            (er-parse-function-tail st name clauses))
          (dict-set! st :idx save))))))
