;; Haskell strict evaluator (Phase 2). ;; ;; Consumes the post-desugar core AST and produces SX values. Strict ;; throughout — laziness and thunks are Phase 3. ;; ;; Value representation: ;; numbers / strings / chars → raw SX values ;; constructor values → tagged lists (con-name first) ;; functions: closure / multifun → {:type "fn" :kind … …} ;; constructor partials → {:type "con-partial" …} ;; built-ins → {:type "builtin" …} ;; ;; Multi-clause top-level definitions are bundled into a single ;; multifun keyed by name; arguments are gathered through currying ;; until arity is reached, then each clause's pattern list is matched ;; in order. Recursive let bindings work because the binding env is ;; built mutably so closures captured during evaluation see the ;; eventual full env. (define hk-dict-copy (fn (d) (let ((nd (dict))) (for-each (fn (k) (dict-set! nd k (get d k))) (keys d)) nd))) ;; ── Function value constructors ────────────────────────────── (define hk-mk-closure (fn (params body env) {:type "fn" :kind "closure" :params params :body body :env env})) (define hk-mk-multifun (fn (arity clauses env) {:type "fn" :kind "multi" :arity arity :clauses clauses :env env :collected (list)})) (define hk-mk-builtin (fn (name fn arity) {:type "builtin" :name name :fn fn :arity arity :collected (list)})) ;; ── Apply a function value to one argument ────────────────── (define hk-apply (fn (f arg) (cond ((not (dict? f)) (raise (str "apply: not a function value: " f))) ((= (get f "type") "fn") (cond ((= (get f "kind") "closure") (hk-apply-closure f arg)) ((= (get f "kind") "multi") (hk-apply-multi f arg)) (:else (raise "apply: unknown fn kind")))) ((= (get f "type") "con-partial") (hk-apply-con-partial f arg)) ((= (get f "type") "builtin") (hk-apply-builtin f arg)) (:else (raise "apply: not a function dict"))))) (define hk-apply-closure (fn (cl arg) (let ((params (get cl "params")) (body (get cl "body")) (env (get cl "env"))) (cond ((empty? params) (raise "apply-closure: no params")) (:else (let ((p1 (first params)) (rest-p (rest params))) (let ((env-after (hk-match p1 arg env))) (cond ((nil? env-after) (raise "pattern match failure in lambda")) ((empty? rest-p) (hk-eval body env-after)) (:else (hk-mk-closure rest-p body env-after)))))))))) (define hk-apply-multi (fn (mf arg) (let ((arity (get mf "arity")) (clauses (get mf "clauses")) (env (get mf "env")) (collected (append (get mf "collected") (list arg)))) (cond ((< (len collected) arity) (assoc mf "collected" collected)) (:else (hk-dispatch-multi clauses collected env)))))) (define hk-dispatch-multi (fn (clauses args env) (cond ((empty? clauses) (raise "non-exhaustive patterns in function definition")) (:else (let ((c (first clauses))) (let ((pats (first c)) (body (first (rest c)))) (let ((env-after (hk-match-args pats args env))) (cond ((nil? env-after) (hk-dispatch-multi (rest clauses) args env)) (:else (hk-eval body env-after)))))))))) (define hk-match-args (fn (pats args env) (cond ((empty? pats) env) (:else (let ((res (hk-match (first pats) (first args) env))) (cond ((nil? res) nil) (:else (hk-match-args (rest pats) (rest args) res)))))))) (define hk-apply-con-partial (fn (cp arg) (let ((name (get cp "name")) (arity (get cp "arity")) (args (append (get cp "args") (list arg)))) (cond ((= (len args) arity) (hk-mk-con name args)) (:else (assoc cp "args" args)))))) (define hk-apply-builtin (fn (b arg) (let ((arity (get b "arity")) (collected (append (get b "collected") (list arg)))) (cond ((< (len collected) arity) (assoc b "collected" collected)) (:else (apply (get b "fn") collected)))))) ;; ── Bool helpers (Bool values are tagged conses) ──────────── (define hk-truthy? (fn (v) (and (list? v) (not (empty? v)) (= (first v) "True")))) (define hk-true (hk-mk-con "True" (list))) (define hk-false (hk-mk-con "False" (list))) (define hk-of-bool (fn (b) (if b hk-true hk-false))) ;; ── Core eval ─────────────────────────────────────────────── (define hk-eval (fn (node env) (cond ((not (list? node)) (raise (str "eval: not a list: " node))) ((empty? node) (raise "eval: empty list node")) (:else (let ((tag (first node))) (cond ((= tag "int") (nth node 1)) ((= tag "float") (nth node 1)) ((= tag "string") (nth node 1)) ((= tag "char") (nth node 1)) ((= tag "var") (hk-eval-var (nth node 1) env)) ((= tag "con") (hk-eval-con-ref (nth node 1))) ((= tag "neg") (- 0 (hk-eval (nth node 1) env))) ((= tag "if") (hk-eval-if node env)) ((= tag "let") (hk-eval-let (nth node 1) (nth node 2) env)) ((= tag "lambda") (hk-mk-closure (nth node 1) (nth node 2) env)) ((= tag "app") (hk-apply (hk-eval (nth node 1) env) (hk-eval (nth node 2) env))) ((= tag "op") (hk-eval-op (nth node 1) (nth node 2) (nth node 3) env)) ((= tag "case") (hk-eval-case (nth node 1) (nth node 2) env)) ((= tag "tuple") (hk-mk-tuple (map (fn (e) (hk-eval e env)) (nth node 1)))) ((= tag "list") (hk-mk-list (map (fn (e) (hk-eval e env)) (nth node 1)))) ((= tag "sect-left") (hk-eval-sect-left (nth node 1) (nth node 2) env)) ((= tag "sect-right") (hk-eval-sect-right (nth node 1) (nth node 2) env)) (:else (raise (str "eval: unknown node tag '" tag "'"))))))))) (define hk-eval-var (fn (name env) (cond ((has-key? env name) (get env name)) ((hk-is-con? name) (hk-eval-con-ref name)) (:else (raise (str "unbound variable: " name)))))) (define hk-eval-con-ref (fn (name) (let ((arity (hk-con-arity name))) (cond ((nil? arity) (raise (str "unknown constructor: " name))) ((= arity 0) (hk-mk-con name (list))) (:else {:type "con-partial" :name name :arity arity :args (list)}))))) (define hk-eval-if (fn (node env) (let ((cv (hk-eval (nth node 1) env))) (cond ((hk-truthy? cv) (hk-eval (nth node 2) env)) ((and (list? cv) (= (first cv) "False")) (hk-eval (nth node 3) env)) ((= cv true) (hk-eval (nth node 2) env)) ((= cv false) (hk-eval (nth node 3) env)) (:else (raise "if: condition is not Bool")))))) (define hk-extend-env-with-match! (fn (env match-env) (for-each (fn (k) (dict-set! env k (get match-env k))) (keys match-env)))) (define hk-eval-let-bind! (fn (b env) (let ((tag (first b))) (cond ((= tag "fun-clause") (let ((name (nth b 1)) (pats (nth b 2)) (body (nth b 3))) (cond ((empty? pats) (dict-set! env name (hk-eval body env))) (:else (dict-set! env name (hk-mk-closure pats body env)))))) ((or (= tag "bind") (= tag "pat-bind")) (let ((pat (nth b 1)) (body (nth b 2))) (let ((val (hk-eval body env))) (let ((res (hk-match pat val env))) (cond ((nil? res) (raise "let: pattern bind failure")) (:else (hk-extend-env-with-match! env res))))))) (:else nil))))) (define hk-eval-let (fn (binds body env) (let ((new-env (hk-dict-copy env))) ;; Pre-seed names for fn-clauses so closures see themselves ;; (mutual recursion across the whole binding group). (for-each (fn (b) (cond ((= (first b) "fun-clause") (dict-set! new-env (nth b 1) nil)) ((and (= (first b) "bind") (list? (nth b 1)) (= (first (nth b 1)) "p-var")) (dict-set! new-env (nth (nth b 1) 1) nil)) (:else nil))) binds) (for-each (fn (b) (hk-eval-let-bind! b new-env)) binds) (hk-eval body new-env)))) (define hk-eval-case (fn (scrut alts env) (let ((sv (hk-eval scrut env))) (hk-try-alts alts sv env)))) (define hk-try-alts (fn (alts val env) (cond ((empty? alts) (raise "case: non-exhaustive patterns")) (:else (let ((alt (first alts))) (let ((pat (nth alt 1)) (body (nth alt 2))) (let ((res (hk-match pat val env))) (cond ((nil? res) (hk-try-alts (rest alts) val env)) (:else (hk-eval body res)))))))))) (define hk-eval-op (fn (op left right env) (let ((lv (hk-eval left env)) (rv (hk-eval right env))) (hk-binop op lv rv)))) (define hk-list-append (fn (a b) (cond ((and (list? a) (= (first a) "[]")) b) ((and (list? a) (= (first a) ":")) (hk-mk-cons (nth a 1) (hk-list-append (nth a 2) b))) (:else (raise "++: not a list"))))) (define hk-binop (fn (op lv rv) (cond ((= op "+") (+ lv rv)) ((= op "-") (- lv rv)) ((= op "*") (* lv rv)) ((= op "/") (/ lv rv)) ((= op "==") (hk-of-bool (= lv rv))) ((= op "/=") (hk-of-bool (not (= lv rv)))) ((= op "<") (hk-of-bool (< lv rv))) ((= op "<=") (hk-of-bool (<= lv rv))) ((= op ">") (hk-of-bool (> lv rv))) ((= op ">=") (hk-of-bool (>= lv rv))) ((= op "&&") (hk-of-bool (and (hk-truthy? lv) (hk-truthy? rv)))) ((= op "||") (hk-of-bool (or (hk-truthy? lv) (hk-truthy? rv)))) ((= op ":") (hk-mk-cons lv rv)) ((= op "++") (hk-list-append lv rv)) (:else (raise (str "unknown operator: " op)))))) (define hk-eval-sect-left (fn (op e env) ;; (e op) = \x -> e op x — bind e once, defer the operator call. (let ((ev (hk-eval e env))) (let ((cenv (hk-dict-copy env))) (dict-set! cenv "__hk-sect-l" ev) (hk-mk-closure (list (list :p-var "__hk-sect-x")) (list :op op (list :var "__hk-sect-l") (list :var "__hk-sect-x")) cenv))))) (define hk-eval-sect-right (fn (op e env) (let ((ev (hk-eval e env))) (let ((cenv (hk-dict-copy env))) (dict-set! cenv "__hk-sect-r" ev) (hk-mk-closure (list (list :p-var "__hk-sect-x")) (list :op op (list :var "__hk-sect-x") (list :var "__hk-sect-r")) cenv))))) ;; ── Top-level program evaluation ──────────────────────────── (define hk-init-env (fn () (let ((env (dict))) (dict-set! env "otherwise" hk-true) (dict-set! env "error" (hk-mk-builtin "error" (fn (msg) (raise (str "*** Exception: " msg))) 1)) (dict-set! env "not" (hk-mk-builtin "not" (fn (b) (hk-of-bool (not (hk-truthy? b)))) 1)) (dict-set! env "id" (hk-mk-builtin "id" (fn (x) x) 1)) env))) (define hk-bind-decls! (fn (env decls) (let ((groups (dict)) (pat-binds (list))) ;; Pass 1: collect fun-clause groups by name; collect pat-binds ;; in source order. Pre-seed env so any name can already be ;; looked up by closures built in pass 2. (for-each (fn (d) (cond ((= (first d) "fun-clause") (let ((name (nth d 1))) (dict-set! groups name (append (if (has-key? groups name) (get groups name) (list)) (list (list (nth d 2) (nth d 3))))) (when (not (has-key? env name)) (dict-set! env name nil)))) ((or (= (first d) "bind") (= (first d) "pat-bind")) (append! pat-binds d)) (:else nil))) decls) ;; Pass 2: install multifuns for arity > 0; mark 0-arity for ;; pass 3. The mutable env means recursive references work. (let ((zero-arity (list))) (for-each (fn (name) (let ((clauses (get groups name))) (let ((arity (len (first (first clauses))))) (cond ((> arity 0) (dict-set! env name (hk-mk-multifun arity clauses env))) (:else (append! zero-arity name)))))) (keys groups)) ;; Pass 3: evaluate 0-arity bodies and pat-binds. (for-each (fn (name) (let ((clauses (get groups name))) (dict-set! env name (hk-eval (first (rest (first clauses))) env)))) zero-arity) (for-each (fn (d) (let ((pat (nth d 1)) (body (nth d 2))) (let ((val (hk-eval body env))) (let ((res (hk-match pat val env))) (cond ((nil? res) (raise "top-level pattern bind failure")) (:else (hk-extend-env-with-match! env res))))))) pat-binds)) env))) (define hk-eval-program (fn (ast) (cond ((nil? ast) (raise "eval-program: nil ast")) ((not (list? ast)) (raise "eval-program: not a list")) (:else (do (hk-register-program! ast) (let ((env (hk-init-env))) (let ((decls (cond ((= (first ast) "program") (nth ast 1)) ((= (first ast) "module") (nth ast 4)) (:else (raise "eval-program: bad shape"))))) (hk-bind-decls! env decls)))))))) ;; ── Source-level convenience ──────────────────────────────── (define hk-run (fn (src) (let ((env (hk-eval-program (hk-core src)))) (cond ((has-key? env "main") (get env "main")) (:else env))))) (define hk-eval-expr-source (fn (src) (hk-eval (hk-core-expr src) (hk-init-env))))