;; Erlang parser — turns a token list into an AST.
;;
;; Shared state lives in the surrounding `let` of `er-parse-*`.
;; All helpers use recursion (no `while` in SX).
;;
;; AST node shapes:
;;   {:type "atom"      :value "foo"}
;;   {:type "integer"   :value "42"}                  ; value kept as string
;;   {:type "float"     :value "3.14"}
;;   {:type "string"    :value "hi"}
;;   {:type "var"       :name "X"}                    ; "_" is wildcard
;;   {:type "nil"}
;;   {:type "tuple"     :elements [...]}
;;   {:type "cons"      :head E :tail E}
;;   {:type "call"      :fun E :args [...]}
;;   {:type "remote"    :mod E :fun E}
;;   {:type "op"        :op OP :args [L R]}
;;   {:type "unop"      :op OP :arg E}
;;   {:type "match"     :lhs P :rhs E}
;;   {:type "send"      :to E :msg E}
;;   {:type "if"        :clauses [{:guards [...] :body [...]} ...]}
;;   {:type "case"      :expr E :clauses [{:pattern P :guards [...] :body [...]} ...]}
;;   {:type "receive"   :clauses [...] :after-ms E-or-nil :after-body [...]}
;;   {:type "fun"       :clauses [...]}
;;   {:type "block"     :exprs [...]}
;;   {:type "try"       :exprs [...] :of-clauses [...] :catch-clauses [...] :after [...]}
;;   Top-level: {:type "module" :name A :attrs [{:name A :args [...]} ...] :functions [...]}
;;              {:type "function" :name A :arity N :clauses [{:name :patterns :guards :body}]}

(define
  er-is-binop?
  (fn
    (tok prec)
    (let
      ((ty (get tok :type)) (v (get tok :value)))
      (cond
        (= prec 0)
        (and (= ty "op") (= v "="))
        (= prec 1)
        (and (= ty "op") (= v "!"))
        (= prec 2)
        (or
          (and (= ty "keyword") (= v "orelse"))
          (and (= ty "keyword") (= v "or"))
          (and (= ty "keyword") (= v "xor")))
        (= prec 3)
        (or
          (and (= ty "keyword") (= v "andalso"))
          (and (= ty "keyword") (= v "and")))
        (= prec 4)
        (and
          (= ty "op")
          (or
            (= v "==")
            (= v "/=")
            (= v "=:=")
            (= v "=/=")
            (= v "<")
            (= v ">")
            (= v "=<")
            (= v ">=")))
        (= prec 5)
        (and (= ty "op") (or (= v "++") (= v "--")))
        (= prec 6)
        (and (= ty "op") (or (= v "+") (= v "-")))
        (= prec 7)
        (or
          (and (= ty "op") (or (= v "*") (= v "/")))
          (and
            (= ty "keyword")
            (or
              (= v "div")
              (= v "rem")
              (= v "band")
              (= v "bor")
              (= v "bxor")
              (= v "bsl")
              (= v "bsr"))))
        :else false))))

(define
  er-any-binop?
  (fn
    (tok min-prec)
    (or
      (and (>= 0 min-prec) (er-is-binop? tok 0))
      (and (>= 1 min-prec) (er-is-binop? tok 1))
      (and (>= 2 min-prec) (er-is-binop? tok 2))
      (and (>= 3 min-prec) (er-is-binop? tok 3))
      (and (>= 4 min-prec) (er-is-binop? tok 4))
      (and (>= 5 min-prec) (er-is-binop? tok 5))
      (and (>= 6 min-prec) (er-is-binop? tok 6))
      (and (>= 7 min-prec) (er-is-binop? tok 7)))))

(define
  er-slice-list
  (fn
    (xs from)
    (if
      (>= from (len xs))
      (list)
      (let
        ((out (list)))
        (for-each
          (fn (i) (append! out (nth xs i)))
          (range from (len xs)))
        out))))

(define
  er-build-cons
  (fn (elems tail) (if (= (len elems) 0) tail {:head (nth elems 0) :tail (er-build-cons (er-slice-list elems 1) tail) :type "cons"})))
