A one-line if takes its else on the next line, lambdas state their types, a restart's report sits on its header, Dir.north names an enum member, messages spell types in the code's syntax, adjacent globals convert adjacent, and a bad map key is one error
This commit is contained in:
parent
dbddc61b30
commit
fd1a7f2b36
53
TODO.org
53
TODO.org
@ -628,51 +628,32 @@ awaiting confirmation, and the build order. Rules out Parinfer, wisp and
|
|||||||
sweet-expressions, and a simplified in-paren syntax — all thin the parens
|
sweet-expressions, and a simplified in-paren syntax — all thin the parens
|
||||||
without removing them.
|
without removing them.
|
||||||
|
|
||||||
** TODO A one-line if cannot take its else on the next line
|
** WAIT A lambda with a block cannot be a call's argument
|
||||||
=if c then a= then =else b= under it is refused. Proposal: an =else=/=elif= at
|
On the author's decision. =sort-by(xs, fn(a, b)= plus a block is refused; the
|
||||||
the if's column continues a one-line if, as F#'s does.
|
lambda has to be bound first with =let=. Proposal: a call ending in =fn(...)= and a trailing =:=
|
||||||
|
|
||||||
** TODO A lambda with a block cannot be a call's argument
|
|
||||||
=sort-by(xs, fn(a, b)= plus a block is refused; the lambda has to be bound first,
|
|
||||||
with its type written. Proposal: a call ending in =fn(...)= and a trailing =:=
|
|
||||||
hands the block to that lambda: =sort-by(xs, fn(a, b)):=.
|
hands the block to that lambda: =sort-by(xs, fn(a, b)):=.
|
||||||
|
|
||||||
** TODO A lambda's parameters cannot be typed
|
** WAIT A condition struct with a parent has no sugar
|
||||||
=fn(a, b)= takes bare names, so a lambda bound by =let= needs
|
On the author's decision. =defstruct(DiskFull, :parent, IoError, [free i64])= is the fallback, with a
|
||||||
=let f: Fn(C, C) -> bool = fn(a, b)=. Proposal: =fn(a: C, b: C)= as in a
|
|
||||||
=fn= definition.
|
|
||||||
|
|
||||||
** TODO A condition struct with a parent has no sugar
|
|
||||||
=defstruct(DiskFull, :parent, IoError, [free i64])= is the fallback, with a
|
|
||||||
paren field vector. Proposal: =struct DiskFull :parent IoError= plus field lines.
|
paren field vector. Proposal: =struct DiskFull :parent IoError= plus field lines.
|
||||||
|
|
||||||
** TODO defmacro has no sugar
|
** WAIT defmacro has no sugar
|
||||||
=defmacro(repeat, [i n & body]):= with a space-separated parameter vector.
|
On the author's decision. =defmacro(repeat, [i n & body]):= with a space-separated parameter vector.
|
||||||
Proposal: =macro repeat(i, n, & body)= plus a block.
|
Proposal: =macro repeat(i, n, & body)= plus a block.
|
||||||
|
|
||||||
** TODO loop/recur has no sugar
|
** WAIT loop/recur has no sugar
|
||||||
=loop([x a y b]):=. Proposal: =loop x = a, y = b= plus a block; =recur(...)=
|
On the author's decision. =loop([x a y b]):=. Proposal: =loop x = a, y = b= plus a block; =recur(...)=
|
||||||
stays a call.
|
stays a call.
|
||||||
|
|
||||||
** TODO A restart's report string is a body line
|
** TODO Hard-coded code in messages is still paren syntax in a .fln file
|
||||||
=:report= and its string sit as two statements under =restart name()=.
|
Types follow the code's syntax now (=Types.spell=). Hints written into a message's
|
||||||
Proposal: =restart name() "report text"= on the header line.
|
text — =(Ptr %s)=, =(clone v)=, =(the T x)= in most of =check.ml= and =parse.ml=, the
|
||||||
|
runtime's =(get 0 :body)= and =(/ x 0)= — still print parens; each is fixed per
|
||||||
|
message as it is met.
|
||||||
|
|
||||||
** TODO An enum member has only a keyword spelling
|
** CANCELLED Sugar for defclass, defgeneric, defmulti and defmethod
|
||||||
=:north= works, =Dir.north= is an unknown name, while a data case is
|
CLOSED: [2026-09-26]
|
||||||
=Shape.Rect=. Proposal: accept =Dir.north= as the member.
|
The fallback, =defmethod(area, point, [p]):=, reads well enough.
|
||||||
|
|
||||||
** TODO Checker and runtime messages print types and fixes in parens
|
|
||||||
In a .fln file, =(Fn [A] R)=, =(Option i32)=, =(get 0 :body)=, =(/ x 0)= and most
|
|
||||||
usage hints still print paren syntax; a handful now print .fln spellings.
|
|
||||||
Proposal: =Types.to_string= and the hints take the syntax of the location's file.
|
|
||||||
|
|
||||||
** TODO flan convert separates adjacent one-line globals with blank lines
|
|
||||||
=once a: i32= on consecutive lines come back one blank line apart.
|
|
||||||
|
|
||||||
** TODO A bad map key type is followed by unknown-name errors for its parts
|
|
||||||
=map-new([const u8], i32)= reports the key, then "unknown name const" and
|
|
||||||
"unknown name i32" — in both syntaxes.
|
|
||||||
|
|
||||||
** TODO The shims in sand.flan can go
|
** TODO The shims in sand.flan can go
|
||||||
=sand.flan= defines =dyn->f64= and =dyn->u32=, one-line functions whose only job
|
=sand.flan= defines =dyn->f64= and =dyn->u32=, one-line functions whose only job
|
||||||
|
|||||||
387
lib/check.ml
387
lib/check.ml
File diff suppressed because it is too large
Load Diff
@ -363,7 +363,7 @@ and head_text (h : Form.t) =
|
|||||||
| Form.Sym s -> fst (sym h s)
|
| Form.Sym s -> fst (sym h s)
|
||||||
| _ -> at 9 h
|
| _ -> at 9 h
|
||||||
|
|
||||||
and list _f h args =
|
and list f h args =
|
||||||
let call () = (head_text h ^ "(" ^ commas args ^ ")", 9) in
|
let call () = (head_text h ^ "(" ^ commas args ^ ")", 9) in
|
||||||
match h.v, args with
|
match h.v, args with
|
||||||
| Form.Sym "quote", [ x ] -> ("'" ^ Form.to_source x, 10)
|
| Form.Sym "quote", [ x ] -> ("'" ^ Form.to_source x, 10)
|
||||||
@ -418,6 +418,10 @@ and list _f h args =
|
|||||||
if glued then (tt ^ s, 9) else call ()
|
if glued then (tt ^ s, 9) else call ()
|
||||||
| Form.Sym s, [ ({ v = Form.Map _; _ } as m) ] when name_ok s && R.capitalised s ->
|
| Form.Sym s, [ ({ v = Form.Map _; _ } as m) ] when name_ok s && R.capitalised s ->
|
||||||
(s ^ fst (expr m), 9)
|
(s ^ fst (expr m), 9)
|
||||||
|
| Form.Sym "the", _ when (match typed_lambda f with Some (_, [ _ ]) -> true | _ -> false) ->
|
||||||
|
(match typed_lambda f with
|
||||||
|
| Some (head, [ body ]) -> (head ^ " = " ^ unit_text body, 0)
|
||||||
|
| _ -> assert false)
|
||||||
| Form.Sym "fn", [ { v = Form.Vec ps; _ }; body ] when List.for_all sym_param ps ->
|
| Form.Sym "fn", [ { v = Form.Vec ps; _ }; body ] when List.for_all sym_param ps ->
|
||||||
("fn(" ^ commas ps ^ ") = " ^ unit_text body, 0)
|
("fn(" ^ commas ps ^ ") = " ^ unit_text body, 0)
|
||||||
| Form.Sym "if", [ c; a; b ] ->
|
| Form.Sym "if", [ c; a; b ] ->
|
||||||
@ -463,6 +467,26 @@ and assign_text ?(lvl = 0) t v =
|
|||||||
and sym_param (p : Form.t) =
|
and sym_param (p : Form.t) =
|
||||||
match p.v with Form.Sym s -> name_ok s | _ -> false
|
match p.v with Form.Sym s -> name_ok s | _ -> false
|
||||||
|
|
||||||
|
(* [(the (Fn [C dyn] R) (fn [a b] ...))] as [fn(a: C, b) -> R], the typed
|
||||||
|
lambda it reads from; [None] for any other shape. *)
|
||||||
|
and typed_lambda (f : Form.t) =
|
||||||
|
let rec tyt (t : Form.t) =
|
||||||
|
match t.v with
|
||||||
|
| Form.List [ { v = Form.Sym (("Fn" | "CFn") as h); _ }; { v = Form.Vec ps; _ }; r ] ->
|
||||||
|
h ^ "(" ^ String.concat ", " (List.map tyt ps) ^ ") -> " ^ tyt r
|
||||||
|
| _ -> at 9 t
|
||||||
|
in
|
||||||
|
match f.v with
|
||||||
|
| Form.List [ { v = Form.Sym "the"; _ };
|
||||||
|
{ v = Form.List [ { v = Form.Sym "Fn"; _ }; { v = Form.Vec ts; _ }; r ]; _ };
|
||||||
|
{ v = Form.List ({ v = Form.Sym "fn"; _ } :: { v = Form.Vec ps; _ } :: body); _ } ]
|
||||||
|
when List.length ts = List.length ps && List.for_all sym_param ps && body <> [] ->
|
||||||
|
let one (n : Form.t) (t : Form.t) =
|
||||||
|
if is_sym "dyn" t then fst (expr n) else fst (expr n) ^ ": " ^ tyt t
|
||||||
|
in
|
||||||
|
Some ("fn(" ^ String.concat ", " (List.map2 one ps ts) ^ ") -> " ^ tyt r, body)
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
(* A type after [:] or [->]: the function-type arrow at the top, a postfix
|
(* A type after [:] or [->]: the function-type arrow at the top, a postfix
|
||||||
term below it. *)
|
term below it. *)
|
||||||
let rec ty (f : Form.t) =
|
let rec ty (f : Form.t) =
|
||||||
@ -750,6 +774,9 @@ and value_lines n prefix (v : Form.t) =
|
|||||||
else if n + String.length inline <= width then [ ind n ^ inline ]
|
else if n + String.length inline <= width then [ ind n ^ inline ]
|
||||||
else
|
else
|
||||||
match v.v with
|
match v.v with
|
||||||
|
| _ when typed_lambda v <> None ->
|
||||||
|
let head, body = Option.get (typed_lambda v) in
|
||||||
|
[ ind n ^ prefix ^ " = " ^ head ] @ block (n + 2) body
|
||||||
| Form.List ({ v = Form.Sym "fn"; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ as body))
|
| Form.List ({ v = Form.Sym "fn"; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ as body))
|
||||||
when List.for_all sym_param ps ->
|
when List.for_all sym_param ps ->
|
||||||
[ ind n ^ prefix ^ " = fn(" ^ commas ps ^ ")" ] @ block (n + 2) body
|
[ ind n ^ prefix ^ " = fn(" ^ commas ps ^ ")" ] @ block (n + 2) body
|
||||||
@ -894,8 +921,14 @@ and sugar n (f : Form.t) : string list option =
|
|||||||
match c.v with
|
match c.v with
|
||||||
| Form.List ({ v = Form.Sym r; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ as b))
|
| Form.List ({ v = Form.Sym r; _ } :: { v = Form.Vec ps; _ } :: (_ :: _ as b))
|
||||||
when def_name r ->
|
when def_name r ->
|
||||||
|
let report, b =
|
||||||
|
match b with
|
||||||
|
| { v = Form.Kw "report"; _ } :: ({ v = Form.Str _; _ } as t) :: (_ :: _ as rest) ->
|
||||||
|
(" " ^ fst (expr t), rest)
|
||||||
|
| _ -> ("", b)
|
||||||
|
in
|
||||||
Option.map
|
Option.map
|
||||||
(fun pt -> (i ^ "restart " ^ r ^ "(" ^ pt ^ ")") :: block (n + 2) b)
|
(fun pt -> (i ^ "restart " ^ r ^ "(" ^ pt ^ ")" ^ report) :: block (n + 2) b)
|
||||||
(params_text ps)
|
(params_text ps)
|
||||||
| _ -> None
|
| _ -> None
|
||||||
in
|
in
|
||||||
@ -1026,7 +1059,8 @@ and let_lines n prs body =
|
|||||||
(* [(let [x (the T v)])] is [let x: T = v]. *)
|
(* [(let [x (the T v)])] is [let x: T = v]. *)
|
||||||
let bind ((t : Form.t), (v : Form.t)) =
|
let bind ((t : Form.t), (v : Form.t)) =
|
||||||
match t.v, v.v with
|
match t.v, v.v with
|
||||||
| Form.Sym x, Form.List [ { v = Form.Sym "the"; _ }; ty_; w ] when def_name x ->
|
| Form.Sym x, Form.List [ { v = Form.Sym "the"; _ }; ty_; w ]
|
||||||
|
when def_name x && typed_lambda v = None ->
|
||||||
("let " ^ x ^ ": " ^ ty ty_, w)
|
("let " ^ x ^ ": " ^ ty ty_, w)
|
||||||
| _ -> ("let " ^ guard (at 8 t), v)
|
| _ -> ("let " ^ guard (at 8 t), v)
|
||||||
in
|
in
|
||||||
@ -1063,11 +1097,11 @@ let program ?source ?macros:m (fs : Form.t list) : string =
|
|||||||
in
|
in
|
||||||
let rec go = function
|
let rec go = function
|
||||||
| [] -> []
|
| [] -> []
|
||||||
| [ x ] -> [ top x ]
|
| [ x ] -> [ (x, top x) ]
|
||||||
| x :: rest -> top (if let_sugar x then in_do x else x) :: go rest
|
| x :: rest -> (x, top (if let_sugar x then in_do x else x)) :: go rest
|
||||||
in
|
in
|
||||||
let text =
|
let text =
|
||||||
try String.concat "\n\n" (go fs) ^ "\n"
|
try Source_text.join_top (go fs) ^ "\n"
|
||||||
with e -> spelling := (fun _ -> None); inside := (fun _ -> false); raise e
|
with e -> spelling := (fun _ -> None); inside := (fun _ -> false); raise e
|
||||||
in
|
in
|
||||||
spelling := (fun _ -> None);
|
spelling := (fun _ -> None);
|
||||||
|
|||||||
@ -386,6 +386,9 @@ let layout ?(snippet = false) ?(base = 1) ?indent (toks : token list) : token ar
|
|||||||
|
|
||||||
type p = { toks : token array; mutable i : int }
|
type p = { toks : token array; mutable i : int }
|
||||||
|
|
||||||
|
(* Set below [params] and [ty], which the expression parser comes before. *)
|
||||||
|
let typed_fn_expr : (p -> Form.t * int) ref = ref (fun _ -> assert false)
|
||||||
|
|
||||||
let peek p = p.toks.(p.i)
|
let peek p = p.toks.(p.i)
|
||||||
let peek_at p k = p.toks.(min (p.i + k) (Array.length p.toks - 1))
|
let peek_at p k = p.toks.(min (p.i + k) (Array.length p.toks - 1))
|
||||||
let advance p =
|
let advance p =
|
||||||
@ -785,6 +788,7 @@ and inline_stmt p : Form.t =
|
|||||||
(* [fn(a, b) = body] is a lambda; [fn(...)] followed by anything else is the
|
(* [fn(a, b) = body] is a lambda; [fn(...)] followed by anything else is the
|
||||||
fallback call spelling of [(fn ...)]. *)
|
fallback call spelling of [(fn ...)]. *)
|
||||||
and fn_expr p =
|
and fn_expr p =
|
||||||
|
if typed_lambda p then !typed_fn_expr p else
|
||||||
let t = advance p in
|
let t = advance p in
|
||||||
let lp = advance p in
|
let lp = advance p in
|
||||||
let args = items p RP lp.loc ~what:"parameters" in
|
let args = items p RP lp.loc ~what:"parameters" in
|
||||||
@ -801,6 +805,21 @@ and fn_expr p =
|
|||||||
0)
|
0)
|
||||||
| _ -> (mk p t.loc (Form.List (sym t.loc "fn" :: args)), 9)
|
| _ -> (mk p t.loc (Form.List (sym t.loc "fn" :: args)), 9)
|
||||||
|
|
||||||
|
(* Whether the [fn(] at point has a [:] among its parameters or a [->]
|
||||||
|
after them: a lambda that states its types. *)
|
||||||
|
and typed_lambda p =
|
||||||
|
let rec go k depth =
|
||||||
|
let t = peek_at p k in
|
||||||
|
match t.tok with
|
||||||
|
| EOF -> false
|
||||||
|
| COLON when depth = 1 -> true
|
||||||
|
| LP | LB | LC -> go (k + 1) (depth + 1)
|
||||||
|
| RP when depth = 1 -> (peek_at p (k + 1)).tok = NAME "->"
|
||||||
|
| RP | RB | RC -> go (k + 1) (depth - 1)
|
||||||
|
| _ -> go (k + 1) depth
|
||||||
|
in
|
||||||
|
go 1 0
|
||||||
|
|
||||||
and span_of_list l args =
|
and span_of_list l args =
|
||||||
match List.rev args with
|
match List.rev args with
|
||||||
| [] -> l
|
| [] -> l
|
||||||
@ -1068,6 +1087,49 @@ let params p (lp : token) =
|
|||||||
in
|
in
|
||||||
go []
|
go []
|
||||||
|
|
||||||
|
(* [fn(a: C, b) -> R = body] is [(the (Fn [C dyn] R) (fn [a b] body))]: the
|
||||||
|
paren [fn] takes its parameters' types from where it is written, and [the]
|
||||||
|
is the form that says what a value is, as in [let x: T = v]. An untyped
|
||||||
|
parameter is dyn, as in a definition, and the return type is required. A
|
||||||
|
block body is added by [lambda_block]. *)
|
||||||
|
let () = typed_fn_expr := fun p ->
|
||||||
|
let t = advance p in
|
||||||
|
let lp = advance p in
|
||||||
|
let ps = params p lp in
|
||||||
|
let rec split = function
|
||||||
|
| n :: ty :: rest -> let ns, ts = split rest in (n :: ns, ty :: ts)
|
||||||
|
| _ -> ([], [])
|
||||||
|
in
|
||||||
|
let names, tys = split ps in
|
||||||
|
let r =
|
||||||
|
match (peek p).tok with
|
||||||
|
| NAME "->" -> ignore (advance p); ty p
|
||||||
|
| _ ->
|
||||||
|
failk "lambda-return" (where_ p)
|
||||||
|
"a lambda that states its parameters' types states its return type \
|
||||||
|
too: fn(%s) -> R = value"
|
||||||
|
(String.concat ", "
|
||||||
|
(List.map2 (fun n (ty : Form.t) ->
|
||||||
|
if ty.v = Form.Sym "dyn" then text_of n else text_of n ^ ": " ^ text_of ty)
|
||||||
|
names tys))
|
||||||
|
in
|
||||||
|
let fty = mk p lp.loc (Form.List [ sym t.loc "Fn"; Form.make (Form.Vec tys) lp.loc; r ]) in
|
||||||
|
let vec = Form.make (Form.Vec names) lp.loc in
|
||||||
|
let wrap body =
|
||||||
|
mk p t.loc (Form.List [ sym t.loc "the"; fty;
|
||||||
|
mk p t.loc (Form.List (sym t.loc "fn" :: vec :: body)) ])
|
||||||
|
in
|
||||||
|
match (peek p).tok with
|
||||||
|
| NAME "=" ->
|
||||||
|
ignore (advance p);
|
||||||
|
let i0 = p.i and t0 = peek p in
|
||||||
|
let body, _ = expr p in
|
||||||
|
(wrap [ unit_slot p i0 t0 body ], 0)
|
||||||
|
| NEWLINE when (peek_at p 1).tok = INDENT -> (wrap [], 0)
|
||||||
|
| _ ->
|
||||||
|
failk "lambda-body" (where_ p)
|
||||||
|
"a lambda's body follows = on its line, or is the block under it"
|
||||||
|
|
||||||
let rec stmts (s : st) : Form.t list =
|
let rec stmts (s : st) : Form.t list =
|
||||||
let p = s.p in
|
let p = s.p in
|
||||||
match (peek p).tok with
|
match (peek p).tok with
|
||||||
@ -1143,6 +1205,15 @@ and then_on_line p =
|
|||||||
|
|
||||||
and lambda_block ?(block_ok = false) (s : st) (e : Form.t) ~after =
|
and lambda_block ?(block_ok = false) (s : st) (e : Form.t) ~after =
|
||||||
let p = s.p in
|
let p = s.p in
|
||||||
|
match e.v with
|
||||||
|
(* A typed lambda waiting for its block, from [typed_fn_expr]. *)
|
||||||
|
| Form.List [ ({ v = Form.Sym "the"; _ } as th); fty;
|
||||||
|
({ v = Form.List [ ({ v = Form.Sym "fn"; _ } as fh); ({ v = Form.Vec _; _ } as vec) ]; _ } as fn_) ]
|
||||||
|
when (peek p).tok = NEWLINE && (peek_at p 1).tok = INDENT ->
|
||||||
|
ignore (advance p);
|
||||||
|
let body = block s ~after in
|
||||||
|
mk p e.loc (Form.List [ th; fty; { fn_ with v = Form.List (fh :: vec :: body) } ])
|
||||||
|
| _ ->
|
||||||
if is_lambda_candidate e && (last p).tok = RP && (peek p).tok = NEWLINE
|
if is_lambda_candidate e && (last p).tok = RP && (peek p).tok = NEWLINE
|
||||||
&& (peek_at p 1).tok = INDENT
|
&& (peek_at p 1).tok = INDENT
|
||||||
then begin
|
then begin
|
||||||
@ -1216,11 +1287,9 @@ and stmt (s : st) : Form.t =
|
|||||||
| NAME w when header_follow p w -> header s w
|
| NAME w when header_follow p w -> header s w
|
||||||
| NAME (("else" | "elif") as w) when one_line_if_above p t ->
|
| NAME (("else" | "elif") as w) when one_line_if_above p t ->
|
||||||
failk "orphan-else" t.loc
|
failk "orphan-else" t.loc
|
||||||
"the if above is a one-line if, which ends with its line, so this %s \
|
"this %s is not at the column of the one-line if above it. An else or \
|
||||||
has no if to belong to. Keep the one-line form on one line:\n\n\
|
elif that continues a one-line if goes at the if's column:\n\n\
|
||||||
\ if c then a else b\n\n\
|
\ if c then a\n else b"
|
||||||
or give each branch a block:\n\n\
|
|
||||||
\ if c\n a\n else\n b"
|
|
||||||
w
|
w
|
||||||
| NAME (("else" | "elif") as w) ->
|
| NAME (("else" | "elif") as w) ->
|
||||||
failk "orphan-else" t.loc
|
failk "orphan-else" t.loc
|
||||||
@ -1455,72 +1524,88 @@ and header (s : st) w : Form.t =
|
|||||||
form [ alias; path ]
|
form [ alias; path ]
|
||||||
| "if" ->
|
| "if" ->
|
||||||
let c, _ = binary p 1 in
|
let c, _ = binary p 1 in
|
||||||
|
(* The elif and else clauses at the if's column, then the whole form.
|
||||||
|
[oneline] when the if was [if c then a]: its clauses may then be
|
||||||
|
one-line too, [elif c then x] and [else y], or take blocks. *)
|
||||||
|
let clauses ~oneline body =
|
||||||
|
let rec elifs acc =
|
||||||
|
match (peek p).tok with
|
||||||
|
| NAME "elif" ->
|
||||||
|
ignore (advance p);
|
||||||
|
let c, _ = binary p 1 in
|
||||||
|
(match (peek p).tok with
|
||||||
|
| NAME "then" when oneline ->
|
||||||
|
ignore (advance p);
|
||||||
|
let x = inline_stmt p in
|
||||||
|
expect_eol p ~after:(text_of x);
|
||||||
|
elifs ((c, [ x ]) :: acc)
|
||||||
|
| NAME "then" ->
|
||||||
|
failk "elif-then" (peek p).loc
|
||||||
|
"elif takes its block on the indented lines under it, with no \
|
||||||
|
then. Put the branch on the next line, indented"
|
||||||
|
| _ ->
|
||||||
|
expect_line_end p ~after:("elif " ^ text_of c);
|
||||||
|
let b = block s ~after:"elif" in
|
||||||
|
elifs ((c, b) :: acc))
|
||||||
|
| _ -> List.rev acc
|
||||||
|
in
|
||||||
|
let els_ = elifs [] in
|
||||||
|
let else_ =
|
||||||
|
match (peek p).tok with
|
||||||
|
| NAME "else" ->
|
||||||
|
let et = advance p in
|
||||||
|
(match (peek p).tok with
|
||||||
|
| NEWLINE -> ignore (advance p); Some (et.loc, block s ~after:"else")
|
||||||
|
| NAME "if" when not oneline ->
|
||||||
|
failk "else-if" (where_ p)
|
||||||
|
"else takes its block on the lines under it. For another test \
|
||||||
|
at this level, write elif c"
|
||||||
|
| _ when oneline ->
|
||||||
|
let x = inline_stmt p in
|
||||||
|
expect_eol p ~after:(text_of x);
|
||||||
|
Some (et.loc, [ x ])
|
||||||
|
| _ -> stray p ~after:"else")
|
||||||
|
| _ -> None
|
||||||
|
in
|
||||||
|
match els_, else_ with
|
||||||
|
| [], None -> named "when" (c :: body)
|
||||||
|
| [], Some (el, e) -> form [ c; blk s l0 body; blk s el e ]
|
||||||
|
| _ ->
|
||||||
|
let pairs =
|
||||||
|
List.concat_map (fun (c, b) -> [ c; blk s c.Form.loc b ]) ((c, body) :: els_)
|
||||||
|
in
|
||||||
|
let tail =
|
||||||
|
match else_ with
|
||||||
|
| Some (el, e) -> [ Form.make (Form.Kw "else") el; blk s el e ]
|
||||||
|
| None -> []
|
||||||
|
in
|
||||||
|
named "cond" (pairs @ tail)
|
||||||
|
in
|
||||||
(match (peek p).tok with
|
(match (peek p).tok with
|
||||||
| NAME "then" ->
|
| NAME "then" ->
|
||||||
ignore (advance p);
|
ignore (advance p);
|
||||||
let a = inline_stmt p in
|
let a = inline_stmt p in
|
||||||
let f =
|
(match (peek p).tok with
|
||||||
match (peek p).tok with
|
| NAME "else" ->
|
||||||
| NAME "else" ->
|
ignore (advance p);
|
||||||
ignore (advance p);
|
let b = inline_stmt p in
|
||||||
let b = inline_stmt p in
|
let f = form [ c; a; b ] in
|
||||||
form [ c; a; b ]
|
expect_eol p ~after:(text_of f);
|
||||||
| NAME "elif" ->
|
f
|
||||||
failk "one-line-elif" (peek p).loc
|
| NAME "elif" ->
|
||||||
"a one-line if has then and else and no elif. Chain another if \
|
failk "one-line-elif" (peek p).loc
|
||||||
after the else — if a then x else if b then y else z — or write \
|
"a one-line if has then and else and no elif. Chain another if \
|
||||||
the if over several lines, where elif goes"
|
after the else — if a then x else if b then y else z — or write \
|
||||||
| _ -> named "when" [ c; a ]
|
the if over several lines, where elif goes"
|
||||||
in
|
| _ ->
|
||||||
expect_eol p ~after:(text_of f);
|
expect_eol p ~after:(text_of (named "when" [ c; a ]));
|
||||||
f
|
(* An else or elif on the next line, at the if's column,
|
||||||
|
continues it. *)
|
||||||
|
clauses ~oneline:true [ a ])
|
||||||
| _ ->
|
| _ ->
|
||||||
expect_line_end p ~after:("if " ^ text_of c);
|
expect_line_end p ~after:("if " ^ text_of c);
|
||||||
let body = block s ~after:("if " ^ text_of c) in
|
let body = block s ~after:("if " ^ text_of c) in
|
||||||
let rec elifs acc =
|
clauses ~oneline:false body)
|
||||||
match (peek p).tok with
|
|
||||||
| NAME "elif" ->
|
|
||||||
ignore (advance p);
|
|
||||||
let c, _ = binary p 1 in
|
|
||||||
(match (peek p).tok with
|
|
||||||
| NAME "then" ->
|
|
||||||
failk "elif-then" (peek p).loc
|
|
||||||
"elif takes its block on the indented lines under it, with no \
|
|
||||||
then. Put the branch on the next line, indented"
|
|
||||||
| _ -> ());
|
|
||||||
expect_line_end p ~after:("elif " ^ text_of c);
|
|
||||||
let b = block s ~after:"elif" in
|
|
||||||
elifs ((c, b) :: acc)
|
|
||||||
| _ -> List.rev acc
|
|
||||||
in
|
|
||||||
let els_ = elifs [] in
|
|
||||||
let else_ =
|
|
||||||
match (peek p).tok with
|
|
||||||
| NAME "else" ->
|
|
||||||
let et = advance p in
|
|
||||||
(match (peek p).tok with
|
|
||||||
| NEWLINE -> ignore (advance p)
|
|
||||||
| NAME "if" ->
|
|
||||||
failk "else-if" (where_ p)
|
|
||||||
"else takes its block on the lines under it. For another test \
|
|
||||||
at this level, write elif c"
|
|
||||||
| _ -> stray p ~after:"else");
|
|
||||||
Some (et.loc, block s ~after:"else")
|
|
||||||
| _ -> None
|
|
||||||
in
|
|
||||||
(match els_, else_ with
|
|
||||||
| [], None -> named "when" (c :: body)
|
|
||||||
| [], Some (el, e) -> form [ c; blk s l0 body; blk s el e ]
|
|
||||||
| _ ->
|
|
||||||
let pairs =
|
|
||||||
List.concat_map (fun (c, b) -> [ c; blk s c.Form.loc b ]) ((c, body) :: els_)
|
|
||||||
in
|
|
||||||
let tail =
|
|
||||||
match else_ with
|
|
||||||
| Some (el, e) -> [ Form.make (Form.Kw "else") el; blk s el e ]
|
|
||||||
| None -> []
|
|
||||||
in
|
|
||||||
named "cond" (pairs @ tail)))
|
|
||||||
| "while" | "until" ->
|
| "while" | "until" ->
|
||||||
let label =
|
let label =
|
||||||
match (peek p).tok, (peek_at p 1).tok with
|
match (peek p).tok, (peek_at p 1).tok with
|
||||||
@ -1639,10 +1724,19 @@ and header (s : st) w : Form.t =
|
|||||||
let name = name_tok p ~what:"the restart's name" in
|
let name = name_tok p ~what:"the restart's name" in
|
||||||
let lp = glued_lp p ~what:"the restart's parameters in parentheses" in
|
let lp = glued_lp p ~what:"the restart's parameters in parentheses" in
|
||||||
let ps = params p lp in
|
let ps = params p lp in
|
||||||
|
(* [restart name() "text"]: the report the break loop shows,
|
||||||
|
[:report "text"] in the clause. *)
|
||||||
|
let report =
|
||||||
|
match (peek p).tok with
|
||||||
|
| ATOM (Form.Str _ as v) ->
|
||||||
|
let st = advance p in
|
||||||
|
[ Form.make (Form.Kw "report") st.loc; Form.make v st.loc ]
|
||||||
|
| _ -> []
|
||||||
|
in
|
||||||
clause_end p ("restart " ^ text_of name ^ "(...)");
|
clause_end p ("restart " ^ text_of name ^ "(...)");
|
||||||
let b = block s ~after:"restart" in
|
let b = block s ~after:"restart" in
|
||||||
let c =
|
let c =
|
||||||
mk p name.loc (Form.List (name :: Form.make (Form.Vec ps) lp.loc :: b))
|
mk p name.loc (Form.List (name :: Form.make (Form.Vec ps) lp.loc :: (report @ b)))
|
||||||
in
|
in
|
||||||
clauses (c :: acc)
|
clauses (c :: acc)
|
||||||
| _ -> List.rev acc
|
| _ -> List.rev acc
|
||||||
|
|||||||
@ -327,13 +327,13 @@ let program ?source (fs : Form.t list) : string =
|
|||||||
cs
|
cs
|
||||||
in
|
in
|
||||||
let text =
|
let text =
|
||||||
String.concat "\n\n"
|
Source_text.join_top
|
||||||
(List.map
|
(List.map
|
||||||
(fun (f : Form.t) ->
|
(fun (f : Form.t) ->
|
||||||
String.concat "\n"
|
(f, String.concat "\n"
|
||||||
(match layout ~inside spell 0 f with
|
(match layout ~inside spell 0 f with
|
||||||
| first :: rest -> Source_text.tag f.loc.Loc.line first :: rest
|
| first :: rest -> Source_text.tag f.loc.Loc.line first :: rest
|
||||||
| [] -> []))
|
| [] -> [])))
|
||||||
fs)
|
fs)
|
||||||
^ "\n"
|
^ "\n"
|
||||||
in
|
in
|
||||||
|
|||||||
@ -206,3 +206,23 @@ let weave ?(starts = []) (cs : comment list) (text : string) : string =
|
|||||||
else s
|
else s
|
||||||
in
|
in
|
||||||
trim s
|
trim s
|
||||||
|
|
||||||
|
(** Top-level forms' printed texts joined with a blank line between them,
|
||||||
|
except that one-line globals written on adjacent lines stay adjacent. *)
|
||||||
|
let join_top (items : (Form.t * string) list) : string =
|
||||||
|
let global (f : Form.t) =
|
||||||
|
match f.v with
|
||||||
|
| Form.List ({ v = Form.Sym ("def" | "defonce" | "defconst"); _ } :: _) -> true
|
||||||
|
| _ -> false
|
||||||
|
in
|
||||||
|
let rec go = function
|
||||||
|
| [] -> []
|
||||||
|
| [ (_, t) ] -> [ t ]
|
||||||
|
| ((a : Form.t), ta) :: (((b : Form.t), tb) :: _ as rest) ->
|
||||||
|
let tight =
|
||||||
|
global a && global b && (not (String.contains ta '\n'))
|
||||||
|
&& (not (String.contains tb '\n')) && b.loc.Loc.line = a.loc.Loc.eline + 1
|
||||||
|
in
|
||||||
|
ta :: (if tight then "\n" else "\n\n") :: go rest
|
||||||
|
in
|
||||||
|
String.concat "" (go items)
|
||||||
|
|||||||
32
lib/types.ml
32
lib/types.ml
@ -229,6 +229,10 @@ let rec equal a b =
|
|||||||
and then only in how a message spells it. *)
|
and then only in how a message spells it. *)
|
||||||
let display : (string, string) Hashtbl.t = Hashtbl.create 16
|
let display : (string, string) Hashtbl.t = Hashtbl.create 16
|
||||||
|
|
||||||
|
(* The same copies as the template and its arguments, for [spell] to write
|
||||||
|
in either syntax. *)
|
||||||
|
let display_app : (string, string * t list) Hashtbl.t = Hashtbl.create 16
|
||||||
|
|
||||||
(* A struct's name as a printed value's head: its own name, or for a generic
|
(* A struct's name as a printed value's head: its own name, or for a generic
|
||||||
struct's copy the template and its arguments, [Pair i32] — so a value
|
struct's copy the template and its arguments, [Pair i32] — so a value
|
||||||
prints as [(Pair i32 {.a 1 .b 2})], the way its type is written. *)
|
prints as [(Pair i32 {.a 1 .b 2})], the way its type is written. *)
|
||||||
@ -238,7 +242,33 @@ let struct_head n =
|
|||||||
String.sub d 1 (String.length d - 2)
|
String.sub d 1 (String.length d - 2)
|
||||||
| _ -> n
|
| _ -> n
|
||||||
|
|
||||||
let rec to_string = function
|
(* A type as the code it is written in spells it: [(Fn [i32] bool)] in a
|
||||||
|
.flan file, [Fn(i32) -> bool] in a .fln one. Messages use it; a spelling
|
||||||
|
that is a key, a symbol or a runtime string stays [to_string]'s. *)
|
||||||
|
let rec spell ~indented t =
|
||||||
|
if not indented then to_string t
|
||||||
|
else
|
||||||
|
let sp = spell ~indented in
|
||||||
|
let call h args = h ^ "(" ^ String.concat ", " args ^ ")" in
|
||||||
|
match t with
|
||||||
|
| Named n ->
|
||||||
|
(match Hashtbl.find_opt display_app n with
|
||||||
|
| Some (g, args) -> call g (List.map sp args)
|
||||||
|
| None -> to_string t)
|
||||||
|
| Slice (Mut, t) -> "[" ^ sp t ^ "]"
|
||||||
|
| Slice (Const, t) -> "[const " ^ sp t ^ "]"
|
||||||
|
| Array (n, t) -> Printf.sprintf "[%Ld %s]" n (sp t)
|
||||||
|
| LArray (n, t) -> Printf.sprintf "[$%s %s]" n (sp t)
|
||||||
|
| Map (k, v) -> call "Map" [ sp k; sp v ]
|
||||||
|
| Ptr (Mut, t) -> call "Ptr" [ sp t ]
|
||||||
|
| Ptr (Const, t) -> call "Ptr" [ "const " ^ sp t ]
|
||||||
|
| Vec t -> call "Vec" [ sp t ]
|
||||||
|
| Option t -> call "Option" [ sp t ]
|
||||||
|
| Fn (ps, r) -> call "Fn" (List.map sp ps) ^ " -> " ^ sp r
|
||||||
|
| CFn (ps, r) -> call "CFn" (List.map sp ps) ^ " -> " ^ sp r
|
||||||
|
| _ -> to_string t
|
||||||
|
|
||||||
|
and to_string = function
|
||||||
| Int k -> ikind_name k
|
| Int k -> ikind_name k
|
||||||
| Float k -> fkind_name k
|
| Float k -> fkind_name k
|
||||||
| Bool -> "bool"
|
| Bool -> "bool"
|
||||||
|
|||||||
@ -88,9 +88,10 @@ reads as `(rl/with-drawing (rl/clear-background rl/black) (game-draw))`.
|
|||||||
Replacing macros with built-in constructs is **not** part of this work.
|
Replacing macros with built-in constructs is **not** part of this work.
|
||||||
|
|
||||||
**Diagnostics may print paren syntax** during the test drive. `Form.to_string`,
|
**Diagnostics may print paren syntax** during the test drive. `Form.to_string`,
|
||||||
`Types.to_string`, the usage strings in `parse.ml` and `check.ml`, and
|
the usage strings in `parse.ml` and `check.ml`, and `Render` print parens
|
||||||
`Render` all print parens today (inventory in section 5). Fixing that waits on
|
today (inventory in section 5). Types in `check.ml`'s messages do not: they
|
||||||
the author deciding to switch.
|
are spelled by `Types.spell`, in the syntax of the code the message is about
|
||||||
|
(section 3, item 9).
|
||||||
|
|
||||||
## 2. Proposed (confirm before building the piece it governs)
|
## 2. Proposed (confirm before building the piece it governs)
|
||||||
|
|
||||||
@ -201,7 +202,10 @@ Each item: the proposal, then the reason in one line.
|
|||||||
- **`if`/`elif`/`else`.** `else` and `elif` sit at the `if`'s column. No `elif`
|
- **`if`/`elif`/`else`.** `else` and `elif` sit at the `if`'s column. No `elif`
|
||||||
reads as `if` (with else) or `when` (without); with `elif` it reads as `cond`.
|
reads as `if` (with else) or `when` (without); with `elif` it reads as `cond`.
|
||||||
One-line form: `if c then a else b`, for use in a `let`. **Built** (a block
|
One-line form: `if c then a else b`, for use in a `let`. **Built** (a block
|
||||||
of one line is that line; of more, `(do …)`).
|
of one line is that line; of more, `(do …)`). An `else` or `elif` on the
|
||||||
|
line after a one-line `if c then a`, at its column, continues it (section
|
||||||
|
3, item 6); each such clause is one-line (`elif c then x`, `else y`) or
|
||||||
|
takes a block.
|
||||||
- **`while c`, `until c`**, optional label first: `while :outer c`. **Built.**
|
- **`while c`, `until c`**, optional label first: `while :outer c`. **Built.**
|
||||||
- **`for i in range(n)`**, `range(a, b)`, `range(a, b, step)` read as
|
- **`for i in range(n)`**, `range(a, b)`, `range(a, b, step)` read as
|
||||||
`dotimes`. `range` here is syntax, not a function. `..` is avoided because
|
`dotimes`. `range` here is syntax, not a function. `..` is avoided because
|
||||||
@ -253,14 +257,17 @@ Each item: the proposal, then the reason in one line.
|
|||||||
v * 2
|
v * 2
|
||||||
```
|
```
|
||||||
`handler-bind` takes the same `on` clauses; the reader moves them in front of
|
`handler-bind` takes the same `on` clauses; the reader moves them in front of
|
||||||
the body, where the form wants them. **Built.**
|
the body, where the form wants them. **Built.** A restart's report text goes
|
||||||
|
on its header, `restart retry() "Try the load again"`, and reads
|
||||||
|
`(retry [] :report "Try the load again" …)`.
|
||||||
- **Unit:** `()` as a statement reads `(do)`; in a type it is `()`. **Built**;
|
- **Unit:** `()` as a statement reads `(do)`; in a type it is `()`. **Built**;
|
||||||
inside an expression `()` stays `()`, and the printer writes a lone `()`
|
inside an expression `()` stays `()`, and the printer writes a lone `()`
|
||||||
statement as `(())`. A bare `()` in a one-line body slot (`fn f() -> () = ()`,
|
statement as `(())`. A bare `()` in a one-line body slot (`fn f() -> () = ()`,
|
||||||
`_ -> ()`, `fn() = ()`, `then ()`) is a statement too, and reads `(do)`.
|
`_ -> ()`, `fn() = ()`, `then ()`) is a statement too, and reads `(do)`.
|
||||||
- **Lambda:** `fn(i, j) = i * 10 + j`, or `fn(i, j)` plus a block. **Built**;
|
- **Lambda:** `fn(i, j) = i * 10 + j`, or `fn(i, j)` plus a block. **Built**;
|
||||||
its parameters are bare names, as `(fn [i j] …)` wants, with no `dyn`.
|
its parameters are bare names, as `(fn [i j] …)` wants, with no `dyn`.
|
||||||
`fn(…)` followed by anything else is the fallback call.
|
`fn(…)` followed by anything else is the fallback call. A lambda may state
|
||||||
|
its types, `fn(a: C, b) -> bool = …` or plus a block (section 3, item 7).
|
||||||
|
|
||||||
### Definitions
|
### Definitions
|
||||||
|
|
||||||
@ -274,10 +281,14 @@ Each item: the proposal, then the reason in one line.
|
|||||||
- `struct Cell` with a `name: Type` line per field. `data Shape` with a line per
|
- `struct Cell` with a `name: Type` line per field. `data Shape` with a line per
|
||||||
case: `Circle(r: f32)`, `Empty`. `enum K` with `lo = -1`, `mid`. `union U` like
|
case: `Circle(r: f32)`, `Empty`. `enum K` with `lo = -1`, `mid`. `union U` like
|
||||||
`struct`. **Built** (an untyped field is `dyn`; `Empty()` is `(Empty [])`).
|
`struct`. **Built** (an untyped field is `dyn`; `Empty()` is `(Empty [])`).
|
||||||
|
A member is `:mid` or `K.mid`, in a value and in a match arm, in both
|
||||||
|
syntaxes (section 3, item 8).
|
||||||
- `import rl "vendor:raylib"`. **Built.**
|
- `import rl "vendor:raylib"`. **Built.**
|
||||||
- **Every other form uses the fallback** (next item) until someone asks for
|
- **Every other form uses the fallback** (next item) until someone asks for
|
||||||
sugar: `defclass`, `defgeneric`, `defmulti`, `defmethod`, `declare`,
|
sugar: `defclass`, `defgeneric`, `defmulti`, `defmethod`, `declare`,
|
||||||
`declare-c`, `defalias`, `defmacro`, `loop`/`recur`, `array-fill`. **Built.**
|
`declare-c`, `defalias`, `defmacro`, `loop`/`recur`, `array-fill`. **Built.**
|
||||||
|
The class forms keep the fallback for good (2026-09-26):
|
||||||
|
`defmethod(area, point, [p]):` reads well enough.
|
||||||
|
|
||||||
### The fallback
|
### The fallback
|
||||||
|
|
||||||
@ -341,6 +352,27 @@ after it, so `~name(x)` is `((unquote name) x)`, and `~(f(x))` unquotes a call.
|
|||||||
omitted. `_` in type position means "fill this in" in Rust and OCaml too,
|
omitted. `_` in type position means "fill this in" in Rust and OCaml too,
|
||||||
and no type can be named `_`.
|
and no type can be named `_`.
|
||||||
|
|
||||||
|
Settled 2026-09-26, after writing programs by hand (`test/syntax/handwritten/`):
|
||||||
|
|
||||||
|
6. **A one-line if continues on the next line.** `if c then a` followed by
|
||||||
|
`else b` (or `elif c2 then d`, or either with a block) at the if's column
|
||||||
|
is one if. An `else` left of that column is refused.
|
||||||
|
7. **Typed lambdas.** `fn(a: C, b) -> R = body`, or plus a block, reads
|
||||||
|
`(the (Fn [C dyn] R) (fn [a b] body))`: the paren `fn` has no typed
|
||||||
|
parameters, and `the` is how a value states its type, as in
|
||||||
|
`let x: T = v`. An untyped parameter is `dyn`; the return type is
|
||||||
|
required. Where a `CFn` of the same signature is wanted, the literal is
|
||||||
|
that `CFn`. The printer writes that form back as the typed lambda.
|
||||||
|
8. **`Dir.north` is the enum member `:north`**, in a value and in a match
|
||||||
|
pattern, in both syntaxes. `:north` stays.
|
||||||
|
9. **Types in messages follow the code's syntax.** `Types.spell ~indented` is
|
||||||
|
the one printer, `Fn(A) -> R`, `Option(i32)`, `Small(4, i32)` for a .fln
|
||||||
|
location and `(Fn [A] R)` for a .flan one; `Types.to_string` stays the
|
||||||
|
spelling for keys, symbols and runtime strings. Hard-coded code in a hint
|
||||||
|
is written per message.
|
||||||
|
10. **`flan convert` keeps adjacent one-line globals adjacent**, in both
|
||||||
|
directions.
|
||||||
|
|
||||||
## 4. Build order
|
## 4. Build order
|
||||||
|
|
||||||
Each step lands on its own, with `dune test --root .` green.
|
Each step lands on its own, with `dune test --root .` green.
|
||||||
|
|||||||
@ -26,7 +26,7 @@ fn flush(field: Ptr(Vec(u8)), row: Ptr(Vec(string))) -> ()
|
|||||||
fn parse-line(line: [const u8]) -> Vec(string)
|
fn parse-line(line: [const u8]) -> Vec(string)
|
||||||
let row = vec-new(string)
|
let row = vec-new(string)
|
||||||
let field = vec-new(u8)
|
let field = vec-new(u8)
|
||||||
let state: State = :start
|
let state = State.start
|
||||||
for i in range(length(line))
|
for i in range(length(line))
|
||||||
let c = line[i]
|
let c = line[i]
|
||||||
match state
|
match state
|
||||||
@ -38,7 +38,7 @@ fn parse-line(line: [const u8]) -> Vec(string)
|
|||||||
else
|
else
|
||||||
push(field, c)
|
push(field, c)
|
||||||
state = :bare
|
state = :bare
|
||||||
:bare ->
|
State.bare ->
|
||||||
if c == separator
|
if c == separator
|
||||||
flush(addr(field), addr(row))
|
flush(addr(field), addr(row))
|
||||||
state = :start
|
state = :start
|
||||||
|
|||||||
@ -18,10 +18,8 @@ const red-time = 3
|
|||||||
fn next(l: Light, pressed: bool) -> Light
|
fn next(l: Light, pressed: bool) -> Light
|
||||||
match l
|
match l
|
||||||
Green(left) ->
|
Green(left) ->
|
||||||
if left > 1 and not pressed
|
if left > 1 and not pressed then Light.Green{.left left - 1}
|
||||||
Light.Green{.left left - 1}
|
else Light.Yellow{.left yellow-time, .walk pressed}
|
||||||
else
|
|
||||||
Light.Yellow{.left yellow-time, .walk pressed}
|
|
||||||
Yellow(left, walk) ->
|
Yellow(left, walk) ->
|
||||||
if left > 1
|
if left > 1
|
||||||
Light.Yellow{.left left - 1, .walk walk}
|
Light.Yellow{.left left - 1, .walk walk}
|
||||||
@ -53,9 +51,7 @@ fn run(ticks: i32, presses: [const i32], fault-at: i32) -> ()
|
|||||||
l = restart-case
|
l = restart-case
|
||||||
error(Fault{.tick t})
|
error(Fault{.tick t})
|
||||||
l
|
l
|
||||||
restart reset()
|
restart reset() "Put the light back to red and carry on"
|
||||||
:report
|
|
||||||
"Put the light back to red and carry on"
|
|
||||||
Light.Red{.left red-time, .walk false}
|
Light.Red{.left red-time, .walk false}
|
||||||
restart flash()
|
restart flash()
|
||||||
Light.Flashing{}
|
Light.Flashing{}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ fn main() -> i32
|
|||||||
let text = "The cat saw the dog. The dog didn't see the cat, but the bird saw both!"
|
let text = "The cat saw the dog. The dog didn't see the cat, but the bird saw both!"
|
||||||
let ws = words(bytes-view(text))
|
let ws = words(bytes-view(text))
|
||||||
let counts = tally(slice(ws))
|
let counts = tally(slice(ws))
|
||||||
let by-count: Fn(Count, Count) -> bool = fn(a, b)
|
let by-count = fn(a: Count, b: Count) -> bool
|
||||||
if a.n != b.n
|
if a.n != b.n
|
||||||
return a.n > b.n
|
return a.n > b.n
|
||||||
bytes<?(a.word, b.word)
|
bytes<?(a.word, b.word)
|
||||||
|
|||||||
@ -592,8 +592,21 @@ let () =
|
|||||||
"indent/lambda-block-in-brackets" "let f = fn(a, b)";
|
"indent/lambda-block-in-brackets" "let f = fn(a, b)";
|
||||||
refuses "an unclosed call swallows the next line" "fn f() -> ()\n push(v, 1\n g()"
|
refuses "an unclosed call swallows the next line" "fn f() -> ()\n push(v, 1\n g()"
|
||||||
"indent/missing-comma" "If the ( on line 2 was meant to close";
|
"indent/missing-comma" "If the ( on line 2 was meant to close";
|
||||||
refuses "else under a one-line if" "if a then b\nelse c"
|
reads "else on the line after a one-line if" "if a then b\nelse c" "(if a b c)";
|
||||||
"indent/orphan-else" "one-line if";
|
reads "elif and else continuing a one-line if"
|
||||||
|
"if a then b\nelif c then d\nelif e\n f()\n g()\nelse\n h()"
|
||||||
|
"(cond a b c d e (do (f) (g)) :else (h))";
|
||||||
|
refuses "else left of a one-line if" "while x\n if a then b\nelse c"
|
||||||
|
"indent/orphan-else" "goes at the if's column";
|
||||||
|
reads "a typed lambda" "f = fn(a: C, b) -> bool = a.n < b"
|
||||||
|
"(set f (the (Fn [C dyn] bool) (fn [a b] (< (.n a) b))))";
|
||||||
|
reads "a typed lambda with a block" "let f = fn(x: i32) -> i32\n let y = x + 1\n y\ng(f)"
|
||||||
|
"(let [f (the (Fn [i32] i32) (fn [x] (let [y (+ x 1)] y)))] (g f))";
|
||||||
|
refuses "a typed lambda states its return type" "f = fn(a: C) = a"
|
||||||
|
"indent/lambda-return" "fn(a: C) -> R = value";
|
||||||
|
reads "a restart's report on its header"
|
||||||
|
"restart-case\n go()\nrestart retry(n: i32) \"Try again\"\n n"
|
||||||
|
"(restart-case (go) (retry [n i32] :report \"Try again\" n))";
|
||||||
reads "a bare () in a body slot does nothing"
|
reads "a bare () in a body slot does nothing"
|
||||||
"fn f() -> () = ()\nfn g(x) -> ()\n match x\n 1 -> h()\n _ -> ()\n k = fn() = ()"
|
"fn f() -> () = ()\nfn g(x) -> ()\n match x\n 1 -> h()\n _ -> ()\n k = fn() = ()"
|
||||||
"(defn f [] () (do))\n(defn g [x dyn] () (match x 1 (h) _ (do)) (set k (fn [] (do))))";
|
"(defn f [] () (do))\n(defn g [x dyn] () (match x 1 (h) _ (do)) (set k (fn [] (do))))";
|
||||||
@ -763,6 +776,18 @@ let () =
|
|||||||
"(defn f [] () (if (> a 1) (let [k 2] (g k))))" " if(a > 1):\n let k = 2";
|
"(defn f [] () (if (> a 1) (let [k 2] (g k))))" " if(a > 1):\n let k = 2";
|
||||||
prints "and inside or keeps its parentheses"
|
prints "and inside or keeps its parentheses"
|
||||||
"(defn f [a bool b bool c bool] bool (or (and a b) c))" "= (a and b) or c";
|
"(defn f [a bool b bool c bool] bool (or (and a b) c))" "= (a and b) or c";
|
||||||
|
prints "a typed lambda prints as one"
|
||||||
|
"(defn f [] () (let [g (the (Fn [C] bool) (fn [c] (> (.n c) 3)))] (h g)))"
|
||||||
|
"let g = fn(c: C) -> bool = c.n > 3";
|
||||||
|
prints "a restart's report goes on its header"
|
||||||
|
"(defn f [] i32 (restart-case (go) (retry [] :report \"Try again\" 7)))"
|
||||||
|
"restart retry() \"Try again\"\n 7";
|
||||||
|
prints "adjacent one-line globals stay adjacent"
|
||||||
|
"(defonce a i32)\n(def b i32 2)\n\n(defconst c 3)\n"
|
||||||
|
"once a: i32\ndef b: i32 = 2\n\nconst c = 3";
|
||||||
|
back "adjacent one-line globals stay adjacent in parens"
|
||||||
|
"once a: i32\ndef b: i32 = 2\n\nconst c = 3\n"
|
||||||
|
"(defonce a i32)\n(def b i32 2)\n\n(defconst c 3)";
|
||||||
prints "a field of a field chains" "(defn f [] () (g (.count (.x w))))" "g(w.x.count)";
|
prints "a field of a field chains" "(defn f [] () (g (.count (.x w))))" "g(w.x.count)";
|
||||||
prints "an else-if chain on one line"
|
prints "an else-if chain on one line"
|
||||||
"(defn f [r] dyn (if (> r 7) :rich (if (> r 4) :fair :poor)))"
|
"(defn f [r] dyn (if (> r 7) :rich (if (> r 4) :fair :poor)))"
|
||||||
@ -983,6 +1008,31 @@ let () =
|
|||||||
[ "write ++(x) or x += 1" ];
|
[ "write ++(x) or x += 1" ];
|
||||||
refused "plusplus-global.fln" "once g = 0\n\nfn main() -> i32\n g--\n 0\n"
|
refused "plusplus-global.fln" "once g = 0\n\nfn main() -> i32\n g--\n 0\n"
|
||||||
[ "write --(g) or g -= 1" ];
|
[ "write --(g) or g -= 1" ];
|
||||||
|
(* Types in a message are in the syntax of the code it is about. *)
|
||||||
|
refused "types.fln" "fn g(x: Option(i32)) -> i32 = 0\n\nfn main() -> i32\n let v = vec-new(i32)\n g(v)\n"
|
||||||
|
[ "expected Option(i32), found Vec(i32)" ];
|
||||||
|
refused "types.flan" "(defn g [x (Option i32)] i32 0)\n(defn main [] i32 (let [v (vec-new i32)] (g v)))\n"
|
||||||
|
[ "expected (Option i32), found (Vec i32)" ];
|
||||||
|
refused "fn-field.fln" "struct R\n f: Fn(i32) -> bool\n\nfn main() -> i32 = 0\n"
|
||||||
|
[ "cannot be Fn(i32) -> bool"; "store a CFn(i32) -> bool" ];
|
||||||
|
refused "generic-struct.fln"
|
||||||
|
"struct Small\n items: [$n $t]\n\nfn main() -> i32\n let s: Small(4, i32) = zeroed()\n let q: i32 = s\n 0\n"
|
||||||
|
[ "found Small(4, i32)" ];
|
||||||
|
(* Dir.north is the member :north. *)
|
||||||
|
checks "enum-qualified.fln"
|
||||||
|
("enum Dir\n north\n south\n\nfn name(d: Dir) -> i32\n match d\n Dir.north -> 1\n :south -> 2\n\n"
|
||||||
|
^ "fn main() -> i32\n let d = Dir.north\n let e: Dir = Dir.south\n name(d) + name(e)\n");
|
||||||
|
refused "enum-qualified-miss.fln"
|
||||||
|
"enum Dir\n north\n\nfn f(d: Dir) -> i32\n match d\n Dir.west -> 1\n _ -> 0\n\nfn main() -> i32 = 0\n"
|
||||||
|
[ "Dir has no member west — it has Dir.north" ];
|
||||||
|
(* A bad key type is one error, not three. *)
|
||||||
|
refused "map-key.fln" "fn main() -> i32\n let m = map-new([const u8], i32)\n 0\n"
|
||||||
|
[ "[const u8] is not a map key" ];
|
||||||
|
(match Front.checked (Filename.concat scratch "map-key.fln") with
|
||||||
|
| exception Loc.Errors (_ :: _ :: _ as ds) ->
|
||||||
|
fail "map-key.fln: %d errors, wanted one" (List.length ds)
|
||||||
|
| exception _ -> ()
|
||||||
|
| _ -> ());
|
||||||
refused "defvar.fln" "defvar(x, 1)\n\nfn main() -> i32 = 0\n"
|
refused "defvar.fln" "defvar(x, 1)\n\nfn main() -> i32 = 0\n"
|
||||||
[ "once x = 1 initialises once"; "def x = 1 re-initialises" ]
|
[ "once x = 1 initialises once"; "def x = 1 re-initialises" ]
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user