The .fln reader takes loop and recur as names wherever the Lisp loop's spellings are not written, and the convert refusal lists its lines in a sentence of their own.

This commit is contained in:
Joseph Ferano 2026-09-26 10:43:10 +07:00
parent fc463cf320
commit 17a7afb297
3 changed files with 28 additions and 10 deletions

View File

@ -1355,7 +1355,7 @@ let refuse_loops (fs : Form.t list) =
let lines = List.sort_uniq compare (List.map (fun (f : Form.t) -> f.loc.Loc.line) uses) in
let notes = List.map (fun (f : Form.t) -> Loc.note f.loc (word f ^ " is here")) rest in
Loc.failk ~notes "convert/no-loop" first.loc
"this file uses loop or recur on line%s %s, and the indented syntax \
"this file uses loop or recur on line%s %s. The indented syntax \
has neither. Rewrite each one in the .flan file as a while or until \
over let variables it changes, then convert again:\n\n\
\ (let [i 0 total 0]\n\
@ -1363,7 +1363,10 @@ let refuse_loops (fs : Form.t list) =
\ (set total (+ total i))\n\
\ (set i (+ i 1))))"
(if List.length lines = 1 then "" else "s")
(String.concat ", " (List.map string_of_int lines))
(match List.rev_map string_of_int lines with
| last :: (_ :: _ as before) ->
String.concat ", " (List.rev before) ^ " and " ^ last
| ls -> String.concat "" ls)
(** A whole file: top-level forms with a blank line between them. [macros]
is [Body_macros.table] of the file; without it, the prelude's and the

View File

@ -776,13 +776,20 @@ and primary p : Form.t * int =
let glued_lp = nxt.tok = LP && not nxt.sp in
if s = "if" && nxt.sp && starts_value nxt.tok then if_expr p
else if s = "fn" && glued_lp then fn_expr p
else if (s = "loop" || s = "recur")
&& (glued_lp || nxt.tok = COLON
|| (nxt.sp && starts_value nxt.tok
&& (match nxt.tok with
| NAME x -> not (x = "=" || List.mem_assoc x assign_ops || is_op_word x)
| _ -> true))
|| (nxt.tok = NEWLINE && (peek_at p 2).tok = INDENT)) then
(* Only the Lisp loop's spellings are refused here, for a message at the
word: [loop x = a, ...], [loop([...]):], a bare [loop] over a block
where a statement or a let's value starts, and [recur(...)]. Anywhere
else [loop] and [recur] are names; [refuse_loops] catches the rest. *)
else if glued_lp && (s = "loop" || s = "recur") then no_loop l0 s
else if s = "loop"
&& ((nxt.sp
&& (match nxt.tok with NAME x -> not (is_op_word x) | _ -> false)
&& (match (peek_at p 2).tok with NAME "=" | COMMA -> true | _ -> false))
|| (nxt.tok = NEWLINE && (peek_at p 2).tok = INDENT
&& (p.i = 0
|| (match (last p).tok with
| NEWLINE | INDENT | DEDENT | NAME "=" -> true
| _ -> false)))) then
no_loop l0 s
else if is_op_word s then begin
if glued_lp || ends_value nxt.tok then begin

View File

@ -628,6 +628,14 @@ let () =
"indent/no-loop" "loop is not";
refuses "a quoted loop" "f('(loop [i 0] (recur i)))" "indent/no-loop" "loop is not";
reads "loop as a name" "loop = 4" "(set loop 4)";
reads "if over loop" "if loop\n 1" "(when loop 1)";
reads "while over loop" "while loop\n g()" "(while loop (g))";
reads "until over loop" "until loop\n g()" "(until loop (g))";
reads "elif over loop" "if recur\n 1\nelif loop\n 2" "(cond recur 1 loop 2)";
reads "a one-line if over loop" "if loop then 1 else 2" "(if loop 1 2)";
reads "a one-line if over recur" "if recur > 0 then recur else 0" "(if (> recur 0) recur 0)";
reads ~global:false "a typed let of loop" "let loop: i32 = 1\nloop" "(let [loop (the i32 1)] loop)";
reads "a match over loop, and an arm of it" "match loop\n loop -> loop" "(match loop loop loop)";
reads "read-only pointer" "let p: Ptr(const u8) = uninit" "(def p (Ptr const u8) uninit)";
(* Statements that fit on a line, in one-line slots. *)
reads "arm statements" "match s\n 1 -> break\n 2 -> continue :outer\n _ -> x += 1"
@ -1013,7 +1021,7 @@ let () =
(fun n ->
if not (Test_support.contains d.Loc.dmsg n) then
fail "the loop refusal does not say %S: %s" n d.Loc.dmsg)
[ "on lines 2, 3, 5"; "(while (< i 10)" ];
[ "on lines 2, 3 and 5. The indented syntax"; "(while (< i 10)" ];
if List.length d.Loc.notes <> 2 then
fail "the loop refusal points at %d more places, wanted 2" (List.length d.Loc.notes))
| exception e -> fail "a loop: %s" (diag_text e))