summaryrefslogtreecommitdiffstats
path: root/lib/account.ml
diff options
context:
space:
mode:
Diffstat (limited to 'lib/account.ml')
-rw-r--r--lib/account.ml96
1 files changed, 96 insertions, 0 deletions
diff --git a/lib/account.ml b/lib/account.ml
new file mode 100644
index 0000000..988b55c
--- /dev/null
+++ b/lib/account.ml
@@ -0,0 +1,96 @@
1open Prelude
2
3(** The 'kernel' of account types: a hierarchy of valid types. A valid type is a
4 path that leads to a node in the hierarchy. *)
5module Type_hierarchy : sig
6 type path
7
8 val children : path -> path list
9 val sub : path -> string -> path option
10 val super : path -> path option
11 val equal_path : path -> path -> bool
12 val is_prefix : path -> prefix:path -> bool
13 val root : path
14 val asset : path
15 val equity : path
16 val expense : path
17 val income : path
18 val liability : path
19end = struct
20 type tree = { car : tree String.Map.t }
21 type path = Root | Sub of string * path
22
23 let canonical : tree =
24 let mk alist = { car = String.Map.of_alist_exn alist } in
25 mk
26 [
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
42 let rec get_node : path -> tree option = function
43 | Root -> Some canonical
44 | Sub (t, p) ->
45 let open Option.Let_syntax in
46 let%bind super = get_node p in
47 Map.find super.car t
48
49 (** Always gives [Some] under valid paths, giving a list of valid paths *)
50 let children (p : path) : path list =
51 let node = Option.value_exn (get_node p) in
52 List.map (Map.keys node.car) ~f:(fun sub -> Sub (sub, p))
53
54 let sub (p : path) (name : string) : path option =
55 let node = Option.value_exn (get_node p) in
56 if Map.mem node.car name then Some (Sub (name, p)) else None
57
58 let super : path -> path option = function
59 | Root -> None
60 | Sub (_, super) -> Some super
61
62 let rec equal_path p1 p2 =
63 match (p1, p2) with
64 | Root, Root -> true
65 | Sub (x1, p1'), Sub (x2, p2') -> String.(x1 = x2) && equal_path p1' p2'
66 | _, _ -> false
67
68 let rec is_prefix (p : path) ~(prefix : path) : bool =
69 match (prefix, p) with
70 | Root, Root | Root, Sub _ -> true
71 | Sub (x1, p'), Sub (x2, prefix') ->
72 String.(x1 = x2) && is_prefix p' ~prefix:prefix'
73 | _ -> false
74
75 let root = Root
76 let asset = sub root "Asset" |> Option.value_exn
77 let equity = sub root "Equity" |> Option.value_exn
78 let expense = sub root "Expense" |> Option.value_exn
79 let income = sub root "Income" |> Option.value_exn
80 let liability = sub root "Liability" |> Option.value_exn
81end
82
83module Type = struct
84 type t = Type_hierarchy.path [@@deriving equal]
85
86 let rec base (t : t) : t option =
87 match Type_hierarchy.super t with
88 | None -> (* [t] is the root type *) None
89 | Some t' ->
90 (* [t] is a base type if its supertype is the root type *)
91 Some (Option.value (base t') ~default:t)
92
93 (** [a] is a strict supertype of [b] *)
94 let is_strict_super a b =
95 Type_hierarchy.is_prefix b ~prefix:a && not ([%equal: t] a b)
96end