session.ml already had this: a compile-time walk over a Tast type that
emits the calls to print a value of it, handling every concrete type the
language has. It was dev-build-only and went to flan_dev_emit, and
prelude.ml justified the per-type print-* functions by saying a real
println had to wait for milestone 5 and generics. It did not. plan.org
specifies println as compiler-provided and per concrete type, which is
not overloading: there is nothing to dispatch on at run time and no
user-supplied printer to choose between, so no type variables appear.
The walk moves to render.ml, parameterised on an emitter and a slot
allocator. The emitter is five functions rather than five extern names
because the two sides are not both extern calls -- the REPL's are, and
stdout's compose a conversion with a write. The slot allocator differs
too: the REPL builds a thunk's frame, println takes slots from the
enclosing function being checked, once per call site.
Two runtime shims, both only reachable from the walk. flan_u64_to_bytes,
because routing u64 through the signed printer makes 0xFFFF...F read as
-1, which is the one way println could disagree with the REPL about a
value both can hold. flan_escape_bytes, so a string nested in a printed
structure is quoted and escaped -- same table as flan_dev_emit_str, noted
in both, because the REPL and println must not disagree about what a
struct looks like.
A string at top level prints raw and nested prints quoted. Not a conflict:
(println "hello") has to print hello, and a struct's string field has to
be distinguishable from the punctuation around it. The split is top-level
vs nested, so it lives in check.ml and not in the walk.
Found on the way: a field of an Option had no gep in emit.ml, so the
walk's Option arm had never run -- the REPL would have failed on one too.
Option is { i8, T } with no declared name, so its layout is now spelled
out. Nothing in the surface language reaches a field of an Option; the
printer does, to read the tag without unwrapping a None.
The print-* functions stay. They print without a newline, which println
cannot express -- slices.flan's show prints elements separated by spaces
-- and they are raw where print is structural.
println.flan covers every arm at -O0 and -O2: the u64, the raw/quoted
split, both Option arms, the depth and span caps, and the slice arm's
loop twice over plus once inside a dotimes, which is where per-call-site
slot allocation would show if it were per-iteration.
116 lines
3.4 KiB
Plaintext
116 lines
3.4 KiB
Plaintext
;;;; println, one row per arm of the structural printer.
|
|
;;;;
|
|
;;;; The walk in render.ml is shared with the REPL, but until now its only
|
|
;;;; coverage was the REPL tests -- and those run a dev build, where the pieces
|
|
;;;; go to flan_dev_emit. println is the same walk with the other emitter, in
|
|
;;;; an ordinary build, so every arm needs saying again here: the two emitters
|
|
;;;; are the one thing the shared module does *not* make identical.
|
|
;;;;
|
|
;;;; Run at -O0 as well as -O2. The slice arm emits a loop over slots taken
|
|
;;;; from the enclosing function's frame, and mem2reg is exactly the pass that
|
|
;;;; would launder a slot mistake into working code.
|
|
|
|
(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])
|
|
|
|
;; Five deep, so the walk hits max_depth (4) and prints "..." rather than
|
|
;; descending forever. A self-containing struct is the case this cap exists
|
|
;; for; five nested ones are the same shape without needing a pointer.
|
|
(defstruct D5 [n i32])
|
|
(defstruct D4 [d D5])
|
|
(defstruct D3 [d D4])
|
|
(defstruct D2 [d D3])
|
|
(defstruct D1 [d D2])
|
|
|
|
(defvar b Blob)
|
|
(defvar col Colour)
|
|
(defvar big u64)
|
|
(defvar small u64)
|
|
(defvar arr [4 i32])
|
|
(defvar wide [10 i32])
|
|
(defvar deep D1)
|
|
(defvar n i32)
|
|
|
|
(defn nothing [] )
|
|
|
|
(defn find-it [s [i32] k i32] (Option i32)
|
|
(dotimes [i (len s)]
|
|
(when (= (at s i) k) (return (Some i))))
|
|
None)
|
|
|
|
(defn main [] i32
|
|
;; A string at top level prints raw. Anywhere else it is quoted, which the
|
|
;; Blob row below shows -- the two are the same value printed two ways on
|
|
;; purpose, and that difference is the thing most likely to be "fixed".
|
|
(println "plain string")
|
|
(println (bytes "plain bytes"))
|
|
|
|
(println 42)
|
|
(println -7)
|
|
(set small 5)
|
|
(println small)
|
|
;; The u64 row. Through the signed printer this reads -1, which is the one
|
|
;; way println could disagree with the REPL about a value both can hold.
|
|
(set big 0xFFFFFFFFFFFFFFFF)
|
|
(println big)
|
|
|
|
(println 3.5)
|
|
(println -0.25)
|
|
|
|
(println true)
|
|
(println false)
|
|
|
|
;; Unit is evaluated and *then* reported: a Unit expression is a call made
|
|
;; for its effect, so emitting () without running it would be a lie.
|
|
(println (nothing))
|
|
|
|
(set col :green)
|
|
(println col)
|
|
;; red is 0, which is also the zero value, so this row says the chain of
|
|
;; comparisons reaches the *first* member and does not fall through to the
|
|
;; number it is erased to.
|
|
(set col :red)
|
|
(println col)
|
|
|
|
;; A pointer is its shape and is never followed -- the only thing that could
|
|
;; make this walk cycle.
|
|
(set n 3)
|
|
(println (addr n))
|
|
|
|
(println (find-it (slice wide 0 10) 0))
|
|
(println (find-it (slice wide 0 10) 99))
|
|
|
|
(set (.id b) 7)
|
|
(set (.name b) "sandy \"quoted\"")
|
|
(set (.x (.pos b)) 1.5)
|
|
(set (.y (.pos b)) -2.0)
|
|
(set (at (.tags b) 1) 42)
|
|
(println b)
|
|
|
|
(set (at arr 2) 9)
|
|
(println arr)
|
|
|
|
;; Ten elements against a span cap of eight: eight, then "...".
|
|
(set (at wide 9) 1)
|
|
(println wide)
|
|
|
|
;; A slice's length is not known until it runs, so this is the arm that
|
|
;; emits a loop rather than unrolling.
|
|
(println (slice arr 1 4))
|
|
|
|
(set (.n (.d (.d (.d (.d deep))))) 5)
|
|
(println deep)
|
|
|
|
;; Two slice printlns in one function, and one inside a loop: the slots the
|
|
;; loop needs are allocated per *call site* at check time, not per iteration.
|
|
(dotimes [i 2]
|
|
(println (slice arr 0 2)))
|
|
(println (slice arr 2 4))
|
|
|
|
;; print is println without the newline.
|
|
(print "a")
|
|
(print "b")
|
|
(println "c")
|
|
0)
|