summaryrefslogtreecommitdiff
path: root/src/StringUtils.ml
blob: 6089efc804844453f873892494a731a91b8995db (plain)
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
(** Utilities to work on strings, character per character.

    They operate on ASCII strings, and are used by the project to convert
    Rust names: Rust names are not fancy, so it shouldn't be a problem.
    
    Rk.: the poor support of OCaml for char manipulation is really annoying...
 *)

let code_0 = 48

let code_9 = 57

let code_A = 65

let code_Z = 90

let code_a = 97

let code_z = 122

let is_lowercase_ascii (c : char) : bool =
  let c = Char.code c in
  code_a <= c && c <= code_z

let is_uppercase_ascii (c : char) : bool =
  let c = Char.code c in
  code_A <= c && c <= code_Z

let is_letter_ascii (c : char) : bool =
  is_lowercase_ascii c || is_uppercase_ascii c

let is_digit_ascii (c : char) : bool =
  let c = Char.code c in
  code_0 <= c && c <= code_9

let lowercase_ascii = Char.lowercase_ascii

let uppercase_ascii = Char.uppercase_ascii

(** Using buffers as per:
    [https://stackoverflow.com/questions/29957418/how-to-convert-char-list-to-string-in-ocaml]
 *)
let string_of_chars (chars : char list) : string =
  let buf = Buffer.create (List.length chars) in
  List.iter (Buffer.add_char buf) chars;
  Buffer.contents buf

let string_to_chars (s : string) : char list =
  let length = String.length s in
  let rec apply i =
    if i = length then [] else String.get s i :: apply (i + 1)
  in
  apply 0

(** This operates on ASCII *)
let to_camel_case (s : string) : string =
  (* There are no [fold_left_map] in [String]... *)
  let mk_upper = ref true in
  (* TODO: remove '_' *)
  let apply (c : char) : char =
    if !mk_upper then (
      mk_upper := c = '_';
      uppercase_ascii c)
    else (
      mk_upper := c = '_';
      c)
  in
  String.map apply s

(** This operates on ASCII *)
let to_snake_case (s : string) : string =
  (* Note that we rebuild the string in invert order *)
  let apply ((prev_is_low, prev_is_digit, acc) : bool * bool * char list)
      (c : char) : bool * bool * char list =
    let acc =
      if prev_is_digit then if is_letter_ascii c then '_' :: acc else acc
      else if prev_is_low then
        if is_lowercase_ascii c || is_digit_ascii c then acc else '_' :: acc
      else acc
    in
    let prev_is_low = is_lowercase_ascii c in
    let prev_is_digit = is_digit_ascii c in
    let c = lowercase_ascii c in
    (prev_is_low, prev_is_digit, c :: acc)
  in
  let _, _, chars =
    List.fold_left apply (false, false, []) (string_to_chars s)
  in
  string_of_chars (List.rev chars)

(** Unit tests *)
let _ =
  assert (is_digit_ascii '3');
  assert (is_digit_ascii '6');
  assert (to_snake_case "HelloWorld36Hello" = "hello_world36_hello");
  assert (to_snake_case "HELLO" = "hello")