From 12cd4feff06cb62baa78a4a47d49a4d19ca054af Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 26 Sep 2026 06:26:36 +0700 Subject: [PATCH] A .fln let takes more bindings on the lines indented under it, a top-level one more globals, and flan convert writes runs of short bindings that way --- lib/indent_printer.ml | 47 +++++- lib/indent_reader.ml | 235 ++++++++++++++++++++-------- spec-syntax.md | 20 ++- test/syntax/handwritten/csv.fln | 4 +- test/syntax/handwritten/lisp.fln | 2 +- test/syntax/handwritten/traffic.fln | 4 +- test/syntax/handwritten/words.fln | 12 +- test/test_syntax.ml | 53 ++++++- 8 files changed, 288 insertions(+), 89 deletions(-) diff --git a/lib/indent_printer.ml b/lib/indent_printer.ml index a86235e1..f2d8658a 100644 --- a/lib/indent_printer.ml +++ b/lib/indent_printer.ml @@ -1362,8 +1362,53 @@ and let_lines n prs body = | first :: more -> Source_text.tag t.loc.Loc.line first :: more | [] -> [] in + (* A let whose body starts with another is one run of bindings: the reader + merges the two back whichever way they are written. *) + let rec absorb prs body = + match body with + | x :: rest when let_sugar x && !quasi = 0 -> + (match if rest = [] then Some x else flatten x rest with + | Some { v = Form.List (_ :: { v = Form.Vec bs; _ } :: inner); _ } -> + (match pairs bs with + | Some ps -> absorb (prs @ ps) inner + | None -> (prs, body)) + | _ -> (prs, body)) + | _ -> (prs, body) + in + let prs, body = absorb prs body in let lines n b = let p, v = bind b in tagged b (value_lines n p v) in - List.concat_map (lines n) prs @ block n body + (* A binding that fits a line with a short value joins a group: the ones + after the first go on the lines under it, lined up with its name. *) + let short n b = + let p, v = bind b in + match value_lines n p v with + | [ l ] when String.length (at 0 v) <= 40 && not (!inside v) -> Some (tagged b [ l ]) + | _ -> None + in + let under b = + let p, v = bind b in + let p = String.sub p 4 (String.length p - 4) in + match value_lines (n + 4) p v with + | [ l ] when String.length (at 0 v) <= 40 && not (!inside v) -> Some (tagged b [ l ]) + | _ -> None + in + let rec emit = function + | [] -> [] + | b :: rest -> + (match short n b with + | None -> lines n b @ emit rest + | Some first -> + let rec group acc = function + | b' :: rest' as all -> + (match under b' with + | Some l -> group (acc @ l) rest' + | None -> (acc, all)) + | [] -> (acc, []) + in + let more, rest = group [] rest in + first @ more @ emit rest) + in + emit prs @ block n body (** 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 c6c8d58d..24ceb68d 100644 --- a/lib/indent_reader.ml +++ b/lib/indent_reader.ml @@ -1474,10 +1474,27 @@ and value_line ?(block_ok = false) (s : st) ~after : Form.t = | Form.List items -> mk p e.loc (Form.List (items @ body)) | _ -> mk p e.loc (Form.List (e :: body))) | COMMA -> - failk "one-binding" (peek p).loc + let c = (peek p).loc in + (* On a let's line, the fix is the let's group: the rest of the line + under the first name. *) + let group = + let file, lines = !source in + if c.Loc.file <> file || c.Loc.line > Array.length lines then None + else + let line = lines.(c.Loc.line - 1) in + let head = String.trim (String.sub line 0 (c.Loc.col - 1)) in + let rest = String.trim (String.sub line c.Loc.col (String.length line - c.Loc.col)) in + if String.length head > 4 && String.sub head 0 4 = "let " && rest <> "" then + Some (Printf.sprintf ":\n\n %s\n %s" head rest) + else None + in + failk "one-binding" c "%s is followed by a comma, and one line binds one name. Put each \ - binding on its own line, one after the other" + binding on its own line%s" (text_of e) + (match group with + | Some g -> ", the ones after the first under its name" ^ g + | None -> ", one after the other") | _ -> lambda_block ~block_ok s e ~after:(text_of e) (* Whether this line has a [then] at depth zero: a one-line if. *) @@ -1501,9 +1518,10 @@ and lambda_block ?(block_ok = false) (s : st) (e : Form.t) ~after = else expect_eol p ~after; e -and let_stmt (s : st) : Form.t list = +(* One binding of a let, [x = v], [x: T = v] or [{a .x} = p], through the + end of its line (and the block its value takes). *) +and binding (s : st) : Form.t * Form.t = let p = s.p in - let t = advance p in let target, _ = unary p in (* [let x: T = v] is [(let [x (the T v)])]: a let binding has no type slot of its own, and [the] is the form that says what a value is. *) @@ -1524,6 +1542,72 @@ and let_stmt (s : st) : Form.t list = Form.make (Form.List [ sym tyf.loc "the"; tyf; v ]) (span p tyf.loc) | None -> v in + (target, v) + +(* The lines indented under a let, each one more binding of it: [let a = 1] + and under it [b = a + 1], lined up with [a]. Anything else there is + refused; [first] is the let's first target, for the message. *) +and binding_lines : 'a. st -> first:Form.t -> one:(unit -> 'a) -> 'a list = + fun s ~first ~one -> + let p = s.p in + if (peek p).tok <> INDENT then [] + else begin + ignore (advance p); + let rec go acc = + match (peek p).tok with + | DEDENT -> ignore (advance p); List.rev acc + | EOF -> List.rev acc + | _ when binding_line p -> go (one () :: acc) + | _ -> + failk "let-block" (where_ p) + "this line is indented under let %s, and the only lines that go \ + there are more bindings of the let, lined up with its first name:\n\n\ + \ let a = 1\n b = a + 1\n\n\ + A let's names last to the end of the block the let is in, so any \ + other line after it goes at the let's column" + (text_of first) + in + go [] + end + +(* Whether the line at point is a binding: a name, or a [[...]] or [{...}] + pattern, then [=] or [: T =]. [x += 1] and [f(x)] are not. *) +and binding_line p = + (* An [=] at the line's own depth, from token [k] on. *) + let rec eq k depth = + match (peek_at p k).tok with + | EOF | NEWLINE | INDENT | DEDENT -> false + | NAME "=" when depth = 0 -> true + | LP | LB | LC -> eq (k + 1) (depth + 1) + | RP | RB | RC -> eq (k + 1) (depth - 1) + | _ -> eq (k + 1) depth + in + match (peek p).tok with + | NAME s when s <> "" && s.[0] <> '.' && not (is_op_word s) -> + (match (peek_at p 1).tok with + | NAME "=" -> (peek_at p 1).sp + | COLON -> eq 2 0 + | _ -> false) + (* [~g = a] in a template. *) + | UNQ -> eq 1 0 + | LB | LC -> + let rec close k depth = + match (peek_at p k).tok with + | EOF | NEWLINE -> false + | LP | LB | LC -> close (k + 1) (depth + 1) + | RP | RB | RC when depth = 1 -> (peek_at p (k + 1)).tok = NAME "=" + | RP | RB | RC -> close (k + 1) (depth - 1) + | _ -> close (k + 1) depth + in + close 0 0 + | _ -> false + +and let_stmt (s : st) : Form.t list = + let p = s.p in + let t = advance p in + let (target, v) = binding s in + let more = binding_lines s ~first:target ~one:(fun () -> binding s) in + let own = List.concat_map (fun (a, b) -> [ a; b ]) ((target, v) :: more) in let make bindings body = let f = mk p t.loc @@ -1538,17 +1622,10 @@ and let_stmt (s : st) : Form.t list = match body with | [ ({ Form.v = Form.List (_ :: { v = Form.Vec bs; _ } :: body); _ } as inner) ] when List.memq inner s.lets -> - make (target :: v :: bs) body - | _ -> make [ target; v ] body + make (own @ bs) body + | _ -> make own body in - (* A let has no block: its name lasts to the end of the block it is in. *) - if (peek p).tok = INDENT then - failk "let-block" (peek_at p 1).loc - "this line is indented under let %s, which takes no block. A let's \ - name lasts to the end of the block the let is in, so the lines after \ - it go at the let's column" - (text_of target) - else [ merged (stmts s) ] + [ merged (stmts s) ] and stmt (s : st) : Form.t = let p = s.p in @@ -1697,57 +1774,7 @@ and header (s : st) w : Form.t = in named (if w = "fn" then "defn" else "defn-") (name :: Form.make (Form.Vec ps) lp.loc :: ret :: (where_clause @ body)) - | "def" | "once" | "const" -> - (* A top-level [let] is read here too, as [def]: [w] is then "def" and - [t] the let. *) - let shown = match t.tok with NAME "let" -> "let" | _ -> w in - let name = name_tok p ~what:"the name being defined" in - let tyf = - match (peek p).tok with - | COLON -> ignore (advance p); Some (ty p) - | _ -> None - in - let v = - match (peek p).tok with - | NAME "=" -> - ignore (advance p); - Some (value_line s ~after:(shown ^ " " ^ text_of name ^ " =")) - | _ -> - expect_eol p ~after:(match tyf with Some f -> text_of f | None -> text_of name); - None - in - let head = - match w with "def" -> "def" | "once" -> "defonce" | _ -> "defconst" - in - if shown = "def" then - failk "def-is-let" l0 - "a global is written with let, at the file's top level:\n\n let %s%s%s" - (text_of name) - (match tyf, v with - | Some t, _ -> ": " ^ text_of t - | None, None -> ": i32" - | None, Some _ -> "") - (match v, tyf with - | Some v, _ -> " = " ^ text_of v - | None, None -> " = 0" - | None, Some _ -> ""); - let items = - match w, tyf, v with - | "const", None, Some v -> [ name; v ] - | "const", Some t, Some v -> [ name; t; v ] - | "const", _, None -> - failk "const-value" l0 - "a const needs its value: const %s = 3" (text_of name) - | _, None, Some v -> [ name; sym name.loc "dyn"; v ] - | _, Some t, None -> [ name; t ] - | _, Some t, Some v -> [ name; t; v ] - | _, None, None -> - failk "def-empty" l0 - "%s %s names neither a type nor a value. Give it one or both: %s %s: \ - i32 = 0" - shown (text_of name) shown (text_of name) - in - named head items + | "def" | "once" | "const" -> def_form s w t l0 | "struct" | "union" -> let name = name_tok p ~what:"the type's name" in (* [struct Pt(x: i32, y: i32)]: the fields on the header's line, as a @@ -2268,6 +2295,65 @@ and header (s : st) w : Form.t = named "quasiquote" [ e ]) | _ -> assert false +(* [let x = v] at the top level, [once x = v] or [const x = v], from the + name on: [t] is the word, and [l0] where the form starts. A top-level let + leaves the lines indented under it to [read_all], which reads each as one + more global. *) +and def_form (s : st) w (t : token) l0 : Form.t = + let p = s.p in + let named head items = mk p l0 (Form.List (sym l0 head :: items)) in + (* A top-level [let] is read here too, as [def]: [w] is then "def" and + [t] the let. *) + let shown = match t.tok with NAME "let" -> "let" | _ -> w in + let name = name_tok p ~what:"the name being defined" in + let tyf = + match (peek p).tok with + | COLON -> ignore (advance p); Some (ty p) + | _ -> None + in + let v = + match (peek p).tok with + | NAME "=" -> + ignore (advance p); + Some (value_line ~block_ok:(shown = "let") s + ~after:(shown ^ " " ^ text_of name ^ " =")) + | _ -> + expect_eol p ~after:(match tyf with Some f -> text_of f | None -> text_of name); + None + in + let head = + match w with "def" -> "def" | "once" -> "defonce" | _ -> "defconst" + in + if shown = "def" then + failk "def-is-let" l0 + "a global is written with let, at the file's top level:\n\n let %s%s%s" + (text_of name) + (match tyf, v with + | Some t, _ -> ": " ^ text_of t + | None, None -> ": i32" + | None, Some _ -> "") + (match v, tyf with + | Some v, _ -> " = " ^ text_of v + | None, None -> " = 0" + | None, Some _ -> ""); + let items = + match w, tyf, v with + | "const", None, Some v -> [ name; v ] + | "const", Some t, Some v -> [ name; t; v ] + | "const", _, None -> + failk "const-value" l0 + "a const needs its value: const %s = 3" (text_of name) + | _, None, Some v -> [ name; sym name.loc "dyn"; v ] + | _, Some t, None -> [ name; t ] + | _, Some t, Some v -> [ name; t; v ] + | _, None, None -> + failk "def-empty" l0 + "%s %s names neither a type nor a value. Give it one or both: %s %s: \ + i32 = 0" + shown (text_of name) shown (text_of name) + in + named head items + (* handler-case, handler-bind and restart-case take nothing on their own line. *) and clause_header_end p w = match (peek p).tok with @@ -2340,8 +2426,25 @@ let read_all ?(line = 1) ?col ?indent ?(global_let = true) ~file src = | EOF -> [] | DEDENT -> ignore (advance s.p); [] | NAME "let" when header_follow s.p "let" -> + let t = peek s.p in let f = header s "def" in - f :: top () + (* The lines indented under it are more globals, one each: a global + is one name, so a pattern there is refused. *) + let first = match f.v with Form.List (_ :: n :: _) -> n | _ -> f in + let more = + binding_lines s ~first ~one:(fun () -> + let l = peek s.p in + (match l.tok with + | LB | LC -> + failk "global-pattern" l.loc + "a global binds one name, and this line under let %s is a \ + pattern. Bind the value to a name, and take it apart inside \ + the function that uses it" + (text_of first) + | _ -> ()); + def_form s "def" t l.loc) + in + f :: more @ top () | _ -> let f = stmt s in f :: top () in let fs = if global_let then top () else stmts s in diff --git a/spec-syntax.md b/spec-syntax.md index f047f23a..411d9bea 100644 --- a/spec-syntax.md +++ b/spec-syntax.md @@ -187,9 +187,23 @@ Each item: the proposal, then the reason in one line. - **`let x = v`** scopes to the end of its block and reads as `(let [x v] rest…)`. Consecutive `let`s merge into one binding vector. - A `let` is always flat: a line indented deeper under `let x = v` is - refused. To end a `let`'s scope early, put it in a `do:` block. - The printer writes every `let` flat. A `let` with statements after it + A `let` takes more bindings on the lines indented under it, lined up with + its first name, each seeing the ones above: + + ``` + let row = r + 1 + col: i32 = c - 1 + {x .x} = p + ``` + + is `(let [row (+ r 1) col (the i32 (- c 1)) {x .x} p] rest…)`. Any other + line indented there is refused. At the top level each such line is one + more global, `(def col i32 …)`; a pattern there is refused, since a global + binds one name. A `let` is otherwise flat: its scope is the rest of its + block. To end it early, put it in a `do:` block. + The printer writes every `let` flat, and a run of bindings whose values + are short (one line, 40 characters or fewer) as one `let` with the rest + under the first name; top-level globals stay one `let` each. A `let` with statements after it takes them into its body; when one of them means an outer name the `let` rebinds, the `let`'s is renamed (`x` to `x-2`, a name the top-level form does not use; a struct pattern is written as `{x-2 .x}` pairs). A macro's diff --git a/test/syntax/handwritten/csv.fln b/test/syntax/handwritten/csv.fln index e5de89b4..6ea4fdad 100644 --- a/test/syntax/handwritten/csv.fln +++ b/test/syntax/handwritten/csv.fln @@ -25,8 +25,8 @@ fn flush(field: Ptr(Vec(u8)), row: Ptr(Vec(str))) -> () fn parse-line(line: [const u8]) -> Vec(str) let row = vec-new(str) - let field = vec-new(u8) - let state = State.start + field = vec-new(u8) + state = State.start for i in range(length(line)) let c = line[i] match state diff --git a/test/syntax/handwritten/lisp.fln b/test/syntax/handwritten/lisp.fln index 6e4ab048..695512d5 100644 --- a/test/syntax/handwritten/lisp.fln +++ b/test/syntax/handwritten/lisp.fln @@ -61,7 +61,7 @@ fn eval-form(e, env) -> dyn last _ -> let f = eval(op, env) - let arg = eval(e[1], env) + arg = eval(e[1], env) eval(get(f, :body), extend(get(f, :env), get(f, :param), arg)) fn run(program) -> () diff --git a/test/syntax/handwritten/traffic.fln b/test/syntax/handwritten/traffic.fln index ccf3d613..bdb50b9e 100644 --- a/test/syntax/handwritten/traffic.fln +++ b/test/syntax/handwritten/traffic.fln @@ -40,8 +40,8 @@ fn show(l: Light) -> str ; whoever handles it may reset the light to red. fn run(ticks: i32, presses: [const i32], fault-at: i32) -> () let l = Light.Red{.left 1, .walk false} - let p = 0 - let t = 0 + p = 0 + t = 0 until :clock t >= ticks let pressed = p < length(presses) and presses[p] == t diff --git a/test/syntax/handwritten/words.fln b/test/syntax/handwritten/words.fln index 90da8830..97f591c1 100644 --- a/test/syntax/handwritten/words.fln +++ b/test/syntax/handwritten/words.fln @@ -18,9 +18,9 @@ fn letter?(c: u8) -> bool ; The words of text, lowercased, in order. fn words(text: [const u8]) -> Vec([const u8]) let out = vec-new([const u8]) - let lower = to-lower(text) - let i = 0 - let n = length(lower) + lower = to-lower(text) + i = 0 + n = length(lower) while :scan i < n until i >= n or letter?(lower[i]) i += 1 @@ -67,11 +67,11 @@ fn main() -> i32 let {.word .n} = counts[i] println(str(word), n) let top: [3 i32] = [counts[0].n counts[1].n counts[2].n] - let [most & others] = top + [most & others] = top println("most frequent seen", most, "times, then", others) let total = i32(length(ws)) - let distinct = i32(length(counts)) - let g = grade(distinct, total) + distinct = i32(length(counts)) + g = grade(distinct, total) println(distinct, "of", total, "distinct:", describe(g)) let longest = reduce(slice(ws), slice(ws[0], 0, 0), fn(a, b) => if length(b) > length(a) then b else a) diff --git a/test/test_syntax.ml b/test/test_syntax.ml index 1d5c7607..3ee7e4c8 100644 --- a/test/test_syntax.ml +++ b/test/test_syntax.ml @@ -539,7 +539,7 @@ let () = (* Statements. *) reads "lets merge" "fn f() -> i32\n let a = 1\n let b = 2\n a + b" "(defn f [] i32 (let [a 1 b 2] (+ a b)))"; - refuses ~global:false "let with a block" "let a = 1\n a\nb" "indent/let-block" "go at the let's column"; + refuses ~global:false "let with a block" "let a = 1\n a\nb" "indent/let-block" "goes at the let's column"; reads ~global:false "flat let" "let a = 1\na\nb" "(let [a 1] a b)"; reads "elif" "if a\n 1\nelif b\n 2\nelse\n 3" "(cond a 1 b 2 :else 3)"; reads "one-line if" "x = if a then 1 else 2" "(set x (if a 1 2))"; @@ -643,6 +643,8 @@ let () = refuses "two assignments" "if a then b = c = d" "indent/assign-in-test" "if a then b = c,"; refuses "a let-bound if with no block" "let r = if a > 1\nr" "indent/expected-block" "if a > 1 takes"; refuses "two bindings on a line" "let v: i32 = a, w = b" "indent/one-binding" "a is followed by a comma"; + refuses "two bindings on a let's line" "fn f()\n let v = a, w = b\n v" "indent/one-binding" + "under its name:\n\n let v = a\n w = b"; reads ~global:false "a let-bound match" "let r = match a\n 1 -> 2\n _ -> 3\nr" "(let [r (match a 1 2 _ 3)] r)"; reads ~global:false "a let-bound if" "let q = if a\n 1\nelse\n 2\nq" "(let [q (if a 1 2)] q)"; reads ~global:false "a let-bound call with a block" "let v = foo(a):\n x\nv" "(let [v (foo a x)] v)"; @@ -659,7 +661,7 @@ let () = "(defmacro m [x] (quasiquote (+ (unquote x) 1)))"; reads ~global:false "typed let" "let x: i32 = 5\nx" "(let [x (the i32 5)] x)"; refuses "a let takes no block" "fn f() -> ()\n let x = 1\n g(x)\n h(x)" - "indent/let-block" "go at the let's column"; + "indent/let-block" "goes at the let's column"; (* Mistakes carried over from other languages, answered in this one. *) refuses "a block without the colon names the call" "with-allocator(a, b)\n g()" "indent/stray-indent" "as in with-allocator(a, b):"; @@ -693,6 +695,29 @@ let () = "indent/lambda-block-left" "Indent it into the block"; refuses "a block lambda's brackets left open" "f(fn(a) =>\n a\n" "indent/unclosed" "ends where this bracket closes"; + (* A let's bindings on the lines under it. *) + reads ~global:false "a let's bindings on indented lines" "let a = 1\n b = a + 1\n c: i32 = b\ng(c)" + "(let [a 1 b (+ a 1) c (the i32 b)] (g c))"; + reads ~global:false "patterns among them" "let p = q()\n {x .x} = p\n [h & t] = xs\ng(x, h)" + "(let [p (q) {x .x} p [h & t] xs] (g x h))"; + reads ~global:false "a binding with a block value" "let a = 1\n b =\n f()\n a\n c = match b\n 1 -> 2\n _ -> 3\ng(c)" + "(let [a 1 b (do (f) a) c (match b 1 2 _ 3)] (g c))"; + reads ~global:false "a let after a group merges into it" "let a = 1\n b = 2\nlet c = 3\ng(c)" + "(let [a 1 b 2 c 3] (g c))"; + reads ~global:false "a group in a body" "fn f()\n let a = 1\n b = 2\n a + b" + "(defn f [] _ (let [a 1 b 2] (+ a b)))"; + refuses ~global:false "a statement under a let" "let a = 1\n g(a)\nh()" + "indent/let-block" "b = a + 1"; + refuses ~global:false "an assignment to a field under a let" "let a = p()\n a.x = 1\nh()" + "indent/let-block" "only lines that go there are more bindings"; + refuses ~global:false "a compound assignment under a let" "let a = 1\n a += 1\nh()" + "indent/let-block" "let's column"; + reads "a top-level group is several globals" "let a = 1\n b: i32 = 2\nfn f() = a" + "(def a dyn 1)\n(def b i32 2)\n(defn f [] _ a)"; + refuses "a global group binds names" "let a = 1\n {x .x} = p" + "indent/global-pattern" "a global binds one name"; + refuses "a statement under a global" "let a = 1\n f(a)" + "indent/let-block" "more bindings of the let"; (* Indices separate as a vector's elements do. *) reads "indices separated by spaces" "x = grid[row col].color-idx" "(set x (.color-idx (at grid row col)))"; @@ -786,18 +811,18 @@ let () = (* A let is always flat: it takes in the rest of its block. *) prints "flat let" "(defn f [] () (let [j 1] (g j)) (h))" " let j = 1\n g(j)\n h()"; prints "a chain of lets, all flat" "(defn f [] () (let [a 1] (let [b 2] (g b)) (k a)) (h))" - " let a = 1\n let b = 2\n g(b)\n k(a)\n h()"; + " let a = 1\n b = 2\n g(b)\n k(a)\n h()"; (* A later statement that means an outer name of the same spelling: the let's own is renamed. *) prints "a later outer name of the same spelling renames the let's" "(defn f [x i32] () (let [x 1] (g x)) (h x))" " let x-2 = 1\n g(x-2)\n h(x)"; prints "the inner let of a chain renamed" - "(defn f [] () (let [a 1] (let [b 2] (g b)) (h b)))" " let a = 1\n let b-2 = 2\n g(b-2)\n h(b)"; + "(defn f [] () (let [a 1] (let [b 2] (g b)) (h b)))" " let a = 1\n b-2 = 2\n g(b-2)\n h(b)"; prints "the binding's own value keeps the outer name" "(defn f [x i32] () (let [x (+ x 1)] (g x)) (h x))" " let x-2 = x + 1\n g(x-2)\n h(x)"; prints "a later binding's value takes the new name" "(defn f [x i32] () (let [x 1 y (+ x 1)] (g y)) (h x))" - " let x-2 = 1\n let y = x-2 + 1\n g(y)\n h(x)"; + " let x-2 = 1\n y = x-2 + 1\n g(y)\n h(x)"; prints "the new name is one the function does not use" "(defn f [x i32] () (let [x 1] (g x x-2)) (h x))" " let x-3 = 1\n g(x-3, x-2)\n h(x)"; prints "a later let of the same name is no mention" @@ -838,10 +863,10 @@ let () = prints "comment is a body in order" "(comment (let [a 1] (g a)) (h))" "comment:\n let a = 1\n g(a)\n h()"; prints "a later lambda keeps the outer name" "(defn f [] i32 (let [x 1] (let [x 5] (g x)) (app (fn [y] (+ x y)) 2)))" - " let x = 1\n let x-2 = 5\n g(x-2)\n app(fn(y) => x + y, 2)"; + " let x = 1\n x-2 = 5\n g(x-2)\n app(fn(y) => x + y, 2)"; prints "a renamed name renamed again counts on" "(defn f [] () (let [x 1] (let [x 2] (let [x 3] (g x)) (g x)) (g x)))" - " let x = 1\n let x-2 = 2\n let x-3 = 3\n g(x-3)\n g(x-2)\n g(x)"; + " let x = 1\n x-2 = 2\n x-3 = 3\n g(x-3)\n g(x-2)\n g(x)"; prints "a macro that names the let's name keeps its scope" "(defmacro show-it [] `(println it))\n(defn f [] () (let [it 1] (let [it 2] (show-it)) (show-it)))" " let it = 1\n do:\n let it = 2\n show-it()\n show-it()"; @@ -898,7 +923,19 @@ let () = "(let [a 1 ; first\n b 2] ; second"; prints "each binding keeps its comment, indented" "(defn f [] i32\n (let [a 1 ; first\n b 2] ; second\n (+ a b)))" - " let a = 1 ; first\n let b = 2 ; second"; + " let a = 1 ; first\n b = 2 ; second"; + prints "a comment line between grouped bindings" + "(defn f [] i32\n (let [a 1\n ;; why b\n b 2]\n (+ a b)))" + " let a = 1\n ;; why b\n b = 2\n"; + prints "a long value starts a let of its own" + "(defn f [] i32 (let [a 1 b (some-function-with-a-long-name alpha beta gamma) c 2] (+ a b c)))" + " let a = 1\n let b = some-function-with-a-long-name(alpha, beta, gamma)\n let c = 2\n"; + prints "a block value ends the group" + "(defn f [] i32 (let [a 1 b 2 c (do (g) a) d 4 e 5] (+ a b c d e)))" + " let a = 1\n b = 2\n let c =\n g()\n a\n let d = 4\n e = 5\n"; + prints "typed and pattern bindings in a group" + "(defn f [p dyn] i32 (let [a (the i32 1) {x .x} p [h & t] xs] (+ a x h)))" + " let a: i32 = 1\n {x .x} = p\n [h & t] = xs\n"; back "comments to parens" "; head\n\nfn main() -> i32\n ; why\n g() ; note\n 0" "; head\n\n(defn main [] i32\n ; why\n (g) ; note\n 0)"; (* Written the way the corpus writes them. *)