type user = [`i | `o | `w] type chan_a = [`b] type chan_b = [`k | `o | `v] type chan_c = [`l] type chan_d = [`i | `m | `s | `t | `n] type chan = [chan_a | chan_b | chan_c | chan_d] type t = [user | chan] let to_string = function | `b -> "b" | `i -> "i" | `k -> "k" | `l -> "l" | `m -> "m" | `n -> "n" | `o -> "o" | `s -> "s" | `t -> "t" | `v -> "v" | `w -> "w" let to_char x = (to_string x).[0] let pp ppf x = Format.pp_print_string ppf (to_string x) let of_char_user = function | 'i' -> `i | 'o' -> `o | 'w' -> `w | _ -> invalid_arg "Mode.of_char_user" let of_char_chan = function | 'b' -> `b | 'i' -> `i | 'k' -> `k | 'l' -> `l | 'm' -> `m | 'n' -> `n | 'o' -> `o | 's' -> `s | 't' -> `t | 'v' -> `v | _ -> invalid_arg "Mode.of_char_chan" let[@warning "+8"] _check_exhaustive (x : chan) = match x with | #chan_a -> () | #chan_b -> () | #chan_c -> () | #chan_d -> () module Set = struct type t = int type elt = [user | chan_d] let[@inline] singleton = function | `i -> 1 | `m -> 2 | `n -> 4 | `o -> 8 | `s -> 16 | `t -> 32 | `w -> 64 let empty = 0 let equal = Int.equal let union = ( lor ) let inter = ( land ) let diff s t = s land Int.lognot t let mem elt s = s land (singleton elt) <> 0 let add elt s = union s (singleton elt) let remove elt s = diff s (singleton elt) let to_string s = let bs = Bytes.create 7 in let bit ch elt i = if mem elt s then (Bytes.set bs i ch; i + 1) else i in let n = 0 |> bit 'i' `i |> bit 'm' `m |> bit 'n' `n |> bit 'o' `o |> bit 's' `s |> bit 't' `t |> bit 'w' `w in Bytes.sub_string bs 0 n let pp ppf s = Format.pp_print_string ppf (to_string s) let of_string s = let chr = function | 'i' -> `i | 'm' -> `m | 'n' -> `n | 'o' -> `o | 's' -> `s | 't' -> `t | 'w' -> `w | _ -> invalid_arg "Irc.Mode.Set.of_string" in String.fold_left (fun s c -> add (chr c) s) empty s end module Parse = struct exception Error let fail fmt = Format.kasprintf (fun _ -> raise Error) fmt type user_mode_set = { add : Set.t; rem : Set.t; } let pp_user_mode_set ppf { add; rem } = match add, rem with | _, 0 -> Format.fprintf ppf "+%a" Set.pp add | 0, _ -> Format.fprintf ppf "-%a" Set.pp rem | _, _ -> Format.fprintf ppf "+%a-%a" Set.pp add Set.pp rem let parse_mode_set str ~of_char ~add ~rem ~init = let rec loop dir acc i = if i >= String.length str then acc else match str.[i] with | '+' -> loop `add acc (i + 1) | '-' -> loop `rem acc (i + 1) | _ -> let mode = try of_char str.[i] with Invalid_argument _ -> fail "unrecognized mode char" in match dir with | `add -> loop dir (add acc mode) (i + 1) | `rem -> loop dir (rem acc mode) (i + 1) | `none -> fail "mode must start with + or -" in loop `none init 0 let user str = parse_mode_set str ~of_char:of_char_user ~init:{ add = Set.empty; rem = Set.empty } ~add:(fun ms m -> { add = Set.add m ms.add; rem = Set.remove m ms.rem }) ~rem:(fun ms m -> { add = Set.remove m ms.add; rem = Set.add m ms.rem }) end let%expect_test _ = let roundtrip of_char_x m = match of_char_x (to_char m) with | m' when m = m' -> () | m' -> Printf.printf "fail:%s!=%s\n" (to_string m) (to_string m') | exception Invalid_argument _ -> Printf.printf "error:%s\n" (to_string m) in let roundtrip_user m = roundtrip of_char_user m in let roundtrip_chan m = roundtrip of_char_chan m in roundtrip_user `i; roundtrip_user `w; roundtrip_chan `i; roundtrip_chan `k; roundtrip_chan `l; roundtrip_chan `m; roundtrip_chan `n; roundtrip_chan `o; roundtrip_chan `s; roundtrip_chan `t; let print_set_nl s = Printf.printf "[%s]\n" (Set.to_string s) in let print_bool_nl b = print_endline (if b then "true" else "false") in print_set_nl Set.empty; [%expect {| [] |}]; print_set_nl Set.(of_string "i"); [%expect {| [i] |}]; print_set_nl Set.(of_string "no"); [%expect {| [no] |}]; print_set_nl Set.(of_string "sm"); [%expect {| [ms] |}]; print_set_nl Set.(of_string "wi"); [%expect {| [iw] |}]; print_bool_nl Set.(mem `i (of_string "ins")); [%expect "true"]; print_bool_nl Set.(mem `w (of_string "ins")); [%expect "false"]; print_bool_nl Set.(mem `w (of_string "wwww")); [%expect "true"]; print_bool_nl Set.(mem `t (of_string "imnosw")); [%expect "false"]; let print_user_mode_set_nl um = Format.printf "%a\n" Parse.pp_user_mode_set um in let expect_parse_error f = try f () |> ignore; print_endline "no error" with Parse.Error -> () | e -> print_endline (Printexc.to_string e) in print_user_mode_set_nl (Parse.user "+iw"); [%expect {| +iw |}]; print_user_mode_set_nl (Parse.user "-wo"); [%expect {| -ow |}]; print_user_mode_set_nl (Parse.user "+i-w"); [%expect {| +i-w |}]; print_user_mode_set_nl (Parse.user "-i+w"); [%expect {| +w-i |}]; print_user_mode_set_nl (Parse.user "+i-i"); [%expect {| -i |}]; print_user_mode_set_nl (Parse.user "-o+o"); [%expect {| +o |}]; expect_parse_error (fun () -> Parse.user "+b"); expect_parse_error (fun () -> Parse.user "w");