- fix hk-eval-let: multi-clause where/let now uses hk-bind-decls! grouping (enables go 0 / go k pattern) - add concatMap/concat/abs/negate to Prelude (list comprehension support) - cache init env in hk-env0 (eval-expr-source 5x faster)
793 lines
24 KiB
Plaintext
793 lines
24 KiB
Plaintext
;; 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)))
|
|
|
|
;; ── Thunks (Phase 3 — laziness) ─────────────────────────────
|
|
;; A thunk wraps an unevaluated AST plus the env in which it was
|
|
;; created. The first call to `hk-force` evaluates the body, replaces
|
|
;; the body with the cached value, and flips `forced`. Subsequent
|
|
;; forces return the cached value directly.
|
|
(define
|
|
hk-mk-thunk
|
|
(fn
|
|
(body env)
|
|
{:type "thunk" :body body :env env :forced false :value nil}))
|
|
|
|
(define
|
|
hk-is-thunk?
|
|
(fn (v) (and (dict? v) (= (get v "type") "thunk"))))
|
|
|
|
(define
|
|
hk-force
|
|
(fn
|
|
(v)
|
|
(cond
|
|
((hk-is-thunk? v)
|
|
(cond
|
|
((get v "forced") (get v "value"))
|
|
(:else
|
|
(let
|
|
((res (hk-force (hk-eval (get v "body") (get v "env")))))
|
|
(dict-set! v "forced" true)
|
|
(dict-set! v "value" res)
|
|
res))))
|
|
(:else v))))
|
|
|
|
;; Recursive force — used at the test/output boundary so test
|
|
;; expectations can compare against fully-evaluated structures.
|
|
(define
|
|
hk-deep-force
|
|
(fn
|
|
(v)
|
|
(let ((fv (hk-force v)))
|
|
(cond
|
|
((not (list? fv)) fv)
|
|
((empty? fv) fv)
|
|
(:else (map hk-deep-force fv))))))
|
|
|
|
;; ── 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 :lazy false :collected (list)}))
|
|
|
|
;; A lazy built-in receives its collected args as raw thunks (or
|
|
;; values, if those happened to be eager) — the implementation is
|
|
;; responsible for forcing exactly what it needs. Used for `seq`
|
|
;; and `deepseq`, which are non-strict in their second argument.
|
|
(define
|
|
hk-mk-lazy-builtin
|
|
(fn
|
|
(name fn arity)
|
|
{:type "builtin" :name name :fn fn :arity arity :lazy true :collected (list)}))
|
|
|
|
;; ── Apply a function value to one argument ──────────────────
|
|
(define
|
|
hk-apply
|
|
(fn
|
|
(f arg)
|
|
(let ((f (hk-force f)))
|
|
(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
|
|
;; Strict built-ins force every collected arg before
|
|
;; calling. Lazy ones (`seq`, `deepseq`) receive the raw
|
|
;; thunks so they can choose what to force.
|
|
(cond
|
|
((get b "lazy") (apply (get b "fn") collected))
|
|
(:else
|
|
(apply
|
|
(get b "fn")
|
|
(map hk-force 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-force (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-mk-thunk (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 "range")
|
|
(let
|
|
((from (hk-force (hk-eval (nth node 1) env)))
|
|
(to (hk-force (hk-eval (nth node 2) env))))
|
|
(hk-build-range from to 1)))
|
|
((= tag "range-step")
|
|
(let
|
|
((from (hk-force (hk-eval (nth node 1) env)))
|
|
(nxt (hk-force (hk-eval (nth node 2) env)))
|
|
(to (hk-force (hk-eval (nth node 3) env))))
|
|
(hk-build-range from to (- nxt from))))
|
|
((= tag "range-from")
|
|
;; [from..] = iterate (+ 1) from — uses the Prelude.
|
|
(hk-eval
|
|
(list
|
|
:app
|
|
(list
|
|
:app
|
|
(list :var "iterate")
|
|
(list
|
|
:sect-right
|
|
"+"
|
|
(list :int 1)))
|
|
(nth node 1))
|
|
env))
|
|
((= 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-force (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)
|
|
;; Reuse hk-bind-decls! so multi-clause fun bindings in where/let
|
|
;; are grouped into multifuns, enabling patterns like:
|
|
;; let { go 0 = [[]]; go k = [...] } in go n
|
|
(let ((new-env (hk-dict-copy env)))
|
|
(hk-bind-decls! new-env binds)
|
|
(hk-eval body new-env))))
|
|
|
|
(define
|
|
hk-eval-case
|
|
(fn
|
|
(scrut alts env)
|
|
(let ((sv (hk-force (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)
|
|
(cond
|
|
;; Cons is non-strict in both args: build a cons cell whose
|
|
;; head and tail are deferred. This is what makes `repeat x =
|
|
;; x : repeat x` and `fibs = 0 : 1 : zipWith (+) fibs (tail
|
|
;; fibs)` terminate.
|
|
((= op ":")
|
|
(hk-mk-cons
|
|
(hk-mk-thunk left env)
|
|
(hk-mk-thunk right env)))
|
|
(:else
|
|
(let
|
|
((lv (hk-force (hk-eval left env)))
|
|
(rv (hk-force (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")))))
|
|
|
|
;; Eager finite-range spine — handles [from..to] and [from,next..to].
|
|
;; Step direction is governed by the sign of `step`; when step > 0 we
|
|
;; stop at to; when step < 0 we stop at to going down.
|
|
(define
|
|
hk-build-range
|
|
(fn
|
|
(from to step)
|
|
(cond
|
|
((and (> step 0) (> from to)) (hk-mk-nil))
|
|
((and (< step 0) (< from to)) (hk-mk-nil))
|
|
((= step 0) (hk-mk-nil))
|
|
(:else
|
|
(hk-mk-cons from (hk-build-range (+ from step) to step))))))
|
|
|
|
(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))
|
|
((= op "mod") (mod lv rv))
|
|
((= op "div") (floor (/ lv rv)))
|
|
((= op "rem") (mod lv rv))
|
|
((= op "quot") (truncate (/ 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 ────────────────────────────
|
|
;; Operator-as-value built-ins — let `(+)`, `(*)`, etc. work as
|
|
;; first-class functions for `zipWith (+)` and friends. Strict in
|
|
;; both args (built-ins are forced via hk-apply-builtin).
|
|
(define
|
|
hk-make-binop-builtin
|
|
(fn
|
|
(name op-name)
|
|
(hk-mk-builtin
|
|
name
|
|
(fn (a b) (hk-binop op-name a b))
|
|
2)))
|
|
|
|
;; Inline Prelude source — loaded into the initial env so simple
|
|
;; programs can use `head`, `take`, `repeat`, etc. without each
|
|
;; user file redefining them. The Prelude itself uses lazy `:` for
|
|
;; the recursive list-building functions.
|
|
(define
|
|
hk-prelude-src
|
|
"head (x:_) = x
|
|
tail (_:xs) = xs
|
|
fst (a, _) = a
|
|
snd (_, b) = b
|
|
take 0 _ = []
|
|
take _ [] = []
|
|
take n (x:xs) = x : take (n - 1) xs
|
|
drop 0 xs = xs
|
|
drop _ [] = []
|
|
drop n (_:xs) = drop (n - 1) xs
|
|
repeat x = x : repeat x
|
|
iterate f x = x : iterate f (f x)
|
|
length [] = 0
|
|
length (_:xs) = 1 + length xs
|
|
map _ [] = []
|
|
map f (x:xs) = f x : map f xs
|
|
filter _ [] = []
|
|
filter p (x:xs) = if p x then x : filter p xs else filter p xs
|
|
zipWith _ [] _ = []
|
|
zipWith _ _ [] = []
|
|
zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys
|
|
fibs = 0 : 1 : zipWith plus fibs (tail fibs)
|
|
plus a b = a + b
|
|
concat [] = []
|
|
concat (xs:xss) = xs ++ concat xss
|
|
concatMap f [] = []
|
|
concatMap f (x:xs) = f x ++ concatMap f xs
|
|
abs x = if x < 0 then 0 - x else x
|
|
negate x = 0 - x
|
|
")
|
|
|
|
(define
|
|
hk-load-into!
|
|
(fn
|
|
(env src)
|
|
(let ((ast (hk-core src)))
|
|
(hk-register-program! ast)
|
|
(let
|
|
((decls
|
|
(cond
|
|
((= (first ast) "program") (nth ast 1))
|
|
((= (first ast) "module") (nth ast 4))
|
|
(:else (list)))))
|
|
(hk-bind-decls! env decls)))))
|
|
|
|
(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))
|
|
;; `seq a b` — strict in `a`, lazy in `b`. Forces `a` to WHNF
|
|
;; and returns `b` unchanged (still a thunk if it was one).
|
|
(dict-set!
|
|
env
|
|
"seq"
|
|
(hk-mk-lazy-builtin
|
|
"seq"
|
|
(fn (a b) (do (hk-force a) b))
|
|
2))
|
|
;; `deepseq a b` — like seq but forces `a` to normal form.
|
|
(dict-set!
|
|
env
|
|
"deepseq"
|
|
(hk-mk-lazy-builtin
|
|
"deepseq"
|
|
(fn (a b) (do (hk-deep-force a) b))
|
|
2))
|
|
;; ── Stub IO monad ─────────────────────────────────────
|
|
;; IO actions are tagged values `("IO" payload)`; `>>=` and
|
|
;; `>>` chain them. Lazy in the action arguments so do-blocks
|
|
;; can be deeply structured without forcing the whole chain
|
|
;; up front.
|
|
(dict-set!
|
|
env
|
|
"return"
|
|
(hk-mk-lazy-builtin
|
|
"return"
|
|
(fn (x) (list "IO" x))
|
|
1))
|
|
(dict-set!
|
|
env
|
|
">>="
|
|
(hk-mk-lazy-builtin
|
|
">>="
|
|
(fn (m f)
|
|
(let ((io-val (hk-force m)))
|
|
(cond
|
|
((and
|
|
(list? io-val)
|
|
(= (first io-val) "IO"))
|
|
(hk-apply (hk-force f) (nth io-val 1)))
|
|
(:else
|
|
(raise "(>>=): left side is not an IO action")))))
|
|
2))
|
|
(dict-set!
|
|
env
|
|
">>"
|
|
(hk-mk-lazy-builtin
|
|
">>"
|
|
(fn (m n)
|
|
(let ((io-val (hk-force m)))
|
|
(cond
|
|
((and
|
|
(list? io-val)
|
|
(= (first io-val) "IO"))
|
|
(hk-force n))
|
|
(:else
|
|
(raise "(>>): left side is not an IO action")))))
|
|
2))
|
|
;; Operators as first-class values
|
|
(dict-set! env "+" (hk-make-binop-builtin "+" "+"))
|
|
(dict-set! env "-" (hk-make-binop-builtin "-" "-"))
|
|
(dict-set! env "*" (hk-make-binop-builtin "*" "*"))
|
|
(dict-set! env "/" (hk-make-binop-builtin "/" "/"))
|
|
(dict-set! env "==" (hk-make-binop-builtin "==" "=="))
|
|
(dict-set! env "/=" (hk-make-binop-builtin "/=" "/="))
|
|
(dict-set! env "<" (hk-make-binop-builtin "<" "<"))
|
|
(dict-set! env "<=" (hk-make-binop-builtin "<=" "<="))
|
|
(dict-set! env ">" (hk-make-binop-builtin ">" ">"))
|
|
(dict-set! env ">=" (hk-make-binop-builtin ">=" ">="))
|
|
(dict-set! env "&&" (hk-make-binop-builtin "&&" "&&"))
|
|
(dict-set! env "||" (hk-make-binop-builtin "||" "||"))
|
|
(dict-set! env "++" (hk-make-binop-builtin "++" "++"))
|
|
(dict-set! env "mod" (hk-make-binop-builtin "mod" "mod"))
|
|
(dict-set! env "div" (hk-make-binop-builtin "div" "div"))
|
|
(dict-set! env "rem" (hk-make-binop-builtin "rem" "rem"))
|
|
(dict-set! env "quot" (hk-make-binop-builtin "quot" "quot"))
|
|
(hk-load-into! env hk-prelude-src)
|
|
env)))
|
|
|
|
(define
|
|
hk-bind-decls!
|
|
(fn
|
|
(env decls)
|
|
(let
|
|
((groups (dict))
|
|
(group-order (list))
|
|
(pat-binds (list)))
|
|
;; Pass 1: collect fun-clause groups by name; track first-seen
|
|
;; order so pass 3 can evaluate 0-arity bodies in source order
|
|
;; (forward references to other 0-arity definitions still need
|
|
;; the earlier name to be bound first).
|
|
(for-each
|
|
(fn (d)
|
|
(cond
|
|
((= (first d) "fun-clause")
|
|
(let
|
|
((name (nth d 1)))
|
|
(when (not (has-key? groups name))
|
|
(append! group-order name))
|
|
(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 (arity > 0) — order doesn't matter
|
|
;; because they're closures; collect 0-arity names in source
|
|
;; order for pass 3.
|
|
(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))))))
|
|
group-order)
|
|
;; Pass 3: evaluate 0-arity bodies and pat-binds in source
|
|
;; order — forward references to a later 0-arity name will
|
|
;; still see its placeholder (nil) and fail noisily, but the
|
|
;; common case of a top-down program works.
|
|
(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)))))
|
|
|
|
;; Eagerly build the Prelude env once at load time; each call to
|
|
;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude.
|
|
(define hk-env0 (hk-init-env))
|
|
|
|
(define
|
|
hk-eval-expr-source
|
|
(fn
|
|
(src)
|
|
(hk-deep-force (hk-eval (hk-core-expr src) (hk-dict-copy hk-env0)))))
|