?? 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:
parent
c9f33feb6c
commit
6c6b5a60cb
32
TODO.org
32
TODO.org
@ -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
|
||||
|
||||
57
lib/check.ml
57
lib/check.ml
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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)))
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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" ]
|
||||
|
||||
@ -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 (>= b \0) (<= 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><<</code> <code>>></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><</code> <code><=</code> <code>></code> <code>>=</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><<</code> <code>>></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><</code> <code><=</code> <code>></code> <code>>=</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<?</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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user