; APL transpile / AST evaluator
;
; Walks parsed AST nodes and evaluates against the runtime.
; Entry points:
;   apl-eval-ast    : node × env → value
;   apl-eval-stmts  : stmt-list × env → value (handles guards, locals, ⍺← default)
;   apl-call-dfn    : dfn-ast × ⍺ × ⍵ → value   (dyadic)
;   apl-call-dfn-m  : dfn-ast × ⍵ → value       (monadic)
;
; Env is a dict; ⍺ stored under "alpha", ⍵ under "omega",
; the dfn-ast itself under "nabla" (for ∇ recursion),
; user names under their literal name.

(define
  apl-monadic-fn
  (fn
    (g)
    (cond
      ((= g "+") apl-plus-m)
      ((= g "-") apl-neg-m)
      ((= g "×") apl-signum)
      ((= g "÷") apl-recip)
      ((= g "⌈") apl-ceil)
      ((= g "⌊") apl-floor)
      ((= g "⍳") apl-iota)
      ((= g "|") apl-abs)
      ((= g "*") apl-exp)
      ((= g "⍟") apl-ln)
      ((= g "!") apl-fact)
      ((= g "○") apl-pi-times)
      ((= g "~") apl-not)
      ((= g "≢") apl-tally)
      ((= g "⍴") apl-shape)
      ((= g "≡") apl-depth)
      ((= g "⊂") apl-enclose)
      ((= g "⊃") apl-disclose)
      ((= g ",") apl-ravel)
      ((= g "⌽") apl-reverse)
      ((= g "⊖") apl-reverse-first)
      ((= g "⍋") apl-grade-up)
      ((= g "⍒") apl-grade-down)
      ((= g "⎕FMT") apl-quad-fmt)
      ((= g "⎕←") apl-quad-print)
      (else (error "no monadic fn for glyph")))))

(define
  apl-dyadic-fn
  (fn
    (g)
    (cond
      ((= g "+") apl-add)
      ((= g "-") apl-sub)
      ((= g "×") apl-mul)
      ((= g "÷") apl-div)
      ((= g "⌈") apl-max)
      ((= g "⌊") apl-min)
      ((= g "*") apl-pow)
      ((= g "⍟") apl-log)
      ((= g "|") apl-mod)
      ((= g "!") apl-binomial)
      ((= g "○") apl-trig)
      ((= g "<") apl-lt)
      ((= g "≤") apl-le)
      ((= g "=") apl-eq)
      ((= g "≥") apl-ge)
      ((= g ">") apl-gt)
      ((= g "≠") apl-ne)
      ((= g "∧") apl-and)
      ((= g "∨") apl-or)
      ((= g "⍱") apl-nor)
      ((= g "⍲") apl-nand)
      ((= g ",") apl-catenate)
      ((= g "⍪") apl-catenate-first)
      ((= g "⍴") apl-reshape)
      ((= g "↑") apl-take)
      ((= g "↓") apl-drop)
      ((= g "⌷") apl-squad)
      ((= g "⌽") apl-rotate)
      ((= g "⊖") apl-rotate-first)
      ((= g "∊") apl-member)
      ((= g "⍳") apl-index-of)
      ((= g "~") apl-without)
      (else (error "no dyadic fn for glyph")))))

(define
  apl-truthy?
  (fn
    (v)
    (let
      ((rv (get v :ravel)))
      (if (and (= (len rv) 1) (= (first rv) 0)) false true))))

(define
  apl-eval-ast
  (fn
    (node env)
    (let
      ((tag (first node)))
      (cond
        ((= tag :num) (apl-scalar (nth node 1)))
        ((= tag :str)
          (let
            ((s (nth node 1)))
            (if
              (= (len s) 1)
              (apl-scalar s)
              (make-array
                (list (len s))
                (map (fn (i) (slice s i (+ i 1))) (range 0 (len s)))))))
        ((= tag :vec)
          (let
            ((items (rest node)))
            (let
              ((vals (map (fn (n) (apl-eval-ast n env)) items)))
              (make-array
                (list (len vals))
                (map (fn (v) (first (get v :ravel))) vals)))))
        ((= tag :name)
          (let
            ((nm (nth node 1)))
            (cond
              ((= nm "⍺") (get env "alpha"))
              ((= nm "⍵") (get env "omega"))
              ((= nm "⎕IO") (apl-quad-io))
              ((= nm "⎕ML") (apl-quad-ml))
              ((= nm "⎕FR") (apl-quad-fr))
              ((= nm "⎕TS") (apl-quad-ts))
              (else (get env nm)))))
        ((= tag :monad)
          (let
            ((fn-node (nth node 1)) (arg (nth node 2)))
            (if
              (and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
              (apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
              ((apl-resolve-monadic fn-node env) (apl-eval-ast arg env)))))
        ((= tag :dyad)
          (let
            ((fn-node (nth node 1))
              (lhs (nth node 2))
              (rhs (nth node 3)))
            (if
              (and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
              (apl-call-dfn
                (get env "nabla")
                (apl-eval-ast lhs env)
                (apl-eval-ast rhs env))
              ((apl-resolve-dyadic fn-node env)
                (apl-eval-ast lhs env)
                (apl-eval-ast rhs env)))))
        ((= tag :program) (apl-eval-stmts (rest node) env))
        ((= tag :dfn) node)
        ((= tag :bracket)
          (let
            ((arr-expr (nth node 1)) (axis-exprs (rest (rest node))))
            (let
              ((arr (apl-eval-ast arr-expr env))
                (axes
                  (map
                    (fn (a) (if (= a :all) nil (apl-eval-ast a env)))
                    axis-exprs)))
              (apl-bracket-multi axes arr))))
        (else (error (list "apl-eval-ast: unknown node tag" tag node)))))))

(define
  apl-eval-stmts
  (fn
    (stmts env)
    (if
      (= (len stmts) 0)
      nil
      (let
        ((stmt (first stmts)) (more (rest stmts)))
        (let
          ((tag (first stmt)))
          (cond
            ((= tag :guard)
              (let
                ((cond-val (apl-eval-ast (nth stmt 1) env)))
                (if
                  (apl-truthy? cond-val)
                  (apl-eval-ast (nth stmt 2) env)
                  (apl-eval-stmts more env))))
            ((and (= tag :assign) (= (nth stmt 1) "⍺"))
              (if
                (get env "alpha")
                (apl-eval-stmts more env)
                (let
                  ((v (apl-eval-ast (nth stmt 2) env)))
                  (apl-eval-stmts more (assoc env "alpha" v)))))
            ((= tag :assign)
              (let
                ((v (apl-eval-ast (nth stmt 2) env)))
                (apl-eval-stmts more (assoc env (nth stmt 1) v))))
            ((= (len more) 0) (apl-eval-ast stmt env))
            (else (begin (apl-eval-ast stmt env) (apl-eval-stmts more env)))))))))

(define
  apl-call-dfn
  (fn
    (dfn-ast alpha omega)
    (let
      ((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha alpha}))
      (apl-eval-stmts stmts env))))

(define
  apl-call-dfn-m
  (fn
    (dfn-ast omega)
    (let
      ((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha nil}))
      (apl-eval-stmts stmts env))))

(define
  apl-tradfn-eval-block
  (fn
    (stmts env)
    (if
      (= (len stmts) 0)
      env
      (let
        ((stmt (first stmts)))
        (apl-tradfn-eval-block (rest stmts) (apl-tradfn-eval-stmt stmt env))))))

(define
  apl-tradfn-eval-while
  (fn
    (cond-expr body env)
    (let
      ((cond-val (apl-eval-ast cond-expr env)))
      (if
        (apl-truthy? cond-val)
        (apl-tradfn-eval-while
          cond-expr
          body
          (apl-tradfn-eval-block body env))
        env))))

(define
  apl-tradfn-eval-for
  (fn
    (var-name items body env)
    (if
      (= (len items) 0)
      env
      (let
        ((env-with-var (assoc env var-name (apl-scalar (first items)))))
        (apl-tradfn-eval-for
          var-name
          (rest items)
          body
          (apl-tradfn-eval-block body env-with-var))))))

(define
  apl-tradfn-eval-select
  (fn
    (val cases default-block env)
    (if
      (= (len cases) 0)
      (apl-tradfn-eval-block default-block env)
      (let
        ((c (first cases)))
        (let
          ((case-val (apl-eval-ast (first c) env)))
          (if
            (= (first (get val :ravel)) (first (get case-val :ravel)))
            (apl-tradfn-eval-block (rest c) env)
            (apl-tradfn-eval-select val (rest cases) default-block env)))))))

(define
  apl-tradfn-eval-stmt
  (fn
    (stmt env)
    (let
      ((tag (first stmt)))
      (cond
        ((= tag :assign)
          (assoc env (nth stmt 1) (apl-eval-ast (nth stmt 2) env)))
        ((= tag :if)
          (let
            ((cond-val (apl-eval-ast (nth stmt 1) env)))
            (if
              (apl-truthy? cond-val)
              (apl-tradfn-eval-block (nth stmt 2) env)
              (apl-tradfn-eval-block (nth stmt 3) env))))
        ((= tag :while)
          (apl-tradfn-eval-while (nth stmt 1) (nth stmt 2) env))
        ((= tag :for)
          (let
            ((iter-val (apl-eval-ast (nth stmt 2) env)))
            (apl-tradfn-eval-for
              (nth stmt 1)
              (get iter-val :ravel)
              (nth stmt 3)
              env)))
        ((= tag :select)
          (let
            ((val (apl-eval-ast (nth stmt 1) env)))
            (apl-tradfn-eval-select val (nth stmt 2) (nth stmt 3) env)))
        ((= tag :trap)
          (let
            ((codes (nth stmt 1))
              (try-block (nth stmt 2))
              (catch-block (nth stmt 3)))
            (guard
              (e
                ((apl-trap-matches? codes e)
                  (apl-tradfn-eval-block catch-block env)))
              (apl-tradfn-eval-block try-block env))))
        ((= tag :throw) (apl-throw (nth stmt 1) (nth stmt 2)))
        (else (begin (apl-eval-ast stmt env) env))))))

(define
  apl-tradfn-loop
  (fn
    (stmts line env result-name)
    (cond
      ((= line 0) (get env result-name))
      ((> line (len stmts)) (get env result-name))
      (else
        (let
          ((stmt (nth stmts (- line 1))))
          (let
            ((tag (first stmt)))
            (cond
              ((= tag :branch)
                (let
                  ((target (apl-eval-ast (nth stmt 1) env)))
                  (let
                    ((target-num (first (get target :ravel))))
                    (apl-tradfn-loop stmts target-num env result-name))))
              (else
                (apl-tradfn-loop
                  stmts
                  (+ line 1)
                  (apl-tradfn-eval-stmt stmt env)
                  result-name)))))))))

(define
  apl-call-tradfn
  (fn
    (tradfn alpha omega)
    (let
      ((stmts (get tradfn :stmts))
        (result-name (get tradfn :result))
        (alpha-name (get tradfn :alpha))
        (omega-name (get tradfn :omega)))
      (let
        ((env-a (if alpha-name (assoc {} alpha-name alpha) {})))
        (let
          ((env-ao (if omega-name (assoc env-a omega-name omega) env-a)))
          (apl-tradfn-loop stmts 1 env-ao result-name))))))

(define
  apl-ast-mentions-list?
  (fn
    (lst target)
    (if
      (= (len lst) 0)
      false
      (if
        (apl-ast-mentions? (first lst) target)
        true
        (apl-ast-mentions-list? (rest lst) target)))))

(define
  apl-ast-mentions?
  (fn
    (node target)
    (cond
      ((not (list? node)) false)
      ((= (len node) 0) false)
      ((and (= (first node) :name) (= (nth node 1) target)) true)
      (else (apl-ast-mentions-list? (rest node) target)))))

(define
  apl-dfn-valence
  (fn
    (dfn-ast)
    (let
      ((body (rest dfn-ast)))
      (cond
        ((apl-ast-mentions-list? body "⍺") :dyadic)
        ((apl-ast-mentions-list? body "⍵") :monadic)
        (else :niladic)))))

(define
  apl-tradfn-valence
  (fn
    (tradfn)
    (cond
      ((get tradfn :alpha) :dyadic)
      ((get tradfn :omega) :monadic)
      (else :niladic))))

(define
  apl-call
  (fn
    (f alpha omega)
    (cond
      ((and (list? f) (> (len f) 0) (= (first f) :dfn))
        (if alpha (apl-call-dfn f alpha omega) (apl-call-dfn-m f omega)))
      ((dict? f) (apl-call-tradfn f alpha omega))
      (else (error "apl-call: not a function")))))

(define
  apl-resolve-monadic
  (fn
    (fn-node env)
    (let
      ((tag (first fn-node)))
      (cond
        ((= tag :fn-glyph) (apl-monadic-fn (nth fn-node 1)))
        ((= tag :derived-fn)
          (let
            ((op (nth fn-node 1)) (inner (nth fn-node 2)))
            (cond
              ((= op "/")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (arr) (apl-reduce f arr))))
              ((= op "⌿")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (arr) (apl-reduce-first f arr))))
              ((= op "\\")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (arr) (apl-scan f arr))))
              ((= op "⍀")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (arr) (apl-scan-first f arr))))
              ((= op "¨")
                (let
                  ((f (apl-resolve-monadic inner env)))
                  (fn (arr) (apl-each f arr))))
              ((= op "⍨")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (arr) (apl-commute f arr))))
              (else (error "apl-resolve-monadic: unsupported op")))))
        ((= tag :fn-name)
          (let
            ((nm (nth fn-node 1)))
            (let
              ((bound (get env nm)))
              (if
                (and
                  (list? bound)
                  (> (len bound) 0)
                  (= (first bound) :dfn))
                (fn (arg) (apl-call-dfn-m bound arg))
                (error "apl-resolve-monadic: name not bound to dfn")))))
        ((= tag :train)
          (let
            ((fns (rest fn-node)))
            (let
              ((n (len fns)))
              (cond
                ((= n 2)
                  (let
                    ((g (apl-resolve-monadic (nth fns 0) env))
                      (h (apl-resolve-monadic (nth fns 1) env)))
                    (fn (arg) (g (h arg)))))
                ((= n 3)
                  (let
                    ((f (apl-resolve-monadic (nth fns 0) env))
                      (g (apl-resolve-dyadic (nth fns 1) env))
                      (h (apl-resolve-monadic (nth fns 2) env)))
                    (fn (arg) (g (f arg) (h arg)))))
                (else (error "monadic train arity not 2 or 3"))))))
        (else (error "apl-resolve-monadic: unknown fn-node tag"))))))

(define
  apl-resolve-dyadic
  (fn
    (fn-node env)
    (let
      ((tag (first fn-node)))
      (cond
        ((= tag :fn-glyph) (apl-dyadic-fn (nth fn-node 1)))
        ((= tag :derived-fn)
          (let
            ((op (nth fn-node 1)) (inner (nth fn-node 2)))
            (cond
              ((= op "¨")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (a b) (apl-each-dyadic f a b))))
              ((= op "⍨")
                (let
                  ((f (apl-resolve-dyadic inner env)))
                  (fn (a b) (apl-commute-dyadic f a b))))
              (else (error "apl-resolve-dyadic: unsupported op")))))
        ((= tag :fn-name)
          (let
            ((nm (nth fn-node 1)))
            (let
              ((bound (get env nm)))
              (if
                (and
                  (list? bound)
                  (> (len bound) 0)
                  (= (first bound) :dfn))
                (fn (a b) (apl-call-dfn bound a b))
                (error "apl-resolve-dyadic: name not bound to dfn")))))
        ((= tag :outer)
          (let
            ((inner (nth fn-node 2)))
            (let
              ((f (apl-resolve-dyadic inner env)))
              (fn (a b) (apl-outer f a b)))))
        ((= tag :derived-fn2)
          (let
            ((f-node (nth fn-node 2)) (g-node (nth fn-node 3)))
            (let
              ((f (apl-resolve-dyadic f-node env))
                (g (apl-resolve-dyadic g-node env)))
              (fn (a b) (apl-inner f g a b)))))
        ((= tag :train)
          (let
            ((fns (rest fn-node)))
            (let
              ((n (len fns)))
              (cond
                ((= n 2)
                  (let
                    ((g (apl-resolve-monadic (nth fns 0) env))
                      (h (apl-resolve-dyadic (nth fns 1) env)))
                    (fn (a b) (g (h a b)))))
                ((= n 3)
                  (let
                    ((f (apl-resolve-dyadic (nth fns 0) env))
                      (g (apl-resolve-dyadic (nth fns 1) env))
                      (h (apl-resolve-dyadic (nth fns 2) env)))
                    (fn (a b) (g (f a b) (h a b)))))
                (else (error "dyadic train arity not 2 or 3"))))))
        (else (error "apl-resolve-dyadic: unknown fn-node tag"))))))

(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
