(define
  lua-tx
  (fn
    (node)
    (cond
      ((= node nil) nil)
      ((not (= (type-of node) "list")) node)
      (else (lua-tx-dispatch (first node) node)))))

(define
  lua-tx-dispatch
  (fn
    (tag node)
    (cond
      ((= tag (quote lua-num)) (nth node 1))
      ((= tag (quote lua-str)) (nth node 1))
      ((= tag (quote lua-nil)) nil)
      ((= tag (quote lua-true)) true)
      ((= tag (quote lua-false)) false)
      ((= tag (quote lua-name)) (make-symbol (nth node 1)))
      ((= tag (quote lua-vararg))
        (error "lua-transpile: ... not yet supported"))
      ((= tag (quote lua-binop)) (lua-tx-binop node))
      ((= tag (quote lua-unop)) (lua-tx-unop node))
      ((= tag (quote lua-call)) (lua-tx-call node))
      ((= tag (quote lua-method-call)) (lua-tx-method-call node))
      ((= tag (quote lua-field)) (lua-tx-field node))
      ((= tag (quote lua-index)) (lua-tx-index node))
      ((= tag (quote lua-table)) (lua-tx-table node))
      ((= tag (quote lua-function)) (lua-tx-function node))
      ((= tag (quote lua-block)) (lua-tx-block node))
      ((= tag (quote lua-local)) (lua-tx-local node))
      ((= tag (quote lua-assign)) (lua-tx-assign node))
      ((= tag (quote lua-if)) (lua-tx-if node))
      ((= tag (quote lua-while)) (lua-tx-while node))
      ((= tag (quote lua-repeat)) (lua-tx-repeat node))
      ((= tag (quote lua-for-num)) (lua-tx-for-num node))
      ((= tag (quote lua-do)) (lua-tx-do node))
      ((= tag (quote lua-break)) (quote lua-break-marker))
      ((= tag (quote lua-return)) (lua-tx-return node))
      ((= tag (quote lua-call-stmt)) (lua-tx (nth node 1)))
      ((= tag (quote lua-local-function)) (lua-tx-local-function node))
      ((= tag (quote lua-function-decl)) (lua-tx-function-decl node))
      (else (error (str "lua-transpile: unknown node " tag))))))

(define
  lua-tx-binop
  (fn
    (node)
    (let
      ((op (nth node 1))
        (a (lua-tx (nth node 2)))
        (b (lua-tx (nth node 3))))
      (cond
        ((= op "+") (list (make-symbol "lua-add") a b))
        ((= op "-") (list (make-symbol "lua-sub") a b))
        ((= op "*") (list (make-symbol "lua-mul") a b))
        ((= op "/") (list (make-symbol "lua-div") a b))
        ((= op "%") (list (make-symbol "lua-mod") a b))
        ((= op "^") (list (make-symbol "lua-pow") a b))
        ((= op "..") (list (make-symbol "lua-concat") a b))
        ((= op "==") (list (make-symbol "lua-eq") a b))
        ((= op "~=") (list (make-symbol "lua-neq") a b))
        ((= op "<") (list (make-symbol "lua-lt") a b))
        ((= op "<=") (list (make-symbol "lua-le") a b))
        ((= op ">") (list (make-symbol "lua-gt") a b))
        ((= op ">=") (list (make-symbol "lua-ge") a b))
        ((= op "and")
          (list
            (make-symbol "let")
            (list (list (make-symbol "_la") a))
            (list
              (make-symbol "if")
              (list (make-symbol "lua-truthy?") (make-symbol "_la"))
              b
              (make-symbol "_la"))))
        ((= op "or")
          (list
            (make-symbol "let")
            (list (list (make-symbol "_la") a))
            (list
              (make-symbol "if")
              (list (make-symbol "lua-truthy?") (make-symbol "_la"))
              (make-symbol "_la")
              b)))
        (else (error (str "lua-transpile: unknown binop " op)))))))

(define
  lua-tx-unop
  (fn
    (node)
    (let
      ((op (nth node 1)) (e (lua-tx (nth node 2))))
      (cond
        ((= op "-") (list (make-symbol "lua-neg") e))
        ((= op "not")
          (list (make-symbol "not") (list (make-symbol "lua-truthy?") e)))
        ((= op "#") (list (make-symbol "lua-len") e))
        (else (error (str "lua-transpile: unknown unop " op)))))))

(define
  lua-tx-call
  (fn
    (node)
    (let
      ((fn-ast (nth node 1)) (args (nth node 2)))
      (cons (lua-tx fn-ast) (map lua-tx args)))))

(define
  lua-tx-method-call
  (fn
    (node)
    (let
      ((obj (lua-tx (nth node 1)))
        (name (nth node 2))
        (args (nth node 3)))
      (cons
        (list (make-symbol "lua-get") obj name)
        (cons obj (map lua-tx args))))))

(define
  lua-tx-field
  (fn
    (node)
    (list (make-symbol "lua-get") (lua-tx (nth node 1)) (nth node 2))))

(define
  lua-tx-index
  (fn
    (node)
    (list
      (make-symbol "lua-get")
      (lua-tx (nth node 1))
      (lua-tx (nth node 2)))))

(define
  lua-tx-table
  (fn
    (node)
    (let
      ((fields (rest node)))
      (cons (make-symbol "lua-make-table") (map lua-tx-table-field fields)))))

(define
  lua-tx-table-field
  (fn
    (f)
    (cond
      ((= (first f) (quote lua-pos))
        (list (make-symbol "list") "pos" (lua-tx (nth f 1))))
      ((= (first f) (quote lua-kv))
        (list
          (make-symbol "list")
          "kv"
          (lua-tx (nth f 1))
          (lua-tx (nth f 2))))
      (else (error "lua-transpile: unknown table field")))))

(define
  lua-tx-function
  (fn
    (node)
    (let
      ((params (nth node 1))
        (is-vararg (nth node 2))
        (body (nth node 3)))
      (let
        ((sym-params (map make-symbol params)))
        (list (make-symbol "fn") sym-params (lua-tx body))))))

(define
  lua-tx-block
  (fn
    (node)
    (let
      ((stmts (rest node)))
      (cond
        ((= (len stmts) 0) nil)
        ((= (len stmts) 1) (lua-tx (first stmts)))
        (else (cons (make-symbol "begin") (map lua-tx stmts)))))))

(define
  lua-tx-local
  (fn
    (node)
    (let
      ((names (nth node 1)) (exps (nth node 2)))
      (cond
        ((= (len names) 1)
          (list
            (make-symbol "define")
            (make-symbol (first names))
            (if (> (len exps) 0) (lua-tx (first exps)) nil)))
        (else
          (cons (make-symbol "begin") (lua-tx-local-pairs names exps 0)))))))

(define
  lua-tx-local-pairs
  (fn
    (names exps i)
    (if
      (>= i (len names))
      (list)
      (cons
        (list
          (make-symbol "define")
          (make-symbol (nth names i))
          (if (< i (len exps)) (lua-tx (nth exps i)) nil))
        (lua-tx-local-pairs names exps (+ i 1))))))

(define
  lua-tx-assign
  (fn
    (node)
    (let
      ((lhss (nth node 1)) (rhss (nth node 2)))
      (cond
        ((= (len lhss) 1)
          (lua-tx-single-assign (first lhss) (lua-tx (first rhss))))
        (else
          (cons (make-symbol "begin") (lua-tx-assign-pairs lhss rhss 0)))))))

(define
  lua-tx-assign-pairs
  (fn
    (lhss rhss i)
    (if
      (>= i (len lhss))
      (list)
      (cons
        (lua-tx-single-assign
          (nth lhss i)
          (if (< i (len rhss)) (lua-tx (nth rhss i)) nil))
        (lua-tx-assign-pairs lhss rhss (+ i 1))))))

(define
  lua-tx-single-assign
  (fn
    (lhs rhs)
    (cond
      ((= (first lhs) (quote lua-name))
        (list (make-symbol "set!") (make-symbol (nth lhs 1)) rhs))
      ((= (first lhs) (quote lua-field))
        (list
          (make-symbol "lua-set!")
          (lua-tx (nth lhs 1))
          (nth lhs 2)
          rhs))
      ((= (first lhs) (quote lua-index))
        (list
          (make-symbol "lua-set!")
          (lua-tx (nth lhs 1))
          (lua-tx (nth lhs 2))
          rhs))
      (else (error "lua-transpile: bad assignment target")))))

(define
  lua-tx-if
  (fn
    (node)
    (let
      ((cnd (lua-tx (nth node 1)))
        (then-body (lua-tx (nth node 2)))
        (elseifs (nth node 3))
        (else-body (nth node 4)))
      (if
        (and (= (len elseifs) 0) (= else-body nil))
        (list
          (make-symbol "when")
          (list (make-symbol "lua-truthy?") cnd)
          then-body)
        (lua-tx-if-cond cnd then-body elseifs else-body)))))

(define
  lua-tx-if-cond
  (fn
    (cnd then-body elseifs else-body)
    (let
      ((clauses (cons (list (list (make-symbol "lua-truthy?") cnd) then-body) (map lua-tx-elseif elseifs))))
      (cons
        (make-symbol "cond")
        (if
          (= else-body nil)
          clauses
          (append
            clauses
            (list (list (make-symbol "else") (lua-tx else-body)))))))))

(define
  lua-tx-elseif
  (fn
    (pair)
    (list
      (list (make-symbol "lua-truthy?") (lua-tx (first pair)))
      (lua-tx (nth pair 1)))))

(define
  lua-tx-while
  (fn
    (node)
    (let
      ((cnd (lua-tx (nth node 1))) (body (lua-tx (nth node 2))))
      (list
        (make-symbol "let")
        (list)
        (list
          (make-symbol "define")
          (make-symbol "_while_loop")
          (list
            (make-symbol "fn")
            (list)
            (list
              (make-symbol "when")
              (list (make-symbol "lua-truthy?") cnd)
              (list
                (make-symbol "begin")
                body
                (list (make-symbol "_while_loop"))))))
        (list (make-symbol "_while_loop"))))))

(define
  lua-tx-repeat
  (fn
    (node)
    (let
      ((body (lua-tx (nth node 1))) (cnd (lua-tx (nth node 2))))
      (list
        (make-symbol "let")
        (list)
        (list
          (make-symbol "define")
          (make-symbol "_repeat_loop")
          (list
            (make-symbol "fn")
            (list)
            (list
              (make-symbol "begin")
              body
              (list
                (make-symbol "when")
                (list
                  (make-symbol "not")
                  (list (make-symbol "lua-truthy?") cnd))
                (list (make-symbol "_repeat_loop"))))))
        (list (make-symbol "_repeat_loop"))))))

(define
  lua-tx-for-num
  (fn
    (node)
    (let
      ((name (nth node 1))
        (start (lua-tx (nth node 2)))
        (stop (lua-tx (nth node 3)))
        (step-ast (nth node 4))
        (body (lua-tx (nth node 5))))
      (let
        ((step (if (= step-ast nil) 1 (lua-tx step-ast))))
        (list
          (make-symbol "let")
          (list
            (list (make-symbol "_for_stop") stop)
            (list (make-symbol "_for_step") step))
          (list (make-symbol "define") (make-symbol name) start)
          (list
            (make-symbol "define")
            (make-symbol "_for_loop")
            (list
              (make-symbol "fn")
              (list)
              (list
                (make-symbol "when")
                (list
                  (make-symbol "lua-for-continue?")
                  (make-symbol name)
                  (make-symbol "_for_stop")
                  (make-symbol "_for_step"))
                (list
                  (make-symbol "begin")
                  body
                  (list
                    (make-symbol "set!")
                    (make-symbol name)
                    (list
                      (make-symbol "+")
                      (make-symbol name)
                      (make-symbol "_for_step")))
                  (list (make-symbol "_for_loop"))))))
          (list (make-symbol "_for_loop")))))))

(define lua-tx-do (fn (node) (lua-tx (nth node 1))))

(define
  lua-tx-return
  (fn
    (node)
    (let
      ((exps (nth node 1)))
      (cond
        ((= (len exps) 0) nil)
        ((= (len exps) 1) (lua-tx (first exps)))
        (else (cons (make-symbol "list") (map lua-tx exps)))))))

(define
  lua-tx-local-function
  (fn
    (node)
    (let
      ((name (nth node 1)) (func (nth node 2)))
      (list (make-symbol "define") (make-symbol name) (lua-tx func)))))

(define
  lua-tx-function-decl
  (fn
    (node)
    (let
      ((target (nth node 1)) (func (nth node 2)))
      (cond
        ((= (first target) (quote lua-name))
          (list
            (make-symbol "define")
            (make-symbol (nth target 1))
            (lua-tx func)))
        ((= (first target) (quote lua-field))
          (list
            (make-symbol "lua-set!")
            (lua-tx (nth target 1))
            (nth target 2)
            (lua-tx func)))
        (else (error "lua-transpile: bad function decl target"))))))

(define lua-transpile (fn (src) (lua-tx (lua-parse src))))

(define
  lua-eval-ast
  (fn (src) (let ((sx (lua-transpile src))) (eval-expr sx))))
