C-x C-e rendered the scalars and refused the rest, which made it a calculator
rather than a REPL. The renderer is now a compile-time walk over the type,
emitting a piece at a time: structs, nested structs, fixed arrays, slices,
options, enums by name, and pointers as their shape. A raylib Color comes back
through the FFI as (rl/Color {:r 17 :g 34 :b 51 :a 68}).
Piecewise emission is what makes composites possible at all - a struct is its
fields with punctuation between them, and concatenating that in generated IR
would need an allocator the language does not have.
u64 now renders, in C, with %llu. It used to refuse because i64->bytes is
signed and it would otherwise come back as -1, but refusing a whole struct
because one field is a u64 is much worse than adding a runtime entry point.
Strings are quoted and escaped in C for the same reason: unescaped content does
not round-trip and reads as a framing bug rather than as the value it is.
An enum renders as :name, recovered from the checker's table as a chain of
comparisons, since members are erased to i32 before the backend sees them; a
value outside the declared members falls through to its number, which is what
you would want to see. A pointer is rendered and never followed - it is the
only thing that could make the walk cycle, and dereferencing one a REPL was
handed is not a safe thing to do on someone's behalf.
Three bounds, easy to conflate. depth and span bound the walk, so sand's
[100 [100 u32]] grid does not unroll into ten thousand render sites. The output
is bounded once in the runtime, since a slice renders through a loop the
compiler cannot bound, and one place enforcing it means no renderer carries a
budget.
emit.ml's cast now treats an enum as the i32 it is. Nothing in the surface
language produces that - a keyword resolves against its enum and never widens -
but the renderer needs an enum's number when it falls outside the members.
39 lines
1.2 KiB
Plaintext
39 lines
1.2 KiB
Plaintext
;;;; The fixture C-x C-e is tested against: one value of every shape the
|
|
;;;; renderer knows, held in globals so an expression has something real to
|
|
;;;; read out of a running process.
|
|
;;;;
|
|
;;;; It keeps running rather than counting reloads, because a thunk only runs
|
|
;;;; when the program next reaches a frame boundary. (agent/wait 5) is both the
|
|
;;;; poll and the pacing — 5ms of nothing, which is what a frame is when there
|
|
;;;; is no frame.
|
|
(import agent "vendor:agent")
|
|
|
|
(defstruct V [x f32 y f32])
|
|
(defstruct Blob [id i32 name string pos V tags [3 i32]])
|
|
(defenum Colour [red 0 green 1 blue 2])
|
|
|
|
(defvar ticks i64)
|
|
(defvar big u64)
|
|
(defvar b Blob)
|
|
(defvar arr [4 i32])
|
|
(defvar col Colour)
|
|
|
|
;;; Read by nothing on purpose: it is here to be *evaluated*, so that C-x C-e
|
|
;;; is tested against a constant living in the program's memory and not only
|
|
;;; against arithmetic the compiler could have done itself.
|
|
(defconst step-by i64 3)
|
|
|
|
(defn main [] i32
|
|
(agent/start "/tmp/flan-printers-fallback.sock")
|
|
(set big 0xFFFFFFFFFFFFFFFF)
|
|
(set (.id b) 7)
|
|
(set (.name b) "sandy \"quoted\"")
|
|
(set (.x (.pos b)) 1.5)
|
|
(set (at (.tags b) 1) 42)
|
|
(set (at arr 2) 9)
|
|
(set col :blue)
|
|
(dotimes [i 4000]
|
|
(agent/wait 5)
|
|
(set ticks (+ ticks 1)))
|
|
0)
|