= compares bools, a match over a bool takes true and false, and a match over a dyn takes keyword arms
This commit is contained in:
commit
af6e00bc2a
6
TODO.org
6
TODO.org
@ -308,10 +308,6 @@ keyword resolves against the expected type and against nothing else, so two enum
|
||||
could always share a member spelling. What the prefix buys is the call site read
|
||||
on its own.
|
||||
|
||||
** NEXT Keyword arms over a dyn, and = on bool
|
||||
Decided 2026-09-25: a match over a dyn takes keyword arms, meaning (= d :k); = and !=
|
||||
compare bools, and a match over a bool takes true/false arms, exhaustive without _.
|
||||
|
||||
** NEXT The .fln printer writes a flat let where the scope does not matter
|
||||
Decided 2026-09-25: a let whose name no later statement of its block mentions prints
|
||||
flat, not as a nested block; a one-argument and/or prints as its argument.
|
||||
@ -326,7 +322,7 @@ guards, or-patterns, literals at any depth, exhaustiveness over the nesting.
|
||||
** DONE match over numbers and strings
|
||||
CLOSED: [2026-09-25]
|
||||
Rules out a literal the scrutinee's type cannot hold (refused, not widened as =(=)=
|
||||
would), keyword arms over a dyn, and a bare-name catch-all: a bare name is a nullary case.
|
||||
would) and a bare-name catch-all: a bare name is a nullary case.
|
||||
|
||||
** DONE match over enums
|
||||
CLOSED: [2026-09-25]
|
||||
|
||||
229
lib/check.ml
229
lib/check.ml
@ -8373,12 +8373,16 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
(* A number, a string or a dyn: the arms are literals, and each is the
|
||||
test (= t lit) over one temporary — the enum's chain, with [=]'s own
|
||||
two lowerings for the test, so a match over a dyn means what [=] over
|
||||
it means. [is_equatable]'s set minus the enums, which are above. *)
|
||||
it means. [is_equatable]'s set minus the enums, which are above, and
|
||||
minus bool, below. *)
|
||||
| (Types.Int _ | Types.Float _ | Types.String | Types.Dyn) as t -> `Lit t
|
||||
(* A bool is a two-member enum spelled true and false: the same chain,
|
||||
and exhaustive without a [_] once both are named. *)
|
||||
| Types.Bool -> `Bool
|
||||
| other ->
|
||||
fail loc
|
||||
"match works on an Option, a data type, an enum, a number, a string \
|
||||
or a dyn, not on %s"
|
||||
"match works on an Option, a data type, an enum, a bool, a number, a \
|
||||
string or a dyn, not on %s"
|
||||
(Types.to_string other)
|
||||
in
|
||||
(* A literal arm, spelled as it was written, for the refusals that name one. *)
|
||||
@ -8398,6 +8402,8 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
| Ast.Byte b when b > 32 && b < 127 -> Printf.sprintf "\\%c" (Char.chr b)
|
||||
| Ast.Byte b -> string_of_int b
|
||||
| Ast.Str t -> Printf.sprintf "%S" t
|
||||
| Ast.Kw k -> ":" ^ k
|
||||
| Ast.Var b -> b
|
||||
| _ -> "this literal"
|
||||
in
|
||||
let what_ty t = match t with Types.Dyn -> "a dyn" | t -> Types.to_string t in
|
||||
@ -8411,11 +8417,106 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
(match t with
|
||||
| Types.String -> "\"yes\" 1 _ 0"
|
||||
| Types.Float _ -> "0.5 1 _ 0"
|
||||
| Types.Dyn -> "5 1 :go 2 _ 0"
|
||||
| _ -> "5 1 _ 0")
|
||||
in
|
||||
let bool_fix () =
|
||||
let name = match scrutinee.Ast.e with Ast.Var n -> n | _ -> "b" in
|
||||
Printf.sprintf "(match %s true 1 false 0)" name
|
||||
in
|
||||
(* The checked literal of each literal arm, by the key [resolve_pat] gave it. *)
|
||||
let lits : (string, Tast.expr) Hashtbl.t = Hashtbl.create 8 in
|
||||
let lit_values = ref [] in
|
||||
(* A literal arm over [t]: its checked value under a fresh key in [lits],
|
||||
refused if [t] cannot hold it or an earlier arm already equals it. *)
|
||||
let lit_arm (a : Ast.arm) t (e : Ast.expr) =
|
||||
let v =
|
||||
(* [=]'s dyn pair checks its literal at dyn, which boxes it. *)
|
||||
match trial ctx (fun () -> check ctx ~want:t e) with
|
||||
| Ok v -> v
|
||||
| Error _ ->
|
||||
(* A literal that does not fit is refused, where [=] would widen
|
||||
the pair and let the arm quietly never match. The literal's
|
||||
own refusal is not repeated: its fixes are casts, and a cast
|
||||
is not a pattern. *)
|
||||
(match t with
|
||||
| Types.Dyn ->
|
||||
fail a.Ast.aloc
|
||||
"this match is over a dyn, which holds a number as an i64 or \
|
||||
an f64, and %s fits in neither. Change the arm to a value an \
|
||||
i64 holds, or remove it" (spell e)
|
||||
| _ -> ());
|
||||
let tn = Types.to_string t in
|
||||
let an =
|
||||
match tn.[0] with
|
||||
| 'a' | 'e' | 'f' | 'i' | 'o' -> "an " ^ tn
|
||||
| _ -> "a " ^ tn
|
||||
in
|
||||
let why =
|
||||
match e.Ast.e, t with
|
||||
| Ast.Str _, _ -> "is a string"
|
||||
| _, Types.String -> "is a number"
|
||||
| Ast.Float x, Types.Int _ when not (Float.is_integer x) ->
|
||||
"is not a whole number"
|
||||
| Ast.Float _, Types.Int _ -> "is a float"
|
||||
| _ -> "does not fit in one"
|
||||
in
|
||||
fail a.Ast.aloc
|
||||
"this match is over %s, so each arm has to be %s, and %s %s. \
|
||||
Change the arm to a value %s holds, or remove it"
|
||||
tn an (spell e) why an
|
||||
in
|
||||
(* The arm's value at the scrutinee's type, and a second arm [=] could
|
||||
not tell from an earlier one is refused, since it can never be
|
||||
reached: 97 and \a are one u8, 0.1 and 0.10000000001 are one f32,
|
||||
and over a dyn 1 and 1.0 are equal. Compared pairwise rather than
|
||||
hashed, because dyn = between an integer and a float goes through
|
||||
the float and is not transitive past 2^53. *)
|
||||
let value =
|
||||
let f32 x = Int32.float_of_bits (Int32.bits_of_float x) in
|
||||
let num x =
|
||||
match t with Types.Float Types.F32 -> `F (f32 x) | _ -> `F x
|
||||
in
|
||||
match e.Ast.e, t with
|
||||
| Ast.Str s, _ -> `S s
|
||||
| (Ast.Int n | Ast.UInt (n, _)), Types.Float _ -> num (Int64.to_float n)
|
||||
| Ast.Byte b, Types.Float _ -> num (float_of_int b)
|
||||
| (Ast.Int n | Ast.UInt (n, _)), _ -> `I n
|
||||
| Ast.Byte b, _ -> `I (Int64.of_int b)
|
||||
| Ast.Float x, _ -> num x
|
||||
| Ast.Kw k, _ -> `K k
|
||||
| Ast.Var b, _ -> `B b
|
||||
| _ -> assert false
|
||||
in
|
||||
let same x y =
|
||||
match x, y with
|
||||
| `I a, `I b -> Int64.equal a b
|
||||
| `F a, `F b -> a = b
|
||||
| `I a, `F b | `F b, `I a -> Int64.to_float a = b
|
||||
| `S a, `S b | `K a, `K b | `B a, `B b -> String.equal a b
|
||||
| _ -> false
|
||||
in
|
||||
(match List.find_opt (fun (w, _) -> same value w) !lit_values with
|
||||
| Some (_, earlier) when earlier = spell e ->
|
||||
fail a.Ast.aloc "this match has two %s arms" earlier
|
||||
| Some (_, earlier) ->
|
||||
fail a.Ast.aloc
|
||||
"this match has two %s arms — %s equals it as %s, so this arm is \
|
||||
never reached. Remove it"
|
||||
earlier (spell e)
|
||||
(match t with
|
||||
| Types.Dyn -> "a dyn"
|
||||
| t ->
|
||||
let tn = Types.to_string t in
|
||||
(match tn.[0] with
|
||||
| 'a' | 'e' | 'f' | 'i' | 'o' -> "an " ^ tn
|
||||
| _ -> "a " ^ tn))
|
||||
| None -> ());
|
||||
lit_values := (value, spell e) :: !lit_values;
|
||||
let key = string_of_int (Hashtbl.length lits) in
|
||||
Hashtbl.replace lits key v;
|
||||
Some key, []
|
||||
in
|
||||
(* Which case each arm names, and the type of each name it binds. This is the
|
||||
whole of what differs between the two subjects; everything below it is
|
||||
shared. *)
|
||||
@ -8447,91 +8548,27 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
"this match is over the enum %s, and %s is not one of its members. An \
|
||||
arm names a member as a keyword: %s" n c
|
||||
(String.concat " " (List.map (fun (m, _) -> ":" ^ m) members))
|
||||
| `Lit t, Ast.Plit e ->
|
||||
let v =
|
||||
(* [=]'s dyn pair checks its literal at dyn, which boxes it. *)
|
||||
match trial ctx (fun () -> check ctx ~want:t e) with
|
||||
| Ok v -> v
|
||||
| Error _ ->
|
||||
(* A literal that does not fit is refused, where [=] would widen
|
||||
the pair and let the arm quietly never match. The literal's
|
||||
own refusal is not repeated: its fixes are casts, and a cast
|
||||
is not a pattern. *)
|
||||
(match t with
|
||||
| Types.Dyn ->
|
||||
fail a.Ast.aloc
|
||||
"this match is over a dyn, which holds a number as an i64 or \
|
||||
an f64, and %s fits in neither. Change the arm to a value an \
|
||||
i64 holds, or remove it" (spell e)
|
||||
| _ -> ());
|
||||
let tn = Types.to_string t in
|
||||
let an =
|
||||
match tn.[0] with
|
||||
| 'a' | 'e' | 'f' | 'i' | 'o' -> "an " ^ tn
|
||||
| _ -> "a " ^ tn
|
||||
in
|
||||
let why =
|
||||
match e.Ast.e, t with
|
||||
| Ast.Str _, _ -> "is a string"
|
||||
| _, Types.String -> "is a number"
|
||||
| Ast.Float x, Types.Int _ when not (Float.is_integer x) ->
|
||||
"is not a whole number"
|
||||
| Ast.Float _, Types.Int _ -> "is a float"
|
||||
| _ -> "does not fit in one"
|
||||
in
|
||||
fail a.Ast.aloc
|
||||
"this match is over %s, so each arm has to be %s, and %s %s. \
|
||||
Change the arm to a value %s holds, or remove it"
|
||||
tn an (spell e) why an
|
||||
in
|
||||
(* The arm's value at the scrutinee's type, and a second arm [=] could
|
||||
not tell from an earlier one is refused, since it can never be
|
||||
reached: 97 and \a are one u8, 0.1 and 0.10000000001 are one f32,
|
||||
and over a dyn 1 and 1.0 are equal. Compared pairwise rather than
|
||||
hashed, because dyn = between an integer and a float goes through
|
||||
the float and is not transitive past 2^53. *)
|
||||
let value =
|
||||
let f32 x = Int32.float_of_bits (Int32.bits_of_float x) in
|
||||
let num x =
|
||||
match t with Types.Float Types.F32 -> `F (f32 x) | _ -> `F x
|
||||
in
|
||||
match e.Ast.e, t with
|
||||
| Ast.Str s, _ -> `S s
|
||||
| (Ast.Int n | Ast.UInt (n, _)), Types.Float _ -> num (Int64.to_float n)
|
||||
| Ast.Byte b, Types.Float _ -> num (float_of_int b)
|
||||
| (Ast.Int n | Ast.UInt (n, _)), _ -> `I n
|
||||
| Ast.Byte b, _ -> `I (Int64.of_int b)
|
||||
| Ast.Float x, _ -> num x
|
||||
| _ -> assert false
|
||||
in
|
||||
let same x y =
|
||||
match x, y with
|
||||
| `I a, `I b -> Int64.equal a b
|
||||
| `F a, `F b -> a = b
|
||||
| `I a, `F b | `F b, `I a -> Int64.to_float a = b
|
||||
| `S a, `S b -> String.equal a b
|
||||
| _ -> false
|
||||
in
|
||||
(match List.find_opt (fun (w, _) -> same value w) !lit_values with
|
||||
| Some (_, earlier) when earlier = spell e ->
|
||||
fail a.Ast.aloc "this match has two %s arms" earlier
|
||||
| Some (_, earlier) ->
|
||||
fail a.Ast.aloc
|
||||
"this match has two %s arms — %s equals it as %s, so this arm is \
|
||||
never reached. Remove it"
|
||||
earlier (spell e)
|
||||
(match t with
|
||||
| Types.Dyn -> "a dyn"
|
||||
| t ->
|
||||
let tn = Types.to_string t in
|
||||
(match tn.[0] with
|
||||
| 'a' | 'e' | 'f' | 'i' | 'o' -> "an " ^ tn
|
||||
| _ -> "a " ^ tn))
|
||||
| None -> ());
|
||||
lit_values := (value, spell e) :: !lit_values;
|
||||
let key = string_of_int (Hashtbl.length lits) in
|
||||
Hashtbl.replace lits key v;
|
||||
Some key, []
|
||||
| `Lit t, Ast.Plit e -> lit_arm a t e
|
||||
(* Over a dyn a keyword is a value like any other, so :north is the arm
|
||||
(= d :north), and true and false are the arms (= d true) and
|
||||
(= d false). *)
|
||||
| `Lit Types.Dyn, Ast.Pkw k ->
|
||||
lit_arm a Types.Dyn { Ast.e = Ast.Kw k; loc = a.Ast.aloc }
|
||||
| `Lit Types.Dyn, Ast.Pctor (("true" | "false") as b, []) ->
|
||||
lit_arm a Types.Dyn { Ast.e = Ast.Var b; loc = a.Ast.aloc }
|
||||
| `Bool, Ast.Pctor (("true" | "false") as b, []) -> Some b, []
|
||||
| `Bool, Ast.Pctor (c, _) ->
|
||||
fail a.Ast.aloc
|
||||
"%s names a case, and this match is over a bool, whose arms are true \
|
||||
and false, as in %s" c (bool_fix ())
|
||||
| `Bool, Ast.Pkw k ->
|
||||
fail a.Ast.aloc
|
||||
":%s is a keyword, and this match is over a bool, whose arms are true \
|
||||
and false, as in %s" k (bool_fix ())
|
||||
| `Bool, Ast.Plit e ->
|
||||
fail a.Ast.aloc
|
||||
"%s is a literal, and this match is over a bool, whose arms are true \
|
||||
and false, as in %s" (spell e) (bool_fix ())
|
||||
| `Lit t, Ast.Pkw k ->
|
||||
fail a.Ast.aloc
|
||||
":%s is an enum member, and this match is over %s, whose arms are \
|
||||
@ -8720,6 +8757,8 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
List.filter_map
|
||||
(fun (m, _) -> if Hashtbl.mem seen m then None else Some (":" ^ m))
|
||||
members
|
||||
| `Bool ->
|
||||
List.filter (fun c -> not (Hashtbl.mem seen c)) [ "true"; "false" ]
|
||||
| `Lit _ -> []
|
||||
in
|
||||
(* No list of literals covers a number, a string or a dyn, so a literal
|
||||
@ -8741,7 +8780,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
~notes:(match subject with
|
||||
| `Data u -> declared_note ctx.env u.Tast.dname
|
||||
| `Enum (n, _) -> declared_note ctx.env n
|
||||
| `Option _ | `Lit _ -> [])
|
||||
| `Option _ | `Bool | `Lit _ -> [])
|
||||
"this match is not exhaustive — %s %s no arm. Add %s, or a _ arm for \
|
||||
the rest"
|
||||
(String.concat ", " missing)
|
||||
@ -8750,7 +8789,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
let ty = match !want with Some t -> t | None -> Types.Never in
|
||||
match subject with
|
||||
| `Option _ | `Data _ -> mk loc ty (Tast.Match (s, arms))
|
||||
| `Enum _ | `Lit _ ->
|
||||
| `Enum _ | `Bool | `Lit _ ->
|
||||
(* The scrutinee once, into a temporary, and then an [if] per arm in the
|
||||
order written. A [_] arm ends the chain, and so does the last arm of a
|
||||
match with none: it is exhaustive by the check above, so the last
|
||||
@ -8773,6 +8812,8 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
|
||||
mk loc s.Tast.ty (Tast.Int (List.assoc m members, Types.I32))
|
||||
in
|
||||
mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; v ]))
|
||||
| `Bool when String.equal m "true" -> local
|
||||
| `Bool -> mk loc Types.Bool (Tast.Prim (Tast.Not, [ local ]))
|
||||
| `Lit Types.Dyn -> dyn_eq loc local (Hashtbl.find lits m)
|
||||
| _ ->
|
||||
mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; Hashtbl.find lits m ]))
|
||||
@ -8827,7 +8868,7 @@ and unknown_name : 'a. ?setting:bool -> ctx -> Loc.t -> string -> 'a =
|
||||
and [x/2] are one name each. When the parts either side of an operator
|
||||
character are a value in scope and a number or another value, that is
|
||||
almost certainly the arithmetic, and the sentence says how to spell it. *)
|
||||
(if Filename.check_suffix loc.Loc.file ".fln" then begin
|
||||
(if Source.indented_at loc then begin
|
||||
let known s =
|
||||
s <> ""
|
||||
&& (String.for_all (fun c -> (c >= '0' && c <= '9') || c = '.') s
|
||||
@ -10180,7 +10221,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
ordering attached — plan.org, Types calls out an ordering as a
|
||||
collation the language has not picked. Backend codegen (emit.ml,
|
||||
x86.ml) has a [Types.String] case in the [Eq]/[Ne] arm and nowhere
|
||||
else. *)
|
||||
else. A bool is the third: two values and no order between them. *)
|
||||
let ok =
|
||||
match name with
|
||||
| "=" | "!=" -> Types.is_equatable a.Tast.ty
|
||||
@ -10193,8 +10234,8 @@ and named_call ?(qualified = false) ctx ~want loc name args =
|
||||
(match name with
|
||||
| "=" | "!=" ->
|
||||
fail loc
|
||||
"%s compares machine numbers, enums and strings, and %s is none of \
|
||||
those" name (Types.to_string a.Tast.ty)
|
||||
"%s compares numbers, enums, strings and bools, and %s is none \
|
||||
of those" name (Types.to_string a.Tast.ty)
|
||||
| _ ->
|
||||
fail loc
|
||||
"%s orders machine numbers and enums, and %s is neither" name
|
||||
|
||||
@ -3800,6 +3800,10 @@ and prim f (e : Tast.expr) (p : Tast.prim) (args : Tast.expr list) =
|
||||
location. Signed, because a member may be declared negative. *)
|
||||
| Types.Enum _ ->
|
||||
ins f "%s = icmp %s %s %s, %s" t (icmp_op true p) (ll x.Tast.ty) a b
|
||||
(* A bool is an i1 here, and only [=]/[!=] reach it: [<] on a bool is
|
||||
refused in check.ml. *)
|
||||
| Types.Bool ->
|
||||
ins f "%s = icmp %s i1 %s, %s" t (icmp_op false p) a b
|
||||
(* A string is ptr+len at this boundary, not a machine word, so there is
|
||||
no [icmp] to reach for — the comparison itself is [flan_str_eq]
|
||||
(runtime/flan_rt.c), bytewise with a length and a same-pointer fast
|
||||
|
||||
@ -1209,6 +1209,22 @@ and header (s : st) w : Form.t =
|
||||
let wt = advance p in
|
||||
let rec preds acc =
|
||||
let e, _ = expr p in
|
||||
(* [and] is how a condition joins tests, so it is what gets written
|
||||
for several predicates; the clause separates them with commas. *)
|
||||
(match e.Form.v with
|
||||
| Form.List ({ Form.v = Form.Sym "and"; _ } :: (_ :: _ as ps)) ->
|
||||
let spell (q : Form.t) =
|
||||
match q.Form.v with
|
||||
| Form.List [ { Form.v = Form.Sym n; _ };
|
||||
{ Form.v = Form.Sym v; _ } ] ->
|
||||
Printf.sprintf "%s(%s)" n v
|
||||
| _ -> Form.to_string q
|
||||
in
|
||||
failk "where-and" e.Form.loc
|
||||
"a where clause separates its predicates with commas, not and \
|
||||
— write where %s"
|
||||
(String.concat ", " (List.map spell ps))
|
||||
| _ -> ());
|
||||
match (peek p).tok with
|
||||
| COMMA -> ignore (advance p); preds (e :: acc)
|
||||
| _ -> List.rev (e :: acc)
|
||||
|
||||
@ -373,6 +373,14 @@ let constraints (body : Form.t list) : Ast.pred list * Form.t list =
|
||||
ignore vloc;
|
||||
{ Ast.pname = name; pvar = String.sub v 1 (String.length v - 1);
|
||||
ploc = p.Form.loc }
|
||||
(* [and] is how a condition joins tests, so it is what gets written
|
||||
for several predicates. The .fln reader refuses its own spelling of
|
||||
this with its own fix; what reaches here is paren code. *)
|
||||
| Form.List ({ Form.v = Form.Sym "and"; _ } :: (_ :: _ as ps)) ->
|
||||
Loc.fail p.Form.loc
|
||||
"a where clause puts several predicates in a vector, not in an and \
|
||||
— write {:where [%s]}"
|
||||
(String.concat " " (List.map Form.to_string ps))
|
||||
| _ ->
|
||||
Loc.fail p.Form.loc
|
||||
"a where predicate is (name? $t), one predicate about one type \
|
||||
|
||||
@ -41,13 +41,24 @@ let syntax_of_field = function
|
||||
| Some ("indented" | "fln") -> Indented
|
||||
| _ -> Paren
|
||||
|
||||
let in_request = ref false
|
||||
|
||||
let with_code ?indent ~syntax ~at f =
|
||||
let s = !code_syntax and a = !code_at and i = !code_indent in
|
||||
let s = !code_syntax and a = !code_at and i = !code_indent
|
||||
and r = !in_request in
|
||||
code_syntax := syntax;
|
||||
code_at := at;
|
||||
code_indent := indent;
|
||||
in_request := true;
|
||||
Fun.protect
|
||||
~finally:(fun () -> code_syntax := s; code_at := a; code_indent := i) f
|
||||
~finally:(fun () ->
|
||||
code_syntax := s; code_at := a; code_indent := i; in_request := r) f
|
||||
|
||||
(* Was the code at [loc] written in the indented syntax? Inside an editor
|
||||
request the request says; outside one a file's name does, as
|
||||
[read_file] decides. *)
|
||||
let indented_at (loc : Loc.t) =
|
||||
if !in_request then !code_syntax = Indented else is_indented loc.Loc.file
|
||||
|
||||
(* The paren reader started at a line and column: [Reader.read_all] always
|
||||
starts at 1:1. *)
|
||||
|
||||
@ -305,8 +305,9 @@ let is_comparable = function Enum _ -> true | t -> is_numeric t
|
||||
|
||||
(* Equality admits one type ordering does not: a string, grown in by the M2
|
||||
queue's item 5 — bytewise, by content and not by address, so two
|
||||
separately built strings with the same bytes are equal. *)
|
||||
let is_equatable = function String -> true | t -> is_comparable t
|
||||
separately built strings with the same bytes are equal. A bool is the
|
||||
other: true and false are two values with no order between them. *)
|
||||
let is_equatable = function String | Bool -> true | t -> is_comparable t
|
||||
|
||||
(* [Never] is the type of an expression that does not produce a value: return,
|
||||
an early-returning `some`, exit. It fits anywhere, and that is the only
|
||||
|
||||
@ -3244,6 +3244,11 @@ and call_native ?at f ~sym ?(chan = false) ~(args : Tast.expr list) ~rty dst =
|
||||
unsupported
|
||||
"%s returns %s by value, which needs SysV return classification this \
|
||||
backend does not have" sym (Types.to_string rty);
|
||||
(* A C bool is 0 or 1 only by the callee's good faith: [(declare f [..]
|
||||
bool "abs")] hands back whatever byte abs left. LLVM declares the
|
||||
return [i1] and reads its low bit, so this does too, and every bool
|
||||
past this point is 0 or 1 — which [=], [not] and [match] assume. *)
|
||||
if Types.equal rty Types.Bool then and_imm f.b ~dst:rax 1;
|
||||
store_loc f ~reg:(if is_float rty then xmm0 else rax) dst rty
|
||||
end
|
||||
|
||||
|
||||
@ -209,10 +209,16 @@ Each item: the proposal, then the reason in one line.
|
||||
"ok" -> "fine"
|
||||
\a -> "a"
|
||||
_ -> "other"
|
||||
|
||||
match ready
|
||||
true -> go()
|
||||
false -> wait()
|
||||
```
|
||||
An arm's body can be an indented block, which reads as `(do …)`. **Built** (a
|
||||
one-line block reads as that line). A number, char or string pattern is the
|
||||
literal as written, compared as `(= t lit)`.
|
||||
literal as written, compared as `(= t lit)`; over a dyn a keyword or
|
||||
`true`/`false` is too. A bool's arms are `true` and `false`, and naming both
|
||||
needs no `_`.
|
||||
- **Conditions**, clauses at the header's column:
|
||||
|
||||
```
|
||||
|
||||
24
test/programs/ffi-bool.flan
Normal file
24
test/programs/ffi-bool.flan
Normal file
@ -0,0 +1,24 @@
|
||||
;;;; A bool from C that is not 0 or 1 reads as its low bit on both backends:
|
||||
;;;; abs(3) is 3, which is true, and equal to abs(1).
|
||||
|
||||
(declare two [n i32] bool "abs")
|
||||
|
||||
(defstruct Flag [on bool])
|
||||
|
||||
(defn main [] i32
|
||||
(let [a (two 3)
|
||||
b (two 1)
|
||||
c (two 0)
|
||||
f (Flag {.on a})
|
||||
v [a b]]
|
||||
(print (= a b)) (print " ")
|
||||
(print (!= a b)) (print " ")
|
||||
(print (= a true)) (print " ")
|
||||
(print (= (.on f) b)) (print " ")
|
||||
(print (= (at v 0) (at v 1))) (print " ")
|
||||
(print (match a true "T" false "F")) (print " ")
|
||||
(print (if a "t" "f")) (print " ")
|
||||
(print (not a)) (print " ")
|
||||
(print (= c false))
|
||||
(println "")
|
||||
0))
|
||||
58
test/programs/match-bool.flan
Normal file
58
test/programs/match-bool.flan
Normal file
@ -0,0 +1,58 @@
|
||||
;;;; = and != on bools, a match over a bool, and keyword arms over a dyn.
|
||||
|
||||
(defstruct Flag [on bool])
|
||||
|
||||
(defn truth [n i32] bool (> n 0))
|
||||
|
||||
(defn same? [a bool b bool] bool (= a b))
|
||||
|
||||
;; Exhaustive without a _ arm: true and false are every bool.
|
||||
(defn word [b bool] string
|
||||
(match b
|
||||
true "yes"
|
||||
false "no"))
|
||||
|
||||
(defn flipped [b bool] i32
|
||||
(match b
|
||||
false 0
|
||||
true 1))
|
||||
|
||||
(defn only-true [b bool] i32
|
||||
(match b
|
||||
true 1
|
||||
_ 0))
|
||||
|
||||
;; Over a dyn :north is the arm (= d :north), beside numbers, strings and
|
||||
;; bools, which are dyn values too.
|
||||
(defn heading [d dyn] string
|
||||
(match d
|
||||
:north "up"
|
||||
:south "down"
|
||||
1 "one"
|
||||
"west" "left"
|
||||
true "true"
|
||||
_ "other"))
|
||||
|
||||
(defn main [] i32
|
||||
(let [f (Flag {.on true})
|
||||
g (Flag {.on false})
|
||||
flags [true false true]]
|
||||
(print (same? true true)) (print " ")
|
||||
(print (same? true false)) (print " ")
|
||||
(print (!= true false)) (print " ")
|
||||
(print (= (.on f) (truth 3))) (print " ")
|
||||
(print (= (.on g) (truth 3))) (print " ")
|
||||
(print (!= (.on g) (at flags 1))) (print " ")
|
||||
(print (= true (at flags 0) (at flags 2))) (println "")
|
||||
(println (word true))
|
||||
(println (word (truth -1)))
|
||||
(print (flipped true)) (print (flipped false))
|
||||
(print (only-true true)) (print (only-true false)) (println "")
|
||||
(println (heading :north))
|
||||
(println (heading :south))
|
||||
(println (heading :east))
|
||||
(println (heading 1.0))
|
||||
(println (heading "west"))
|
||||
(println (heading true))
|
||||
(println (heading false))
|
||||
0))
|
||||
@ -445,6 +445,26 @@ let () =
|
||||
match_lit_out;
|
||||
outputs ~dev:true "match over literals, dev" "programs/match-literal.flan"
|
||||
match_lit_out;
|
||||
(* heading 1.0 is "one": a keyword arm sits in the same dyn = chain as
|
||||
the literal arms. *)
|
||||
let match_bool_out =
|
||||
"true false true true false false true\nyes\nno\n1010\nup\ndown\n\
|
||||
other\none\nleft\ntrue\nother\n"
|
||||
in
|
||||
outputs "match over a bool and keywords" "programs/match-bool.flan"
|
||||
match_bool_out;
|
||||
outputs ~opt:"-O0" "match over a bool and keywords, -O0"
|
||||
"programs/match-bool.flan" match_bool_out;
|
||||
outputs ~x86:true "match over a bool and keywords, --x86"
|
||||
"programs/match-bool.flan" match_bool_out;
|
||||
outputs ~dev:true "match over a bool and keywords, dev"
|
||||
"programs/match-bool.flan" match_bool_out;
|
||||
let ffi_bool_out = "true false true true true T t false true\n" in
|
||||
outputs "a bool from C" "programs/ffi-bool.flan" ffi_bool_out;
|
||||
outputs ~opt:"-O0" "a bool from C, -O0" "programs/ffi-bool.flan"
|
||||
ffi_bool_out;
|
||||
outputs ~x86:true "a bool from C, --x86" "programs/ffi-bool.flan"
|
||||
ffi_bool_out;
|
||||
(* update, ++ and -- evaluate their place's subexpressions once: the
|
||||
counts are the number of calls an index or a key function got. *)
|
||||
let update_out = "3\n11 20 90\n1 1 3\n16\n2\n7 1\n32\n2 50\n" in
|
||||
|
||||
@ -1888,6 +1888,11 @@ let () =
|
||||
rejects_check ":where is reserved at a defn body's head, even alone"
|
||||
"(defn f [] dyn {:where 1})\n(defn main [] i32 0)"
|
||||
~needle:"a where predicate is";
|
||||
rejects_check "several where predicates joined with and"
|
||||
"(defn f [x $t y $u] i32 {:where (and (ordered? $t) (equal? $u))} 0)\n\
|
||||
(defn main [] i32 (f 1 2))"
|
||||
~needle:"a where clause puts several predicates in a vector, not in an \
|
||||
and — write {:where [(ordered? $t) (equal? $u)]}";
|
||||
|
||||
(* Keywords: dyn where nothing else is asked, still an enum member where an
|
||||
enum is, and refused where a concrete non-dyn type is wanted. *)
|
||||
@ -2693,7 +2698,7 @@ let () =
|
||||
accepts "a wildcard arm is exhaustive"
|
||||
"(defn g [] (Option i32) None) (defn f [] i32 (match (g) (Some v) v _ 0))";
|
||||
rejects_check "match on a non-Option"
|
||||
"(defn f [x bool] i32 (match x _ 0))" ~needle:"match works on an Option";
|
||||
"(defn f [x [u8]] i32 (match x _ 0))" ~needle:"match works on an Option";
|
||||
|
||||
(* ── Names, order-independence, entry point ────────────────────── *)
|
||||
accepts "mutually recursive, no forward declaration"
|
||||
@ -4683,9 +4688,9 @@ let () =
|
||||
(k ^ "(defn f [k K] i32 (match k :lo 1 _ \"x\"))")
|
||||
~needle:"expected i32, found string";
|
||||
rejects_check "match over something that is none of them"
|
||||
"(defn f [n bool] i32 (match n _ 2))"
|
||||
~needle:"match works on an Option, a data type, an enum, a number, a \
|
||||
string or a dyn, not on bool";
|
||||
"(defn f [n [u8]] i32 (match n _ 2))"
|
||||
~needle:"match works on an Option, a data type, an enum, a bool, a \
|
||||
number, a string or a dyn, not on [u8]";
|
||||
|
||||
(* ── match over literals ───────────────────────────────────────── *)
|
||||
|
||||
@ -4762,6 +4767,51 @@ let () =
|
||||
rejects_check "a literal match over a byte slice, which = does not compare"
|
||||
"(defn f [b [u8]] i32 (match b \"a\" 1 _ 0))"
|
||||
~needle:"not on [u8]";
|
||||
|
||||
(* ── match over a bool, keyword arms over a dyn ────────────────── *)
|
||||
|
||||
accepts "a match over a bool naming both is exhaustive"
|
||||
"(defn f [b bool] i32 (match b true 1 false 0))";
|
||||
accepts "a match over a bool, one arm and _"
|
||||
"(defn f [b bool] i32 (match b false 1 _ 0))";
|
||||
rejects_check "a match over a bool naming one of them"
|
||||
"(defn f [b bool] i32 (match b true 1))"
|
||||
~needle:"this match is not exhaustive — false has no arm. Add it, or a _ \
|
||||
arm for the rest";
|
||||
rejects_check "true named twice"
|
||||
"(defn f [b bool] i32 (match b true 1 true 2 _ 0))"
|
||||
~needle:"this match has two true arms";
|
||||
rejects_check "a number arm over a bool"
|
||||
"(defn f [b bool] i32 (match b 1 1 _ 0))"
|
||||
~needle:"1 is a literal, and this match is over a bool, whose arms are \
|
||||
true and false, as in (match b true 1 false 0)";
|
||||
rejects_check "a keyword arm over a bool"
|
||||
"(defn f [b bool] i32 (match b :yes 1 _ 0))"
|
||||
~needle:":yes is a keyword, and this match is over a bool";
|
||||
accepts "keyword arms over a dyn, beside numbers, strings and bools"
|
||||
"(defn f [d dyn] i32 (match d :north 1 :south 2 5 3 \"w\" 4 true 5 _ 0))";
|
||||
rejects_check "keyword arms over a dyn need a _ arm"
|
||||
"(defn f [d dyn] i32 (match d :north 1 :south 2))"
|
||||
~needle:"covers every dyn value. Add a _ arm for the rest, as in (match d \
|
||||
5 1 :go 2 _ 0)";
|
||||
rejects_check "a keyword named twice over a dyn"
|
||||
"(defn f [d dyn] i32 (match d :north 1 :north 2 _ 0))"
|
||||
~needle:"this match has two :north arms";
|
||||
rejects_check "true named twice over a dyn"
|
||||
"(defn f [d dyn] i32 (match d true 1 true 2 _ 0))"
|
||||
~needle:"this match has two true arms";
|
||||
accepts "a keyword arm over an enum still names a member"
|
||||
(k ^ "(defn f [k K] i32 (match k :lo 1 _ 0))");
|
||||
(* = and != compare bools; ordering them stays refused. *)
|
||||
accepts "= on bools" "(defn f [a bool b bool] bool (= a b))";
|
||||
accepts "!= on bools, chained" "(defn f [a bool b bool] bool (!= a b true))";
|
||||
rejects_check "< on bools"
|
||||
"(defn f [a bool b bool] bool (< a b))"
|
||||
~needle:"< orders machine numbers and enums, and bool is neither";
|
||||
rejects_check "= on a struct names what it compares"
|
||||
"(defstruct P [x i32])\n(defn f [a P b P] bool (= a b))"
|
||||
~needle:"= compares numbers, enums, strings and bools, and P is none of \
|
||||
those";
|
||||
(* A destructuring pattern in an arm's binds is a name position like any
|
||||
other. *)
|
||||
rejects_check "a pattern inside a match arm's binds"
|
||||
|
||||
@ -287,6 +287,11 @@ let () =
|
||||
"(defn g [h (Fn [i32 i32] bool)] () (h 1 2))";
|
||||
reads "untyped parameter is dyn" "fn id(x) -> dyn = x" "(defn id [x dyn] dyn x)";
|
||||
refuses "no return type" "fn f(x)\n x" "indent/return-type" "-> i32";
|
||||
(* The fix is .fln's commas whatever the file is called: [read] names it
|
||||
<syntax>. *)
|
||||
refuses "where predicates joined with and"
|
||||
"fn f(x: $t, y: $u) -> i32 where ordered?($t) and equal?($u)\n 0"
|
||||
"indent/where-and" "commas, not and — write where ordered?($t), equal?($u)";
|
||||
(* Characters, lexed before brackets and separators. *)
|
||||
reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])";
|
||||
reads "character arguments" "f(\\,, \\))" "(f \\, \\))";
|
||||
@ -364,6 +369,10 @@ let () =
|
||||
"(match s (Circle r) r _ (do (a) (b)))";
|
||||
reads "match over literals" "match n\n 5 -> a\n -2.5 -> b\n \"go\" -> c\n \\a -> d\n _ -> e"
|
||||
"(match n 5 a -2.5 b \"go\" c \\a d _ e)";
|
||||
reads "match over a bool" "match b\n true -> a\n false -> b"
|
||||
"(match b true a false b)";
|
||||
reads "keyword arms over a dyn" "match d\n :north -> a\n 1 -> b\n _ -> c"
|
||||
"(match d :north a 1 b _ c)";
|
||||
reads "handler-bind moves the clauses" "handler-bind\n f()\non E(c)\n g(c)"
|
||||
"(handler-bind [(E [c] (g c))] (f))";
|
||||
reads "quote block"
|
||||
|
||||
@ -1097,8 +1097,10 @@ as <code>first-even</code> does above.</p>
|
||||
|
||||
<p><code>(Option T)</code> is how absence is spelled: a lookup miss, an empty
|
||||
collection, the end of a stream. <code>match</code> works on an <code>Option</code>, a
|
||||
<code>defdata</code> and an enum, whose arms name cases; and on a number, a string or a
|
||||
<code>dyn</code>, whose arms are literals — <code>(match n 0 "zero" -1 "none" _ "some")</code>
|
||||
<code>defdata</code> and an enum, whose arms name cases; on a <code>bool</code>, whose arms
|
||||
are <code>true</code> and <code>false</code> and need no <code>_</code>; and on a number, a string or a
|
||||
<code>dyn</code>, whose arms are literals — <code>(match n 0 "zero" -1 "none" _ "some")</code>,
|
||||
or over a <code>dyn</code> also keywords such as <code>:north</code>
|
||||
— each compared with <code>=</code>, with a <code>_</code> arm required for the rest.
|
||||
<code>some</code> unwraps
|
||||
<code>Some</code> and early-returns <code>None</code> from the enclosing function.</p>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user