blob: 1db6c7b1c04b2a71cd618d82b9050ae8701d1fe0 (
about) (
plain) (
blame)
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
|
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 sexp_of_t iban = Sexp.Atom iban
let equal = String.equal
|