Using Format.sprintf

The documentation for Format.sprintf says

[…] returns a string containing the result of formatting the arguments.

But how can that function know how to format the arguments if it is not given a formatter argument? Indeed, it needs to know the margins (and current state) of the formatter where it would output the string in order to format it accordingly.

To give a small example:

let () = Format.set_margin 3;;
let () = Format.printf "a@ a@ a";;
let () = let s = Format.sprintf "a@ a@ a" in Format.printf "%s" s;;

outputs two different answers: in the last line, Format.sprintf does not “know” that I will want to use that string s to print in a formatter with margin 3, so it does not break the line.

What can I do to obtain a string that is formatted as if it were to be printed in a given formatter?

In my usecase, I need to obtain such a string because I need to “post-process” it before effectively printing it in my formatter. Is it a case where I have to use Format.str_formatter (or Format.formatter_of_buffer with another buffer than Format.stdbuf)? There are also some related comments in the documentation for Format.dprintf, but since that function does not produce a string, I think that does not answer my particular problem.

Side remark: should the documentation of Format.(a)sprintf be made more precise by writing something like

[…] returns a string containing the result of formatting the arguments as if it were output on [std_formatter] in a fresh state.

?

Thanks!

Does sprintf not respect the margins set on str_formatter?

At any rate, you can use fprintf with a custom buffer formatter that you flush to get the results, and set any properties you need on that formatter

1 Like

Format.asprintf (the non-deprecated version of Format.sprintf) uses the str_formatter as a formatter. Thus, what you wanted to write was

let () =
  Format.pp_set_geometry ~margin:3 ~max_indent:2 Format.str_formatter;
  let s = Format.asprintf "a@ a@ a" in Format.printf "%s" s

Note however that using a string as a partially formatted message is generally a bad idea because rendering to string removes too much contextual information.

If you don’t need to inspect the contents of the string Format.dprintf is generally a better choice. Otherwise, you may need to post-process either the format string or use an intermediary representation like format-doc.

2 Likes

Thanks for both the answer and the advice.

Format.sprintf appears not to be deprecated as of 5.4, see OCaml library : Format (although it looks indeed completely subsumed by Format.asprintf; same with Format.k(a)sprintf).

Exactly what I needed, thanks! That sounds obvious once you know it, but that is exactly what is missing from OCaml library : Format.

Well, I should have checked the solution before replying: at least on OCaml 4.14.2 toplevel, it does not seem to work:

$ ocaml
OCaml version 4.14.2
[...]
# let () =
  Format.pp_set_geometry ~margin:3 ~max_indent:2 Format.str_formatter;
  let s = Format.asprintf "a@ a@ a@ a@ a" in Format.printf "%s@." s;;    
a a a a
a
# let _ =
  Format.pp_set_geometry ~margin:3 ~max_indent:2 Format.str_formatter;
  let s = Format.asprintf "a@ a@ a@ a@ a" in s;;    
- : string = "a a a a\na"
# 

Unless I’m misunderstanding, the intent of the above pp_set_geometry is that s would have a \n after the second a?

You are right, the str_formatter is not connected to the sprintf function, I should have checked.

It is thus probably better to create your own asprintf function:

let asprintf' ~margin ~max_indent fmt =
   let b = Buffer.create 17 in
   let ppf = Format.formatter_of_buffer b in
   Format.pp_set_geometry ~margin ~max_indent ppf;
   Format.kfprintf (fun ppf ->
      Format.pp_print_flush ppf ();
      Buffer.contents b
   ) ppf fmt
2 Likes

For the sake of completeness, it is also possible to get ahold of the formatter used by asprintf: https://discuss.ocaml.org/t/disabling-format-wrapping/15180/3?u=johnj

2 Likes