A struct or union fits on one line, struct Pt(x: i32, y: i32), and flan convert writes that form when it fits.

This commit is contained in:
Joseph Ferano 2026-09-26 05:28:08 +07:00
parent de0c8791f3
commit 905a4c53be
8 changed files with 78 additions and 8 deletions

View File

@ -1153,6 +1153,11 @@ Before it at the same level, else out to the line that owns this block."
((member w '("defer" "quote" "else")) alone)
((member w '("fn" "fn-"))
(not (re-search-forward "[ \t]=[ \t]" end t)))
;; `struct Pt(x: i32)' has its fields on the line.
((member w '("struct" "union"))
(not (save-excursion
(goto-char w-end)
(looking-at "[ \t]+[^][ \t\n(){},;\":]+("))))
((member w '("if" "elif")) (not (flan-fln--then start)))
(t t)))))
;; `let r = match n', `x = if c', `fn f(x) = match x', a lambda
@ -1606,7 +1611,8 @@ lambda or a `Fn(...)' type, and not after a match arm's."
flan--definers))
(1 font-lock-keyword-face) (2 font-lock-function-name-face))
;; A condition's parent, `struct DiskFull :parent IoError'.
(,(concat "^struct[ \t]+[^][ \t\n(){},;\":]+[ \t]+:parent[ \t]+" flan-fln--name-re)
(,(concat "^struct[ \t]+[^][ \t\n(){},;\":]+\\(?:([^)\n]*)\\)?[ \t]+:parent[ \t]+"
flan-fln--name-re)
1 font-lock-type-face)
(,(concat "^\\(?:def\\|once\\|const\\)[ \t]+" flan-fln--name-re)
1 font-lock-variable-name-face)

View File

@ -90,6 +90,10 @@ struct Oops :parent Error
type Count = i64
struct Pair(a: i64, b: i64)
fn pair-sum(p: Pair) -> i64 = p.a + p.b
fn counted(n: Count) -> Count = n + 1
fn oops-code() -> i64
@ -240,6 +244,8 @@ comment():
("fn dir" "(dir :north)" "7")
("struct Oops" "(oops-code)" "7")
("type Count" "(counted 1)" "2")
("struct Pair" "(pair-sum (Pair {.a 1 .b 2}))" "3")
("fn pair-sum" "(pair-sum (Pair {.a 1 .b 2}))" "3")
("fn counted" "(counted 1)" "2")
("fn oops-code" "(oops-code)" "7")
("macro dbl-of" "(use-mac 5)" "10")

View File

@ -903,6 +903,23 @@ defconst(k, 3)
(test-flan-fln--tabs "macro repeat(i, n, & body)\n|" 1) 2)
(test-flan-fln--is "and a struct with a parent"
(test-flan-fln--tabs "struct DiskFull :parent IoError\n|" 1) 2)
(test-flan-fln--is "but not a struct on one line"
(test-flan-fln--tabs "struct Pt(x: i32, y: i32)\n|" 1) 0)
(test-flan-fln--is "nor one with a parent"
(test-flan-fln--tabs "struct D(free: i64) :parent IoError\n|" 1) 0)
(test-flan-fln--in "struct D(free: i64) :parent IoError\n\nunion U(a: i32)\n"
(font-lock-ensure)
(let ((face (lambda (needle)
(save-excursion (goto-char (point-min)) (search-forward needle)
(get-text-property (match-beginning 0) 'face)))))
(test-flan-fln--is "a one-line struct's name is a type" (funcall face "D(") 'font-lock-type-face)
(test-flan-fln--is "its field's type too" (funcall face "i64") 'font-lock-type-face)
(test-flan-fln--is "and its parent" (funcall face "IoError") 'font-lock-type-face)
(test-flan-fln--is "a one-line union's name" (funcall face "U(") 'font-lock-type-face))
(goto-char (point-min))
(test-flan-fln--is "a one-line struct is a top-level form of one line"
(test-flan-fln--thing 'flan-fln-toplevel)
"struct D(free: i64) :parent IoError"))
(test-flan-fln--is "a one-line fn whose value is a match opens it"
(test-flan-fln--tabs "fn f(x) = match x\n|" 1) 2)
(test-flan-fln--is "no deeper after a one-line else"

View File

@ -1060,7 +1060,19 @@ and sugar n (f : Form.t) : string list option =
| [ _; pf; { v = Form.Vec fs; _ } ] -> (" :parent " ^ ty pf, fs)
| _ -> assert false
in
(* One line when it fits, [struct Pt(x: i32, y: i32)], and a line per
field otherwise. *)
let one =
match fs, params_text fs with
| _ :: _, Some pt ->
let line =
i ^ (if d = "defstruct" then "struct " else "union ") ^ name ^ "(" ^ pt ^ ")" ^ parent
in
if String.length line <= width && not (!inside f) then Some [ line ] else None
| _ -> None
in
(match pairs fs with
| _ when one <> None -> one
| Some prs when List.for_all (fun ((f : Form.t), _) ->
match f.v with Form.Sym x -> def_name x | _ -> false) prs ->
Some

View File

@ -1531,6 +1531,13 @@ and header (s : st) w : Form.t =
named head items
| "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
data case writes them, with no block under it. *)
let inline =
match (peek p).tok with
| LP when not (peek p).sp -> let lp = advance p in Some (params p lp)
| _ -> None
in
(* [struct DiskFull :parent IoError]: a condition's parent, before the
fields as in the paren form. *)
let parent =
@ -1541,9 +1548,17 @@ and header (s : st) w : Form.t =
Some (Form.make (Form.Kw "parent") kt.loc, pt)
| _ -> None
in
expect_eol_block p
~after:(match parent with Some (_, pt) -> text_of pt | None -> w ^ " " ^ text_of name);
let after =
match parent, inline with
| Some (_, pt), _ -> text_of pt
| None, Some _ -> ")"
| None, None -> w ^ " " ^ text_of name
in
let fields =
match inline with
| Some fs -> expect_eol p ~after; fs
| None ->
expect_eol_block p ~after;
lines s (fun () ->
let f = name_tok p ~what:"a field's name" in
let tf =

View File

@ -290,7 +290,10 @@ Each item: the proposal, then the reason in one line.
syntaxes (section 3, item 8). A condition names its parent after the name,
`struct DiskFull :parent IoError` with its field lines, reading
`(defstruct DiskFull :parent IoError [free i64])`; with no field lines it
reads `(defstruct IoError :parent Error)`. **Built.**
reads `(defstruct IoError :parent Error)`. A struct or union fits on one
line with its fields in parentheses, `struct Pt(x: i32, y: i32)` or
`struct DiskFull(free: i64) :parent IoError`; `flan convert` writes that
when it fits the line and no comment sits among the fields. **Built.**
- `type Row = Vec(i32)` reads `(defalias Row (Vec i32))`. **Built.**
- `macro repeat(i, n, & body)` plus a block reads
`(defmacro repeat [i n & body] …)`. A parameter is a bare name, a

View File

@ -6,9 +6,7 @@ struct Overdraft :parent Error
account: i32
short: i64
struct Audit
account: i32
amount: i64
struct Audit(account: i32, amount: i64)
once balances: [4 i64]
once audits: i32

View File

@ -563,6 +563,12 @@ let () =
"handler-case\n g()\non E(c)\n h(c)\non = 2"
"(handler-case (g) [(E [c] (h c))])\n(set on 2)";
reads "an else assigned after an if" "if a\n b\nelse = 2" "(when a b)\n(set else 2)";
reads "a one-line struct" "struct Pt(x: i32, y)" "(defstruct Pt [x i32 y dyn])";
reads "a one-line union" "union U(a: i32)" "(defunion U [a i32])";
reads "a one-line struct with a parent" "struct D(free: i64) :parent Io"
"(defstruct D :parent Io [free i64])";
refuses "a one-line struct takes no block" "struct Pt(x: i32)\n y: i32" "indent/stray-indent"
"takes no block";
reads "a parent and no fields" "struct Io :parent Error" "(defstruct Io :parent Error)";
reads "macro" "macro repeat(i, n, & body)\n quote\n f(~i)\n ~@body"
"(defmacro repeat [i n & body] (quasiquote (do (f (unquote i)) (unquote-splicing body))))";
@ -836,7 +842,14 @@ let () =
"(defn f [] () (set data 3) (set loop 4) (set on 5))" " data = 3\n loop = 4\n on = 5";
prints "a type alias" "(defalias Row (Vec i32))" "type Row = Vec(i32)";
prints "a struct with a parent" "(defstruct D :parent Io [free i64])"
"struct D :parent Io\n free: i64";
"struct D(free: i64) :parent Io";
prints "a struct on one line" "(defstruct Pt [x i32 y dyn])" "struct Pt(x: i32, y)\n";
prints "a union too" "(defunion U [a i32 b f32])" "union U(a: i32, b: f32)";
prints "a struct too long for a line takes a line per field"
("(defstruct W [" ^ String.concat " " (List.init 8 (Printf.sprintf "field-number-%d i32")) ^ "])")
"struct W\n field-number-0: i32\n";
prints "and so does one with a comment among its fields"
"(defstruct C [a i32 ; first\n b i32])" "struct C\n a: i32 ; first\n b: i32";
prints "a parent with no fields" "(defstruct D :parent Io)" "struct D :parent Io";
prints "an empty field vector under a parent keeps the fallback"
"(defstruct D :parent Io [])" "defstruct(D, :parent, Io, [])";