This package provides a PPX extension for string-interplation, that
expands into Fmt
-based code. So the extension
[%fmt_str {|a b $(c) d e|}]
can be used anywhere an expression can be used. The payload must be a
string literal. In that string literal, OCaml expressions can be
interpolated, and formatting-combinators (in the style of fmt
) or
Printf format-specifiers (e.g. %d
) can be specified along with the
expressions. There are copious examples below
To install this package via opam:
opam install pa_ppx_fmtformat
to build and test from source:
opam install pa_ppx
opam install --deps-only -t .
and then
make sys test
# #use "topfind.camlp5";;
- : unit = ()
Findlib has been successfully loaded. Additional directives:
#require "package";; to load a package
#list;; to list the available packages
#camlp4o;; to load camlp4 (standard syntax)
#camlp4r;; to load camlp4 (revised syntax)
#predicates "p,q,...";; to set these predicates
Topfind.reset();; to force that packages will be reloaded
#thread;; to enable threads
- : unit = ()
Additional Camlp5 directives:
#camlp5o;; to load camlp5 (standard syntax)
#camlp5r;; to load camlp5 (revised syntax)
- : unit = ()
# #camlp5o ;;
# #require "camlp5.pr_o";;
# #require "pa_ppx_fmtformat" ;;
First, some simple examples generating strings:
# let c = "argle" in [%fmt_str {|a b $(c) d e|}] ;;
- : string = "a b argle d e"
# {%fmt_str|a b c $("argle") d e f|} ;;
- : string = "a b c argle d e f"
# let c = "argle" in {%fmt_str|a b c $(c|Dump.string) d e f|} ;;
- : string = "a b c \"argle\" d e f"
# let c = "argle" in {%fmt_str|a b c $(c|string) d e f|} ;;
- : string = "a b c argle d e f"
# let c = "argle" in {%fmt_str|a b c $(c|%s) d e f|} ;;
- : string = "a b c argle d e f"
# let c = 3.1415 in {%fmt_str|a b c $(c|%f) d e f|} ;;
- : string = "a b c 3.141500 d e f"
# {%fmt_str| $$ % |} ;;
- : string = " $ % "
And then to stdout. Notice that the generated function has type unit
Fmt.t
so it can be used directly in further invocations.
# {%fmt_pf|a b c $("argle") d e f@.|} Fmt.stdout () ;;
a b c argle d e f
- : unit = ()
# let f = {%fmt_pf|a b c $("argle") d e f@.|} ;;
val f : Format.formatter -> unit -> unit = <fun>
# Fmt.(pf stdout "here: %a@." f ()) ;;
here: a b c argle d e f
- : unit = ()
# let _ =
let hello = "Hello" in
let world = "world" in
[%fmt_pf "${hello} ${world}!@."] Fmt.stdout () ;;
Hello world!
- : unit = ()
# let _ =
[%fmt_pf "pi = ${Float.pi|%.10f}@."] Fmt.stdout () ;;
pi = 3.1415926536
- : unit = ()
# let pair_formatter = Fmt.(parens (pair ~sep:(const string ", ") string string)) ;;
val pair_formatter : (string * string) Fmt.t = <fun>
# let _ =
[%fmt_pf {|${("foo", "bar")| pair_formatter}@.|}] Fmt.stdout ();;
(foo, bar)
- : unit = ()
# #require "pp-binary-ints";;
# module Pp_Bin = Pp_binary_ints.Int;;
module Pp_Bin = Pp_binary_ints.Int
# let _ =
[%fmt_pf "the answer to life the universe and everything ${42|of_to_string Pp_Bin.to_string}@."] Fmt.stdout () ;;
the answer to life the universe and everything 0b10_1010
- : unit = ()
# let _ =
[%fmt_pf "the answer to life the universe and everything ${42|of_to_string (Pp_Bin.make_to_string ~min_width:20 ())}@."] Fmt.stdout () ;;
the answer to life the universe and everything 0b000_0000_0010_1010
- : unit = ()
Instead of defining a "pair formatter" (to match what ppx_pyformat
's
example does), one can put the complex formatter-code right into the
interpolated expression.
# let _ =
[%fmt_pf {|${("foo", "bar")| parens (pair ~sep:(const string ", ") string string) }@.|}] Fmt.stdout () ;;
(foo, bar)
- : unit = ()
The contents of the string in [%fmt_str <string>]
consist in either
plain text, or interpolated expressions.
Plain text is anything other than $
. To express a $
, double it,
viz. $$
.
One thing to remember is that in Fmt
format-strings, to emit a
literal %
, one typically writes a doubled %%
. But since a PPX
rewriter is managing the entire process, it does the doubling for you:
you just write:
# {%fmt_str|%|} ;;
- : string = "%"
The simplest interpolated expression is of the form $(...)
but all of the following are accepted:
-
$(...)
,$(|...|)
-
$[...]
,$[|...|]
-
${...}
,${|...|}
-
$<...>
,$<|...|>
So basically, $ followed by any of [ (, [, {, < ], optionally |, and then at the end, the matching text. Between these 8 forms, it should be possible to enclose any interpolated expression without difficulty, I would think.
In the text surrounded by these delimiter, anything other than the end-string is acceptable, and there is no provision made for escaping.
The contents of the interpolated expression can be of three forms:
an interpolated expression of the form $(abc|%d)
specifies that the
expression abc
will be formatted with %d
. So {%fmt_str|a $(abc|%d)|}
expands to
Fmt.(str "a %d" abc)
.
an interpolated expression of the form $(abc|int)
specifies that the
expression abc
will be formatted with the Fmt formatter int
. So {%fmt_str|a $(abc|int)|}
expands to
Fmt.(str "a %a" int abc)
.
an interpolated expression of the form $(abc)
specifies that the
expression abc
will be formatted with %s
. So {%fmt_str|a $(abc)|}
expands to
Fmt.(str "a %s" abc)
.