summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/account.ml188
1 files changed, 151 insertions, 37 deletions
diff --git a/lib/account.ml b/lib/account.ml
index 988b55c..3a1aff0 100644
--- a/lib/account.ml
+++ b/lib/account.ml
@@ -4,45 +4,65 @@ open Prelude
4 path that leads to a node in the hierarchy. *) 4 path that leads to a node in the hierarchy. *)
5module Type_hierarchy : sig 5module Type_hierarchy : sig
6 type path 6 type path
7 type polarity = Increase_on_debit | Increase_on_credit
7 8
8 val children : path -> path list 9 val children : path -> path list
9 val sub : path -> string -> path option 10 val sub : path -> string -> path option
10 val super : path -> path option 11 val super : path -> path option
11 val equal_path : path -> path -> bool 12 val equal_path : path -> path -> bool
12 val is_prefix : path -> prefix:path -> bool 13 val is_prefix : path -> prefix:path -> bool
13 val root : path 14 val polarity : path -> polarity
14 val asset : path 15 val asset : path
15 val equity : path 16 val equity : path
16 val expense : path 17 val expense : path
17 val income : path 18 val income : path
18 val liability : path 19 val liability : path
19end = struct 20end = struct
21 type polarity = Increase_on_debit | Increase_on_credit
20 type tree = { car : tree String.Map.t } 22 type tree = { car : tree String.Map.t }
21 type path = Root | Sub of string * path 23 type trunk = { car : (polarity * tree) String.Map.t }
24 type path = Base of string | Sub of string * path
22 25
23 let canonical : tree = 26 let rec path_to_list ?(suffix = []) p =
24 let mk alist = { car = String.Map.of_alist_exn alist } in 27 match p with
25 mk 28 | Base x -> x :: suffix
26 [ 29 | Sub (x, p') -> path_to_list ~suffix:(x :: suffix) p'
27 ( "Asset",
28 mk
29 [
30 ("Accounts_receivable", mk []);
31 ("Bank", mk [ ("Savings", mk []); ("Checking", mk []) ]);
32 ("Cash", mk []);
33 ("Mutual_fund", mk []);
34 ("Stock", mk []);
35 ] );
36 ("Equity", mk []);
37 ("Expense", mk []);
38 ("Income", mk []);
39 ("Liability", mk [ ("Accounts_payable", mk []); ("Credit", mk []) ]);
40 ]
41 30
42 let rec get_node : path -> tree option = function 31 let canonical : trunk =
43 | Root -> Some canonical 32 let mk alist : tree = { car = String.Map.of_alist_exn alist } in
33 {
34 car =
35 String.Map.of_alist_exn
36 [
37 ( "Asset",
38 ( Increase_on_debit,
39 mk
40 [
41 ("Accounts_receivable", mk []);
42 ("Bank", mk [ ("Savings", mk []); ("Checking", mk []) ]);
43 ("Cash", mk []);
44 ("Mutual_fund", mk []);
45 ("Stock", mk []);
46 ] ) );
47 ("Equity", (Increase_on_credit, mk []));
48 ("Expense", (Increase_on_debit, mk []));
49 ("Income", (Increase_on_credit, mk []));
50 ( "Liability",
51 ( Increase_on_credit,
52 mk [ ("Accounts_payable", mk []); ("Credit", mk []) ] ) );
53 ];
54 }
55
56 (* In this module, only the following two function entertains the
57 option that the given path may not be valid (i.e., it does not
58 throw an exception for invalid paths). *)
59 let rec get_node : path -> tree option =
60 let open Option.Let_syntax in
61 function
62 | Base x ->
63 let%map _, t = Map.find canonical.car x in
64 t
44 | Sub (t, p) -> 65 | Sub (t, p) ->
45 let open Option.Let_syntax in
46 let%bind super = get_node p in 66 let%bind super = get_node p in
47 Map.find super.car t 67 Map.find super.car t
48 68
@@ -56,28 +76,33 @@ end = struct
56 if Map.mem node.car name then Some (Sub (name, p)) else None 76 if Map.mem node.car name then Some (Sub (name, p)) else None
57 77
58 let super : path -> path option = function 78 let super : path -> path option = function
59 | Root -> None 79 | Base _ -> None
60 | Sub (_, super) -> Some super 80 | Sub (_, super) -> Some super
61 81
62 let rec equal_path p1 p2 = 82 let rec equal_path p1 p2 =
63 match (p1, p2) with 83 match (p1, p2) with
64 | Root, Root -> true 84 | Base x1, Base x2 -> String.(x1 = x2)
65 | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2' 85 | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2'
66 | _, _ -> false 86 | _, _ -> false
67 87
68 let rec is_prefix (p : path) ~(prefix : path) : bool = 88 let is_prefix (p : path) ~(prefix : path) : bool =
69 match (prefix, p) with 89 List.is_prefix (path_to_list p) ~prefix:(path_to_list prefix)
70 | Root, Root | Root, Sub _ -> true 90 ~equal:String.equal
71 | Sub (x1, p'), Sub (x2, prefix') -> 91
72 String.(x1 = x2) && is_prefix p' ~prefix:prefix' 92 let rec polarity = function
73 | _ -> false 93 | Base x ->
94 let pol, _ = Map.find_exn canonical.car x in
95 pol
96 | Sub (_, p') -> polarity p'
97
98 let assert_valid acc =
99 match get_node acc with None -> failwith "invalid base account" | _ -> acc
74 100
75 let root = Root 101 let asset = Base "Asset" |> assert_valid
76 let asset = sub root "Asset" |> Option.value_exn 102 let equity = Base "Equity" |> assert_valid
77 let equity = sub root "Equity" |> Option.value_exn 103 let expense = Base "Expense" |> assert_valid
78 let expense = sub root "Expense" |> Option.value_exn 104 let income = Base "Income" |> assert_valid
79 let income = sub root "Income" |> Option.value_exn 105 let liability = Base "Liability" |> assert_valid
80 let liability = sub root "Liability" |> Option.value_exn
81end 106end
82 107
83module Type = struct 108module Type = struct
@@ -94,3 +119,92 @@ module Type = struct
94 let is_strict_super a b = 119 let is_strict_super a b =
95 Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b) 120 Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b)
96end 121end
122
123module Debit_credit = struct
124 type t = Debit | Credit [@@deriving string, sexp_of]
125
126 (* let opposite = function Debit -> Credit | Credit -> Debit *)
127end
128
129module Money = struct
130 module Amount : sig
131 type t
132
133 val equal : t -> t -> bool
134 val compare : t -> t -> int
135 val of_bigint : Bigint.t -> t option
136 val to_bigint : t -> Bigint.t
137 val ( + ) : t -> t -> t
138 val ( = ) : t -> t -> bool
139 val sexp_of_t : t -> Sexp.t
140 val zero : t
141 end = struct
142 type t = Bigint.t [@@deriving sexp_of]
143
144 let equal = Bigint.equal
145 let compare = Bigint.compare
146 let of_bigint x = if Bigint.(zero <= x) then Some x else None
147 let to_bigint x = x
148 let ( + ) x y = Bigint.(x + y)
149 let ( = ) = equal
150 let zero = Bigint.zero
151 end
152
153 module Diff : sig
154 type t
155
156 val equal : t -> t -> bool
157 val compare : t -> t -> int
158 val of_bigint : Bigint.t -> t
159 val to_bigint : t -> Bigint.t
160 val ( + ) : t -> t -> t
161 val ( +% ) : t -> Amount.t -> t
162 val ( - ) : t -> t -> t
163 val ( -% ) : t -> Amount.t -> t
164 val ( = ) : t -> t -> bool
165 val neg : t -> t
166 val ( ~$ ) : int -> t
167 val sexp_of_t : t -> Sexp.t
168
169 val of_amount :
170 Amount.t -> Debit_credit.t -> on_debit:[ `Incr | `Decr ] -> t
171 end = struct
172 type t = Bigint.t [@@deriving sexp_of]
173
174 let equal = Bigint.equal
175 let compare = Bigint.compare
176 let of_bigint x = x
177 let to_bigint x = x
178 let ( + ) x y = Bigint.(x + y)
179 let ( +% ) x y = x + of_bigint (Amount.to_bigint y)
180 let ( - ) x y = Bigint.(x - y)
181 let ( -% ) x y = x - of_bigint (Amount.to_bigint y)
182 let ( = ) = equal
183 let neg = Bigint.neg
184 let ( ~$ ) = Fn.compose of_bigint Bigint.of_int
185
186 let of_amount x (dc : Debit_credit.t) ~on_debit =
187 match (dc, on_debit) with
188 | Debit, `Incr -> of_bigint (Amount.to_bigint x)
189 | Credit, `Incr -> neg (of_bigint (Amount.to_bigint x))
190 | Debit, `Decr -> neg (of_bigint (Amount.to_bigint x))
191 | Credit, `Decr -> of_bigint (Amount.to_bigint x)
192 end
193end
194
195module Commodity_id = struct
196 type t = string [@@deriving equal, compare, sexp]
197
198 module Map = Map.Make (struct
199 type nonrec t = t [@@deriving equal, compare, sexp]
200 end)
201end
202
203module Account = struct
204 type t = Type.t * node
205
206 and node =
207 (* Balance in some commodity *)
208 | Leaf of Commodity_id.t * Money.Diff.t
209 | Subtree of node String.Map.t
210end