602 lines
12 KiB
C++
602 lines
12 KiB
C++
#include "expr.h"
|
|
|
|
namespace ledger {
|
|
|
|
balance_t node_t::compute(const std::time_t begin,
|
|
const std::time_t end,
|
|
const item_t * item) const
|
|
{
|
|
balance_t temp;
|
|
|
|
switch (type) {
|
|
case CONSTANT_A:
|
|
temp = constant_a;
|
|
break;
|
|
|
|
case CONSTANT_T:
|
|
temp = amount_t((unsigned int) constant_t);
|
|
break;
|
|
|
|
case AMOUNT:
|
|
temp = item->value.quantity;
|
|
break;
|
|
case COST:
|
|
temp = item->value.cost;
|
|
break;
|
|
|
|
case BALANCE:
|
|
temp = item->total.quantity - item->value.quantity;
|
|
break;
|
|
case COST_BALANCE:
|
|
temp = item->total.cost - item->value.cost;
|
|
break;
|
|
|
|
case TOTAL:
|
|
temp = item->total.quantity;
|
|
break;
|
|
case COST_TOTAL:
|
|
temp = item->total.cost;
|
|
break;
|
|
|
|
case DATE:
|
|
temp = amount_t((unsigned int) item->date);
|
|
break;
|
|
|
|
case INDEX:
|
|
temp = amount_t(item->index + 1);
|
|
break;
|
|
|
|
case BEGIN_DATE:
|
|
temp = amount_t((unsigned int) begin);
|
|
break;
|
|
|
|
case END_DATE:
|
|
temp = amount_t((unsigned int) end);
|
|
break;
|
|
|
|
case F_ARITH_MEAN:
|
|
assert(left);
|
|
temp = left->compute(begin, end, item);
|
|
temp /= amount_t(item->index + 1);
|
|
break;
|
|
|
|
case F_NEG:
|
|
assert(left);
|
|
temp = left->compute(begin, end, item).negated();
|
|
break;
|
|
|
|
case F_ABS:
|
|
assert(left);
|
|
temp = abs(left->compute(begin, end, item));
|
|
break;
|
|
|
|
case F_REGEXP:
|
|
assert(mask);
|
|
temp = (item->account &&
|
|
mask->match(item->account->fullname())) ? 1 : 0;
|
|
break;
|
|
|
|
case F_VALUE: {
|
|
assert(left);
|
|
temp = left->compute(begin, end, item);
|
|
|
|
std::time_t moment = -1;
|
|
if (right) {
|
|
switch (right->type) {
|
|
case DATE: moment = item->date; break;
|
|
case BEGIN_DATE: moment = begin; break;
|
|
case END_DATE: moment = end; break;
|
|
default: assert(0); break;
|
|
}
|
|
}
|
|
temp = temp.value(moment);
|
|
break;
|
|
}
|
|
|
|
case O_NOT:
|
|
temp = left->compute(begin, end, item) ? 0 : 1;
|
|
break;
|
|
|
|
case O_QUES:
|
|
temp = left->compute(begin, end, item);
|
|
if (temp)
|
|
temp = right->left->compute(begin, end, item);
|
|
else
|
|
temp = right->right->compute(begin, end, item);
|
|
break;
|
|
|
|
case O_AND:
|
|
case O_OR:
|
|
case O_EQ:
|
|
case O_LT:
|
|
case O_LTE:
|
|
case O_GT:
|
|
case O_GTE:
|
|
case O_ADD:
|
|
case O_SUB:
|
|
case O_MUL:
|
|
case O_DIV: {
|
|
assert(left);
|
|
assert(right);
|
|
balance_t left_bal = left->compute(begin, end, item);
|
|
balance_t right_bal = right->compute(begin, end, item);
|
|
switch (type) {
|
|
case O_AND: temp = (left_bal && right_bal) ? 1 : 0; break;
|
|
case O_OR: temp = (left_bal || right_bal) ? 1 : 0; break;
|
|
case O_EQ: temp = (left_bal == right_bal) ? 1 : 0; break;
|
|
case O_LT: temp = (left_bal < right_bal) ? 1 : 0; break;
|
|
case O_LTE: temp = (left_bal <= right_bal) ? 1 : 0; break;
|
|
case O_GT: temp = (left_bal > right_bal) ? 1 : 0; break;
|
|
case O_GTE: temp = (left_bal >= right_bal) ? 1 : 0; break;
|
|
case O_ADD: temp = left_bal + right_bal; break;
|
|
case O_SUB: temp = left_bal - right_bal; break;
|
|
case O_MUL: temp = left_bal * right_bal; break;
|
|
case O_DIV: temp = left_bal / right_bal; break;
|
|
default: assert(0); break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LAST:
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
void dump_tree(std::ostream& out, node_t * node)
|
|
{
|
|
switch (node->type) {
|
|
case CONSTANT_A: out << "CONST[" << node->constant_a << "]"; break;
|
|
case CONSTANT_T: out << "DATE/TIME[" << node->constant_t << "]"; break;
|
|
case AMOUNT: out << "AMOUNT"; break;
|
|
case COST: out << "COST"; break;
|
|
case DATE: out << "DATE"; break;
|
|
case INDEX: out << "INDEX"; break;
|
|
case BALANCE: out << "BALANCE"; break;
|
|
case COST_BALANCE: out << "COST_BALANCE"; break;
|
|
case TOTAL: out << "TOTAL"; break;
|
|
case COST_TOTAL: out << "COST_TOTAL"; break;
|
|
case BEGIN_DATE: out << "BEGIN"; break;
|
|
case END_DATE: out << "END"; break;
|
|
|
|
case F_ARITH_MEAN:
|
|
out << "MEAN(";
|
|
dump_tree(out, node->left);
|
|
out << ")";
|
|
break;
|
|
|
|
case F_NEG:
|
|
out << "ABS(";
|
|
dump_tree(out, node->left);
|
|
out << ")";
|
|
break;
|
|
|
|
case F_ABS:
|
|
out << "ABS(";
|
|
dump_tree(out, node->left);
|
|
out << ")";
|
|
break;
|
|
|
|
case F_REGEXP:
|
|
assert(node->mask);
|
|
out << "RE(" << node->mask->pattern << ")";
|
|
break;
|
|
|
|
case F_VALUE:
|
|
out << "VALUE(";
|
|
dump_tree(out, node->left);
|
|
if (node->right) {
|
|
out << ", ";
|
|
dump_tree(out, node->right);
|
|
}
|
|
out << ")";
|
|
break;
|
|
|
|
case O_NOT:
|
|
out << "!";
|
|
dump_tree(out, node->left);
|
|
break;
|
|
|
|
case O_QUES:
|
|
dump_tree(out, node->left);
|
|
out << "?";
|
|
dump_tree(out, node->right->left);
|
|
out << ":";
|
|
dump_tree(out, node->right->right);
|
|
break;
|
|
|
|
case O_AND:
|
|
case O_OR:
|
|
case O_EQ:
|
|
case O_LT:
|
|
case O_LTE:
|
|
case O_GT:
|
|
case O_GTE:
|
|
case O_ADD:
|
|
case O_SUB:
|
|
case O_MUL:
|
|
case O_DIV:
|
|
out << "(";
|
|
dump_tree(out, node->left);
|
|
switch (node->type) {
|
|
case O_AND: out << " & "; break;
|
|
case O_OR: out << " | "; break;
|
|
case O_EQ: out << "="; break;
|
|
case O_LT: out << "<"; break;
|
|
case O_LTE: out << "<="; break;
|
|
case O_GT: out << ">"; break;
|
|
case O_GTE: out << ">="; break;
|
|
case O_ADD: out << "+"; break;
|
|
case O_SUB: out << "-"; break;
|
|
case O_MUL: out << "*"; break;
|
|
case O_DIV: out << "/"; break;
|
|
default: assert(0); break;
|
|
}
|
|
dump_tree(out, node->right);
|
|
out << ")";
|
|
break;
|
|
|
|
case LAST:
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
node_t * parse_term(std::istream& in, ledger_t * ledger);
|
|
|
|
inline node_t * parse_term(const char * p, ledger_t * ledger) {
|
|
std::istringstream stream(p);
|
|
return parse_term(stream, ledger);
|
|
}
|
|
|
|
node_t * parse_term(std::istream& in, ledger_t * ledger)
|
|
{
|
|
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
|
|
ident = "0";
|
|
} else {
|
|
while (! in.eof() && std::isdigit(c) || c == '.') {
|
|
in.get(c);
|
|
ident += c;
|
|
c = in.peek();
|
|
}
|
|
}
|
|
|
|
if (! ident.empty()) {
|
|
node = new node_t(CONSTANT_A);
|
|
node->constant_a.parse(ident, ledger);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
in.get(c);
|
|
switch (c) {
|
|
// Basic terms
|
|
case 'a': node = new node_t(AMOUNT); break;
|
|
case 'c': node = new node_t(COST); break;
|
|
case 'd': node = new node_t(DATE); break;
|
|
case 'b': node = new node_t(BEGIN_DATE); break;
|
|
case 'e': node = new node_t(END_DATE); break;
|
|
case 'i': node = new node_t(INDEX); break;
|
|
case 'B': node = new node_t(BALANCE); break;
|
|
case 'T': node = new node_t(TOTAL); break;
|
|
case 'C': node = new node_t(COST_TOTAL); break;
|
|
|
|
// Compound terms
|
|
case 'v': node = parse_expr("P(a,d)", ledger); break;
|
|
case 'V': node = parse_term("P(T,d)", ledger); break;
|
|
case 'g': node = parse_expr("v-c", ledger); break;
|
|
case 'G': node = parse_expr("V-C", ledger); break;
|
|
case 'o': node = parse_expr("d-b", ledger); break;
|
|
case 'w': node = parse_expr("e-d", ledger); break;
|
|
|
|
// Functions
|
|
case '-':
|
|
node = new node_t(F_NEG);
|
|
node->left = parse_term(in, ledger);
|
|
break;
|
|
|
|
case 'A': // absolute value ("positive")
|
|
node = new node_t(F_ABS);
|
|
node->left = parse_term(in, ledger);
|
|
break;
|
|
|
|
case 'M':
|
|
node = new node_t(F_ARITH_MEAN);
|
|
node->left = parse_term(in, ledger);
|
|
break;
|
|
|
|
case 'D': {
|
|
node = new node_t(O_SUB);
|
|
node->left = parse_term("a", ledger);
|
|
node->right = parse_term(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case 'P':
|
|
node = new node_t(F_VALUE);
|
|
if (in.peek() == '(') {
|
|
in.get(c);
|
|
node->left = parse_expr(in, ledger);
|
|
if (in.peek() == ',') {
|
|
in.get(c);
|
|
node->right = parse_expr(in, ledger);
|
|
}
|
|
if (in.peek() == ')')
|
|
in.get(c);
|
|
} else {
|
|
node->left = parse_term(in, ledger);
|
|
}
|
|
break;
|
|
|
|
// Other
|
|
case '/': {
|
|
std::string ident;
|
|
|
|
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(F_REGEXP);
|
|
node->mask = new mask_t(ident);
|
|
} else {
|
|
assert(0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '(':
|
|
node = parse_expr(in, ledger);
|
|
if (in.peek() == ')')
|
|
in.get(c);
|
|
else
|
|
assert(0);
|
|
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(CONSTANT_T);
|
|
if (! parse_date(ident.c_str(), &node->constant_t))
|
|
assert(0);
|
|
} else {
|
|
assert(0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
in.unget();
|
|
break;
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
node_t * parse_mul_expr(std::istream& in, ledger_t * ledger)
|
|
{
|
|
node_t * node = NULL;
|
|
|
|
node = parse_term(in, ledger);
|
|
|
|
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(O_MUL);
|
|
node->left = prev;
|
|
node->right = parse_term(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case '/': {
|
|
node_t * prev = node;
|
|
node = new node_t(O_DIV);
|
|
node->left = prev;
|
|
node->right = parse_term(in, ledger);
|
|
break;
|
|
}
|
|
}
|
|
c = in.peek();
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
node_t * parse_add_expr(std::istream& in, ledger_t * ledger)
|
|
{
|
|
node_t * node = NULL;
|
|
|
|
node = parse_mul_expr(in, ledger);
|
|
|
|
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(O_ADD);
|
|
node->left = prev;
|
|
node->right = parse_mul_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case '-': {
|
|
node_t * prev = node;
|
|
node = new node_t(O_SUB);
|
|
node->left = prev;
|
|
node->right = parse_mul_expr(in, ledger);
|
|
break;
|
|
}
|
|
}
|
|
c = in.peek();
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
node_t * parse_logic_expr(std::istream& in, ledger_t * ledger)
|
|
{
|
|
node_t * node = NULL;
|
|
|
|
if (in.peek() == '!') {
|
|
char c;
|
|
in.get(c);
|
|
node = new node_t(O_NOT);
|
|
node->left = parse_logic_expr(in, ledger);
|
|
return node;
|
|
}
|
|
|
|
node = parse_add_expr(in, ledger);
|
|
|
|
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(O_EQ);
|
|
node->left = prev;
|
|
node->right = parse_add_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case '<': {
|
|
node_t * prev = node;
|
|
node = new node_t(O_LT);
|
|
if (in.peek() == '=') {
|
|
in.get(c);
|
|
node->type = O_LTE;
|
|
}
|
|
node->left = prev;
|
|
node->right = parse_add_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case '>': {
|
|
node_t * prev = node;
|
|
node = new node_t(O_GT);
|
|
if (in.peek() == '=') {
|
|
in.get(c);
|
|
node->type = O_GTE;
|
|
}
|
|
node->left = prev;
|
|
node->right = parse_add_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (! in.eof())
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
node_t * parse_expr(std::istream& in, ledger_t * ledger)
|
|
{
|
|
node_t * node = NULL;
|
|
|
|
node = parse_logic_expr(in, ledger);
|
|
|
|
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(O_AND);
|
|
node->left = prev;
|
|
node->right = parse_logic_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case '|': {
|
|
node_t * prev = node;
|
|
node = new node_t(O_OR);
|
|
node->left = prev;
|
|
node->right = parse_logic_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
case '?': {
|
|
node_t * prev = node;
|
|
node = new node_t(O_QUES);
|
|
node->left = prev;
|
|
node_t * choices = new node_t(O_COL);
|
|
node->right = choices;
|
|
choices->left = parse_logic_expr(in, ledger);
|
|
c = in.peek();
|
|
assert(c == ':');
|
|
in.get(c);
|
|
choices->right = parse_logic_expr(in, ledger);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (! in.eof())
|
|
assert(0);
|
|
break;
|
|
}
|
|
c = in.peek();
|
|
}
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
} // namespace ledger
|
|
|
|
#ifdef TEST
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
ledger::dump_tree(std::cout, ledger::parse_expr(argv[1], NULL));
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
#endif
|