forked from alex-eg/sex
implement closures
This commit is contained in:
@@ -270,4 +270,59 @@ compiles."
|
||||
"_Static_assert(sizeof(int) == 4, \"int is four bytes\")"))
|
||||
(test-assert "and not the header macro"
|
||||
(not (emits? (in-fn "(static-assert (== (sizeof int) 4) \"x\")")
|
||||
"static_assert(")))))
|
||||
"static_assert("))))
|
||||
|
||||
;; A closure is a code pointer beside its captures. The struct is
|
||||
;; named from the signature, so separate translation units agree on
|
||||
;; it, and calling one goes through `code' with `env' passed first.
|
||||
;;
|
||||
;; A closure struct, and the helper its calls go through, are each
|
||||
;; emitted once per signature; the registries deciding that are
|
||||
;; compile-time state like the type databases, and outlive a single
|
||||
;; `sex->c' here. So every case below that looks for a *definition*
|
||||
;; uses a signature of its own -- cases looking at a call site can
|
||||
;; share one.
|
||||
(test-group "closures"
|
||||
(test-assert "the type becomes a struct named for its signature"
|
||||
(emits? "(fn f ((c (closure ((int)) int))) int (return (c 1)))"
|
||||
"struct ƛint_int"))
|
||||
;; The environment is one shared union, declared in the prelude --
|
||||
;; its layout is part of the ABI two units agree on, so it cannot
|
||||
;; depend on what either file contains. `sex->c' has no prelude, so
|
||||
;; what is visible here is the member
|
||||
(test-assert "whose environment is the shared union"
|
||||
(emits? "(fn f ((c (closure ((float)) int))) int (return (c 1.0)))"
|
||||
"union ƛenv env;"))
|
||||
;; The receiver goes through a helper rather than being written
|
||||
;; out twice, so that `[table (++ i)]' evaluates its index once,
|
||||
;; exactly as it would for an array of function pointers
|
||||
(test-assert "a call passes the receiver to a helper"
|
||||
(emits? "(fn f ((c (closure ((int)) int))) int (return (c 1)))"
|
||||
"ƛint_int_call(c, 1)"))
|
||||
(test-assert "and the helper is what dereferences it"
|
||||
(emits? "(fn f ((c (closure ((long)) int))) int (return (c 1)))"
|
||||
"ƛc.code(&ƛc.env, ƛa0)"))
|
||||
(test-assert "a subscript receiver is evaluated once"
|
||||
(emits? "(fn f ((t (¤ (closure ((int)) int) 4)) (i int)) int (return ((¤ t (++ i)) 1)))"
|
||||
"ƛint_int_call(t[++i], 1)"))
|
||||
(test-assert "so is a member receiver"
|
||||
(emits? "(struct h ((cb (closure ((int)) int))))
|
||||
(fn f ((s (struct h))) int (return ((. s cb) 1)))"
|
||||
"ƛint_int_call(s.cb, 1)"))
|
||||
(test-assert "a captured name is rebound in the lifted body"
|
||||
(emits? "(fn f ((n int)) (closure ((char)) int) (return (closure ((x char)) int (n) (return n))))"
|
||||
"int n = ƛcaptures->n;"))
|
||||
(test-assert "captures are checked against the environment"
|
||||
(emits? "(fn f ((n int)) (closure ((short)) int) (return (closure ((x short)) int (n) (return n))))"
|
||||
"_Static_assert(sizeof(struct"))
|
||||
;; A closure over nothing has no record to point at, and C has no
|
||||
;; empty struct to declare for it
|
||||
(test-assert "no captures means no capture record"
|
||||
(not (emits? "(fn f () (closure () int) (return (closure () int () (return 7))))"
|
||||
"_captures {")))
|
||||
;; The receiver is written twice, so a name used as an argument
|
||||
;; must not be mistaken for a call of its own
|
||||
(test-assert "a closure passed as an argument stays a value"
|
||||
(emits? "(fn g ((c (closure ((int)) int))) int (return 0))
|
||||
(fn f ((c (closure ((int)) int))) int (return (g c)))"
|
||||
"g(c)"))))
|
||||
|
||||
Reference in New Issue
Block a user