An enum and an integer convert, both ways, when you say so
This commit is contained in:
parent
3e3d3b28f0
commit
d2bd022094
31
NEXT.md
31
NEXT.md
@ -216,12 +216,31 @@ vague intention — if it is listed, someone has already established it is real.
|
||||
Ranked by how often they were hit, top two first because they are walls rather than conveniences:
|
||||
|
||||
1. ~~No number reaches `draw-text`.~~ **Fixed** by `(string b)`.
|
||||
2. **An enum parameter cannot be driven by a loop variable.** `(rl/get-gamepad-axis-movement pad i)` with an `i32`
|
||||
index: *expected rl/GamepadAxis, found i32*, and the other direction refuses too — *i32 converts a number, found
|
||||
rl/Gesture*. The obvious escape is closed as well: a second `declare-c` of the same symbol with an `i32` face gives
|
||||
*one declare-c per C function, and another Flan name for it is a defn* — and a `defn` renames without retyping.
|
||||
Two individually-correct rules composing into a wall; the caller writes a `cond` over the members instead. Cost:
|
||||
every gamepad-axis loop, and four comparisons on the raw gesture bitfield.
|
||||
2. ~~An enum parameter cannot be driven by a loop variable.~~ **Fixed** by explicit conversions in both directions:
|
||||
`(i32 k)` takes an enum to its integer, `(GamepadAxis n)` takes an integer to an enum. Neither is an instruction —
|
||||
an enum is an i32 at run time and `emit.ml`'s `cast` already reduced one to that before choosing an opcode — so the
|
||||
change is a guard in `check.ml`'s cast arm and nothing in the backend. The rule the refusals came from is
|
||||
deliberately *not* relaxed: a bare integer still does not fit an enum parameter, so `:spcae` is still an error at
|
||||
the call site. The rule was "an integer must not arrive silently", and a written `(GamepadAxis i)` is not silent.
|
||||
The other escape stays closed too — one `declare-c` per C function — and no longer needs to be open.
|
||||
- **A value that is no declared member is allowed**, deliberately. raylib's gesture is a bitfield and an OR of
|
||||
flags is a legal `Gesture` that is no single member; and `session.ml`'s printer already falls through to the
|
||||
number for an out-of-range enum, on purpose, so refusing to construct one while agreeing to print it would be
|
||||
incoherent. An `Option` would make every site unwrap for no safety bought, and a literal-only refusal would
|
||||
catch nothing, because the bitfield case is a run-time value.
|
||||
- **Only an integer converts *to* an enum.** Not a float, and not another enum — a cross-enum hop goes through
|
||||
`(i32 x)` so both ends are written down. Enum → any numeric is always allowed: lossless to i32 by construction,
|
||||
and a narrower target truncates by the rule every int→int cast already follows.
|
||||
- **The comparisons needed nothing else.** `(> (i32 g) 255)` checks because `binary` takes the non-literal side
|
||||
first; `binary` was deliberately left ignorant of enums, since teaching it would be the implicit conversion this
|
||||
avoids.
|
||||
- **A bit-set type later builds on this rather than replacing it.** It would be its own type with its own
|
||||
operations and would still want a named escape to the underlying integer for the FFI, spelled the same way. If
|
||||
`Gesture` becomes one, the `(i32 g)` calls stay valid and only the range tests migrate to a membership test.
|
||||
- One parse fix came with it: `defenum` names were not in `parse.ml`'s type set, so a local enum could not be a
|
||||
function's return type. They are in it now under a key of their own, admitted as a bare symbol and never as a
|
||||
list head — because `(Key n)` is a *value* now, and putting `Key` in `types` would make a body starting with one
|
||||
be eaten as a return type.
|
||||
3. **`break` is not implemented.** Declined deliberately rather than built — see below.
|
||||
4. **A `let` binding takes no type annotation**, so a fixed array is either a top-level `defvar` or a literal with
|
||||
every element spelled out. `(let [pts [4 rl/Vector2]] …)` parses as a two-element array literal and fails with
|
||||
|
||||
@ -63,32 +63,32 @@
|
||||
|
||||
;; The C's read-out loop is `for (i = 0; i < GetGamepadAxisCount(gamepad); i++)
|
||||
;; DrawText(TextFormat("AXIS %i: %.02f", i, GetGamepadAxisMovement(gamepad, i)))`
|
||||
;; — an axis selected by a loop variable. That cannot go through the binding,
|
||||
;; twice over:
|
||||
;; — an axis selected by a loop variable, which is what this is: an integer
|
||||
;; converts to an enum when the conversion is written by name.
|
||||
;;
|
||||
;; It used to be a six-armed cond over the members, because an enum is its own
|
||||
;; type in the checker and a bare i32 does not fit one:
|
||||
;;
|
||||
;; expected rl/GamepadAxis, found i32
|
||||
;;
|
||||
;; because an integer does not convert to an enum and a keyword can only name
|
||||
;; one member; and a second declare-c of the same C function with an i32
|
||||
;; parameter is refused as well —
|
||||
;; That rule is unchanged and should be. It is what makes a misspelled
|
||||
;; `:lft-x` an error *here*, at the call site, rather than a wrong axis read
|
||||
;; later — which is the whole reason an enum is a type at all rather than a
|
||||
;; pile of i32 constants. What changed is that the conversion can be said out
|
||||
;; loud: the rule was never "an integer is dangerous", it was "an integer must
|
||||
;; not arrive silently", and (rl/GamepadAxis index) is not silent.
|
||||
;;
|
||||
;; rl/get-gamepad-axis-movement and rl/get-gamepad-axis-movement-by-index
|
||||
;; both bind the C function GetGamepadAxisMovement — one declare-c per C
|
||||
;; function, and another Flan name for it is a defn
|
||||
;; (The other escape stays closed, and should: a second declare-c of
|
||||
;; GetGamepadAxisMovement with an i32 face is still refused — one declare-c
|
||||
;; per C function — and a defn wrapper still cannot retype a parameter. It is
|
||||
;; no longer needed.)
|
||||
;;
|
||||
;; — and a defn wrapper cannot change a parameter's type. So the index is
|
||||
;; turned back into a member here, by hand, which is the only shape left. It
|
||||
;; covers the six axes raylib names; a pad reporting more reads 0.0 for the
|
||||
;; extras, and the count is clamped below so they are not drawn at all.
|
||||
;; An index raylib does not name converts too, rather than being refused, and
|
||||
;; raylib bounds-checks its own axis and answers 0.0. So the (min 6 ...) clamp
|
||||
;; at the call site is what keeps the read-out to the six axes raylib names —
|
||||
;; load-bearing now rather than cosmetic, since it replaced the :else arm.
|
||||
(defn axis-at [pad i32 index i32] f32
|
||||
(cond
|
||||
(= index 0) (rl/get-gamepad-axis-movement pad :left-x)
|
||||
(= index 1) (rl/get-gamepad-axis-movement pad :left-y)
|
||||
(= index 2) (rl/get-gamepad-axis-movement pad :right-x)
|
||||
(= index 3) (rl/get-gamepad-axis-movement pad :right-y)
|
||||
(= index 4) (rl/get-gamepad-axis-movement pad :left-trigger)
|
||||
(= index 5) (rl/get-gamepad-axis-movement pad :right-trigger)
|
||||
:else 0.0))
|
||||
(rl/get-gamepad-axis-movement pad (rl/GamepadAxis index)))
|
||||
|
||||
(defn draw-pad-background []
|
||||
(rl/draw-rectangle-rounded
|
||||
|
||||
@ -5,20 +5,32 @@
|
||||
;;;; the language. Four things it needed, in descending order of how much they
|
||||
;;;; cost.
|
||||
;;;;
|
||||
;;;; **An enum does not convert to an integer.** The C treats the gesture as
|
||||
;;;; the raw bitfield it is and compares it with `<` and `>`:
|
||||
;;;; **An enum converts to an integer, when you say so.** The C treats the
|
||||
;;;; gesture as the raw bitfield it is and compares it with `<` and `>`:
|
||||
;;;; `currentGesture > 255` picks out the two pinches, `> 15` the four swipes,
|
||||
;;;; `!= 4` excludes hold, `< 3` admits tap and double-tap. `get-gesture-
|
||||
;;;; detected` answers an `rl/Gesture`, and
|
||||
;;;; detected` answers an `rl/Gesture`, and that used to end the discussion —
|
||||
;;;;
|
||||
;;;; i32 converts a number, found rl/Gesture
|
||||
;;;;
|
||||
;;;; so none of those comparisons can be written. They are spelled out below as
|
||||
;;;; named predicates over keyword equalities — `pinch?`, `swipe?`, `tapish?`.
|
||||
;;;; That is arguably better source than the magic numbers were, and it is
|
||||
;;;; strictly more checkable, but it is not a choice: it is the only shape
|
||||
;;;; available, and a gesture raylib adds later would silently fall out of
|
||||
;;;; `swipe?` where the C's `> 15` would have caught it.
|
||||
;;;; — so the three range tests were spelled out as keyword equalities, one
|
||||
;;;; arm per member. They are `(> (i32 g) 255)`, `(> (i32 g) 15)` and
|
||||
;;;; `(< (i32 g) 3)` now, still behind the named predicates below, and the fix
|
||||
;;;; is not brevity: the enumerated version was *wrong about the future*. A
|
||||
;;;; gesture raylib adds later falls silently out of a list of four members,
|
||||
;;;; where the C's `> 15` catches it. The range test is the honest reading of
|
||||
;;;; a bitfield and now it is the one written.
|
||||
;;;;
|
||||
;;;; The rule the refusal came from is unchanged and worth keeping: an enum is
|
||||
;;;; its own type in the checker, so `:tpa` is an error at the call site
|
||||
;;;; instead of a number that is wrong later, and a bare integer still does not
|
||||
;;;; fit an `rl/Gesture` parameter. `(i32 g)` does not weaken that — it is
|
||||
;;;; named, and it is at the site. The rule was "an integer must not arrive
|
||||
;;;; silently", not "an integer is dangerous".
|
||||
;;;;
|
||||
;;;; The `!= 4` stays a keyword comparison, `(not (= g :hold))`. That one was
|
||||
;;;; never a range test; it is a single member, and the C's 4 is a magic
|
||||
;;;; number the keyword reads better than.
|
||||
;;;;
|
||||
;;;; **No sin or cos.** The prelude has sqrt-f32 — one `declare` over libm,
|
||||
;;;; with a comment explaining why it is not a builtin — and nothing else
|
||||
@ -76,18 +88,26 @@
|
||||
|
||||
;; ── The comparisons the C makes on the raw bitfield ─────────────────
|
||||
;;
|
||||
;; See the header: an rl/Gesture will not convert to an i32, so `> 255` and
|
||||
;; friends are these instead.
|
||||
;; A gesture is a flag: 1, 2, 4, 8 … up to 512, so the ranges are the C's way
|
||||
;; of asking which family a gesture belongs to. (i32 g) is what lets that be
|
||||
;; written; they are named here rather than inline because a bare 255 at a
|
||||
;; call site says nothing, and because a gesture raylib adds later lands in
|
||||
;; the right family without this file being edited.
|
||||
|
||||
(defn pinch? [g rl/Gesture] bool ; the C's `> 255`
|
||||
(or (= g :pinch-in) (= g :pinch-out)))
|
||||
(> (i32 g) 255))
|
||||
|
||||
(defn swipe? [g rl/Gesture] bool ; the C's `> 15`
|
||||
(or (or (= g :swipe-right) (= g :swipe-left))
|
||||
(or (= g :swipe-up) (= g :swipe-down))))
|
||||
(> (i32 g) 15))
|
||||
|
||||
(defn tapish? [g rl/Gesture] bool ; the C's `< 3`
|
||||
(or (= g :tap) (= g :double-tap)))
|
||||
(< (i32 g) 3))
|
||||
|
||||
;; Two orderings these impose, both of them the C's as well. A pinch is above
|
||||
;; 255 and therefore above 15, so swipe? has to be asked after the pinches
|
||||
;; rather than before; and tapish? admits :none, which is 0, so it belongs
|
||||
;; under a :none guard. The C's switch over single members hides both; a range
|
||||
;; test cannot.
|
||||
|
||||
(defn gesture-name [g rl/Gesture] string
|
||||
(cond
|
||||
@ -109,9 +129,12 @@
|
||||
(= g :tap) rl/blue
|
||||
(= g :double-tap) rl/skyblue
|
||||
(= g :drag) rl/lime
|
||||
(swipe? g) rl/red
|
||||
;; The two pinches are above 255 and so are above 15 as well: swipe? is a
|
||||
;; range test and has to be asked after them, not before. The C gets this
|
||||
;; for free by being a switch over single members.
|
||||
(= g :pinch-in) rl/violet
|
||||
(= g :pinch-out) rl/orange
|
||||
(swipe? g) rl/red
|
||||
:else rl/black))
|
||||
|
||||
;; ── The log ─────────────────────────────────────────────────────────
|
||||
|
||||
55
lib/check.ml
55
lib/check.ml
@ -1359,13 +1359,60 @@ and named_call ctx ~want loc name args =
|
||||
arity loc name 0 args;
|
||||
prim Tast.Argv (Types.Slice Types.String) []
|
||||
|
||||
(* ── casts: (i32 x), (f64 x) ───────────────────────────────────── *)
|
||||
(* ── casts: (i32 x), (f64 x), and an enum both ways ────────────────
|
||||
|
||||
(i32 e) and (GamepadAxis n) are written here rather than in an arm of
|
||||
their own because they are the same operation: an enum is an i32 at run
|
||||
time — Types.Enum says so — and emit.ml's [cast] already reduces one to
|
||||
its i32 before choosing an instruction. So both directions cost nothing:
|
||||
src and target are equal after that reduction and [cast] answers the
|
||||
value unchanged.
|
||||
|
||||
Why this does not give the typo back. The property worth keeping is that
|
||||
:spcae at a call site is an error at that site, and it still is: a
|
||||
keyword resolves against the parameter's enum and a bare integer does not
|
||||
fit one. What changes is only that a program can *say* it means the
|
||||
conversion, by name, at the site. The rule was never "an integer is
|
||||
dangerous", it was "an integer must not arrive silently", and a written
|
||||
(GamepadAxis i) is not silent.
|
||||
|
||||
Three sub-decisions:
|
||||
|
||||
1. enum → any numeric is always allowed and never checked. It is lossless
|
||||
to i32 by construction, and a narrower target truncates by the same
|
||||
rule every int→int cast already follows — no special case, and (f32 e)
|
||||
means (f32 (i32 e)) rather than an arbitrary refusal.
|
||||
|
||||
2. integer → enum accepts a value that is not a declared member. raylib's
|
||||
gesture is a bitfield and an OR of flags is a legal Gesture that is no
|
||||
single member, so refusing it would refuse correct programs; and
|
||||
session.ml's printer already falls through to the number for an
|
||||
out-of-range enum, on purpose, so refusing to *construct* one while
|
||||
blessing its display would be incoherent. An Option would make every
|
||||
site unwrap for no safety bought, and a literal-only refusal would
|
||||
catch nothing — the bitfield case is a run-time value.
|
||||
|
||||
3. Only an integer converts *to* an enum. Not a float, which has no
|
||||
meaning here, and not another enum: an enum-to-enum hop goes through
|
||||
(i32 x) so that both ends are written down. *)
|
||||
| _ when Hashtbl.mem ctx.env.enums name ->
|
||||
arity loc name 1 args;
|
||||
let target = resolve_name ctx.env ~seen:[] loc name in
|
||||
let a = check ctx (List.hd args) in
|
||||
(match a.Tast.ty with
|
||||
| Types.Int _ -> ()
|
||||
| other ->
|
||||
fail loc "%s converts an integer to an enum, found %s — an enum or a \
|
||||
float goes through (i32 x) first" name
|
||||
(Types.to_string other));
|
||||
prim (Tast.Cast target) target [ a ]
|
||||
| _ when is_cast name && List.length args = 1 ->
|
||||
let target = resolve_name ctx.env ~seen:[] loc name in
|
||||
let a = check ctx (List.hd args) in
|
||||
if not (Types.is_numeric a.Tast.ty) then
|
||||
fail loc "%s converts a number, found %s" name
|
||||
(Types.to_string a.Tast.ty);
|
||||
(match a.Tast.ty with
|
||||
| Types.Enum _ -> ()
|
||||
| t when Types.is_numeric t -> ()
|
||||
| t -> fail loc "%s converts a number, found %s" name (Types.to_string t));
|
||||
prim (Tast.Cast target) target [ a ]
|
||||
|
||||
(* ── ordinary calls ────────────────────────────────────────────── *)
|
||||
|
||||
11
lib/parse.ml
11
lib/parse.ml
@ -802,7 +802,8 @@ and qualified_type types s =
|
||||
|
||||
and is_type_form types (f : Form.t) =
|
||||
match f.v with
|
||||
| Sym s -> Names.mem s types || qualified_type types s
|
||||
| Sym s ->
|
||||
Names.mem s types || Names.mem ("enum " ^ s) types || qualified_type types s
|
||||
| Vec _ -> true (* [T] and [n T] are only types *)
|
||||
| Map _ -> true (* {K V} in this position *)
|
||||
| List ({ v = Sym n; _ } :: _) ->
|
||||
@ -831,6 +832,14 @@ let declared_types (forms : Form.t list) : Names.t =
|
||||
it that nothing imported. *)
|
||||
| List [ { v = Sym "import"; _ }; { v = Sym a; _ }; { v = Str _; _ } ] ->
|
||||
Names.add ("import " ^ a) acc
|
||||
(* An enum is a type too, but under its own key rather than beside the
|
||||
structs, because [(Key n)] is now a *value* — the integer-to-enum
|
||||
conversion — and putting Key in [types] would make [is_type_form]
|
||||
read that as a type application and eat it as a return type. So an
|
||||
enum name counts only as a bare symbol, which is the one position it
|
||||
can appear in as a type, and never as a list head. *)
|
||||
| List [ { v = Sym "defenum"; _ }; { v = Sym n; _ }; _ ] ->
|
||||
Names.add ("enum " ^ n) acc
|
||||
| _ -> acc)
|
||||
builtin_types forms
|
||||
|
||||
|
||||
71
test/programs/enum-convert.flan
Normal file
71
test/programs/enum-convert.flan
Normal file
@ -0,0 +1,71 @@
|
||||
;;;; 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)
|
||||
@ -270,4 +270,22 @@
|
||||
(Some p) (show-v p)
|
||||
None (println "no crossing"))
|
||||
|
||||
;; An enum reaching a declare-c parameter through (Enum n): the axis is a
|
||||
;; loop variable, which is what nothing could express before. No pad is
|
||||
;; attached, so raylib answers its own resting value per axis — 0.0 for the
|
||||
;; four stick axes and -1.0 for the two triggers, which rest at the negative
|
||||
;; end. That split is what makes this pin worth having: it shows six
|
||||
;; distinct i32s arriving as six distinct GamepadAxis members rather than
|
||||
;; one constant answered six times.
|
||||
(print "axes")
|
||||
(dotimes [i 6]
|
||||
(print " ")
|
||||
(print (rl/get-gamepad-axis-movement 0 (rl/GamepadAxis i))))
|
||||
;; And an axis that is no declared member. raylib bounds-checks it itself and
|
||||
;; answers 0.0; the conversion does not refuse it, for the same reason the
|
||||
;; printer shows an out-of-range enum as its number.
|
||||
(print " past-end ")
|
||||
(print (rl/get-gamepad-axis-movement 0 (rl/GamepadAxis 9)))
|
||||
(println "")
|
||||
|
||||
0)
|
||||
|
||||
@ -324,7 +324,8 @@ let () =
|
||||
point on line yes\npoint off line no\n\
|
||||
point in poly yes\npoint outside poly no\n\
|
||||
in square, four corners yes\nout of triangle, three no\n\
|
||||
no crossing\n"
|
||||
no crossing\n\
|
||||
axes 0 0 0 0 -1 -1 past-end 0\n"
|
||||
in
|
||||
if Sys.command "ldconfig -p 2>/dev/null | grep -q libraylib" = 0 then begin
|
||||
outputs "raylib ffi, headless" "programs/raylib-ffi.flan" raylib_out;
|
||||
@ -916,6 +917,28 @@ ERR@7 unexpected token: not the kind the caller was reading
|
||||
outputs ~opt:"-O0" "enum comparison, -O0" "programs/enum-compare.flan"
|
||||
enum_out;
|
||||
|
||||
(* Converting an enum, explicitly, in both directions: (i32 k) and (K n).
|
||||
An enum is an i32 at run time, so neither direction is an instruction
|
||||
and the interesting thing is what the checker will let through — which
|
||||
is why this is here and at -O0 rather than only in test_flan. The rows
|
||||
are the three members out, a round trip back, a value that is no
|
||||
declared member and the comparisons it exists for, a narrowing and a
|
||||
widening of a negative member, and an enum parameter driven by a loop
|
||||
variable, which is the whole point.
|
||||
|
||||
The two bare prints in the middle are the ones that hold the design up
|
||||
rather than merely exercising it: 7 prints as 7 and 1 prints as :hi.
|
||||
Allowing a non-member to be *built* is only coherent because the
|
||||
printer already shows one as its number, and this is where that is
|
||||
asserted rather than asserted about. *)
|
||||
let enum_conv_out =
|
||||
"-1\n0\n1\n1\n-1\n7\n7\n:hi\nnot a member\nabove 3\n-1\n255\n1\n\
|
||||
lo\nmid\nhi\nother\n"
|
||||
in
|
||||
outputs "enum conversion" "programs/enum-convert.flan" enum_conv_out;
|
||||
outputs ~opt:"-O0" "enum conversion, -O0" "programs/enum-convert.flan"
|
||||
enum_conv_out;
|
||||
|
||||
|
||||
(* ── declare-c: the generated FFI shim (lib/shim.ml) ────────────────
|
||||
The raylib package is the proof that the generator is real — 84
|
||||
|
||||
@ -633,6 +633,39 @@ let () =
|
||||
~needle:"has no member :spcae";
|
||||
accepts "a keyword that is a member"
|
||||
"(defenum Key [space 32 r 82]) (defn g [k Key]) (defn f [] (g :r))";
|
||||
(* Converting an enum, explicitly, in both directions. The point of the
|
||||
conversion is that it is written at the site: a bare integer still does
|
||||
not fit an enum parameter, so the checked property — a typo is an error
|
||||
here rather than a wrong number later — is untouched. *)
|
||||
accepts "an enum converts to an integer"
|
||||
"(defenum Key [space 32]) (defn f [k Key] i32 (i32 k))";
|
||||
accepts "an enum converts to a float, through its i32"
|
||||
"(defenum Key [space 32]) (defn f [k Key] f32 (f32 k))";
|
||||
accepts "an integer converts to an enum"
|
||||
"(defenum Key [space 32]) (defn g [k Key]) (defn f [i i32] (g (Key i)))";
|
||||
accepts "a value that is no declared member converts"
|
||||
"(defenum Key [space 32]) (defn g [k Key]) (defn f [] (g (Key 999)))";
|
||||
rejects_check "an integer still does not fit an enum on its own"
|
||||
"(defenum Key [space 32]) (defn g [k Key]) (defn f [i i32] (g i))"
|
||||
~needle:"expected Key";
|
||||
rejects_check "an enum does not convert to another enum"
|
||||
"(defenum A [x 1]) (defenum B [y 1]) (defn f [a A] B (B a))"
|
||||
~needle:"converts an integer to an enum";
|
||||
rejects_check "a float does not convert to an enum"
|
||||
"(defenum Key [space 32]) (defn f [x f32] Key (Key x))"
|
||||
~needle:"converts an integer to an enum";
|
||||
(* An enum is a return type, which needed parse.ml to know enum names. It
|
||||
knows them under a key of their own: (Key n) is a value now, so putting
|
||||
Key in [types] would make a body starting with one be eaten as a return
|
||||
type — the exact trap [is_type_form]'s comment is about. *)
|
||||
accepts "an enum is a return type"
|
||||
"(defenum Key [space 32]) (defn f [i i32] Key (Key i))";
|
||||
accepts "an enum conversion at the head of a body is not a return type"
|
||||
"(defenum Key [space 32]) (defn g [k Key]) \
|
||||
(defn f [] (Key 1) (g :space))";
|
||||
rejects_check "an enum conversion takes one argument"
|
||||
"(defenum Key [space 32]) (defn f [] Key (Key 1 2))"
|
||||
~needle:"1 argument";
|
||||
(* A folded constant skips [check], so its range check has to be its own. *)
|
||||
rejects_check "a folded constant is still range-checked"
|
||||
"(defconst c u8 300) (defn f [] u8 c)" ~needle:"does not fit in u8";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user