Need help with formatting lists

I want to print list and its contents as horizontally or vertically placed blocks where head element has leading [ and other elements have leading ;. Printing integers works fine

[ 1; 1; 1
; 1; 1; 1]

but printing complex structures goes messy

[ { a=1
  , b="x" }
; { a=1
  , b="x" }
; { a=1111
  , b=
    "xxxx"
  }; {
     a=1111
     , 
     b=
     "xxxx"
     }]

How can I get line breaks before every ; in last example? Do I need explicit line breaks?

Full code:

type t = {a : int; b: string}

let fmt_t fmt {a; b} = 
  Format.fprintf fmt
        "@[<hov>@[{@ @[a@,=@,%a@]@]@ @[, @[b@,=@,%a@]@]@ }@]"
        (fun fmt -> Format.fprintf fmt "%d") a
        (fun fmt -> Format.fprintf fmt "%S") b

let fmt_list fa fmt xs = 
  Format.fprintf fmt "@[@,["; 
      let () = match xs with
         | [] -> ()
         | x::xs ->
            Format.fprintf fmt "@[ %a@]" fa x;
            List.iter (Format.fprintf fmt "@[; %a@]" fa) xs;
      in
      Format.fprintf fmt "]@]"

let () =
  let open Format in
  let str = {a=1; b="x"} in 
  let str2 = {a=1111; b="xxxx"} in 
  pp_set_margin std_formatter 12;
  printf "%a\n" (fmt_list (fun fmt -> fprintf fmt "%d"))
    [ 1; 1; 1; 1; 1; 1; ];
  printf "%a\n" (fmt_list fmt_t)
    [ str; str; str2; str2;  ];
  ()

1 Like

First, when you are stress-testing formatting code, you should use pp_set_geometry and not pp_set_margin in order to not forget to set the indentation limit (before OCaml 4.08, you need to call pp_set_max_indent after the call to pp_set_margin).

Second, in general, you are opening too many boxes.
In particular, nested boxes with no break hints inside the outer box can be simplified to just the inner box.
For instance, in

         | x::xs ->
            Format.fprintf fmt "@[ %a@]" fa x;

the opened box only matter if %a emits break hints at the toplevel (i.e. not inside any boxes).

Similarly, boxes set the left indentation: when you open a box discarding negative indentation and exceptional events, the contents of the box is printed on the right of the opening box.

Thus in

List.iter (Format.fprintf fmt "@[; %a@]" fa) xs;

You are constraining the formatter to print every new elements of the list to the right of the start of previous elements.

Another point is that there are no break hints in your list printer, so the only breaks are the implicit break due to going over the maximum indentation. If you want to have break before ; you need to add some break hints.

For instance, you could rewrite fmt_list using pp_print_list as

let fmt_list fa ppf xs =
  let semi ppf () = Format.fprintf ppf "@ ; " in
  Format.fprintf ppf "@[[%a]@]"
    (Format.pp_print_list ~pp_sep:semi fa) xs

leads to

[{ a=1
, b=“x” }
; { a=1111
, b=
“xxxx”
}
; { a=1111
, b=
“xxxx”
}]

In this case, you have a break every semicolon, but this only happen due to the strong margin constraint.
If need to have a break on every semicolon, you could use a v box:

let fmt_list fa ppf xs =
 let semi ppf () = Format.fprintf ppf "@ ; " in
 Format.fprintf ppf "@[<v>[%a]@]"
   (Format.pp_print_list ~pp_sep:semi fa) xs
1 Like

It seems that your reply has created more questions then answers :slight_smile: Let me try more concrete question.

In code below I tried to print a list in kind of literate style: the head right after [ and other elements right after semicolon. Saying right after I mean that it should be placed in horizontal box. But for some reason my understanding of horizontal boxes differs from the behavior of pretty-printer: it seems that it can split box for the actual list contents in two (comma appear just under semicolon). Any tips?

let fmt_t fmt {a; b} =
  Format.fprintf fmt "@[<hov>";
  Format.fprintf fmt "@[{ a@,=@,%a@]"
        (fun fmt -> Format.fprintf fmt "%d") a;
  Format.fprintf fmt "@[, b@,=@,%a@]"
        (fun fmt -> Format.fprintf fmt "%S") b;
  Format.fprintf fmt "@ }@]"

let fmt_list fa fmt = function
  | [] -> Format.fprintf fmt "[]"
  | x::xs ->
    Format.fprintf fmt "@[<v>"; 
    Format.fprintf fmt "@[<h>[ %a@]@," fa x;
    List.iter (Format.fprintf fmt "@[<h>; %a@]@," fa) xs;
    Format.fprintf fmt "]@]"
[ { a=1
, b="x" }
; { a=1
, b="x" }
; { a=1111
, b="xxxx" }
; { a=1111
, b="xxxx" }
]

The breaks at commas are due to the maximum indentation limit: if a box of which the parent box does not fit fully on the line is opened past the maximum indentation limit, it is rejected to the left. Here, you could try to set the maximum indentation to a more reasonable value (10 or 11).

(Also, I am not sure you are using

"..%a..." ... (fun fmt -> Format.fprintf fmt "%d") ...

rather than just %d.
)