flan/test/programs/enum-convert.flan

72 lines
3.0 KiB
Plaintext

;;;; Converting between an enum and an integer, both ways, explicitly.
;;;;
;;;; An enum is an i32 at run time and its own type in the checker. That is
;;;; what makes :spcae an error at the call site rather than a wrong number
;;;; later, and it is deliberately not weakened here: a bare integer still
;;;; does not fit an enum parameter, and a keyword still resolves against the
;;;; enum the site expects. What is added is a way to *say* the conversion,
;;;; by name, where it is meant — (i32 k) and (K n).
;;;;
;;;; Both directions are free. emit.ml's cast reduces an enum to its i32
;;;; before choosing an instruction, so src and target are the same type and
;;;; the value is answered unchanged; there is no instruction to see at -O0
;;;; either, which is why this runs at both optimisation levels.
(defenum K [lo -1 mid 0 hi 1])
(defn num [k K] i32 (i32 k))
;; An enum parameter driven by a loop variable, which is the shape this exists
;; for: the caller has an index, not a member.
(defn name-at [i i32] string
(let [k (K i)]
(cond (= k :lo) "lo"
(= k :mid) "mid"
(= k :hi) "hi"
:else "other")))
(defn main [] i32
;; enum → i32. Lossless by construction: i32 is the representation.
(println (num :lo))
(println (num :mid))
(println (num :hi))
;; i32 → enum, then back. A round trip is the identity both ways because
;; neither direction is a conversion at run time.
(println (num (K 1)))
(println (i32 (K (num :lo))))
;; A value that is no declared member is allowed. raylib's gesture bitfield
;; is an OR of flags and is exactly this, and the printer already falls
;; through to the number for an out-of-range enum, so refusing to construct
;; one while agreeing to print it would be incoherent.
(let [odd (K 7)]
(println (num odd))
;; And printed as itself, which is the half that makes this coherent: the
;; structural printer is a comparison chain over the declared members and
;; falls through to the number when none match. Agreeing to show a value
;; outside the members while refusing to build one would be the
;; incoherence.
(println odd)
;; The contrast: a value that IS a member prints as the member.
(println (K 1))
(println (if (= odd :hi) "member" "not a member"))
;; The comparisons the raw bitfield wants, which need nothing beyond the
;; conversion: the enum goes to i32 and the literal follows it.
(println (if (> (i32 odd) 3) "above 3" "not above 3")))
;; A narrower or wider target truncates and extends by the same rule every
;; int→int cast follows — lo is -1, so i64 sign-extends and u8 wraps.
(println (i64 (K -1)))
(println (u8 (K -1)))
;; A float target means (f32 (i32 k)); nothing special. Written as (K 1)
;; rather than :hi because a bare keyword outside an enum-typed position has
;; no enum to resolve against, and that refusal is unchanged.
(println (f32 (K 1)))
;; And the index-driven loop the whole thing is for.
(dotimes [i 4]
(println (name-at (- i 1))))
0)