From 95d50b25c990e8c945ce2507b16ff3c8b039d286 Mon Sep 17 00:00:00 2001 From: Rutger Broekhoff Date: Mon, 25 Aug 2025 19:48:19 +0200 Subject: OCaml --- lib/iban.ml | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lib/iban.ml (limited to 'lib/iban.ml') diff --git a/lib/iban.ml b/lib/iban.ml new file mode 100644 index 0000000..6e47e9d --- /dev/null +++ b/lib/iban.ml @@ -0,0 +1,87 @@ +open Core +open Option.Let_syntax + +type t = string + +(* Modulo-97 arithmetic. Prevents us from having to use Zarith here. *) +module M97 : sig + type t + + val of_int : int -> t + val lt : t -> t -> bool + val equal : t -> t -> bool + val ( * ) : t -> t -> t + val ( + ) : t -> t -> t + val ( ~$ ) : int -> t +end = struct + type t = int + + let of_int x = x % 97 + let equal = Int.( = ) + let lt = Int.( < ) + let ( * ) x y = x * y % 97 + let ( + ) x y = (x + y) % 97 + let ( ~$ ) = of_int +end + +let m97_of_alnum c = + let v = Char.to_int c in + if Char.is_digit c then Some (M97.of_int (v - Char.to_int '0')) + else if Char.is_alpha c then + if Char.is_lowercase c then Some (M97.of_int (v - Char.to_int 'a' + 10)) + else Some (M97.of_int (v - Char.to_int 'A' + 10)) + else None + +let m97_of_digit c = + match m97_of_alnum c with Some v when M97.(lt v ~$10) -> Some v | _ -> None + +let m97_of_alpha c = + match m97_of_alnum c with + | Some v when not M97.(lt v ~$10) -> Some v + | _ -> None + +let string_fold_option ~(init : 'a) ~(f : 'a -> char -> 'a option) s = + let rec go i (acc : 'a) : 'a option = + if i >= String.length s then Some acc + else Option.(f acc (String.unsafe_get s i) >>= go (i + 1)) + in + go 0 init + +let m97_of_iban s = + string_fold_option s ~init:`In_country1 ~f:(fun st c -> + match st with + | `In_country1 -> + let%map co1 = m97_of_alpha c in + `In_country2 co1 + | `In_country2 co1 -> + let%map co2 = m97_of_alpha c in + `In_check1 M97.((co1 * ~$100) + co2) + | `In_check1 co -> + let%map ch1 = m97_of_digit c in + `In_check2 (co, ch1) + | `In_check2 (co, ch1) -> + let%map ch2 = m97_of_digit c in + `In_bban M97.(co, (ch1 * ~$10) + ch2, ~$0) + | `In_bban (co, ch, bban) -> + let%map v = m97_of_alnum c in + let bban' = + M97.(if lt v ~$10 then (bban * ~$10) + v else (bban * ~$100) + v) + in + `In_bban (co, ch, bban')) + |> function + | Some (`In_bban (co, ch, bban)) -> + Some M97.((bban * ~$1000000) + (co * ~$100) + ch) + | _ -> None + +let check_iban s = + String.length s <= 34 && Option.exists (m97_of_iban s) ~f:M97.(equal ~$1) + +let make s : t option = if check_iban s then Some s else None +let to_string = Fn.id + +let of_string s = + match make s with + | Some iban -> iban + | None -> Printf.failwithf "Iban.of_string: %S" s () + +let equal = String.equal -- cgit v1.2.3