summaryrefslogtreecommitdiff
path: root/parse.cc
diff options
context:
space:
mode:
Diffstat (limited to 'parse.cc')
-rw-r--r--parse.cc566
1 files changed, 283 insertions, 283 deletions
diff --git a/parse.cc b/parse.cc
index 19ab1219..6a79eae8 100644
--- a/parse.cc
+++ b/parse.cc
@@ -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