package ppx_pyformat

  1. Overview
  2. Docs

Source file ppx_pyformat_runtime.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
external format_int : string -> int -> string = "caml_format_int"

external bytes_unsafe_blit_string :
  string -> int -> bytes -> int -> int -> unit
  = "caml_blit_string"
  [@@noalloc]

let align_left c w s =
  let len = String.length s in
  if len >= w then
    s
  else
    let b = Bytes.create w in
    bytes_unsafe_blit_string s 0 b 0 len;
    Bytes.unsafe_fill b len (w - len) c;
    Bytes.unsafe_to_string b

let align_right c w s =
  let len = String.length s in
  if len >= w then
    s
  else
    let b = Bytes.create w and fill_len = w - len in
    Bytes.unsafe_fill b 0 fill_len c;
    bytes_unsafe_blit_string s 0 b fill_len len;
    Bytes.unsafe_to_string b

let align_center c w s =
  let len = String.length s in
  if len >= w then
    s
  else
    let b = Bytes.create w in
    let left_len = (w - len) / 2 in
    let right_len = w - len - left_len in
    Bytes.unsafe_fill b 0 left_len c;
    bytes_unsafe_blit_string s 0 b left_len len;
    Bytes.unsafe_fill b (left_len + len) right_len c;
    Bytes.unsafe_to_string b

type padding_config = char * int
type sign = Plus | Minus | Space
type grouping_option = Comma | Underscore

(** get sign string of number *)
let sign_str_of_num is_positive sign num =
  match sign with
  | Plus when is_positive num -> "+"
  | Minus when is_positive num -> ""
  | Space when is_positive num -> " "
  | _ -> "-"

let sign_str_of_int = sign_str_of_num (fun num -> num >= 0)
let sign_str_of_float = sign_str_of_num (fun num -> not (Float.sign_bit num))

let grouping_config_of_grouping_option grouping_option =
  match grouping_option with
  | Some Underscore -> Some ('_', 3)
  | Some Comma -> Some (',', 3)
  | None -> None

(** insert grouping separator into string *)
let insert_grouping separator sep_width str =
  let l = String.length str in
  if l <= sep_width then
    str
  else
    let rem = Int.rem l sep_width in
    let width, ini_len =
      if rem = 0 then
        ((l / sep_width * (sep_width + 1)) - 1, sep_width)
      else
        ((l / sep_width * (sep_width + 1)) + rem, rem)
    in
    let b = Bytes.create width in
    let rec impl spos bpos =
      if bpos < width then (
        Bytes.set b bpos separator;
        Bytes.blit_string str spos b (bpos + 1) sep_width;
        impl (spos + sep_width) (bpos + sep_width + 1))
    in
    Bytes.blit_string str 0 b 0 ini_len;
    impl ini_len ini_len;
    Bytes.unsafe_to_string b

(** handle grouping and padding option *)
let handle_padding_grouping padding grouping prefix num_str suffix =
  let formated =
    match (padding, grouping) with
    | Some (c, w), Some (gc, gw) ->
        let num_w = w - String.length prefix - String.length suffix in
        (* grouping separator only applied to {0} fill *)
        if c = '0' then
          (* max 1 for avoiding _0000 situation, so filling extra 0 *)
          let act_w = (num_w / (gw + 1) * gw) + max 1 (num_w mod (gw + 1)) in
          align_right c act_w num_str |> insert_grouping gc gw
        else
          num_str |> insert_grouping gc gw |> align_right c num_w
    | Some (c, w), None ->
        let num_w = w - String.length prefix - String.length suffix in
        align_right c num_w num_str
    | None, Some (gc, gw) -> insert_grouping gc gw num_str
    | None, None -> num_str
  in
  prefix ^ formated ^ suffix

(** handle grouping and padding option for int string *)
let handle_int_padding_grouping pad grouping prefix num_str =
  handle_padding_grouping pad grouping prefix num_str ""

(** handle upper option *)
let handle_upper upper str = if upper then String.uppercase_ascii str else str

let rec string_of_binary_int_impl (b, l) cur =
  if cur = 0 then
    Bytes.sub_string b (64 - l) l
  else (
    if cur mod 2 = 0 then
      Bytes.unsafe_set b (63 - l) '0'
    else
      Bytes.unsafe_set b (63 - l) '1';
    string_of_binary_int_impl (b, l + 1) (Int.shift_right cur 1))

(** convert int to binary string. only take non-negative number *)
let string_of_binary_int num =
  if num = 0 then
    "0"
  else
    let b = Bytes.create 64 in
    string_of_binary_int_impl (b, 0) num

let int_to_binary
    ?padding
    ?(sign = Minus)
    ?(alternate_form = false)
    ?(grouping = false)
    num =
  let prefix = sign_str_of_int sign num ^ if alternate_form then "0b" else "" in
  let grouping = if grouping then Some ('_', 4) else None in
  let num_str = string_of_binary_int (abs num) in
  handle_int_padding_grouping padding grouping prefix num_str

(* since char does not take {Pad}, will dispatch align in rewriter *)
let int_to_char num = Char.chr num |> String.make 1

let int_to_decimal ?padding ?(sign = Minus) ?grouping_option num =
  let prefix = sign_str_of_int sign num in
  let grouping = grouping_config_of_grouping_option grouping_option in
  let num_str = string_of_int (abs num) in
  handle_int_padding_grouping padding grouping prefix num_str

let int_to_octal
    ?padding
    ?(sign = Minus)
    ?(alternate_form = false)
    ?(grouping = false)
    num =
  let prefix = sign_str_of_int sign num ^ if alternate_form then "0o" else "" in
  let grouping = if grouping then Some ('_', 4) else None in
  let num_str = format_int "%o" (abs num) in
  handle_int_padding_grouping padding grouping prefix num_str

let int_to_hexadecimal
    ?padding
    ?(sign = Minus)
    ?(alternate_form = false)
    ?(grouping = false)
    ?(upper = false)
    num =
  let prefix =
    sign_str_of_int sign num
    ^ if not alternate_form then "" else if upper then "0X" else "0x"
  in
  let grouping = if grouping then Some ('_', 4) else None in
  let num_str = abs num |> format_int "%x" |> handle_upper upper in
  handle_int_padding_grouping padding grouping prefix num_str

let is_special_float num = not (Float.is_finite num)

let handle_special_float ?padding ~sign ~upper ?(suffix = "") num =
  let prefix = sign_str_of_float sign num in
  let num_str = Float.abs num |> string_of_float |> handle_upper upper in
  handle_padding_grouping padding None prefix num_str suffix

(** turn string into char list  *)
let char_list_of_string s =
  let rec impl i l = if i < 0 then l else impl (i - 1) (s.[i] :: l) in
  impl (String.length s - 1) []

let remove_trailing_zero ~is_scientific remove_zero str =
  if (not remove_zero) || not (String.contains str '.') then
    str
  else
    let num_str, suffix =
      if is_scientific then
        let len = String.length str in
        (String.sub str 0 (len - 4), String.sub str (len - 4) 4)
      else
        (str, "")
    in
    let _, l =
      List.fold_left
        (fun (check, l) cur ->
          if not check then
            (false, l)
          else if cur = '0' then
            (true, l + 1)
          else if cur = '.' then
            (false, l + 1)
          else
            (false, l))
        (true, 0)
        (char_list_of_string num_str |> List.rev)
    in
    String.sub num_str 0 (String.length num_str - l) ^ suffix

let string_of_scientific_float ?(precision = 6) num =
  Printf.sprintf "%.*e" precision num

let float_to_scientific_impl
    ?padding
    ?(sign = Minus)
    ?(alternate_form = false)
    ?grouping_option
    ?(precision = 6)
    ?(upper = false)
    ~remove_zero
    num =
  if is_special_float num then
    handle_special_float ?padding ~sign ~upper num
  else
    let prefix = sign_str_of_float sign num in
    let grouping = grouping_config_of_grouping_option grouping_option in
    let num_str =
      Float.abs num
      |> string_of_scientific_float ~precision
      |> handle_upper upper
      |> remove_trailing_zero ~is_scientific:true
           (remove_zero && not alternate_form)
    in
    let int_str = String.sub num_str 0 1 in
    let fac_str = String.sub num_str 1 (String.length num_str - 1) in
    let suffix =
      if precision = 0 && alternate_form then
        "." ^ fac_str
      else
        fac_str
    in
    handle_padding_grouping padding grouping prefix int_str suffix

let float_to_scientific = float_to_scientific_impl ~remove_zero:false

let string_of_fixed_point_float ?(precision = 6) num =
  Printf.sprintf "%.*f" precision num

let float_to_fixed_point_impl
    ?padding
    ?(sign = Minus)
    ?(alternate_form = false)
    ?grouping_option
    ?(precision = 6)
    ?(upper = false)
    ~suffix
    ~remove_zero
    num =
  if is_special_float num then
    handle_special_float ?padding ~sign ~upper ~suffix num
  else
    let prefix = sign_str_of_float sign num in
    let grouping = grouping_config_of_grouping_option grouping_option in
    let num_str =
      Float.abs num
      |> string_of_fixed_point_float ~precision
      |> handle_upper upper
      |> remove_trailing_zero ~is_scientific:false
           (remove_zero && not alternate_form)
    in
    let int_str, fac_str =
      match String.split_on_char '.' num_str with
      | [ int_str ] -> (int_str, "")
      | [ int_str; fac_str ] -> (int_str, fac_str)
      | _ -> ("", num_str)
    in
    let suffix =
      if String.length fac_str > 0 || alternate_form then
        "." ^ fac_str ^ suffix
      else
        fac_str ^ suffix
    in
    handle_padding_grouping padding grouping prefix int_str suffix

let float_to_fixed_point =
  float_to_fixed_point_impl ~suffix:"" ~remove_zero:false

let float_to_general
    ?padding
    ?(sign = Minus)
    ?(alternate_form = false)
    ?grouping_option
    ?(precision = 6)
    ?(upper = false)
    num =
  let precision = max precision 1 in
  let exp = Float.abs num |> Float.log10 |> Float.floor |> int_of_float in
  let format_func =
    if -4 <= exp && exp < precision then
      float_to_fixed_point_impl ~precision:(precision - 1 - exp) ~suffix:""
    else
      float_to_scientific_impl ~precision:(precision - 1)
  in
  format_func ?padding ~sign ~alternate_form ?grouping_option ~upper
    ~remove_zero:true num

let float_to_percentage
    ?padding
    ?sign
    ?alternate_form
    ?grouping_option
    ?precision
    ?upper
    num =
  float_to_fixed_point_impl ?padding ?sign ?alternate_form ?grouping_option
    ?precision ?upper ~suffix:"%" ~remove_zero:false (num *. 100.)
OCaml

Innovation. Community. Security.