diff --git a/lib/indent_printer.ml b/lib/indent_printer.ml index a412e3fd..62325b7a 100644 --- a/lib/indent_printer.ml +++ b/lib/indent_printer.ml @@ -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 diff --git a/lib/indent_reader.ml b/lib/indent_reader.ml index f3cbc56f..03b13c6f 100644 --- a/lib/indent_reader.ml +++ b/lib/indent_reader.ml @@ -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 diff --git a/test/test_syntax.ml b/test/test_syntax.ml index 4adf305a..fc970edb 100644 --- a/test/test_syntax.ml +++ b/test/test_syntax.ml @@ -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))