John, thanks very much for taking the time to write this up. As you
know this is one the areas I've burned the most cycles trying to make
it 100% compatible and robust. Knowledge of this recipe should help
considerably in my next crack at it.
I think you may have seen this, but http://paste.lisp.org/display/
75617 shows an alternate balance report format which I think my
clients will find easier to read, and which also may be easier to
implement. I'll probably be adding this one as an option.
On Feb 17, 2009, at 1:43 PM, John Wiegley wrote:
Simon,
Since you've been re-implementing the balance report in Haskell, I
thought I'd share with you in pseudocode how I rewrote it for Ledger
3.0, since the old method never stopped with the bugs. The new
scheme uses a 5 stage algorithm, with each stage gathering
information for the next:
STEP 1
Based on the user's query, walk through all the transactions in
their journal, finding which ones to include in the account
subtotals. For each transaction that matches, mark the account as
VISITED.
STEP 2
Recursively walk the accounts tree, depth-first, computing aggregate
totals and aggregate "counts" (number of transactions contributing
to the aggregate total).
STEP 3
Walk the account tree again, breadth-first, and for every VISITED
account, check whether it matches the user's "display predicate".
If so, mark the account as MATCHING.
STEP 4
Do an in-order traversal of the account tree. Except for the top-
most account (which serves strictly as a container for the other
accounts):
a. If the account was MATCHING, or two or more of its children are
MATCHING or had descendents who were MATCHING, display the account.
b. Otherwise, if the account had *any* children or descendants who
were VISITED and *no* children or descendants who were MATCHING,
then apply the display predicate from STEP 3 to the account. If
it matches, also print this account. (This step allows -E to
report empty accounts which otherwise did match the original
query).
STEP 5
When printing an account, display a "depth spacer" followed by the
"partial name".
The partial name is found by taking the base account's name, then
prepending to it every non-MATCHING parent until a MATCHING parent
is found.
The depth spacer is found by outputting two spaces for every
MATCHING parent.
This way, "Assets:Bank:Checking" might be reported as:
Assets
Bank
Checking
or
Assets
Bank:Checking
or
Assets:Bank:Checking
Depending on whether the parents were or were not reported for their
own reasons.
John