= 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:
Joseph Ferano 2026-09-25 22:00:51 +07:00
commit af6e00bc2a
15 changed files with 361 additions and 110 deletions

View File

@ -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 could always share a member spelling. What the prefix buys is the call site read
on its own. 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 ** 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 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. 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 ** DONE match over numbers and strings
CLOSED: [2026-09-25] CLOSED: [2026-09-25]
Rules out a literal the scrutinee's type cannot hold (refused, not widened as =(=)= 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 ** DONE match over enums
CLOSED: [2026-09-25] CLOSED: [2026-09-25]

View File

@ -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 (* 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 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 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 | (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 -> | other ->
fail loc fail loc
"match works on an Option, a data type, an enum, a number, a string \ "match works on an Option, a data type, an enum, a bool, a number, a \
or a dyn, not on %s" string or a dyn, not on %s"
(Types.to_string other) (Types.to_string other)
in in
(* A literal arm, spelled as it was written, for the refusals that name one. *) (* 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 when b > 32 && b < 127 -> Printf.sprintf "\\%c" (Char.chr b)
| Ast.Byte b -> string_of_int b | Ast.Byte b -> string_of_int b
| Ast.Str t -> Printf.sprintf "%S" t | Ast.Str t -> Printf.sprintf "%S" t
| Ast.Kw k -> ":" ^ k
| Ast.Var b -> b
| _ -> "this literal" | _ -> "this literal"
in in
let what_ty t = match t with Types.Dyn -> "a dyn" | t -> Types.to_string t in let what_ty t = match t with Types.Dyn -> "a dyn" | t -> Types.to_string t in
@ -8411,43 +8417,19 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
(match t with (match t with
| Types.String -> "\"yes\" 1 _ 0" | Types.String -> "\"yes\" 1 _ 0"
| Types.Float _ -> "0.5 1 _ 0" | Types.Float _ -> "0.5 1 _ 0"
| Types.Dyn -> "5 1 :go 2 _ 0"
| _ -> "5 1 _ 0") | _ -> "5 1 _ 0")
in 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. *) (* 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 lits : (string, Tast.expr) Hashtbl.t = Hashtbl.create 8 in
let lit_values = ref [] in let lit_values = ref [] in
(* Which case each arm names, and the type of each name it binds. This is the (* A literal arm over [t]: its checked value under a fresh key in [lits],
whole of what differs between the two subjects; everything below it is refused if [t] cannot hold it or an earlier arm already equals it. *)
shared. *) let lit_arm (a : Ast.arm) t (e : Ast.expr) =
let resolve_pat (a : Ast.arm) =
match subject, a.Ast.pat with
| _, Ast.Pwild -> None, []
| `Option elem, Ast.Pctor ("Some", [ x ]) -> Some "Some", [ (x, elem) ]
| `Option _, Ast.Pctor ("Some", _) ->
fail a.Ast.aloc "the Some pattern binds exactly one name"
| `Option _, Ast.Pctor ("None", []) -> Some "None", []
| `Option _, Ast.Pctor ("None", _) -> fail a.Ast.aloc "None binds no names"
| `Option _, Ast.Pctor (c, _) ->
fail a.Ast.aloc
"%s is not a case of Option — the cases are Some and None" c
| `Enum (n, members), Ast.Pkw k ->
if not (List.mem_assoc k members) then begin
let all =
String.concat " " (List.map (fun (m, _) -> ":" ^ m) members)
in
match member_near_miss members k with
| Some (m, _) ->
fail a.Ast.aloc "%s has no member :%s — did you mean :%s? It has %s"
n k m all
| None -> fail a.Ast.aloc "%s has no member :%s — it has %s" n k all
end;
Some k, []
| `Enum (n, members), Ast.Pctor (c, _) ->
fail a.Ast.aloc
"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 = let v =
(* [=]'s dyn pair checks its literal at dyn, which boxes it. *) (* [=]'s dyn pair checks its literal at dyn, which boxes it. *)
match trial ctx (fun () -> check ctx ~want:t e) with match trial ctx (fun () -> check ctx ~want:t e) with
@ -8502,6 +8484,8 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
| (Ast.Int n | Ast.UInt (n, _)), _ -> `I n | (Ast.Int n | Ast.UInt (n, _)), _ -> `I n
| Ast.Byte b, _ -> `I (Int64.of_int b) | Ast.Byte b, _ -> `I (Int64.of_int b)
| Ast.Float x, _ -> num x | Ast.Float x, _ -> num x
| Ast.Kw k, _ -> `K k
| Ast.Var b, _ -> `B b
| _ -> assert false | _ -> assert false
in in
let same x y = let same x y =
@ -8509,7 +8493,7 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
| `I a, `I b -> Int64.equal a b | `I a, `I b -> Int64.equal a b
| `F a, `F b -> a = b | `F a, `F b -> a = b
| `I a, `F b | `F b, `I a -> Int64.to_float a = b | `I a, `F b | `F b, `I a -> Int64.to_float a = b
| `S a, `S b -> String.equal a b | `S a, `S b | `K a, `K b | `B a, `B b -> String.equal a b
| _ -> false | _ -> false
in in
(match List.find_opt (fun (w, _) -> same value w) !lit_values with (match List.find_opt (fun (w, _) -> same value w) !lit_values with
@ -8532,6 +8516,59 @@ and check_match ctx ?(tail = false) ?want loc scrutinee arms =
let key = string_of_int (Hashtbl.length lits) in let key = string_of_int (Hashtbl.length lits) in
Hashtbl.replace lits key v; Hashtbl.replace lits key v;
Some key, [] 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. *)
let resolve_pat (a : Ast.arm) =
match subject, a.Ast.pat with
| _, Ast.Pwild -> None, []
| `Option elem, Ast.Pctor ("Some", [ x ]) -> Some "Some", [ (x, elem) ]
| `Option _, Ast.Pctor ("Some", _) ->
fail a.Ast.aloc "the Some pattern binds exactly one name"
| `Option _, Ast.Pctor ("None", []) -> Some "None", []
| `Option _, Ast.Pctor ("None", _) -> fail a.Ast.aloc "None binds no names"
| `Option _, Ast.Pctor (c, _) ->
fail a.Ast.aloc
"%s is not a case of Option — the cases are Some and None" c
| `Enum (n, members), Ast.Pkw k ->
if not (List.mem_assoc k members) then begin
let all =
String.concat " " (List.map (fun (m, _) -> ":" ^ m) members)
in
match member_near_miss members k with
| Some (m, _) ->
fail a.Ast.aloc "%s has no member :%s — did you mean :%s? It has %s"
n k m all
| None -> fail a.Ast.aloc "%s has no member :%s — it has %s" n k all
end;
Some k, []
| `Enum (n, members), Ast.Pctor (c, _) ->
fail a.Ast.aloc
"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 -> 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 -> | `Lit t, Ast.Pkw k ->
fail a.Ast.aloc fail a.Ast.aloc
":%s is an enum member, and this match is over %s, whose arms are \ ":%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 List.filter_map
(fun (m, _) -> if Hashtbl.mem seen m then None else Some (":" ^ m)) (fun (m, _) -> if Hashtbl.mem seen m then None else Some (":" ^ m))
members members
| `Bool ->
List.filter (fun c -> not (Hashtbl.mem seen c)) [ "true"; "false" ]
| `Lit _ -> [] | `Lit _ -> []
in in
(* No list of literals covers a number, a string or a dyn, so a literal (* 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 ~notes:(match subject with
| `Data u -> declared_note ctx.env u.Tast.dname | `Data u -> declared_note ctx.env u.Tast.dname
| `Enum (n, _) -> declared_note ctx.env n | `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 \ "this match is not exhaustive — %s %s no arm. Add %s, or a _ arm for \
the rest" the rest"
(String.concat ", " missing) (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 let ty = match !want with Some t -> t | None -> Types.Never in
match subject with match subject with
| `Option _ | `Data _ -> mk loc ty (Tast.Match (s, arms)) | `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 (* 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 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 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)) mk loc s.Tast.ty (Tast.Int (List.assoc m members, Types.I32))
in in
mk loc Types.Bool (Tast.Prim (Tast.Eq, [ local; v ])) 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) | `Lit Types.Dyn -> dyn_eq loc local (Hashtbl.find lits m)
| _ -> | _ ->
mk loc Types.Bool (Tast.Prim (Tast.Eq, [ 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 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 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. *) 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 = let known s =
s <> "" s <> ""
&& (String.for_all (fun c -> (c >= '0' && c <= '9') || c = '.') 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 ordering attached — plan.org, Types calls out an ordering as a
collation the language has not picked. Backend codegen (emit.ml, collation the language has not picked. Backend codegen (emit.ml,
x86.ml) has a [Types.String] case in the [Eq]/[Ne] arm and nowhere 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 = let ok =
match name with match name with
| "=" | "!=" -> Types.is_equatable a.Tast.ty | "=" | "!=" -> Types.is_equatable a.Tast.ty
@ -10193,8 +10234,8 @@ and named_call ?(qualified = false) ctx ~want loc name args =
(match name with (match name with
| "=" | "!=" -> | "=" | "!=" ->
fail loc fail loc
"%s compares machine numbers, enums and strings, and %s is none of \ "%s compares numbers, enums, strings and bools, and %s is none \
those" name (Types.to_string a.Tast.ty) of those" name (Types.to_string a.Tast.ty)
| _ -> | _ ->
fail loc fail loc
"%s orders machine numbers and enums, and %s is neither" name "%s orders machine numbers and enums, and %s is neither" name

View File

@ -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. *) location. Signed, because a member may be declared negative. *)
| Types.Enum _ -> | Types.Enum _ ->
ins f "%s = icmp %s %s %s, %s" t (icmp_op true p) (ll x.Tast.ty) a b 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 (* 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] 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 (runtime/flan_rt.c), bytewise with a length and a same-pointer fast

View File

@ -1209,6 +1209,22 @@ and header (s : st) w : Form.t =
let wt = advance p in let wt = advance p in
let rec preds acc = let rec preds acc =
let e, _ = expr p in 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 match (peek p).tok with
| COMMA -> ignore (advance p); preds (e :: acc) | COMMA -> ignore (advance p); preds (e :: acc)
| _ -> List.rev (e :: acc) | _ -> List.rev (e :: acc)

View File

@ -373,6 +373,14 @@ let constraints (body : Form.t list) : Ast.pred list * Form.t list =
ignore vloc; ignore vloc;
{ Ast.pname = name; pvar = String.sub v 1 (String.length v - 1); { Ast.pname = name; pvar = String.sub v 1 (String.length v - 1);
ploc = p.Form.loc } 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 Loc.fail p.Form.loc
"a where predicate is (name? $t), one predicate about one type \ "a where predicate is (name? $t), one predicate about one type \

View File

@ -41,13 +41,24 @@ let syntax_of_field = function
| Some ("indented" | "fln") -> Indented | Some ("indented" | "fln") -> Indented
| _ -> Paren | _ -> Paren
let in_request = ref false
let with_code ?indent ~syntax ~at f = 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_syntax := syntax;
code_at := at; code_at := at;
code_indent := indent; code_indent := indent;
in_request := true;
Fun.protect 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 (* The paren reader started at a line and column: [Reader.read_all] always
starts at 1:1. *) starts at 1:1. *)

View File

@ -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 (* 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 queue's item 5 — bytewise, by content and not by address, so two
separately built strings with the same bytes are equal. *) separately built strings with the same bytes are equal. A bool is the
let is_equatable = function String -> true | t -> is_comparable t 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, (* [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 an early-returning `some`, exit. It fits anywhere, and that is the only

View File

@ -3244,6 +3244,11 @@ and call_native ?at f ~sym ?(chan = false) ~(args : Tast.expr list) ~rty dst =
unsupported unsupported
"%s returns %s by value, which needs SysV return classification this \ "%s returns %s by value, which needs SysV return classification this \
backend does not have" sym (Types.to_string rty); 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 store_loc f ~reg:(if is_float rty then xmm0 else rax) dst rty
end end

View File

@ -209,10 +209,16 @@ Each item: the proposal, then the reason in one line.
"ok" -> "fine" "ok" -> "fine"
\a -> "a" \a -> "a"
_ -> "other" _ -> "other"
match ready
true -> go()
false -> wait()
``` ```
An arm's body can be an indented block, which reads as `(do …)`. **Built** (a 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 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: - **Conditions**, clauses at the header's column:
``` ```

View 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))

View 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))

View File

@ -445,6 +445,26 @@ let () =
match_lit_out; match_lit_out;
outputs ~dev:true "match over literals, dev" "programs/match-literal.flan" outputs ~dev:true "match over literals, dev" "programs/match-literal.flan"
match_lit_out; 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 (* update, ++ and -- evaluate their place's subexpressions once: the
counts are the number of calls an index or a key function got. *) 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 let update_out = "3\n11 20 90\n1 1 3\n16\n2\n7 1\n32\n2 50\n" in

View File

@ -1888,6 +1888,11 @@ let () =
rejects_check ":where is reserved at a defn body's head, even alone" rejects_check ":where is reserved at a defn body's head, even alone"
"(defn f [] dyn {:where 1})\n(defn main [] i32 0)" "(defn f [] dyn {:where 1})\n(defn main [] i32 0)"
~needle:"a where predicate is"; ~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 (* 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. *) enum is, and refused where a concrete non-dyn type is wanted. *)
@ -2693,7 +2698,7 @@ let () =
accepts "a wildcard arm is exhaustive" accepts "a wildcard arm is exhaustive"
"(defn g [] (Option i32) None) (defn f [] i32 (match (g) (Some v) v _ 0))"; "(defn g [] (Option i32) None) (defn f [] i32 (match (g) (Some v) v _ 0))";
rejects_check "match on a non-Option" 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 ────────────────────── *) (* ── Names, order-independence, entry point ────────────────────── *)
accepts "mutually recursive, no forward declaration" accepts "mutually recursive, no forward declaration"
@ -4683,9 +4688,9 @@ let () =
(k ^ "(defn f [k K] i32 (match k :lo 1 _ \"x\"))") (k ^ "(defn f [k K] i32 (match k :lo 1 _ \"x\"))")
~needle:"expected i32, found string"; ~needle:"expected i32, found string";
rejects_check "match over something that is none of them" rejects_check "match over something that is none of them"
"(defn f [n bool] i32 (match n _ 2))" "(defn f [n [u8]] i32 (match n _ 2))"
~needle:"match works on an Option, a data type, an enum, a number, a \ ~needle:"match works on an Option, a data type, an enum, a bool, a \
string or a dyn, not on bool"; number, a string or a dyn, not on [u8]";
(* ── match over literals ───────────────────────────────────────── *) (* ── match over literals ───────────────────────────────────────── *)
@ -4762,6 +4767,51 @@ let () =
rejects_check "a literal match over a byte slice, which = does not compare" rejects_check "a literal match over a byte slice, which = does not compare"
"(defn f [b [u8]] i32 (match b \"a\" 1 _ 0))" "(defn f [b [u8]] i32 (match b \"a\" 1 _ 0))"
~needle:"not on [u8]"; ~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 (* A destructuring pattern in an arm's binds is a name position like any
other. *) other. *)
rejects_check "a pattern inside a match arm's binds" rejects_check "a pattern inside a match arm's binds"

View File

@ -287,6 +287,11 @@ let () =
"(defn g [h (Fn [i32 i32] bool)] () (h 1 2))"; "(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)"; 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"; 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. *) (* Characters, lexed before brackets and separators. *)
reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])"; reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])";
reads "character arguments" "f(\\,, \\))" "(f \\, \\))"; reads "character arguments" "f(\\,, \\))" "(f \\, \\))";
@ -364,6 +369,10 @@ let () =
"(match s (Circle r) r _ (do (a) (b)))"; "(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" 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)"; "(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)" reads "handler-bind moves the clauses" "handler-bind\n f()\non E(c)\n g(c)"
"(handler-bind [(E [c] (g c))] (f))"; "(handler-bind [(E [c] (g c))] (f))";
reads "quote block" reads "quote block"

View File

@ -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 <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 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>defdata</code> and an enum, whose arms name cases; on a <code>bool</code>, whose arms
<code>dyn</code>, whose arms are literals &mdash; <code>(match n 0 "zero" -1 "none" _ "some")</code> 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 &mdash; <code>(match n 0 "zero" -1 "none" _ "some")</code>,
or over a <code>dyn</code> also keywords such as <code>:north</code>
&mdash; each compared with <code>=</code>, with a <code>_</code> arm required for the rest. &mdash; each compared with <code>=</code>, with a <code>_</code> arm required for the rest.
<code>some</code> unwraps <code>some</code> unwraps
<code>Some</code> and early-returns <code>None</code> from the enclosing function.</p> <code>Some</code> and early-returns <code>None</code> from the enclosing function.</p>