?? continues a line, a chain or an unwrap is no place to assign, nested marks and a ? on a value say what to write, a lowercase type takes ?, and a predicate reads as a word with its name beside it.

This commit is contained in:
Joseph Ferano 2026-09-26 15:36:49 +07:00
parent c9f33feb6c
commit 6c6b5a60cb
11 changed files with 251 additions and 99 deletions

View File

@ -19,6 +19,12 @@ prelude and vendor rewritten in .fln; tests and examples converted with =flan co
then =flan convert=, the .flan source path and =flan-mode= removed. Nothing tracks what
.flan can no longer say. Step 1 is done: the name rule with the =is-=/=has-= renames
through the prelude, vendor and raylib, and =T?=, =??=, =x!=, =if let g = x= and =a?.b=.
** TODO The stepper does not step inside an optional chain
=Ast.step_expr= treats a =Chain= as a leaf (its catch-all), so nothing in a chain's
body gets a step point of its own.
** WAIT flan convert prints the reader's names for the optionals
=a?.b= prints as =(?. [~o1 a] (.b ~o1))= and =x!= as =(!! x)=. Parked: convert is being
removed (130).
** WAIT A cheap front for dyn sequences
Decided 2026-09-26 (128) to pause: dyn vectors are mutable, so taking from the front
shifts every element. Options were a linked list (cons/first/rest) or storing the dyn
@ -649,7 +655,7 @@ exact convention, which is the direction the author is interested in.
** DONE defclass is a named dyn map with a shape tag
CLOSED: [2026-09-20]
An instance is a dyn map with its class in the object header, so =get=, =put=,
=has-key?= and =length= need no new operation. CLOS dispatch and Clojure's
=has-key= and =length= need no new operation. CLOS dispatch and Clojure's
arbitrary dispatch are one mechanism: a class dispatcher is the shape tag of the
first argument used as the dispatch function. Method bodies are inlined into one
dispatcher function, so a generic is one top-level name and one cell — adding a
@ -823,15 +829,15 @@ CLOSED: [2026-09-20]
Numeric scalars bound to one type variable resolve to the join of them all, which
walks back the same day's "widening does not cross a generic binding". A joinless
pair is deferred and re-asked against the final binding, which is what makes
acceptance order-independent. =integer?= exists because =numeric?= admits floats,
acceptance order-independent. =is-integer= exists because =is-numeric= admits floats,
where the branch spelling of =abs= hands back a negative zero.
** DONE A conversion is legal at a bounded variable when it is legal at every type the bound admits
CLOSED: [2026-09-21]
A machine-type target needs =numeric?=; an enum target needs =integer?=;
=ordered?=, =equal?= and =hashable?= admit nothing. A predicate gates an operation
A machine-type target needs =is-numeric=; an enum target needs =is-integer=;
=is-ordered=, =is-equal= and =is-hashable= admit nothing. A predicate gates an operation
by what it claims, not by the set it happens to denote this week — which is why
=ordered?= is refused even though every type it admits today converts.
=is-ordered= is refused even though every type it admits today converts.
** DONE The Ptr and union arms of the fill boundary are relaxable
CLOSED: [2026-09-25]
@ -854,8 +860,8 @@ A =where= clause tells the abstract pass what it may assume, so the body checks
the definition and the call stays =(sort xs)=. Not a type class — a predicate
carries nothing and gates a builtin the compiler already has. The fork that said
an unconstrained =+= over a type variable must be rejected turned out to be false,
and it is not what Odin does. The five predicates are =ordered?=, =equal?=,
=hashable?=, =numeric?= and =integer?=.
and it is not what Odin does. The five predicates are =is-ordered=, =is-equal=,
=is-hashable=, =is-numeric= and =is-integer=.
** DONE A type variable takes a $ sigil
CLOSED: [2026-09-13]
@ -873,15 +879,15 @@ the name as written, where the tables are keyed on the bare name.
** DONE Milestone 5 was mostly already there
CLOSED: [2026-09-20]
What the lane added was a written integer zero standing where a =numeric?=-bounded
variable stands — legal because every type =numeric?= admits is an integer or a
What the lane added was a written integer zero standing where a variable bounded by
=is-numeric= stands — legal because every type =is-numeric= admits is an integer or a
float — the widening boundary, and a refusal for instantiating a type variable at
=dyn=, which names =defgeneric=/=defmethod= as the other spelling. A float literal
is still refused at a =numeric?= variable, since the predicate covers both halves.
is still refused at an =is-numeric= variable, since the predicate covers both halves.
** DONE hashable? gates the type and not the operations
** DONE is-hashable gates the type and not the operations
CLOSED: [2026-09-13]
=put=, =get=, =has-key?=, =reserve=, =clone= and =map-remove= are deferred to the
=put=, =get=, =has-key=, =reserve=, =clone= and =map-remove= are deferred to the
instantiation, joining =print= and =println=. The membership rule is not a
headcount: either the operation cannot fail after substituting, or a declared
predicate gives its failure somewhere to land. A generic that does not declare the
@ -2192,7 +2198,7 @@ implicit, any other is written. The rest of the page is left for its rewrite.
** DONE plan.org's Types section lists a predicate that no longer exists
CLOSED: [2026-09-25]
The five predicates are the checker's five, with =integer?= in place of
The five predicates are the checker's five, with =is-integer= in place of
=copyable?=, and the section no longer names =(Handle $t)= or =pool-new=.
** DONE plan.org's Data model section still describes move-only containers

View File

@ -3040,6 +3040,19 @@ let rec reaches_dyn (t : Types.t) =
which is the whole reason predicates exist — under the no-constraint rule
[(sort xs)] had to become [(sort-by xs (fn [a b] (< a b)))] at every call
site in the corpus. *)
(* A predicate as a word in a sentence, [ordered (is-ordered)], and a where
clause as the file at [loc] spells one. [v] is the variable with its [$]. *)
let pred_word p =
let w =
if String.length p > 3 && String.sub p 0 3 = "is-" then String.sub p 3 (String.length p - 3)
else p
in
if w = p then p else Printf.sprintf "%s (%s)" w p
let where_text loc p v =
if Source.indented_at loc then Printf.sprintf "where %s(%s)" p v
else Printf.sprintf "{:where (%s %s)}" p v
let unconstrained env loc op ~needs (t : Types.t) =
if generic_ty t then
match tyvar_of t with
@ -3047,10 +3060,11 @@ let unconstrained env loc op ~needs (t : Types.t) =
| _ ->
Loc.failk "check/unconstrained-type-variable" loc
"%s over the type variable %s: nothing declares %s %s. Write \
{:where (%s %s)} at the head of the body, or take the operation as \
%s at the head of the body, or take the operation as \
a parameter, a (Fn [%s %s] ...), and call it here"
op (tyname loc t) (tyname loc t) needs needs
(tyname loc t) (tyname loc t) (tyname loc t)
op (tyname loc t) (tyname loc t) (pred_word needs)
(where_text loc needs (tyname loc t))
(tyname loc t) (tyname loc t)
(* ── The runaway instantiation, refused by name rather than by depth ────
@ -11201,6 +11215,13 @@ and check_call ctx ~want loc (head : Ast.expr) (args : Ast.expr list) =
(rt loc Types.Unit "flan_dev_reg_note_res_done"
[ mk loc Types.String (Tast.Str owner) ])
| _ -> fail loc "internal: %%res-done takes a name — a compiler bug")
(* [i32?] where a value goes: the type, not a value. *)
| Ast.Var "Option" when fln_source loc ->
fail loc
"%s is an Option type, and a value is wanted here. On a value that may \
hold nothing, x?.field reads through it, x! unwraps it and x ?? d gives \
a default"
(match Loc.snippet loc with Some t -> t | None -> "this")
| Ast.Var name -> named_call ctx ~want loc name args
(* ((Ptr Color) p): a pointer cast, spelled the way (i32 x) is — the type
is the head. It changes what the pointer is said to point at and nothing
@ -11412,7 +11433,7 @@ and cast_operand ctx loc name ~needs ?also ~what ~is v =
| ps ->
Printf.sprintf
"The where clause says %s is %s, and that does not make it %s" v
(String.concat " and " ps) is
(String.concat " and " (List.map pred_word ps)) is
in
let fix =
let alt clause =
@ -11423,8 +11444,11 @@ and cast_operand ctx loc name ~needs ?also ~what ~is v =
| None -> ""
in
if ctx.env.tvpreds = [] then
Printf.sprintf "write {:where (%s $%s)} at the head of the body%s"
needs v (alt "{:where (%s $%s)}")
Printf.sprintf "write %s at the head of the body%s"
(where_text loc needs ("$" ^ v))
(if Source.indented_at loc then alt "where %s($%s)" else alt "{:where (%s $%s)}")
else if Source.indented_at loc then
Printf.sprintf "add %s($%s) to the where clause%s" needs v (alt "%s($%s)")
else
Printf.sprintf "add (%s $%s) to the where clause%s" needs v
(alt "(%s $%s)")
@ -12632,7 +12656,7 @@ and peeks_string ctx (a : Ast.expr) =
(* = and != over a String and a String or a str: the texts' bytes compared,
through the str view each has, which is the comparison str already has.
The orderings are refused as they are on a str, naming bytes<?. *)
The orderings are refused as they are on a str, naming is-bytes-less. *)
and string_compare ctx ~want loc name p args =
(match name with
| "=" | "!=" -> ()
@ -13228,7 +13252,7 @@ and named_call ?(qualified = false) ctx ~want loc name args =
int_literal loc ~want:(Some ty) ~preds:ctx.env.tvpreds 0L
| _ ->
fail a.Ast.loc
"%s takes a is-numeric type, and %s is not one — as in (%s i32)" name
"%s takes a numeric type (is-numeric), and %s is not one — as in (%s i32)" name
(tyname loc ty) name
in
expect ctx loc ~want v
@ -16132,15 +16156,17 @@ and generic_call ctx ~want loc name vars pats pret args =
match List.assoc_opt p.Ast.pvar !subst with
| Some (Types.Var v) when not (declares ctx.env.tvpreds v p.Ast.pname) ->
Loc.failk "check/predicate-not-carried" loc
"%s is written {:where (%s $%s)}, and this call passes the \
"%s is written %s, and this call passes the \
type variable $%s, which nothing here declares %s. Add \
{:where (%s $%s)} to this function's own clause"
name p.Ast.pname p.Ast.pvar v p.Ast.pname p.Ast.pname v
%s to this function's own clause"
name (where_text loc p.Ast.pname ("$" ^ p.Ast.pvar)) v
(pred_word p.Ast.pname) (where_text loc p.Ast.pname ("$" ^ v))
| Some t when not (open_ty t) && not (pred_holds p.Ast.pname t) ->
Loc.failk "check/predicate-unsatisfied" loc
"%s is written {:where (%s $%s)}, and this call passes %s, \
"%s is written %s, and this call passes %s, \
which is not %s"
name p.Ast.pname p.Ast.pvar (tyname loc t) p.Ast.pname
name (where_text loc p.Ast.pname ("$" ^ p.Ast.pvar)) (tyname loc t)
(pred_word p.Ast.pname)
| _ -> ())
gfn.Ast.fwhere);
expect ctx loc ~want (mk loc cret (Tast.Call (name, targs)))
@ -16190,9 +16216,10 @@ and instantiate env loc gname vars subst cparams cret =
if not (pred_holds p.Ast.pname t) then
Loc.failk "check/predicate-unsatisfied" loc
"this call instantiates %s at $%s = %s, and %s is not %s. %s is \
written {:where (%s $%s)} — pass a type the predicate admits"
written %s — pass a type the predicate admits"
gname p.Ast.pvar (tyname loc t) (tyname loc t)
p.Ast.pname gname p.Ast.pname p.Ast.pvar)
(pred_word p.Ast.pname) gname
(where_text loc p.Ast.pname ("$" ^ p.Ast.pvar)))
fn.Ast.fwhere;
if Hashtbl.mem env.fns sym then
fail loc

View File

@ -479,7 +479,8 @@ and list f h args =
((if t <> "" && t.[0] = '~' then "~(" ^ t ^ ")" else "~" ^ t), 13)
| Form.Sym "unquote-splicing", [ x ] -> ("~@" ^ at 13 x, 13)
| Form.Sym s, _ :: _ :: _
when R.is_binop (infix_op s) && s <> "==" && not (s = "!=" && List.length args > 2) ->
when R.is_binop (infix_op s) && s <> "==" && s <> "??"
&& not (s = "!=" && List.length args > 2) ->
let op = infix_op s in
let lvl = Option.get (R.binop_level op) in
let first = List.hd args and rest = List.tl args in

View File

@ -56,7 +56,11 @@ let binops =
[ ("or", 1); ("and", 2);
("==", 4); ("!=", 4); ("<", 4); ("<=", 4); (">", 4); (">=", 4);
("||", 5); ("^^", 6); ("&&", 7);
("<<", 8); (">>", 8); ("+", 9); ("-", 9); ("*", 10); ("/", 10); ("%", 10) ]
("<<", 8); (">>", 8); ("+", 9); ("-", 9); ("*", 10); ("/", 10); ("%", 10);
(* [??] is here for the line rules — a line ending in it, or one starting
with it, continues — and is read by [operand] between 4 and 5, so no
level of [binary]'s is its own. *)
("??", 0) ]
let binop_level s = List.assoc_opt s binops
let is_binop s = binop_level s <> None
@ -149,6 +153,10 @@ let cmp_fresh () = incr cmp_n; Printf.sprintf "~cmp%d" !cmp_n
(* The reader's fresh names for an optional chain's payload, per [read_all]. *)
let opt_n = ref 0
(* Whether a type is being read, set for [ty]'s extent: there a [?] after
any name is [Option], [grain?] included. *)
let in_type = ref false
(* A [-] glued to one of these starts a negation: [-x] is [(- x)]. Anything
else keeps the Lisp reading, so [--], [->] and [-=] stay names. *)
let is_neg_char c =
@ -215,15 +223,20 @@ let question_fix name =
in
pkg ^ fixed
let name_refused loc whole =
let name_refused ?(typed = false) loc whole =
let drop c s = String.concat "" (String.split_on_char c s) in
let n = String.length whole in
if n > 1 && whole.[n - 1] = '?' && not (String.contains (String.sub whole 0 (n - 1)) '?')
&& not (String.contains whole '!') then
let base = String.sub whole 0 (n - 1) in
Loc.failk "indent/question-name" loc
"%s is not a name: a name cannot contain ?.\n\n\
A name for a yes-or-no question starts with is- or has- instead: %s"
whole (question_fix (String.sub whole 0 (n - 1)))
A name for a yes-or-no question starts with is- or has- instead: %s%s"
whole (question_fix base)
(if typed then
Printf.sprintf "\n\nIf %s is a type, %s is Option(%s), written where a \
type goes, after : or ->" base whole base
else "")
else
let c = if String.contains whole '!' then '!' else '?' in
Loc.failk "indent/mark-in-name" loc
@ -321,16 +334,33 @@ let lex ?(line = 1) ?(col = 1) ~file src : token list =
| Some d -> String.sub pre (d + 1) (String.length pre - d - 1)
| None -> pre
in
(* The word after this run, for an operator written without its
spaces: [x!= y] is [x != y]. *)
let after_word r =
if r <> "" then r
else
let j = ref st.Reader.pos in
while !j < String.length src && src.[!j] = ' ' do incr j done;
let k = ref !j in
while !k < String.length src && not (Reader.is_delimiter src.[!k]) do incr k done;
if !k > !j then String.sub src !j (!k - !j) else "b"
in
let tail r = String.sub r 1 (String.length r - 1) in
if ch = '?' then begin
if rest = "?" && pre <> "" && type_like last && next <> '(' then
failk "nested-option" (piece line (col + wa) (String.length whole))
"%s is not read: ?? is the default operator. An Option of an \
Option is written Option(%s?)" whole last;
if rest <> "" && rest.[0] = '?' && pre <> "" then
failk "unspaced-operator" (piece line at 2)
"?? is an operator here, and a binary operator has a space on \
each side: %s ?? %s" pre
(let r = String.sub rest 1 (String.length rest - 1) in
if r = "" then "d" else r);
each side: %s ?? %s" pre (after_word (tail rest));
(* A [?] at the end of a name is a type's, [T?], or one the
parser refuses as part of a name; which, only the parser
knows. Before [(] or inside a name it is a name's. *)
let ends = rest = "" && next <> '(' in
let chain = (rest <> "" && rest.[0] = '.') || (rest = "" && next = '[') in
if (ends && (pre = "" || type_like last)) || chain then begin
if ends || chain then begin
some_part ~after col pre;
emit QUEST (piece line at 1);
marks ~after:true (at + 1) rest
@ -341,7 +371,11 @@ let lex ?(line = 1) ?(col = 1) ~file src : token list =
if rest <> "" && rest.[0] = '=' then
failk "unspaced-operator" (piece line at 2)
"!= is an operator here, and a binary operator has a space on \
each side: %s != %s" pre (String.sub rest 1 (String.length rest - 1));
each side: %s != %s" pre (after_word (tail rest));
if rest <> "" && rest.[0] = '!' then
failk "double-unwrap" (piece line at 2)
"!! is not read. To unwrap an Option of an Option, unwrap \
each level: (%s!)!" (if pre = "" then "x" else pre);
if (rest = "" && next <> '(') || (rest <> "" && rest.[0] = '.') then begin
some_part ~after col pre;
emit BANG (piece line at 1);
@ -351,7 +385,7 @@ let lex ?(line = 1) ?(col = 1) ~file src : token list =
end
in
let wordy = String.exists (fun c -> Reader.is_digit c || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) body in
if wordy || body = "?" || body = "!" then marks col body else plain col body;
if wordy || body = "?" || body = "!" || body = "!!" then marks col body else plain col body;
if colon then emit COLON (piece line (col + n - 1) 1)
end
in
@ -817,7 +851,19 @@ let negative_literal = function
(* Something followed a complete value where nothing may. The two shapes that
get their own sentence are the ones a Lisp hand writes: [a -1] and
[f (x)]. *)
(* A [?] glued to the name just read, somewhere a type is not: the name
is what was meant, [let ok? = 1] or [fn f(ok?: bool)]. *)
let mark_after p =
let t = peek p in
match t.tok, (last p).tok with
| QUEST, NAME n when (not t.sp) && p.i > 0 && n <> "" ->
let l = (last p).loc in
let n = if n.[0] = '.' then String.sub n 1 (String.length n - 1) else n in
name_refused ~typed:(type_like n) { l with Loc.eline = t.loc.Loc.eline; ecol = t.loc.Loc.ecol } (n ^ "?")
| _ -> ()
let stray p ~after =
mark_after p;
let t = peek p in
match t.tok with
| ATOM _ when t.sp && negative_literal t.tok ->
@ -863,13 +909,14 @@ let expect p tok ~what =
let t = peek p in
if t.tok = tok then ignore (advance p)
else
let () = mark_after p in
failk "expected" (where_ p) "expected %s here, and found %s" what
(show t.tok)
let expect_name p s ~what =
match (peek p).tok with
| NAME n when n = s -> ignore (advance p)
| t -> failk "expected" (where_ p) "expected %s here, and found %s" what (show t)
| t -> mark_after p; failk "expected" (where_ p) "expected %s here, and found %s" what (show t)
(* The lets whose first value is being read, innermost first: the column
of the let's first name, the name, and the let's line. A line at that
@ -938,6 +985,23 @@ let text_of (f : Form.t) =
let s = match from_source with Some t -> t | None -> Form.to_source f in
if String.length s > 40 then String.sub s 0 37 ^ "..." else s
(* An assignment's place that is an optional chain or an unwrap: neither
names storage, and the forms they read to would show the reader's names. *)
let no_place (e : Form.t) =
match e.v with
| Form.List ({ v = Form.Sym "?."; _ } :: _) ->
failk "chain-assign" e.loc
"%s is an optional chain, and a chain cannot be assigned to: when it \
holds nothing there is no place to write. Unwrap it first with if let, \
then assign through the name it binds"
(text_of e)
| Form.List [ { v = Form.Sym "!!"; _ }; _ ] ->
failk "chain-assign" e.loc
"%s unwraps a value, and a value cannot be assigned to. Unwrap it with \
if let, then assign through the name it binds"
(text_of e)
| _ -> ()
let unclosed p c l0 =
failk "unclosed" l0
~notes:[ Loc.note (where_ p) "the input ends here, still inside it" ]
@ -1171,8 +1235,34 @@ and postfix p =
[ sym t.loc "?.";
Form.make (Form.Vec [ sym t.loc h; f ]) f.loc;
rest ]), 12)
(* [T?]: the lexer lets a [?] end only a type's name or a closer. *)
(* [T?]. Where a type is read, after any name; where a value is, after
what can only be a type — [vec-new(i32?)] — and refused after a
name or a value. *)
| QUEST ->
let typish =
match f.v with
| Form.Sym n -> type_like n
| Form.Vec _ -> true
| Form.List ({ v = Form.Sym h; _ } :: _) ->
h <> "" && h.[0] >= 'A' && h.[0] <= 'Z'
| _ -> false
in
if not (!in_type || typish) then begin
match f.v with
| Form.List [ { v = Form.Sym h; loc = hl }; _ ] when String.length h > 1 && h.[0] = '.' ->
name_refused { hl with Loc.eline = t.loc.Loc.eline; ecol = t.loc.Loc.ecol }
(String.sub h 1 (String.length h - 1) ^ "?")
| Form.Sym n ->
name_refused ~typed:true
{ f.loc with Loc.eline = t.loc.Loc.eline; ecol = t.loc.Loc.ecol }
(n ^ "?")
| _ ->
failk "value-question" t.loc
"? after %s is not read: ? goes after a type, as in i32?. On a \
value that may hold nothing, %s?.field reads through it, %s! \
unwraps it and %s ?? d gives a default"
(text_of f) (text_of f) (text_of f) (text_of f)
end;
ignore (advance p);
loop (mk p l0 (Form.List [ sym l0 "Option"; f ]), 12)
| BANG ->
@ -1393,10 +1483,12 @@ and inline_stmt p : Form.t =
let e, _ = expr p in
match (peek p).tok with
| NAME "=" ->
no_place e;
let eq = advance p in
let v, _ = expr p in
mk p t.loc (Form.List [ sym eq.loc "set"; e; v ])
| NAME op when List.mem_assoc op assign_ops ->
no_place e;
let eq = advance p in
let v, _ = expr p in
mk p t.loc (compound eq.loc (List.assoc op assign_ops) e v (span p e.loc))
@ -1686,7 +1778,9 @@ and map_items p open_loc =
type, [Fn(A, B) -> R], which reads as [(Fn [A B] R)]. *)
let rec ty p : Form.t =
let l0 = (peek p).loc in
let f, _ = postfix p in
let was = !in_type in
in_type := true;
let f, _ = Fun.protect ~finally:(fun () -> in_type := was) (fun () -> postfix p) in
match f.v, (peek p).tok with
| Form.List (({ v = Form.Sym ("Fn" | "CFn"); _ } as h) :: args), NAME "->"
when (last p).tok = RP ->
@ -2195,10 +2289,12 @@ and expr_stmt (s : st) : Form.t =
let e, _ = expr p in
match (peek p).tok with
| NAME "=" ->
no_place e;
let eq = advance p in
let v = value_line s ~after:(text_of e ^ " =") in
mk p t0.loc (Form.List [ sym eq.loc "set"; e; v ])
| NAME op when List.mem_assoc op assign_ops ->
no_place e;
let eq = advance p in
let v = value_line s ~after:(text_of e ^ " " ^ op) in
let o = List.assoc op assign_ops in

View File

@ -1688,7 +1688,7 @@ let source = {flan|
;; different parts of the input, and a caller can see which one came first.
;;
;; It keeps a name of its own rather than collapsing into sort, and the
;; reason is the point of the predicates: a [u8] is not is-ordered and cannot
;; reason is the point of the predicates: a [u8] is not ordered (is-ordered) and cannot
;; be, because < is defined on machine numbers and comparing two slices
;; lexicographically is a loop and not an instruction. is-bytes-less is that loop.
;; So this is the shape a generic takes when the operation it needs is not a

View File

@ -37,7 +37,7 @@
(+ x x))
;; is-equal admits = and !=; is-ordered admits < <= > >= min max, and entails
;; equal?.
;; is-equal.
(defn count-of [s [$t] x $t] i32
{:where (is-equal $t)}
(let [n 0]
@ -64,7 +64,7 @@
;; re-checks (> x 0) with $t substituted, and *that* is where the literal is
;; built at the concrete width and range-checked.
;;
;; The -t? suffix is because the prelude now carries pos?/neg?/is-zero itself.
;; The -t suffix is because the prelude now carries is-pos/is-neg/is-zero itself.
;; These are the same three bodies written in an ordinary program, which is
;; what says the machinery belongs to the language and not to the prelude.
(defn is-pos-t [x $t] bool {:where (is-numeric $t)} (> x 0))

View File

@ -111,7 +111,7 @@
(println (abs-f64 -1.5))
(println (abs-f32 -2.5))
;; The integer?-only operations, per width.
;; The is-integer-only operations, per width.
(println (low-bits 255 3))
(println (low-bits (u16 65535) (u16 4)))
(println (low-bits (i64 1023) (i64 5)))

View File

@ -3812,13 +3812,13 @@ let () =
onto whichever of the two buffers the branch chose.
The six [true]s and the five numbers after the first [8] are the
literal-at-a-type-variable family: three written bodies — pos?/neg?/
zero-p?, next-after and plus-300 — reaching i8, u8, u16, i32, i64, f32
literal-at-a-type-variable family: three written bodies — is-pos-t, is-neg-t,
is-zero-t, next-after and plus-300 — reaching i8, u8, u16, i32, i64, f32
and f64. [255] is next-after at u8 and is the one that would say
whether the placeholder width the abstract pass builds had leaked into
a copy; [301] is plus-300 at i32, whose range check belongs to the copy
and not to the definition. The [true true false] after them is the
prelude's own pos?/zero?/is-neg — the same three bodies under their real
prelude's own is-pos/is-zero/is-neg — the same three bodies under their real
names — and the middle one is is-zero at -0.0, which IEEE says is zero
and which this does not second-guess. *)
let generics_out =
@ -3849,7 +3849,7 @@ let () =
lines are the collapsed abs at six widths and both signed minimums
(which answer themselves; the negation wraps). The [0 0] after them is
the libm float pair at -0.0, the sign-bit clear no integer body
spells. Then the integer?-only operations at several widths, and last
spells. Then the is-integer-only operations at several widths, and last
the join family: [true true], [6 6] and [42] are mixed widths at one
$t answering identically in both argument orders, from one copy at
the wider type (TODO.org, "abs is one generic, and a bound joins to the
@ -3954,7 +3954,7 @@ let () =
and cannot change. *)
refuses "a package generic's bound, refused at the call"
"programs/pkg-generic-reject.flan"
"str is not is-ordered";
"str is not ordered (is-ordered)";
refuses "and the refusal quotes the clause the package wrote"
"programs/pkg-generic-reject.flan" "{:where (is-ordered $t)}";
@ -3987,7 +3987,7 @@ let () =
chain of instantiations and not a depth it gave up at. *)
refuses "an unconstrained operator in a generic body"
"programs/generic-reject.flan"
"nothing declares $t is-numeric";
"nothing declares $t numeric (is-numeric)";
refuses "an unconstrained operator names the way out"
"programs/generic-reject.flan" "{:where (is-numeric $t)}";
refuses "a runaway instantiation" "programs/generic-runaway.flan"
@ -4015,7 +4015,7 @@ let () =
requirement the author wrote down. What is asserted is that it names
the type passed and the predicate it failed, and not the body. *)
refuses "a generic over maps, instantiated at a key that cannot be hashed"
"programs/generic-map-reject.flan" "f64 is not is-hashable";
"programs/generic-map-reject.flan" "f64 is not hashable (is-hashable)";
refuses "and it names the type the call site asked for"
"programs/generic-map-reject.flan" "at $t = f64";

View File

@ -1553,7 +1553,7 @@ let () =
accepts "all-distinct over a type variable"
"(defn three [a $t b $t c $t] bool {:where (is-equal $t)} (!= a b c))";
rejects_check "a chain still wants the right predicate"
~needle:"nothing declares $t is-ordered"
~needle:"nothing declares $t ordered (is-ordered)"
"(defn between [a $t b $t c $t] bool {:where (is-equal $t)} (< a b c))";
(* One operand and none. Both would have to be [true] whatever they were
handed, which is a typo carrying a value. *)
@ -6818,7 +6818,7 @@ let () =
accepts "is-numeric admits +"
"(defn add [a $t b $t] $t {:where (is-numeric $t)} (+ a b))";
rejects_check "is-equal does not admit <"
~needle:"nothing declares $t is-ordered"
~needle:"nothing declares $t ordered (is-ordered)"
"(defn less [a $t b $t] bool {:where (is-equal $t)} (< a b))";
(* The entailments, which are the reason a signature is one predicate long
rather than two. Every type the language orders is a number or an enum,
@ -6846,36 +6846,36 @@ let () =
accepts "is-integer admits the shifts"
"(defn dbl [x $t] $t {:where (is-integer $t)} (<< x 1))";
rejects_check "is-numeric does not admit bit-and"
~needle:"nothing declares $t is-integer"
~needle:"nothing declares $t integer (is-integer)"
"(defn is-low [x $t] bool {:where (is-numeric $t)} (= (bit-and x 1) 1))";
rejects_check "nor the shifts"
~needle:"nothing declares $t is-integer"
~needle:"nothing declares $t integer (is-integer)"
"(defn dbl [x $t] $t {:where (is-numeric $t)} (<< x 1))";
(* An integer?-bounded caller satisfies a numeric?-bounded callee: the
entailment carries across generic calls exactly as ordered?-over-equal?
(* A caller bounded by is-integer satisfies a callee bounded by is-numeric: the
entailment carries across generic calls exactly as is-ordered-over-equal?
does. *)
accepts "is-integer carries a is-numeric callee"
accepts "is-integer carries an is-numeric callee"
"(defn z? [x $t] bool {:where (is-numeric $t)} (= x 0))\n\
(defn odd-z? [x $t] bool {:where (is-integer $t)} (z? (bit-and x 1)))";
(* The integer literal is admitted at a bounded variable by the same arm
under both bounds — the bound promises the literal a meaning at every
type the variable can become, and is-integer's types are a subset of
is-numeric's. *)
accepts "an integer literal stands where an integer?-bounded $t is wanted"
accepts "an integer literal stands where an is-integer-bounded $t is wanted"
"(defn bump [x $t] $t {:where (is-integer $t)} (+ x 300))";
(* A float at is-integer, refused at the call that asked, naming the bound. *)
rejects_check "a float does not instantiate an integer?-bounded variable"
~needle:"f64 is not is-integer"
rejects_check "a float does not instantiate an is-integer-bounded variable"
~needle:"f64 is not integer (is-integer)"
"(defn bump [x $t] $t {:where (is-integer $t)} (+ x 1))\n\
(defn main [] () (println (bump 1.5)))";
(* And dyn is refused by the bound too — the clause's own refusal, the more
specific of the two answers, exactly as at numeric?. *)
rejects_check "dyn does not instantiate an integer?-bounded variable"
~needle:"dyn is not is-integer"
specific of the two answers, exactly as at is-numeric. *)
rejects_check "dyn does not instantiate an is-integer-bounded variable"
~needle:"dyn is not integer (is-integer)"
"(defn bump [x $t] $t {:where (is-integer $t)} (+ x 1))\n\
(defonce d dyn 5)\n\
(defn main [] () (println (bump d)))";
(* A float literal inside an integer?-bounded body is refused at the
(* A float literal inside an is-integer-bounded body is refused at the
definition, in the bound's own words: there is no instantiation at which
it means anything. *)
rejects_check "a float literal has no meaning under is-integer"
@ -6914,7 +6914,7 @@ let () =
what is a number, so a conversion under one of them alone is refused —
and the message says which predicate to write. *)
rejects_check "is-ordered does not admit a conversion"
~needle:"The where clause says t is is-ordered, and that does not make it \
~needle:"The where clause says t is ordered (is-ordered), and that does not make it \
a number or an enum — add (is-numeric $t) to the where clause, or \
(is-enum $t) for an enum"
"(defn to32 [x $t] i32 {:where (is-ordered $t)} (i32 x))";
@ -6933,7 +6933,7 @@ let () =
"(defn to32 [x $t] i32 (i32 x))";
(* is-enum is the other bound a conversion to a number takes: it admits the
enums, which convert as an i32, and entails is-ordered and is-equal but not
numeric?. The running side is programs/enum-generic.flan. *)
is-numeric. The running side is programs/enum-generic.flan. *)
accepts "is-enum admits the conversion from an enum"
"(defn code [x $t] i32 {:where (is-enum $t)} (i32 x))";
accepts "and compares, being is-ordered and is-equal"
@ -6942,7 +6942,7 @@ let () =
~needle:"$t"
"(defn sum [a $t b $t] $t {:where (is-enum $t)} (+ a b))";
rejects_check "and admits no integer at the call"
~needle:"i32 is not is-enum"
~needle:"i32 is not enum (is-enum)"
"(defn code [x $t] i32 {:where (is-enum $t)} (i32 x))\n\
(defn f [] i32 (code (i32 3)))";
rejects_check "nor the conversion to an enum, which needs an integer"
@ -6952,7 +6952,7 @@ let () =
(* The operand of a cast to a *variable* target is asked the same question
the target was: the target's bound says nothing about a second variable
standing in the argument. *)
accepts "a cast to a variable target takes a is-numeric operand"
accepts "a cast to a variable target takes an is-numeric operand"
"(defn conv [x $u y $t] $t {:where [(is-numeric $t) (is-numeric $u)]} \
(if (< y y) (t x) (t x)))";
rejects_check "a cast to a variable target refuses an is-ordered operand"
@ -6968,7 +6968,7 @@ let () =
(defn as-k [n $t] K {:where (is-integer $t)} (K n))";
rejects_check "is-numeric does not, because it admits floats"
~needle:"K converts an integer to an enum. The where clause says t is \
is-numeric, and that does not make it an integer — add \
numeric (is-numeric), and that does not make it an integer — add \
(is-integer $t) to the where clause"
"(defenum K [lo -1 hi 1])\n\
(defn as-k [n $t] K {:where (is-numeric $t)} (K n))";
@ -7000,7 +7000,7 @@ let () =
"(defn same [a $t b $t] bool {:where (is-equal $t)} (= a b)) \
(defn f [] bool (same \"a\" \"b\"))";
rejects_check "is-ordered $t instantiated at string"
~needle:"is not is-ordered"
~needle:"is not ordered (is-ordered)"
"(defn less [a $t b $t] bool {:where (is-ordered $t)} (< a b)) \
(defn f [] bool (less \"a\" \"b\"))";
@ -7255,7 +7255,7 @@ let () =
there is no instantiation of a [is-numeric] variable at which the literal
has no meaning. That is the whole rule, and the four pins below are its
two halves and its one asymmetry. *)
accepts "an integer literal stands where a is-numeric type variable is wanted"
accepts "an integer literal stands where an is-numeric type variable is wanted"
"(defn above-zero? [x $t] bool {:where (is-numeric $t)} (> x 0))";
accepts "and in arithmetic, answering the variable"
"(defn next [x $t] $t {:where (is-numeric $t)} (+ x 1))";
@ -7292,7 +7292,7 @@ let () =
pass defers to the instantiation — but only under the predicate, which is
what gives the deferred refusal somewhere to land. Without one the type
itself is refused where it is written, at the definition. *)
rejects_check "a map keyed by a type variable that is not is-hashable"
rejects_check "a map keyed by a type variable that is not hashable (is-hashable)"
~needle:"is not a map key"
"(defn f [m (Map $t i32)] i32 {:where (is-numeric $t)} (length m))";
accepts "and is-hashable is what says it is"
@ -7559,7 +7559,7 @@ let () =
"(defstruct Pair [a $t b $t]) \
(defn f [] f64 (let [p (Pair 1 2.5)] (+ (.a p) (.b p))))";
rejects_check "a callee's predicate names the caller's variable with its $"
~needle:"passes the type variable $t, which nothing here declares is-ordered"
~needle:"passes the type variable $t, which nothing here declares ordered (is-ordered)"
"(defn f [s [$t]] () (sort s))";
accepts "a defonce of a generic struct's copy"
"(defstruct Pair [a $t b $t]) (defonce g (Pair i32)) \
@ -7885,7 +7885,7 @@ let () =
"(defn f [x $t] $t {:where (is-integer $t)} (max-value t))";
rejects_check "max-value at a type that is not a number names the bound"
"(defn f [] str (max-value str))"
~needle:"max-value takes a is-numeric type, and str is not one";
~needle:"max-value takes a numeric type (is-numeric), and str is not one";
rejects_check "max-value of a value says it takes a type"
"(defn f [x i32] i32 (max-value x))" ~needle:"max-value takes a type";
accepts "max-of of a slice is the prelude's reduction"

View File

@ -1414,6 +1414,28 @@ let () =
refused "coalesce-i32.fln"
"fn main()\n let x = 5\n println(x ?? 1)\n"
[ "the left side of ?? is i32, which always holds a value" ];
(* After review: ?? continues a line, a chain is no place, nested marks
and a ? on a value say what to write, and a lowercase type takes ?. *)
reads "a line ending in ?? continues" "x = a ??\n 5" "(set x (?? a 5))";
reads "a line starting with ?? continues" "x = a\n ?? 5" "(set x (?? a 5))";
refuses "a chain is not a place" "q?.x = 5" "indent/chain-assign" "Unwrap it first with if let";
refuses "nor under +=" "q?.x += 5" "indent/chain-assign" "cannot be assigned to";
refuses "an unwrap is not a place" "x! = 5" "indent/chain-assign" "a value cannot be assigned to";
refuses "T?? is not read" "fn f(a: i32??) = a" "indent/nested-option" "Option(i32?)";
refuses "x!! is not read" "y = x!!" "indent/double-unwrap" "(x!)!";
refuses "!= with its space missing names what follows" "y = x!= z"
"indent/unspaced-operator" "x != z";
refuses "? on a value" "y = f(x)?" "indent/value-question" "f(x)?.field reads through it";
refuses "? on a name in a value" "y = ready?" "indent/question-name"
"If ready is a type, ready? is Option(ready)";
refuses "? on a parameter's name" "fn f(ok?: bool) = ok" "indent/question-name" "is-ok";
reads "a lowercase type takes ?" "fn f(a: grain?, b: [grain?]) -> grain? = a"
"(defn f [a (Option grain) b [(Option grain)]] (Option grain) a)";
refused "option-value.fln" "fn main()\n let y = i32?\n"
[ "i32? is an Option type, and a value is wanted here" ];
refused "where-fln.fln"
"fn big(a: $t, b: $t) -> bool = a < b\n\nfn main()\n println(big(1, 2))\n"
[ "nothing declares $t ordered (is-ordered)"; "Write where is-ordered($t)" ];
refused "chain-i32.fln"
"struct P\n x: i32\n\nfn main()\n let p = P{.x 1}\n println(p?.x)\n"
[ "?. has nothing to test. Write . instead" ]

View File

@ -698,7 +698,7 @@ program was edited elsewhere would not be worth having.</p>
<p>Braces and brackets in expression position write dyn literals:
<code>{:a 1 :b "two"}</code> is a dyn map and <code>[1 2 3]</code> is a dyn vector.
<code>get</code>, <code>put</code>, <code>has-key?</code>, <code>at</code> and
<code>get</code>, <code>put</code>, <code>has-key</code>, <code>at</code> and
<code>length</code> read and write them, the same names the typed
<code>Map</code> and <code>Vec</code> answer to. <code>(.hp m)</code> is
<code>(get m :hp)</code> and <code>(at m :hp)</code> is too; <code>set</code> on
@ -839,7 +839,7 @@ its fields, and omitted fields are zeroed.</p>
<p>Those are the bytes <code>h</code> and <code>i</code>. There is no character type — a
byte is a <code>u8</code> — but there is a byte literal, so <code>\h</code> is 104 and
<code>\space</code> is 32, and the prelude's <code>digit?</code> reads as
<code>\space</code> is 32, and the prelude's <code>is-digit</code> reads as
<code>(and (&gt;= b \0) (&lt;= b \9))</code>. To see a byte as a letter rather than as a
number, print a slice of them: <code>print</code> writes a <code>[u8]</code> as
its bytes.</p>
@ -1282,7 +1282,7 @@ whichever call site first reaches a type that happens to work. That is deliberat
<em>not</em> Odin's rule, which checks a polymorphic body per instantiation:</p>
<pre><code class="sh">+ over the type variable t is refused: a type variable supports only what it is
declared to support, and nothing here says t is numeric?. Write {:where (is-numeric
declared to support, and nothing declares t numeric (is-numeric). Write {:where (is-numeric
$t)} at the head of the body, or take the operation as a parameter — a
(Fn [t t] ...) — and call it here</code></pre>
@ -1294,21 +1294,21 @@ are six predicates, and each gates builtins the compiler already has:</p>
<div class="scroll">
<table>
<tr><th>Predicate</th><th>What it admits</th></tr>
<tr><td><code>integer?</code></td><td><code>bit-and</code> <code>bit-or</code> <code>bit-xor</code> <code>&lt;&lt;</code> <code>&gt;&gt;</code> — every integer type, no float</td></tr>
<tr><td><code>numeric?</code></td><td><code>+</code> <code>-</code> <code>*</code> <code>/</code> <code>%</code>, and a cast <code>(t x)</code></td></tr>
<tr><td><code>enum?</code></td><td>a cast to a number, <code>(i32 x)</code> — every enum type</td></tr>
<tr><td><code>ordered?</code></td><td><code>&lt;</code> <code>&lt;=</code> <code>&gt;</code> <code>&gt;=</code> <code>min</code> <code>max</code></td></tr>
<tr><td><code>equal?</code></td><td><code>=</code> and <code>!=</code></td></tr>
<tr><td><code>hashable?</code></td><td>the variable as a <code>Map</code> key — <code>(map-new t V)</code>, <code>get</code>, <code>put</code>, <code>has-key?</code></td></tr>
<tr><td><code>is-integer</code></td><td><code>bit-and</code> <code>bit-or</code> <code>bit-xor</code> <code>&lt;&lt;</code> <code>&gt;&gt;</code> — every integer type, no float</td></tr>
<tr><td><code>is-numeric</code></td><td><code>+</code> <code>-</code> <code>*</code> <code>/</code> <code>%</code>, and a cast <code>(t x)</code></td></tr>
<tr><td><code>is-enum</code></td><td>a cast to a number, <code>(i32 x)</code> — every enum type</td></tr>
<tr><td><code>is-ordered</code></td><td><code>&lt;</code> <code>&lt;=</code> <code>&gt;</code> <code>&gt;=</code> <code>min</code> <code>max</code></td></tr>
<tr><td><code>is-equal</code></td><td><code>=</code> and <code>!=</code></td></tr>
<tr><td><code>is-hashable</code></td><td>the variable as a <code>Map</code> key — <code>(map-new t V)</code>, <code>get</code>, <code>put</code>, <code>has-key</code></td></tr>
</table>
</div>
<p>They entail each other in one direction, so one clause usually does:
<code>integer?</code> gives <code>numeric?</code>, <code>numeric?</code> gives
<code>ordered?</code>, and <code>ordered?</code> gives <code>equal?</code>;
<code>enum?</code> gives <code>ordered?</code> too. A
<code>sort</code> that compares its elements declares <code>ordered?</code> and
nothing else, and the prelude's <code>abs</code> declares <code>integer?</code>
<code>is-integer</code> gives <code>is-numeric</code>, <code>is-numeric</code> gives
<code>is-ordered</code>, and <code>is-ordered</code> gives <code>is-equal</code>;
<code>is-enum</code> gives <code>is-ordered</code> too. A
<code>sort</code> that compares its elements declares <code>is-ordered</code> and
nothing else, and the prelude's <code>abs</code> declares <code>is-integer</code>
alone — the bound is what keeps its integer body away from the floats, whose
<code>abs-f32</code>/<code>abs-f64</code> are libm's sign-bit clear.</p>
@ -1330,7 +1330,7 @@ the predicate admits</code></pre>
their legality is only decidable after substituting: <code>println</code> over a
variable, which selects the structural printer per copy, and the <code>Map</code>
operations over a variable key, whose hash and equality are concrete symbols chosen from
the concrete key type. The <code>Map</code> half is what <code>hashable?</code> buys —
the concrete key type. The <code>Map</code> half is what <code>is-hashable</code> buys —
without the clause, the type <code>(Map $t i32)</code> is refused where it is
<em>written</em>, and with it the refusal moves to the call site that names an
unhashable key.</p>
@ -1416,17 +1416,17 @@ over.</p>
<tr><th>Group</th><th>Names</th></tr>
<tr><td>slice algorithms, over one type variable</td><td><code>swap</code>, <code>reverse</code>, <code>sort</code>, <code>sort-by</code>, <code>index-of</code>, <code>min-of</code>, <code>max-of</code>, <code>map-in-place</code>, <code>reduce</code>, <code>filter</code></td></tr>
<tr><td>the per-type layer that stays</td><td><code>sum-i32</code>, <code>sum-f32</code> — the element and the accumulator are different types, which one variable cannot say</td></tr>
<tr><td>bytes</td><td><code>bytes=?</code>, <code>bytes&lt;?</code>, <code>bytes-ci=?</code>, <code>starts-with?</code>, <code>ends-with?</code>, <code>index-of-bytes</code>, <code>trim</code>, <code>digit?</code>, <code>space?</code>, <code>sort-bytes</code></td></tr>
<tr><td>bytes</td><td><code>is-bytes-equal</code>, <code>is-bytes-less</code>, <code>is-bytes-ci-equal</code>, <code>has-prefix</code>, <code>has-suffix</code>, <code>index-of-bytes</code>, <code>trim</code>, <code>is-digit</code>, <code>is-space</code>, <code>sort-bytes</code></td></tr>
<tr><td>parsing</td><td><code>parse-i64</code>, <code>parse-f64</code></td></tr>
<tr><td>text</td><td><code>split-on-byte</code>, <code>split-next</code>, <code>split</code>, <code>lower-ascii</code>, <code>upper-ascii</code>, <code>to-lower</code>, <code>to-upper</code></td></tr>
<tr><td>building bytes</td><td><code>append</code>, <code>append-i64</code>, <code>append-f64</code>, <code>concat</code>, <code>join</code>, <code>repeat-bytes</code>, <code>replace-bytes</code>, <code>slices-new</code>, <code>format-f64</code></td></tr>
<tr><td>UTF-8</td><td><code>decode-rune</code>, <code>rune-at</code>, <code>rune-count</code>, <code>rune-size</code>, <code>rune-start?</code>, <code>valid-utf8?</code>, <code>encode-rune</code></td></tr>
<tr><td>UTF-8</td><td><code>decode-rune</code>, <code>rune-at</code>, <code>rune-count</code>, <code>rune-size</code>, <code>is-rune-start</code>, <code>is-valid-utf8</code>, <code>encode-rune</code></td></tr>
<tr><td>numbers</td><td><code>sign-f32</code>, <code>lerp</code>, <code>clamp</code>, <code>floor-f32</code>, <code>ceil-f32</code>, <code>round-f32</code>, <code>abs</code> (generic over every integer width), the constants <code>pi-f32</code>, <code>pi-f64</code>, <code>tau-f32</code>, <code>tau-f64</code>, and libm through a <code>declare</code> at both widths: <code>sqrt</code>, <code>abs</code>, <code>floor</code>, <code>ceil</code>, <code>round</code>, <code>fmod</code>, <code>sin</code>, <code>cos</code>, <code>tan</code>, <code>asin</code>, <code>acos</code>, <code>atan</code>, <code>atan2</code>, <code>log</code>, <code>log2</code>, <code>log10</code>, <code>exp</code>, <code>pow</code>, <code>hypot</code>, <code>cbrt</code> — each spelled <code>-f32</code> or <code>-f64</code></td></tr>
<tr><td>time</td><td><code>monotonic-ns</code>, <code>monotonic-seconds</code>, <code>unix-ns</code>, <code>unix-seconds</code>, <code>sleep-ns</code>, <code>sleep-seconds</code>, and <code>ns-per-second</code> and its two smaller siblings</td></tr>
<tr><td>files</td><td><code>file-exists?</code> and <code>file-size</code>, which answer a value; <code>slurp</code>, <code>barf</code>, <code>delete-file</code>, <code>rename-file</code> and <code>make-directory</code>, which signal <code>FileError</code> under <code>retry</code> and <code>use-value</code></td></tr>
<tr><td>files</td><td><code>file-exists</code> and <code>file-size</code>, which answer a value; <code>slurp</code>, <code>barf</code>, <code>delete-file</code>, <code>rename-file</code> and <code>make-directory</code>, which signal <code>FileError</code> under <code>retry</code> and <code>use-value</code></td></tr>
<tr><td>the operating system</td><td><code>getenv</code>, which answers an <code>(Option [u8])</code> viewing the process environment</td></tr>
<tr><td>random</td><td><code>rand-seed</code>, <code>rand-int</code> (a <code>u64</code>), <code>rand</code> (an <code>f64</code> in [0, 1)), <code>rand-bool</code>, <code>rand-int-range</code> (an <code>i64</code>), <code>rand-float-range</code>. A seeded PRNG written in Flan, one draw per call, so the same seed gives the same numbers on every target. Reproducible and predictable are the same property: a holder of one result can work back to the state and know the rest. Right for a grid or a shuffle, wrong for a key</td></tr>
<tr><td>forms, for macros</td><td><code>form-nil</code>, <code>form-cons</code>, <code>form-append</code>, <code>form-rest</code>, <code>form-items</code>, <code>form-pair</code>, <code>form-sym?</code>, <code>form-is-sym?</code>, <code>gensym</code>, and <code>unless</code> and <code>into</code>, which are macros written here rather than special forms</td></tr>
<tr><td>forms, for macros</td><td><code>form-nil</code>, <code>form-cons</code>, <code>form-append</code>, <code>form-rest</code>, <code>form-items</code>, <code>form-pair</code>, <code>is-form-named</code>, <code>is-form-sym</code>, <code>gensym</code>, and <code>unless</code> and <code>into</code>, which are macros written here rather than special forms</td></tr>
<tr><td>the rest</td><td><code>pause</code>, which signals the <code>Pause</code> condition the break loop stops on, and <code>embed-find</code></td></tr>
</table>
</div>
@ -1477,7 +1477,7 @@ integer-exact in nanoseconds for a hundred days of process life, which is why th
monotonic origin is the first read and not boot.</p>
<p><strong>The file surface is split by whether a handler could do anything.</strong>
<code>file-exists?</code> and <code>file-size</code> answer a <code>bool</code> and an
<code>file-exists</code> and <code>file-size</code> answer a <code>bool</code> and an
<code>(Option i64)</code>: absence is the reply, not a fault, and a condition would make
the ordinary case pay for a handler search. <code>slurp</code>, <code>barf</code>,
<code>delete-file</code>, <code>rename-file</code> and <code>make-directory</code> signal
@ -1908,8 +1908,8 @@ Two directives:</p>
# behind three innocuous Flan names.
exclude Mem*
# The kebab rule gives is-window-ready. Lisp spells a predicate with a ?.
name IsWindowReady window-ready?</code></pre>
# A Flan face the kebab rule would not give.
name GetFPS fps</code></pre>
<p>An excluded function still says it was excluded rather than going quiet, and a name
override changes only the Flan face — the C symbol is kept verbatim in the declaration,