summaryrefslogtreecommitdiff
path: root/valexpr.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2004-08-04 03:12:26 -0400
committerJohn Wiegley <johnw@newartisans.com>2004-08-04 03:12:26 -0400
commit1741c80fe4f9dd896d4c8912b2ba35b14500ca1c (patch)
treecfdcb49cc929b023b71ecd9e126eff4830d77a0c /valexpr.cc
parentf570e6644fce27b612562282b11d00fd0691873d (diff)
downloadfork-ledger-1741c80fe4f9dd896d4c8912b2ba35b14500ca1c.tar.gz
fork-ledger-1741c80fe4f9dd896d4c8912b2ba35b14500ca1c.tar.bz2
fork-ledger-1741c80fe4f9dd896d4c8912b2ba35b14500ca1c.zip
rewrote the way registers are computed -- again
Diffstat (limited to 'valexpr.cc')
-rw-r--r--valexpr.cc804
1 files changed, 804 insertions, 0 deletions
diff --git a/valexpr.cc b/valexpr.cc
new file mode 100644
index 00000000..889c57c8
--- /dev/null
+++ b/valexpr.cc
@@ -0,0 +1,804 @@
+#include "valexpr.h"
+#include "error.h"
+#include "datetime.h"
+#include "textual.h"
+
+#include <pcre.h>
+
+namespace ledger {
+
+mask_t::mask_t(const std::string& pat) : exclude(false)
+{
+ const char * p = pat.c_str();
+ if (*p == '-') {
+ exclude = true;
+ p++;
+ while (std::isspace(*p))
+ p++;
+ }
+ else if (*p == '+') {
+ p++;
+ while (std::isspace(*p))
+ p++;
+ }
+ pattern = p;
+
+ const char *error;
+ int erroffset;
+ regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
+ &error, &erroffset, NULL);
+ if (! regexp)
+ std::cerr << "Warning: Failed to compile regexp: " << pattern
+ << std::endl;
+}
+
+mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern)
+{
+ const char *error;
+ int erroffset;
+ regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS,
+ &error, &erroffset, NULL);
+ assert(regexp);
+}
+
+bool mask_t::match(const std::string& str) const
+{
+ static int ovec[30];
+ int result = pcre_exec((pcre *)regexp, NULL,
+ str.c_str(), str.length(), 0, 0, ovec, 30);
+ return result >= 0 && ! exclude;
+}
+
+mask_t::~mask_t() {
+ pcre_free((pcre *)regexp);
+}
+
+#if 1
+
+bool matches(const masks_list& regexps, const std::string& str,
+ bool * by_exclusion)
+{
+ if (regexps.empty())
+ return false;
+
+ bool match = false;
+ bool definite = false;
+
+ for (masks_list::const_iterator r = regexps.begin();
+ r != regexps.end();
+ r++) {
+ static int ovec[30];
+ int result = pcre_exec((pcre *)(*r).regexp, NULL,
+ str.c_str(), str.length(), 0, 0, ovec, 30);
+ if (result >= 0) {
+ match = ! (*r).exclude;
+ definite = true;
+ }
+ else if ((*r).exclude) {
+ if (! match)
+ match = ! definite;
+ }
+ else {
+ definite = true;
+ }
+ }
+
+ if (by_exclusion)
+ *by_exclusion = match && ! definite && by_exclusion;
+
+ return match;
+}
+
+#endif
+
+void node_t::compute(balance_t& result, const details_t& details) const
+{
+ switch (type) {
+ case CONSTANT_A:
+ result = constant_a;
+ break;
+
+ case CONSTANT_T:
+ result = (unsigned int) constant_t;
+ break;
+
+ case AMOUNT:
+ if (details.xact)
+ result = details.xact->amount;
+ else if (details.account)
+ result = details.account->value.quantity;
+ break;
+
+ case COST:
+ if (details.xact)
+ result = details.xact->cost;
+ else if (details.account)
+ result = details.account->value.cost;
+ break;
+
+ case BALANCE:
+ if (details.balance) {
+ result = details.balance->quantity;
+ if (details.xact)
+ result -= details.xact->amount;
+ else if (details.account)
+ result -= details.account->value.quantity;
+ }
+ break;
+
+ case COST_BALANCE:
+ if (details.balance) {
+ result = details.balance->cost;
+ if (details.xact)
+ result -= details.xact->cost;
+ else if (details.account)
+ result -= details.account->value.cost;
+ }
+ break;
+
+ case TOTAL:
+ if (details.balance)
+ result = details.balance->quantity;
+ else if (details.account)
+ result = details.account->total.quantity;
+ break;
+ case COST_TOTAL:
+ if (details.balance)
+ result = details.balance->cost;
+ else if (details.account)
+ result = details.account->total.cost;
+ break;
+
+ case DATE:
+ if (details.entry)
+ result = (unsigned int) details.entry->date;
+ break;
+
+ case CLEARED:
+ if (details.entry)
+ result = details.entry->state == entry_t::CLEARED;
+ break;
+
+ case REAL:
+ if (details.xact)
+ result = bool(details.xact->flags & TRANSACTION_VIRTUAL);
+ break;
+
+ case INDEX:
+ if (details.index)
+ result = *details.index + 1;
+ break;
+
+ case F_ARITH_MEAN:
+ if (details.index) {
+ assert(left);
+ left->compute(result, details);
+ result /= amount_t(*details.index + 1);
+ }
+ break;
+
+ case F_NEG:
+ assert(left);
+ left->compute(result, details);
+ result.negate();
+ break;
+
+ case F_ABS:
+ assert(left);
+ left->compute(result, details);
+ result = abs(result);
+ break;
+
+ case F_PAYEE_MASK:
+ assert(mask);
+ if (details.entry)
+ result = mask->match(details.entry->payee);
+ break;
+
+ case F_ACCOUNT_MASK:
+ assert(mask);
+ if (details.account)
+ result = mask->match(details.account->fullname());
+ break;
+
+ case F_VALUE: {
+ assert(left);
+ left->compute(result, details);
+
+ std::time_t moment = -1;
+ if (right && details.entry) {
+ switch (right->type) {
+ case DATE: moment = details.entry->date; break;
+ default:
+ throw compute_error("Invalid date passed to P(v,d)");
+ }
+ }
+ result = result.value(moment);
+ break;
+ }
+
+ case O_NOT:
+ left->compute(result, details);
+ result = result ? false : true;
+ break;
+
+ case O_QUES:
+ assert(left);
+ assert(right);
+ assert(right->type == O_COL);
+ left->compute(result, details);
+ if (result)
+ right->left->compute(result, details);
+ else
+ right->right->compute(result, details);
+ break;
+
+ case O_AND:
+ assert(left);
+ assert(right);
+ left->compute(result, details);
+ if (result)
+ right->compute(result, details);
+ break;
+
+ case O_OR:
+ assert(left);
+ assert(right);
+ left->compute(result, details);
+ if (! result)
+ right->compute(result, details);
+ break;
+
+ case O_EQ:
+ case O_LT:
+ case O_LTE:
+ case O_GT:
+ case O_GTE: {
+ assert(left);
+ assert(right);
+ left->compute(result, details);
+ balance_t temp = result;
+ right->compute(result, details);
+ switch (type) {
+ case O_EQ: result = temp == result; break;
+ case O_LT: result = temp < result; break;
+ case O_LTE: result = temp <= result; break;
+ case O_GT: result = temp > result; break;
+ case O_GTE: result = temp >= result; break;
+ default: assert(0); break;
+ }
+ break;
+ }
+
+ case O_ADD:
+ case O_SUB:
+ case O_MUL:
+ case O_DIV: {
+ assert(left);
+ assert(right);
+ right->compute(result, details);
+ balance_t temp = result;
+ left->compute(result, details);
+ switch (type) {
+ case O_ADD: result += temp; break;
+ case O_SUB: result -= temp; break;
+ case O_MUL: result *= temp; break;
+ case O_DIV: result /= temp; break;
+ default: assert(0); break;
+ }
+ break;
+ }
+
+ case LAST:
+ default:
+ assert(0);
+ break;
+ }
+}
+
+node_t * parse_term(std::istream& in);
+
+inline node_t * parse_term(const char * p) {
+ std::istringstream stream(p);
+ return parse_term(stream);
+}
+
+node_t * parse_term(std::istream& in)
+{
+ node_t * node = NULL;
+
+ char c = in.peek();
+ if (std::isdigit(c) || c == '.' || c == '{') {
+ std::string ident;
+
+ if (c == '{') {
+ in.get(c);
+ c = in.peek();
+ while (! in.eof() && c != '}') {
+ in.get(c);
+ ident += c;
+ c = in.peek();
+ }
+ if (c == '}')
+ in.get(c);
+ else
+ throw expr_error("Missing '}'");
+ } else {
+ while (! in.eof() && std::isdigit(c) || c == '.') {
+ in.get(c);
+ ident += c;
+ c = in.peek();
+ }
+ }
+
+ if (! ident.empty()) {
+ node = new node_t(node_t::CONSTANT_A);
+ node->constant_a.parse(ident);
+ }
+ return node;
+ }
+
+ in.get(c);
+ switch (c) {
+ // Basic terms
+ case 'a': node = new node_t(node_t::AMOUNT); break;
+ case 'c': node = new node_t(node_t::COST); break;
+ case 'd': node = new node_t(node_t::DATE); break;
+ case 'X': node = new node_t(node_t::CLEARED); break;
+ case 'R': node = new node_t(node_t::REAL); break;
+ case 'i': node = new node_t(node_t::INDEX); break;
+ case 'B': node = new node_t(node_t::BALANCE); break;
+ case 'T': node = new node_t(node_t::TOTAL); break;
+ case 'C': node = new node_t(node_t::COST_TOTAL); break;
+
+ // Compound terms
+ case 'v': node = parse_expr("P(a,d)"); break;
+ case 'V': node = parse_term("P(T,d)"); break;
+ case 'g': node = parse_expr("v-c"); break;
+ case 'G': node = parse_expr("V-C"); break;
+ case 'o': node = parse_expr("d-b"); break;
+ case 'w': node = parse_expr("e-d"); break;
+
+ // Functions
+ case '-':
+ node = new node_t(node_t::F_NEG);
+ node->left = parse_term(in);
+ break;
+
+ case 'A':
+ node = new node_t(node_t::F_ABS);
+ node->left = parse_term(in);
+ break;
+
+ case 'M':
+ node = new node_t(node_t::F_ARITH_MEAN);
+ node->left = parse_term(in);
+ break;
+
+ case 'D': {
+ node = new node_t(node_t::O_SUB);
+ node->left = parse_term("a");
+ node->right = parse_term(in);
+ break;
+ }
+
+ case 'P':
+ node = new node_t(node_t::F_VALUE);
+ if (in.peek() == '(') {
+ in.get(c);
+ node->left = parse_expr(in);
+ if (in.peek() == ',') {
+ in.get(c);
+ node->right = parse_expr(in);
+ }
+ if (in.peek() == ')')
+ in.get(c);
+ else
+ throw expr_error("Missing ')'");
+ } else {
+ node->left = parse_term(in);
+ }
+ break;
+
+ // Other
+ case '/': {
+ std::string ident;
+ bool payee_mask = false;
+
+ c = in.peek();
+ if (c == '/') {
+ payee_mask = true;
+ in.get(c);
+ c = in.peek();
+ }
+
+ while (! in.eof() && c != '/') {
+ in.get(c);
+ if (c == '\\')
+ in.get(c);
+ ident += c;
+ c = in.peek();
+ }
+
+ if (c == '/') {
+ in.get(c);
+ node = new node_t(payee_mask ?
+ node_t::F_PAYEE_MASK : node_t::F_ACCOUNT_MASK);
+ node->mask = new mask_t(ident);
+ } else {
+ throw expr_error("Missing closing '/'");
+ }
+ break;
+ }
+
+ case '(':
+ node = parse_expr(in);
+ if (in.peek() == ')')
+ in.get(c);
+ else
+ throw expr_error("Missing ')'");
+ break;
+
+ case '[': {
+ std::string ident;
+
+ c = in.peek();
+ while (! in.eof() && c != ']') {
+ in.get(c);
+ ident += c;
+ c = in.peek();
+ }
+ if (c == ']') {
+ in.get(c);
+ node = new node_t(node_t::CONSTANT_T);
+ if (! parse_date(ident.c_str(), &node->constant_t))
+ throw expr_error("Failed to parse date");
+ } else {
+ throw expr_error("Missing ']'");
+ }
+ break;
+ }
+
+ default:
+ in.unget();
+ break;
+ }
+
+ return node;
+}
+
+node_t * parse_mul_expr(std::istream& in)
+{
+ node_t * node = NULL;
+
+ node = parse_term(in);
+
+ if (node && ! in.eof()) {
+ char c = in.peek();
+ while (c == '*' || c == '/') {
+ in.get(c);
+ switch (c) {
+ case '*': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_MUL);
+ node->left = prev;
+ node->right = parse_term(in);
+ break;
+ }
+
+ case '/': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_DIV);
+ node->left = prev;
+ node->right = parse_term(in);
+ break;
+ }
+ }
+ c = in.peek();
+ }
+ }
+
+ return node;
+}
+
+node_t * parse_add_expr(std::istream& in)
+{
+ node_t * node = NULL;
+
+ node = parse_mul_expr(in);
+
+ if (node && ! in.eof()) {
+ char c = in.peek();
+ while (c == '+' || c == '-') {
+ in.get(c);
+ switch (c) {
+ case '+': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_ADD);
+ node->left = prev;
+ node->right = parse_mul_expr(in);
+ break;
+ }
+
+ case '-': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_SUB);
+ node->left = prev;
+ node->right = parse_mul_expr(in);
+ break;
+ }
+ }
+ c = in.peek();
+ }
+ }
+
+ return node;
+}
+
+node_t * parse_logic_expr(std::istream& in)
+{
+ node_t * node = NULL;
+
+ if (in.peek() == '!') {
+ char c;
+ in.get(c);
+ node = new node_t(node_t::O_NOT);
+ node->left = parse_logic_expr(in);
+ return node;
+ }
+
+ node = parse_add_expr(in);
+
+ if (node && ! in.eof()) {
+ char c = in.peek();
+ if (c == '=' || c == '<' || c == '>') {
+ in.get(c);
+ switch (c) {
+ case '=': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_EQ);
+ node->left = prev;
+ node->right = parse_add_expr(in);
+ break;
+ }
+
+ case '<': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_LT);
+ if (in.peek() == '=') {
+ in.get(c);
+ node->type = node_t::O_LTE;
+ }
+ node->left = prev;
+ node->right = parse_add_expr(in);
+ break;
+ }
+
+ case '>': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_GT);
+ if (in.peek() == '=') {
+ in.get(c);
+ node->type = node_t::O_GTE;
+ }
+ node->left = prev;
+ node->right = parse_add_expr(in);
+ break;
+ }
+
+ default:
+ if (! in.eof()) {
+ std::ostringstream err;
+ err << "Unexpected character '" << c << "'";
+ throw expr_error(err.str());
+ }
+ }
+ }
+ }
+
+ return node;
+}
+
+node_t * parse_expr(std::istream& in)
+{
+ node_t * node = NULL;
+
+ node = parse_logic_expr(in);
+
+ if (node && ! in.eof()) {
+ char c = in.peek();
+ while (c == '&' || c == '|' || c == '?') {
+ in.get(c);
+ switch (c) {
+ case '&': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_AND);
+ node->left = prev;
+ node->right = parse_logic_expr(in);
+ break;
+ }
+
+ case '|': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_OR);
+ node->left = prev;
+ node->right = parse_logic_expr(in);
+ break;
+ }
+
+ case '?': {
+ node_t * prev = node;
+ node = new node_t(node_t::O_QUES);
+ node->left = prev;
+ node_t * choices = new node_t(node_t::O_COL);
+ node->right = choices;
+ choices->left = parse_logic_expr(in);
+ c = in.peek();
+ if (c != ':') {
+ std::ostringstream err;
+ err << "Unexpected character '" << c << "'";
+ throw expr_error(err.str());
+ }
+ in.get(c);
+ choices->right = parse_logic_expr(in);
+ break;
+ }
+
+ default:
+ if (! in.eof()) {
+ std::ostringstream err;
+ err << "Unexpected character '" << c << "'";
+ throw expr_error(err.str());
+ }
+ }
+ c = in.peek();
+ }
+ }
+
+ return node;
+}
+
+} // namespace ledger
+
+
+#ifdef TEST
+
+namespace ledger {
+
+static void dump_tree(std::ostream& out, node_t * node)
+{
+ switch (node->type) {
+ case node_t::CONSTANT_A:
+ out << "CONST[" << node->constant_a << "]";
+ break;
+ case node_t::CONSTANT_T:
+ out << "DATE/TIME[" << node->constant_t << "]";
+ break;
+
+ case node_t::AMOUNT: out << "AMOUNT"; break;
+ case node_t::COST: out << "COST"; break;
+ case node_t::DATE: out << "DATE"; break;
+ case node_t::CLEARED: out << "CLEARED"; break;
+ case node_t::REAL: out << "REAL"; break;
+ case node_t::INDEX: out << "INDEX"; break;
+ case node_t::BALANCE: out << "BALANCE"; break;
+ case node_t::COST_BALANCE: out << "COST_BALANCE"; break;
+ case node_t::TOTAL: out << "TOTAL"; break;
+ case node_t::COST_TOTAL: out << "COST_TOTAL"; break;
+
+ case node_t::F_ARITH_MEAN:
+ out << "MEAN(";
+ dump_tree(out, node->left);
+ out << ")";
+ break;
+
+ case node_t::F_NEG:
+ out << "ABS(";
+ dump_tree(out, node->left);
+ out << ")";
+ break;
+
+ case node_t::F_ABS:
+ out << "ABS(";
+ dump_tree(out, node->left);
+ out << ")";
+ break;
+
+ case node_t::F_PAYEE_MASK:
+ assert(node->mask);
+ out << "P_MASK(" << node->mask->pattern << ")";
+ break;
+
+ case node_t::F_ACCOUNT_MASK:
+ assert(node->mask);
+ out << "A_MASK(" << node->mask->pattern << ")";
+ break;
+
+ case node_t::F_VALUE:
+ out << "VALUE(";
+ dump_tree(out, node->left);
+ if (node->right) {
+ out << ", ";
+ dump_tree(out, node->right);
+ }
+ out << ")";
+ break;
+
+ case node_t::O_NOT:
+ out << "!";
+ dump_tree(out, node->left);
+ break;
+
+ case node_t::O_QUES:
+ dump_tree(out, node->left);
+ out << "?";
+ dump_tree(out, node->right->left);
+ out << ":";
+ dump_tree(out, node->right->right);
+ break;
+
+ case node_t::O_AND:
+ case node_t::O_OR:
+ out << "(";
+ dump_tree(out, node->left);
+ switch (node->type) {
+ case node_t::O_AND: out << " & "; break;
+ case node_t::O_OR: out << " | "; break;
+ default: assert(0); break;
+ }
+ dump_tree(out, node->right);
+ out << ")";
+ break;
+
+ case node_t::O_EQ:
+ case node_t::O_LT:
+ case node_t::O_LTE:
+ case node_t::O_GT:
+ case node_t::O_GTE:
+ out << "(";
+ dump_tree(out, node->left);
+ switch (node->type) {
+ case node_t::O_EQ: out << "="; break;
+ case node_t::O_LT: out << "<"; break;
+ case node_t::O_LTE: out << "<="; break;
+ case node_t::O_GT: out << ">"; break;
+ case node_t::O_GTE: out << ">="; break;
+ default: assert(0); break;
+ }
+ dump_tree(out, node->right);
+ out << ")";
+ break;
+
+ case node_t::O_ADD:
+ case node_t::O_SUB:
+ case node_t::O_MUL:
+ case node_t::O_DIV:
+ out << "(";
+ dump_tree(out, node->left);
+ switch (node->type) {
+ case node_t::O_ADD: out << "+"; break;
+ case node_t::O_SUB: out << "-"; break;
+ case node_t::O_MUL: out << "*"; break;
+ case node_t::O_DIV: out << "/"; break;
+ default: assert(0); break;
+ }
+ dump_tree(out, node->right);
+ out << ")";
+ break;
+
+ case node_t::LAST:
+ default:
+ assert(0);
+ break;
+ }
+}
+
+} // namespace ledger
+
+int main(int argc, char *argv[])
+{
+ ledger::dump_tree(std::cout, ledger::parse_expr(argv[1]));
+ std::cout << std::endl;
+}
+
+#endif // TEST