flan/test/programs/printers.flan
Joseph Ferano 20fedd4ad8 Printers for every shape a value can have
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.
2026-09-11 07:18:07 +07:00

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)