Skip to content

camlp5/pa_ppx_fmtformat

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pa_ppx_fmtformat version 0.01: Fmt-based string-interpolation

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

Installation and Invocation

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

In the OCaml toplevel

# #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" ;;

Examples

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 = ()

Examples shamelessly cribbed from ppx_pyformat' (and modified for `pa_ppx_fmtformat)

# 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 = ()

Syntax of interpolation text

The contents of the string in [%fmt_str <string>] consist in either plain text, or interpolated expressions.

Plain text

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 = "%"

Interpolated expressions

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:

interpolated expression with format-specifier: $( <expression> | <format-specifier> )

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).

interpolated expression with Fmt formatter: $( <expression> | <Fmt formatter expression> )

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).

interpolated expression without specifier/formatter: $( <expression> )

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).

A word about whitespace in interpolated expressions

An interpolated expression consists in either two parts (separated by |) or one part (with no | present). In either case, leading/trailing whitespace in the parts is ignored/removed before further processing. Internal whitespace is preserved.

About

A PPX rewriter to provide string-interpolation, using Fmt as the underlying mechanism

Resources

License

Stars

Watchers

Forks

Packages

No packages published