diff options
Diffstat (limited to 'parse.cc')
-rw-r--r-- | parse.cc | 566 |
1 files changed, 283 insertions, 283 deletions
@@ -7,28 +7,34 @@ namespace ledger { +static inline char * skip_ws(char * ptr) +{ + while (std::isspace(*ptr)) + ptr++; + return ptr; +} + static inline char * next_element(char * buf, bool variable = false) { char * p; - // Convert any tabs to spaces, for simplicity's sake - for (p = buf; *p; p++) - if (*p == '\t') - *p = ' '; + if (variable) { + // Convert any tabs to spaces, for simplicity's sake + for (p = buf; *p; p++) + if (*p == '\t') + *p = ' '; - if (variable) p = std::strstr(buf, " "); - else + } else { p = std::strchr(buf, ' '); + } if (! p) return NULL; *p++ = '\0'; - while (std::isspace(*p)) - p++; - return p; + return skip_ws(p); } static const char *formats[] = { @@ -44,17 +50,17 @@ static const char *formats[] = { NULL }; -bool parse_date(const std::string& date_str, std::time_t * result, +bool parse_date(const char * date_str, std::time_t * result, const int year = -1) { struct std::tm when; - std::time_t now = std::time(NULL); + std::time_t now = std::time(NULL); struct std::tm * now_tm = std::localtime(&now); for (const char ** f = formats; *f; f++) { memset(&when, INT_MAX, sizeof(struct std::tm)); - if (strptime(date_str.c_str(), *f, &when)) { + if (strptime(date_str, *f, &when)) { when.tm_hour = 0; when.tm_min = 0; when.tm_sec = 0; @@ -78,14 +84,130 @@ bool parse_date(const std::string& date_str, std::time_t * result, return false; } -static int linenum = 0; +#define MAX_LINE 1024 + +static int linenum; + +transaction * parse_transaction(std::istream& in, state * ledger) +{ + transaction * xact = new transaction(); + + static char line[MAX_LINE + 1]; + in.getline(line, MAX_LINE); + linenum++; + + char * p = line; + p = skip_ws(p); + + // The call to `next_element' will skip past the account name, + // and return a pointer to the beginning of the amount. Once + // we know where the amount is, we can strip off any + // transaction note, and parse it. + + char * cost_str = next_element(p, true); + char * note_str; + + // If there is no amount given, it is intended as an implicit + // amount; we must use the opposite of the value of the + // preceding transaction. + + if (! cost_str || ! *cost_str || *cost_str == ';') { + if (cost_str && *cost_str) { + while (*cost_str == ';' || std::isspace(*cost_str)) + cost_str++; + xact->note = cost_str; + } + + xact->cost = NULL; + } + else { + note_str = std::strchr(cost_str, ';'); + if (note_str) { + *note_str++ = '\0'; + xact->note = skip_ws(note_str); + } + + for (char * t = cost_str + (std::strlen(cost_str) - 1); + std::isspace(*t); + t--) + *t = '\0'; + + xact->cost = create_amount(cost_str); + } + + if (*p == '[' || *p == '(') { + xact->is_virtual = true; + xact->specified = true; + xact->must_balance = *p == '['; + p++; + + char * e = p + (std::strlen(p) - 1); + assert(*e == ')' || *e == ']'); + *e = '\0'; + } + + xact->acct = ledger->find_account(p); -static void finalize_entry(entry * curr, bool compute_balances) + if (ledger->compute_balances && xact->cost) + xact->acct->balance.credit(xact->cost); + + return xact; +} + +entry * parse_entry(std::istream& in, state * ledger) { - assert(curr); - assert(! curr->xacts.empty()); + entry * curr = new entry; + + static char line[MAX_LINE + 1]; + in.getline(line, MAX_LINE); + linenum++; + + // Parse the date + + char * next = next_element(line); + if (! parse_date(line, &curr->date, ledger->current_year)) { + std::cerr << "Error, line " << linenum + << ": Failed to parse date: " << line << std::endl; + return NULL; + } - // Scan through and compute the total balance for the entry. + // Parse the optional cleared flag: * + + if (*next == '*') { + curr->cleared = true; + next = skip_ws(++next); + } + + // Parse the optional code: (TEXT) + + if (*next == '(') { + if (char * p = std::strchr(next++, ')')) { + *p++ = '\0'; + curr->code = next; + next = skip_ws(p); + } + } + + // Parse the description text + + curr->desc = next; + + // Parse all of the transactions associated with this entry + + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) + if (transaction * xact = parse_transaction(in, ledger)) + curr->xacts.push_back(xact); + + // If there were no transactions, throw away the entry + + if (curr->xacts.empty()) { + delete curr; + return NULL; + } + + // Scan through and compute the total balance for the entry. This + // is used for auto-calculating the value of entries with no cost, + // and the per-unit price of unpriced commodities. totals balance; @@ -139,10 +261,10 @@ static void finalize_entry(entry * curr, bool compute_balances) if (! empty_allowed || balance.amounts.empty() || balance.amounts.size() != 1) { - std::cerr << "Error, line " << (linenum - 1) + std::cerr << "Error, line " << linenum << ": Transaction entry is lacking an amount." << std::endl; - return; + return NULL; } empty_allowed = false; @@ -154,7 +276,7 @@ static void finalize_entry(entry * curr, bool compute_balances) (*x)->cost = (*i).second->value(); (*x)->cost->negate(); - if (compute_balances) + if (ledger->compute_balances) (*x)->acct->balance.credit((*x)->cost); } @@ -162,47 +284,56 @@ static void finalize_entry(entry * curr, bool compute_balances) // transactions and create new virtual transactions for all that // apply. - if (main_ledger.compute_virtual) { - for (state::virtual_map_iterator m = main_ledger.virtual_mapping.begin(); - m != main_ledger.virtual_mapping.end(); - m++) { - std::list<transaction *> xacts; - - for (std::list<transaction *>::iterator x = curr->xacts.begin(); - x != curr->xacts.end(); - x++) { - if ((*x)->is_virtual || - ! ledger::matches(*((*m).first), (*x)->acct->as_str())) - continue; - - for (std::list<transaction *>::iterator i = (*m).second->begin(); - i != (*m).second->end(); - i++) { - transaction * t; + for (state::virtual_map_iterator m = ledger->virtual_mapping.begin(); + m != ledger->virtual_mapping.end(); + m++) { + std::list<transaction *> xacts; - assert((*i)->is_virtual); - assert((*i)->cost); + for (std::list<transaction *>::iterator x = curr->xacts.begin(); + x != curr->xacts.end(); + x++) { + if ((*x)->is_virtual || + ! ledger::matches(*((*m).first), (*x)->acct->as_str())) + continue; - if ((*i)->cost->comm()) { - t = new transaction((*i)->acct, (*i)->cost); - } else { - amount * temp = (*x)->cost->value(); - t = new transaction((*i)->acct, temp->value((*i)->cost)); - delete temp; - } + for (std::list<transaction *>::iterator i = (*m).second->begin(); + i != (*m).second->end(); + i++) { + transaction * t; + + if ((*i)->cost->comm()) { + t = new transaction((*i)->acct, (*i)->cost); + } else { + amount * temp = (*x)->cost->value(); + t = new transaction((*i)->acct, temp->value((*i)->cost)); + delete temp; + } + + t->is_virtual = (*i)->is_virtual; + t->must_balance = (*i)->must_balance; - t->is_virtual = true; - t->must_balance = (*i)->must_balance; + // If there is already a virtual transaction for the + // account under consideration, and it's `must_balance' + // flag matches, then simply add this amount to that + // transaction. - // If there is already a virtual transaction for the - // account under consideration, and it's `must_balance' - // flag matches, then simply add this amount to that - // transaction. + bool added = false; - bool added = false; + for (std::list<transaction *>::iterator y = xacts.begin(); + y != xacts.end(); + y++) { + if ((*y)->is_virtual && (*y)->acct == t->acct && + (*y)->must_balance == t->must_balance) { + (*y)->cost->credit(t->cost); + delete t; + added = true; + break; + } + } - for (std::list<transaction *>::iterator y = xacts.begin(); - y != xacts.end(); + if (! added) + for (std::list<transaction *>::iterator y = curr->xacts.begin(); + y != curr->xacts.end(); y++) { if ((*y)->is_virtual && (*y)->acct == t->acct && (*y)->must_balance == t->must_balance) { @@ -213,42 +344,28 @@ static void finalize_entry(entry * curr, bool compute_balances) } } - if (! added) - for (std::list<transaction *>::iterator y = curr->xacts.begin(); - y != curr->xacts.end(); - y++) { - if ((*y)->is_virtual && (*y)->acct == t->acct && - (*y)->must_balance == t->must_balance) { - (*y)->cost->credit(t->cost); - delete t; - added = true; - break; - } - } - - if (! added) - xacts.push_back(t); - } + if (! added) + xacts.push_back(t); } + } - // Add to the current entry any virtual transactions which were - // created. We have to do this afterward, otherwise the - // iteration above is screwed up if we try adding new - // transactions during the traversal. + // Add to the current entry any virtual transactions which were + // created. We have to do this afterward, otherwise the + // iteration above is screwed up if we try adding new + // transactions during the traversal. - for (std::list<transaction *>::iterator x = xacts.begin(); - x != xacts.end(); - x++) { - curr->xacts.push_back(*x); + for (std::list<transaction *>::iterator x = xacts.begin(); + x != xacts.end(); + x++) { + curr->xacts.push_back(*x); - if (compute_balances) - (*x)->acct->balance.credit((*x)->cost); - } + if (ledger->compute_balances) + (*x)->acct->balance.credit((*x)->cost); } } // Compute the balances again, just to make sure it all comes out - // right (i.e., to zero for every commodity). + // right (i.e., zero for every commodity). if (! curr->validate()) { std::cerr << "Error, line " << (linenum - 1) @@ -256,230 +373,113 @@ static void finalize_entry(entry * curr, bool compute_balances) << std::endl; curr->print(std::cerr); curr->validate(true); - return; + delete curr; + return NULL; } - - // If it's OK, add it to the general ledger's list of entries. - - main_ledger.entries.push_back(curr); + return curr; } -////////////////////////////////////////////////////////////////////// -// -// Ledger parser -// - -bool parse_ledger(std::istream& in, bool compute_balances) +void parse_automated_transactions(std::istream& in, state * ledger) { - char line[1024]; - int current_year = -1; - entry * curr = NULL; + static char line[MAX_LINE + 1]; - while (! in.eof()) { - in.getline(line, 1023); - linenum++; - - if (line[0] == '\n') { - continue; - } - else if (std::isdigit(line[0])) { - if (curr && ! curr->xacts.empty()) - finalize_entry(curr, compute_balances); - curr = new entry; - - char * next = next_element(line); - if (! parse_date(line, &curr->date, current_year)) { - std::cerr << "Error, line " << linenum - << ": Failed to parse date: " << line << std::endl; - continue; - } - - if (*next == '*') { - curr->cleared = true; - - next++; - while (std::isspace(*next)) - next++; - } - - if (*next == '(') { - char * p = std::strchr(next, ')'); - if (p) { - *p++ = '\0'; - curr->code = next; - next = p; - - next++; - while (std::isspace(*next)) - next++; - } - } - - curr->desc = next; - } - else if (curr && std::isspace(line[0])) { - transaction * xact = new transaction(); - - char * p = line; - while (std::isspace(*p)) - p++; - - // The call to `next_element' will skip past the account name, - // and return a pointer to the beginning of the amount. Once - // we know where the amount is, we can strip off any - // transaction note, and parse it. - - char * cost_str = next_element(p, true); - char * note_str; - - // If there is no amount given, it is intended as an implicit - // amount; we must use the opposite of the value of the - // preceding transaction. - - if (! cost_str || ! *cost_str || *cost_str == ';') { - if (cost_str && *cost_str) { - while (*cost_str == ';' || std::isspace(*cost_str)) - cost_str++; - xact->note = cost_str; - } + std::list<mask> * masks = NULL; - xact->cost = NULL; - } - else { - note_str = std::strchr(cost_str, ';'); - if (note_str) { - *note_str++ = '\0'; - while (std::isspace(*note_str)) - note_str++; - xact->note = note_str; - } + while (! in.eof() && in.peek() == '=') { + in.getline(line, MAX_LINE); + linenum++; - for (char * t = cost_str + (std::strlen(cost_str) - 1); - std::isspace(*t); - t--) - *t = '\0'; + char * p = line + 1; + p = skip_ws(p); - xact->cost = create_amount(cost_str); - } + if (! masks) + masks = new std::list<mask>; + masks->push_back(mask(p)); + } - if (*p == '[' || *p == '(') { - xact->is_virtual = true; - xact->specified = true; - xact->must_balance = *p == '['; - p++; + std::list<transaction *> * xacts = NULL; - char * e = p + (std::strlen(p) - 1); - assert(*e == ')' || *e == ']'); - *e = '\0'; - } + while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) { + if (transaction * xact = parse_transaction(in, ledger)) { + if (! xacts) + xacts = new std::list<transaction *>; - if (xact->is_virtual && ! main_ledger.compute_virtual) { - delete xact; + if (! xact->cost) { + std::cerr << "Error, line " << (linenum - 1) + << ": All automated transactions must have a value." + << std::endl; } else { - xact->acct = main_ledger.find_account(p); - if (compute_balances && xact->cost) - xact->acct->balance.credit(xact->cost); - - curr->xacts.push_back(xact); + xacts->push_back(xact); } } - else if (line[0] == 'Y') { - current_year = std::atoi(line + 2); - } } - if (curr && ! curr->xacts.empty()) - finalize_entry(curr, compute_balances); - - return true; + if (masks && xacts) + ledger->virtual_mapping.insert(state::virtual_map_pair(masks, xacts)); + else if (masks) + delete masks; + else if (xacts) + delete xacts; } -void parse_virtual_mappings(const std::string& path) -{ - main_ledger.mapping_file = path; - - std::ifstream maps(main_ledger.mapping_file.c_str()); - - char line[1024]; - int linenum = 0; - - std::list<mask> * masks = NULL; - std::list<transaction *> * xacts = NULL; - - while (! maps.eof()) { - maps.getline(line, 1023); - linenum++; - - // The format of each entry is: - // - // REGEXP1 - // REGEXP2... - // ACCOUNT AMOUNT - // ACCOUNT AMOUNT... - // - // If AMOUNT has a commodity, that exact amount is always - // transacted whenever a REGEXP is matched. If it has no - // commodity, then it is taken as the multiplier, the result of - // which is transacted instead. - // - // If one of REGEXP is the word "{BEGIN}", then those - // transactions will be entered before parsing has begin. - - if (std::isspace(line[0])) { - if (! xacts) - xacts = new std::list<transaction *>; - - char * p = line; - while (std::isspace(*p)) - p++; - - char * cost_str = next_element(p, true); - account * acct = main_ledger.find_account(p); - transaction * xact = new transaction(acct, create_amount(cost_str)); - - xact->is_virtual = true; - xact->must_balance = false; - - assert(masks); - assert(! masks->empty()); - if (masks->size() == 1 && - masks->front().pattern == "{BEGIN}") { - entry * opening = new entry; +////////////////////////////////////////////////////////////////////// +// +// Ledger parser +// - opening->date = std::time(NULL); - opening->cleared = true; - opening->desc = "Opening Balance"; +state * parse_ledger(std::istream& in, regexps_map& regexps, + bool compute_balances) +{ + static char line[MAX_LINE + 1]; + char c; - opening->xacts.push_back(xact); - main_ledger.entries.push_back(opening); - } else { - xacts->push_back(xact); - } - } - else if (line[0] != '\0') { - if (xacts) { - std::pair<state::virtual_map_iterator, bool> result = - main_ledger.virtual_mapping.insert - (state::virtual_map_pair(masks, xacts)); - assert(result.second); - - masks = NULL; - xacts = NULL; - } + state * ledger = new state; + main_ledger = ledger; - if (! masks) - masks = new std::list<mask>; + ledger->compute_balances = compute_balances; - masks->push_back(mask(line)); + linenum = 0; + while (! in.eof()) { + switch (in.peek()) { + case -1: // end of file + return ledger; + + case '\n': + linenum++; + case '\r': // skip blank lines + in.get(c); + break; + + case 'Y': // set the current year + in >> c; + in >> ledger->current_year; + break; + + case ';': // a comment line + in.getline(line, MAX_LINE); + linenum++; + break; + + case '-': + case '+': // permanent regexps + in.getline(line, MAX_LINE); + linenum++; + + // Add the regexp to whatever masks currently exist + regexps.push_back(mask(line)); + break; + + case '=': // automated transactions + parse_automated_transactions(in, ledger); + break; + + default: + if (entry * ent = parse_entry(in, ledger)) + ledger->entries.push_back(ent); + break; } } - - if (xacts) { - std::pair<state::virtual_map_iterator, bool> result = - main_ledger.virtual_mapping.insert - (state::virtual_map_pair(masks, xacts)); - assert(result.second); - } + return ledger; } } // namespace ledger |