;; lib/kernel/eval.sx — Kernel evaluator.
;;
;; The evaluator is `lookup-and-combine`: there are no hardcoded special
;; forms. Even $if / $define! / $lambda are ordinary operatives bound in
;; the standard environment (Phase 4). This file builds the dispatch
;; machinery and the operative/applicative tagged-value protocol.
;;
;; Tagged values
;; -------------
;;   {:refl-tag :env :bindings DICT :parent PARENT-OR-NIL}
;;     A first-class Kernel environment. Bindings is a mutable SX dict
;;     keyed by symbol name; parent walks up the lookup chain. Shape
;;     and operations are inherited from lib/guest/reflective/env.sx
;;     (canonical wire shape) — Kernel-side names are thin wrappers.
;;
;;   {:knl-tag :operative :impl FN}
;;     Primitive operative. FN receives (args dyn-env) — args are the
;;     UN-evaluated argument expressions, dyn-env is the calling env.
;;
;;   {:knl-tag :operative :params P :env-param EP :body B :static-env SE}
;;     User-defined operative (built by $vau). Same tag; dispatch in
;;     kernel-call-operative forks on which keys are present.
;;
;;   {:knl-tag :applicative :underlying OP}
;;     An applicative wraps an operative. Calls evaluate args first,
;;     then forward to the underlying operative.
;;
;; The env-param of a user operative may be the sentinel :knl-ignore,
;; in which case the dynamic env is not bound.
;;
;; Public API
;;   (kernel-eval EXPR ENV)                     — primary entry
;;   (kernel-combine COMBINER ARGS DYN-ENV)
;;   (kernel-call-operative OP ARGS DYN-ENV)
;;   (kernel-bind-params! ENV PARAMS ARGS)
;;   (kernel-make-env) / (kernel-extend-env P)
;;   (kernel-env-bind! E N V) / (kernel-env-lookup E N)
;;   (kernel-env-has? E N) / (kernel-env? V)
;;   (kernel-make-primitive-operative IMPL)
;;   (kernel-make-primitive-applicative IMPL)
;;   (kernel-make-user-operative PARAMS EPARAM BODY STATIC-ENV)
;;   (kernel-wrap OP) / (kernel-unwrap APP)
;;   (kernel-operative? V) / (kernel-applicative? V) / (kernel-combiner? V)
;;
;; Consumes: lib/kernel/parser.sx (kernel-string?, kernel-string-value)

;; ── Environments — delegated to lib/guest/reflective/env.sx ──────
;; The env values themselves now carry `:refl-tag :env` (shared with the
;; reflective kit). Kernel's API names stay; bodies are thin wrappers.

(define kernel-env?        refl-env?)
(define kernel-make-env    refl-make-env)
(define kernel-extend-env  refl-extend-env)
(define kernel-env-bind!   refl-env-bind!)
(define kernel-env-has?    refl-env-has?)
(define kernel-env-lookup  refl-env-lookup)

;; ── Tagged-value constructors and predicates ─────────────────────

(define kernel-make-primitive-operative (fn (impl) {:impl impl :knl-tag :operative}))

(define
  kernel-make-user-operative
  (fn (params eparam body static-env) {:knl-tag :operative :static-env static-env :params params :body body :env-param eparam}))

(define
  kernel-operative?
  (fn (v) (and (dict? v) (= (get v :knl-tag) :operative))))

(define
  kernel-applicative?
  (fn (v) (and (dict? v) (= (get v :knl-tag) :applicative))))

(define
  kernel-combiner?
  (fn (v) (or (kernel-operative? v) (kernel-applicative? v))))

(define
  kernel-wrap
  (fn
    (op)
    (cond
      ((kernel-operative? op) {:knl-tag :applicative :underlying op})
      (:else (error "kernel-wrap: argument must be an operative")))))

(define
  kernel-unwrap
  (fn
    (app)
    (cond
      ((kernel-applicative? app) (get app :underlying))
      (:else (error "kernel-unwrap: argument must be an applicative")))))

(define
  kernel-make-primitive-applicative
  (fn
    (impl)
    (kernel-wrap
      (kernel-make-primitive-operative (fn (args dyn-env) (impl args))))))

;; As above, but IMPL receives (args dyn-env). Used by combinators that
;; re-enter the evaluator (map, filter, reduce, apply, eval, ...).
(define kernel-make-primitive-applicative-with-env
  (fn (impl)
    (kernel-wrap
      (kernel-make-primitive-operative
        (fn (args dyn-env) (impl args dyn-env))))))

;; ── The evaluator ────────────────────────────────────────────────

(define
  kernel-eval
  (fn
    (expr env)
    (cond
      ((number? expr) expr)
      ((boolean? expr) expr)
      ((nil? expr) expr)
      ((kernel-string? expr) (kernel-string-value expr))
      ((string? expr) (kernel-env-lookup env expr))
      ((list? expr)
        (cond
          ((= (length expr) 0) expr)
          (:else
            (let
              ((combiner (kernel-eval (first expr) env))
                (args (rest expr)))
              (kernel-combine combiner args env)))))
      (:else (error (str "kernel-eval: unknown form: " expr))))))

(define
  kernel-combine
  (fn
    (combiner args dyn-env)
    (cond
      ((kernel-operative? combiner)
        (kernel-call-operative combiner args dyn-env))
      ((kernel-applicative? combiner)
        (kernel-combine
          (get combiner :underlying)
          (kernel-eval-args args dyn-env)
          dyn-env))
      (:else (error (str "kernel-eval: not a combiner: " combiner))))))

;; Operatives may be primitive (:impl is a host fn) or user-defined
;; (carry :params / :env-param / :body / :static-env). The dispatch
;; fork is here so kernel-combine stays small.
(define
  kernel-call-operative
  (fn
    (op args dyn-env)
    (cond
      ((dict-has? op :impl) ((get op :impl) args dyn-env))
      ((dict-has? op :body)
        (let
          ((local (kernel-extend-env (get op :static-env))))
          (kernel-bind-params! local (get op :params) args)
          (let
            ((eparam (get op :env-param)))
            (when
              (not (= eparam :knl-ignore))
              (kernel-env-bind! local eparam dyn-env)))
          ;; :body is a list of forms — evaluate in sequence, return last.
          (knl-eval-body (get op :body) local)))
      (:else (error "kernel-call-operative: malformed operative")))))

(define knl-eval-body
  (fn (forms env)
    (cond
      ((= (length forms) 1) (kernel-eval (first forms) env))
      (:else
        (begin
          (kernel-eval (first forms) env)
          (knl-eval-body (rest forms) env))))))

;; Phase 3 supports a flat parameter list only — destructuring later.
(define
  kernel-bind-params!
  (fn
    (env params args)
    (cond
      ((or (nil? params) (= (length params) 0))
        (cond
          ((or (nil? args) (= (length args) 0)) nil)
          (:else (error "kernel-call: too many arguments"))))
      ((or (nil? args) (= (length args) 0))
        (error "kernel-call: too few arguments"))
      (:else
        (begin
          (kernel-env-bind! env (first params) (first args))
          (kernel-bind-params! env (rest params) (rest args)))))))

(define
  kernel-eval-args
  (fn
    (args env)
    (cond
      ((or (nil? args) (= (length args) 0)) (list))
      (:else
        (cons
          (kernel-eval (first args) env)
          (kernel-eval-args (rest args) env))))))

(define
  kernel-eval-program
  (fn
    (forms env)
    (cond
      ((or (nil? forms) (= (length forms) 0)) nil)
      ((= (length forms) 1) (kernel-eval (first forms) env))
      (:else
        (begin
          (kernel-eval (first forms) env)
          (kernel-eval-program (rest forms) env))))))
