diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/ledger.ml | 454 | ||||
| -rw-r--r-- | lib/prelude.ml | 23 |
2 files changed, 364 insertions, 113 deletions
diff --git a/lib/ledger.ml b/lib/ledger.ml index 7805179..058cc65 100644 --- a/lib/ledger.ml +++ b/lib/ledger.ml | |||
| @@ -96,72 +96,251 @@ module Labels = struct | |||
| 96 | | Sexp.Atom _ -> of_sexp_error "Labels.t_of_sexp: list needed" sexp | 96 | | Sexp.Atom _ -> of_sexp_error "Labels.t_of_sexp: list needed" sexp |
| 97 | end | 97 | end |
| 98 | 98 | ||
| 99 | module Money : sig | 99 | module Debit_credit = struct |
| 100 | type t | 100 | type t = Debit | Credit [@@deriving string, sexp_of] |
| 101 | 101 | ||
| 102 | val equal : t -> t -> bool | 102 | (* let opposite = function Debit -> Credit | Credit -> Debit *) |
| 103 | val compare : t -> t -> int | ||
| 104 | val of_bigint : Bigint.t -> t | ||
| 105 | val to_bigint : t -> Bigint.t | ||
| 106 | val ( + ) : t -> t -> t | ||
| 107 | val ( - ) : t -> t -> t | ||
| 108 | val ( = ) : t -> t -> bool | ||
| 109 | val ( ~$ ) : int -> t | ||
| 110 | val sexp_of_t : t -> Sexp.t | ||
| 111 | end = struct | ||
| 112 | type t = Bigint.t [@@deriving sexp_of] | ||
| 113 | |||
| 114 | let equal = Bigint.equal | ||
| 115 | let compare = Bigint.compare | ||
| 116 | let of_bigint = Fn.id | ||
| 117 | let to_bigint = Fn.id | ||
| 118 | let ( + ) x y = Bigint.(x + y) | ||
| 119 | let ( - ) x y = Bigint.(x - y) | ||
| 120 | let ( = ) = equal | ||
| 121 | let ( ~$ ) = Fn.compose of_bigint Bigint.of_int | ||
| 122 | end | 103 | end |
| 123 | 104 | ||
| 124 | type commodity_id = string | 105 | module Money = struct |
| 125 | (* TODO: consider making this UUID *) [@@deriving equal, compare, sexp] | 106 | module Amount : sig |
| 107 | type t | ||
| 108 | |||
| 109 | val equal : t -> t -> bool | ||
| 110 | val compare : t -> t -> int | ||
| 111 | val of_bigint : Bigint.t -> t option | ||
| 112 | val to_bigint : t -> Bigint.t | ||
| 113 | val ( + ) : t -> t -> t | ||
| 114 | val ( = ) : t -> t -> bool | ||
| 115 | val sexp_of_t : t -> Sexp.t | ||
| 116 | val zero : t | ||
| 117 | end = struct | ||
| 118 | type t = Bigint.t [@@deriving sexp_of] | ||
| 119 | |||
| 120 | let equal = Bigint.equal | ||
| 121 | let compare = Bigint.compare | ||
| 122 | let of_bigint x = if Bigint.(zero <= x) then Some x else None | ||
| 123 | let to_bigint x = x | ||
| 124 | let ( + ) x y = Bigint.(x + y) | ||
| 125 | let ( = ) = equal | ||
| 126 | let zero = Bigint.zero | ||
| 127 | end | ||
| 128 | |||
| 129 | module Diff : sig | ||
| 130 | type t | ||
| 131 | |||
| 132 | val equal : t -> t -> bool | ||
| 133 | val compare : t -> t -> int | ||
| 134 | val of_bigint : Bigint.t -> t | ||
| 135 | val to_bigint : t -> Bigint.t | ||
| 136 | val ( + ) : t -> t -> t | ||
| 137 | val ( +% ) : t -> Amount.t -> t | ||
| 138 | val ( - ) : t -> t -> t | ||
| 139 | val ( -% ) : t -> Amount.t -> t | ||
| 140 | val ( = ) : t -> t -> bool | ||
| 141 | val neg : t -> t | ||
| 142 | val ( ~$ ) : int -> t | ||
| 143 | val sexp_of_t : t -> Sexp.t | ||
| 144 | |||
| 145 | val of_amount : | ||
| 146 | Amount.t -> Debit_credit.t -> on_debit:[ `Incr | `Decr ] -> t | ||
| 147 | end = struct | ||
| 148 | type t = Bigint.t [@@deriving sexp_of] | ||
| 149 | |||
| 150 | let equal = Bigint.equal | ||
| 151 | let compare = Bigint.compare | ||
| 152 | let of_bigint x = x | ||
| 153 | let to_bigint x = x | ||
| 154 | let ( + ) x y = Bigint.(x + y) | ||
| 155 | let ( +% ) x y = x + of_bigint (Amount.to_bigint y) | ||
| 156 | let ( - ) x y = Bigint.(x - y) | ||
| 157 | let ( -% ) x y = x - of_bigint (Amount.to_bigint y) | ||
| 158 | let ( = ) = equal | ||
| 159 | let neg = Bigint.neg | ||
| 160 | let ( ~$ ) = Fn.compose of_bigint Bigint.of_int | ||
| 161 | |||
| 162 | let of_amount x (dc : Debit_credit.t) ~on_debit = | ||
| 163 | match (dc, on_debit) with | ||
| 164 | | Debit, `Incr -> of_bigint (Amount.to_bigint x) | ||
| 165 | | Credit, `Incr -> neg (of_bigint (Amount.to_bigint x)) | ||
| 166 | | Debit, `Decr -> neg (of_bigint (Amount.to_bigint x)) | ||
| 167 | | Credit, `Decr -> of_bigint (Amount.to_bigint x) | ||
| 168 | end | ||
| 169 | end | ||
| 126 | 170 | ||
| 127 | type scalar = | 171 | module Commodity_id = struct |
| 128 | | Amount of Money.t | 172 | type t = string [@@deriving equal, compare, sexp] |
| 129 | | Rate of { in_primary_commodity : Money.t; rate : Bigdecimal.t } | ||
| 130 | [@@deriving equal, compare, sexp_of] | ||
| 131 | 173 | ||
| 132 | module Account_id = struct | 174 | module Map = Map.Make (struct |
| 133 | type t = string list [@@deriving sexp, compare] | 175 | type nonrec t = t [@@deriving equal, compare, sexp] |
| 176 | end) | ||
| 134 | end | 177 | end |
| 135 | 178 | ||
| 136 | type account = { | 179 | (* |
| 137 | id : Account_id.t; | 180 | type scalar = |
| 138 | description : string list; | 181 | | Amount of Money.Amount.t |
| 139 | commodity_id : commodity_id; | 182 | | Rate of { in_primary_commodity : Money.Amount.t; rate : Bigdecimal.t } |
| 140 | balance : Money.t; | 183 | [@@deriving equal, compare, sexp_of] *) |
| 141 | } | 184 | |
| 142 | [@@deriving sexp_of] | 185 | module Account = struct |
| 186 | (* The contents of an account of category 'a *) | ||
| 187 | type 'a core = | ||
| 188 | (* Comprises of subaccounts of its subcategories *) | ||
| 189 | | Node of 'a String.Map.t | ||
| 190 | (* Comprises of subaccounts of its own category *) | ||
| 191 | | Ind of 'a t String.Map.t | ||
| 192 | (* Has no subaccounts, has a balance in a certain commodity *) | ||
| 193 | | Leaf of Commodity_id.t * Money.Diff.t | ||
| 194 | |||
| 195 | and extra = { description : String.t } | ||
| 196 | and 'a t = extra * 'a core | ||
| 197 | |||
| 198 | (* The category of the five top-level categories *) | ||
| 199 | type global | ||
| 200 | |||
| 201 | (* The five top-level categories *) | ||
| 202 | type asset | ||
| 203 | type equity | ||
| 204 | type expense | ||
| 205 | type income | ||
| 206 | type liability | ||
| 207 | |||
| 208 | (* No subcategories *) | ||
| 209 | type final | ||
| 210 | |||
| 211 | (* Subaccounts under the five top-level categories *) | ||
| 212 | type 'a f = | ||
| 213 | | Accounts_payable : final f t -> liability f | ||
| 214 | | Accounts_receivable : final f t -> asset f | ||
| 215 | | Bank : final f t -> asset f | ||
| 216 | | Cash : final f t -> asset f | ||
| 217 | | Credit : final f t -> liability f | ||
| 218 | | Mutual_fund : final f t -> asset f | ||
| 219 | | Stock : final f t -> asset f | ||
| 220 | |||
| 221 | module Ft_mapper = struct | ||
| 222 | type nonrec 'b t = { car : 'a. 'a f t -> ('b * 'a f t) option } | ||
| 223 | |||
| 224 | let map (type b c) (f : c t) : b f -> (c * b f) option = | ||
| 225 | let open Option.Let_syntax in | ||
| 226 | function | ||
| 227 | | Accounts_payable v -> | ||
| 228 | let%map c, v' = f.car v in | ||
| 229 | (c, Accounts_payable v') | ||
| 230 | | Accounts_receivable v -> | ||
| 231 | let%map c, v' = f.car v in | ||
| 232 | (c, Accounts_receivable v') | ||
| 233 | | Bank v -> | ||
| 234 | let%map c, v' = f.car v in | ||
| 235 | (c, Bank v') | ||
| 236 | | Cash v -> | ||
| 237 | let%map c, v' = f.car v in | ||
| 238 | (c, Cash v') | ||
| 239 | | Credit v -> | ||
| 240 | let%map c, v' = f.car v in | ||
| 241 | (c, Credit v') | ||
| 242 | | Mutual_fund v -> | ||
| 243 | let%map c, v' = f.car v in | ||
| 244 | (c, Mutual_fund v') | ||
| 245 | | Stock v -> | ||
| 246 | let%map c, v' = f.car v in | ||
| 247 | (c, Stock v') | ||
| 248 | end | ||
| 249 | |||
| 250 | module Top_level (F : sig | ||
| 251 | type 'a t | ||
| 252 | end) = | ||
| 253 | struct | ||
| 254 | type t = | ||
| 255 | | Asset of asset F.t | ||
| 256 | | Equity of equity F.t | ||
| 257 | | Expense of expense F.t | ||
| 258 | | Income of income F.t | ||
| 259 | | Liability of liability F.t | ||
| 260 | end | ||
| 261 | |||
| 262 | module Top_level_type = Top_level (struct | ||
| 263 | type nonrec 'a t = unit | ||
| 264 | end) | ||
| 265 | |||
| 266 | (* I swear the bullshit stops here *) | ||
| 267 | module F0 = struct | ||
| 268 | include Top_level (struct | ||
| 269 | type nonrec 'a t = 'a f t | ||
| 270 | end) | ||
| 271 | |||
| 272 | let type_ : t -> Top_level_type.t = function | ||
| 273 | | Asset _ -> Asset () | ||
| 274 | | Equity _ -> Equity () | ||
| 275 | | Expense _ -> Expense () | ||
| 276 | | Income _ -> Income () | ||
| 277 | | Liability _ -> Liability () | ||
| 278 | end | ||
| 279 | |||
| 280 | (* All accounts *) | ||
| 281 | type world = F0.t String.Map.t | ||
| 282 | |||
| 283 | module Path = struct | ||
| 284 | type t = string list [@@deriving compare, sexp] | ||
| 285 | |||
| 286 | module Map = Map.Make (struct | ||
| 287 | type nonrec t = t [@@deriving compare, sexp] | ||
| 288 | end) | ||
| 289 | end | ||
| 290 | |||
| 291 | let world_inst : world = | ||
| 292 | String.Map.of_alist_exn | ||
| 293 | [ | ||
| 294 | ( "Assets", | ||
| 295 | F0.Asset | ||
| 296 | ( { description = "assets" }, | ||
| 297 | Ind | ||
| 298 | (String.Map.of_alist_exn | ||
| 299 | [ | ||
| 300 | ( "Current", | ||
| 301 | ( { description = "current" }, | ||
| 302 | Node | ||
| 303 | (String.Map.of_alist_exn | ||
| 304 | [ | ||
| 305 | ( "Checking", | ||
| 306 | Bank | ||
| 307 | ( { description = "bnak accounts" }, | ||
| 308 | Ind | ||
| 309 | (String.Map.of_alist_exn | ||
| 310 | [ | ||
| 311 | ( "ING", | ||
| 312 | ( { description = "ING bank" }, | ||
| 313 | Leaf ("EUC", Money.Diff.(~$0)) | ||
| 314 | ) ); | ||
| 315 | ( "N26", | ||
| 316 | ( { description = "ING bank" }, | ||
| 317 | Leaf ("EUC", Money.Diff.(~$0)) | ||
| 318 | ) ); | ||
| 319 | ]) ) ); | ||
| 320 | ]) ) ); | ||
| 321 | ]) ) ); | ||
| 322 | ] | ||
| 323 | end | ||
| 143 | 324 | ||
| 144 | type bal_assert = { | 325 | type bal_assert = { |
| 145 | account : Account_id.t; | 326 | account : Account.Path.t; |
| 146 | amount : Money.t; | ||
| 147 | labels : Labels.t; | 327 | labels : Labels.t; |
| 328 | bal : Money.Diff.t; | ||
| 148 | } | 329 | } |
| 149 | [@@deriving sexp_of] | 330 | [@@deriving sexp_of] |
| 150 | 331 | ||
| 151 | module Account_id_map = Map.Make (Account_id) | ||
| 152 | |||
| 153 | module Debit_credit = struct | ||
| 154 | type t = Debit | Credit [@@deriving string, sexp_of] | ||
| 155 | |||
| 156 | let opposite = function Debit -> Credit | Credit -> Debit | ||
| 157 | end | ||
| 158 | |||
| 159 | module Tx : sig | 332 | module Tx : sig |
| 333 | type entry = { | ||
| 334 | dc : Debit_credit.t; | ||
| 335 | commodity : Commodity_id.t; | ||
| 336 | amount : Money.Amount.t; | ||
| 337 | assertion : Money.Diff.t option; | ||
| 338 | } | ||
| 339 | |||
| 160 | (* Private because we only want to allow constructing balanced transactions. *) | 340 | (* Private because we only want to allow constructing balanced transactions. *) |
| 161 | type t = private { | 341 | type t = private { |
| 162 | cleared : Date.t option; | 342 | cleared : Date.t option; |
| 163 | commodity_id : commodity_id; | 343 | entries : entry Account.Path.Map.t; |
| 164 | entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t; | ||
| 165 | labels : Labels.t; | 344 | labels : Labels.t; |
| 166 | } | 345 | } |
| 167 | 346 | ||
| @@ -169,17 +348,23 @@ module Tx : sig | |||
| 169 | 348 | ||
| 170 | val make : | 349 | val make : |
| 171 | cleared:Date.t option -> | 350 | cleared:Date.t option -> |
| 172 | commodity_id:commodity_id -> | 351 | entries:entry Account.Path.Map.t -> |
| 173 | entries:(Debit_credit.t * scalar * Money.t option) Account_id_map.t -> | ||
| 174 | labels:Labels.t -> | 352 | labels:Labels.t -> |
| 175 | (t, error) result | 353 | (t, error) result |
| 176 | 354 | ||
| 177 | val sexp_of_t : t -> Sexp.t | 355 | val sexp_of_t : t -> Sexp.t |
| 178 | end = struct | 356 | end = struct |
| 357 | type entry = { | ||
| 358 | dc : Debit_credit.t; | ||
| 359 | commodity : Commodity_id.t; | ||
| 360 | amount : Money.Amount.t; | ||
| 361 | assertion : Money.Diff.t option; | ||
| 362 | } | ||
| 363 | [@@deriving sexp_of] | ||
| 364 | |||
| 179 | type t = { | 365 | type t = { |
| 180 | cleared : Date.t option; | 366 | cleared : Date.t option; |
| 181 | commodity_id : commodity_id; | 367 | entries : entry Account.Path.Map.t; |
| 182 | entries : (Debit_credit.t * scalar * Money.t option) Account_id_map.t; | ||
| 183 | labels : Labels.t; | 368 | labels : Labels.t; |
| 184 | } | 369 | } |
| 185 | [@@deriving sexp_of] | 370 | [@@deriving sexp_of] |
| @@ -187,22 +372,18 @@ end = struct | |||
| 187 | type error = Unbalanced | 372 | type error = Unbalanced |
| 188 | 373 | ||
| 189 | let is_balanced entries = | 374 | let is_balanced entries = |
| 190 | Map.fold entries | 375 | Map.fold entries ~init:Commodity_id.Map.empty |
| 191 | ~init:Money.(~$0, ~$0) | 376 | ~f:(fun ~key:_ ~data comm_balances -> |
| 192 | ~f:(fun ~key:_ ~data:(type_, scalar, _oassert) (ds, cs) -> | 377 | Map.update comm_balances data.commodity ~f:(fun ocomm_bal -> |
| 193 | let m = | 378 | let comm_bal = Option.value ocomm_bal ~default:Money.Diff.(~$0) in |
| 194 | match scalar with | 379 | match data.dc with |
| 195 | | Amount m -> m | 380 | | Debit_credit.Debit -> Money.Diff.(comm_bal +% data.amount) |
| 196 | | Rate { in_primary_commodity = m; _ } -> m | 381 | | Debit_credit.Credit -> Money.Diff.(comm_bal -% data.amount))) |
| 197 | in | 382 | |> Map.for_all ~f:(fun comm_bal -> Money.Diff.(comm_bal = ~$0)) |
| 198 | match type_ with | 383 | |
| 199 | | Debit_credit.Debit -> Money.(ds + m, cs) | 384 | let make ~cleared ~entries ~labels = |
| 200 | | Debit_credit.Credit -> Money.(ds, cs + m)) | ||
| 201 | |> fun (ds, cs) -> Money.(ds = cs) | ||
| 202 | |||
| 203 | let make ~cleared ~commodity_id ~entries ~labels = | ||
| 204 | if not (is_balanced entries) then Error Unbalanced | 385 | if not (is_balanced entries) then Error Unbalanced |
| 205 | else Ok { cleared; commodity_id; entries; labels } | 386 | else Ok { cleared; entries; labels } |
| 206 | end | 387 | end |
| 207 | 388 | ||
| 208 | type item = | 389 | type item = |
| @@ -212,53 +393,100 @@ type item = | |||
| 212 | 393 | ||
| 213 | type t = item list [@@deriving sexp_of] | 394 | type t = item list [@@deriving sexp_of] |
| 214 | 395 | ||
| 215 | module Account = struct | ||
| 216 | type global_type = Asset | Equity | Liability | Expense | Income | ||
| 217 | [@@deriving compare, sexp] | ||
| 218 | |||
| 219 | type asset | ||
| 220 | type global | ||
| 221 | |||
| 222 | type 'a subcategory = | ||
| 223 | | Asset : asset subcategory option -> global subcategory | ||
| 224 | | Checking : asset subcategory | ||
| 225 | |||
| 226 | type 'a t = Sub of ('a, 'a t) category String.Map.t | ||
| 227 | |||
| 228 | let world : global t = | ||
| 229 | Sub | ||
| 230 | (String.Map.of_alist_exn [ ("Assets", Asset (Some ( | ||
| 231 | String.Map.of_alist_exn [ | ||
| 232 | ("Checking", Checking) | ||
| 233 | ] | ||
| 234 | ))) ]) | ||
| 235 | end | ||
| 236 | |||
| 237 | (* | ||
| 238 | module World = struct | 396 | module World = struct |
| 239 | type t = (commodity_id * Money.t) Account_id_map.t | 397 | type t = Account.world |
| 240 | 398 | ||
| 241 | let empty : t = Account_id_map.empty | 399 | let empty : t = String.Map.empty |
| 242 | 400 | ||
| 243 | let apply_tx_entry_base aid primary_commodity debit_credit scalar = | 401 | let update_bal_fn = (f : |
| 244 | let amount = Scalar.to_amount ~commodity:primary_commodity scalar in | 402 | Account.Top_level_type.t -> |
| 245 | Map.update aid ~f:(function | 403 | Commodity_id.t -> |
| 246 | | None -> | 404 | Money.Diff.t -> |
| 247 | 405 | ('a * Money.Diff.t) option) | |
| 248 | (* | 406 | |
| 249 | let assert_bal aid sc world = | 407 | (* Stretching the type system a little :) *) |
| 250 | 408 | let rec update_bal_aux ttype subaid | |
| 251 | let apply_tx_entry aid (dc, sc, oassert) world = *) | 409 | (f : |
| 252 | 410 | Account.Top_level_type.t -> | |
| 253 | let apply_tx (tx : Tx.t) world = | 411 | Commodity_id.t -> |
| 254 | Map.fold tx.entries ~init:world ~f:(fun ~key:account_id ~data:(type_, scalar, _oassert) world -> | 412 | Money.Diff.t -> |
| 255 | 413 | ('a * Money.Diff.t) option) : 'a Account.Ft_mapper.t = | |
| 256 | 414 | { | |
| 257 | ) | 415 | car = |
| 416 | (fun in_acc -> | ||
| 417 | let open Option.Let_syntax in | ||
| 418 | match (subaid, in_acc) with | ||
| 419 | | [], (extra, Account.Leaf (acc_comm, acc_bal)) -> | ||
| 420 | let%bind x, acc_bal' = f ttype acc_comm acc_bal in | ||
| 421 | Some (x, (extra, Account.Leaf (acc_comm, acc_bal'))) | ||
| 422 | | [], _ -> None | ||
| 423 | | subaid0 :: subaid, (extra, Node subaccs) -> | ||
| 424 | let open Option.Let_syntax in | ||
| 425 | let%bind subacc = Map.find subaccs subaid0 in | ||
| 426 | let%map x, subacc' = | ||
| 427 | Account.Ft_mapper.map (update_bal_aux ttype subaid f) subacc | ||
| 428 | in | ||
| 429 | ( x, | ||
| 430 | ( extra, | ||
| 431 | Account.Node (Map.set subaccs ~key:subaid0 ~data:subacc') ) ) | ||
| 432 | | subaid0 :: subaid, (extra, Ind subaccs) -> | ||
| 433 | let open Option.Let_syntax in | ||
| 434 | let%bind subacc = Map.find subaccs subaid0 in | ||
| 435 | let%map x, subacc' = (update_bal_aux ttype subaid f).car subacc in | ||
| 436 | ( x, | ||
| 437 | (extra, Account.Ind (Map.set subaccs ~key:subaid0 ~data:subacc')) | ||
| 438 | ) | ||
| 439 | | _ :: _, (_, Leaf _) -> None); | ||
| 440 | } | ||
| 441 | |||
| 442 | (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount] | ||
| 443 | (commodity: [in_comm]) in [world], giving the updated world and the pre | ||
| 444 | and post balances for [aid] iff the account exists in [world]. *) | ||
| 445 | let update_bal aid dc by_amount in_comm (world : t) : | ||
| 446 | ((Money.Diff.t * Money.Diff.t) * t) option = | ||
| 447 | match aid with | ||
| 448 | | [] -> None | ||
| 449 | | aid0 :: subaid -> ( | ||
| 450 | let open Option.Let_syntax in | ||
| 451 | match%bind Map.find world aid0 with | ||
| 452 | | Asset acc -> | ||
| 453 | let diff = Money.Diff.of_amount by_amount dc ~on_debit:`Incr in | ||
| 454 | let%map bals, acc' = (update_bal_aux subaid diff in_comm).car acc in | ||
| 455 | (bals, Map.set world ~key:aid0 ~data:(Asset acc')) | ||
| 456 | | Expense acc -> | ||
| 457 | let diff = Money.Diff.of_amount by_amount dc ~on_debit:`Incr in | ||
| 458 | let%map bals, acc' = (update_bal_aux subaid diff in_comm).car acc in | ||
| 459 | (bals, Map.set world ~key:aid0 ~data:(Expense acc')) | ||
| 460 | | Income acc -> | ||
| 461 | let diff = Money.Diff.of_amount by_amount dc ~on_debit:`Decr in | ||
| 462 | let%map bals, acc' = (update_bal_aux subaid diff in_comm).car acc in | ||
| 463 | (bals, Map.set world ~key:aid0 ~data:(Income acc')) | ||
| 464 | | Liability acc -> | ||
| 465 | let diff = Money.Diff.of_amount by_amount dc ~on_debit:`Decr in | ||
| 466 | let%map bals, acc' = (update_bal_aux subaid diff in_comm).car acc in | ||
| 467 | (bals, Map.set world ~key:aid0 ~data:(Liability acc')) | ||
| 468 | | Equity acc -> | ||
| 469 | let diff = Money.Diff.of_amount by_amount dc ~on_debit:`Decr in | ||
| 470 | let%map bals, acc' = (update_bal_aux subaid diff in_comm).car acc in | ||
| 471 | (bals, Map.set world ~key:aid0 ~data:(Equity acc'))) | ||
| 472 | |||
| 473 | let apply_tx (tx : Tx.t) world : t option = | ||
| 474 | Map.fold_option tx.entries ~init:world | ||
| 475 | ~f:(fun ~key:aid ~(data : Tx.entry) world -> | ||
| 476 | let open Option.Let_syntax in | ||
| 477 | let%bind (_old_bal, new_bal), world = | ||
| 478 | update_bal aid data.dc data.amount data.commodity world | ||
| 479 | in | ||
| 480 | match data.assertion with | ||
| 481 | | None -> Some world | ||
| 482 | | Some bal_ass -> | ||
| 483 | if Money.Diff.(bal_ass = new_bal) then Some world else None) | ||
| 258 | 484 | ||
| 259 | let apply : item -> t -> t = function | 485 | let apply : item -> t -> t option = function |
| 260 | | Tx_item tx -> apply_tx tx | 486 | | Tx_item tx -> apply_tx tx |
| 261 | | Bal_assert_item ba -> apply_ba ba | 487 | | Bal_assert_item ba -> apply_ba ba |
| 262 | end *) | 488 | end |
| 489 | |||
| 490 | module Ctxd_item = struct end | ||
| 263 | 491 | ||
| 264 | let make = Fn.id | 492 | let make = Fn.id |
diff --git a/lib/prelude.ml b/lib/prelude.ml index 57f7af3..f571a4d 100644 --- a/lib/prelude.ml +++ b/lib/prelude.ml | |||
| @@ -16,6 +16,29 @@ module List = struct | |||
| 16 | go | 16 | go |
| 17 | end | 17 | end |
| 18 | 18 | ||
| 19 | module Map = struct | ||
| 20 | include Map | ||
| 21 | |||
| 22 | let fold_result (m : ('k, 'v, _) t) ~(init : 'acc) | ||
| 23 | ~(f : key:'k -> data:'v -> 'acc -> ('acc, 'err) result) : | ||
| 24 | ('acc, 'err) result = | ||
| 25 | fold_until m ~init | ||
| 26 | ~f:(fun ~key ~data acc -> | ||
| 27 | match f ~key ~data acc with | ||
| 28 | | Ok acc' -> Continue acc' | ||
| 29 | | Error _ as res -> Stop res) | ||
| 30 | ~finish:(fun v -> Ok v) | ||
| 31 | |||
| 32 | let fold_option (m : ('k, 'v, _) t) ~(init : 'acc) | ||
| 33 | ~(f : key:'k -> data:'v -> 'acc -> 'acc option) : 'acc option = | ||
| 34 | fold_until m ~init | ||
| 35 | ~f:(fun ~key ~data acc -> | ||
| 36 | match f ~key ~data acc with | ||
| 37 | | Some acc' -> Continue acc' | ||
| 38 | | None -> Stop None) | ||
| 39 | ~finish:(fun v -> Some v) | ||
| 40 | end | ||
| 41 | |||
| 19 | module Z = struct | 42 | module Z = struct |
| 20 | include Z | 43 | include Z |
| 21 | 44 | ||