summaryrefslogtreecommitdiff
path: root/src/format.cc
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2012-03-02 10:58:16 -0600
committerJohn Wiegley <johnw@newartisans.com>2012-03-02 10:58:16 -0600
commit35ace8816adb76e80e99afb947d66777b31ba43f (patch)
treef036049fe73dcb4ad32768c155c675adef481b51 /src/format.cc
parent8b9b9e9e723cc24a030f0cb5ae101d0978a0aa81 (diff)
downloadfork-ledger-35ace8816adb76e80e99afb947d66777b31ba43f.tar.gz
fork-ledger-35ace8816adb76e80e99afb947d66777b31ba43f.tar.bz2
fork-ledger-35ace8816adb76e80e99afb947d66777b31ba43f.zip
Improvements to format parsing
Fixes #337
Diffstat (limited to 'src/format.cc')
-rw-r--r--src/format.cc270
1 files changed, 172 insertions, 98 deletions
diff --git a/src/format.cc b/src/format.cc
index a391fdf1..4ff1fc19 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -70,6 +70,27 @@ void format_t::element_t::dump(std::ostream& out) const
}
namespace {
+ struct format_mapping_t {
+ char letter;
+ const char * expr;
+ } single_letter_mappings[] = {
+ { 'd', "date" },
+ { 'S', "filename" },
+ { 'B', "beg_pos" },
+ { 'b', "beg_line" },
+ { 'E', "end_pos" },
+ { 'e', "end_line" },
+ { 'X', "cleared" },
+ { 'Y', "xact.cleared" },
+ { 'C', "code" },
+ { 'P', "payee" },
+ { 'a', "account.name" },
+ { 'A', "account" },
+ { 't', "justify(scrub(display_amount), $min, $max, $left, color)" },
+ { 'T', "justify(scrub(display_total), $min, $max, $left, color)" },
+ { 'N', "note" },
+ };
+
expr_t parse_single_expression(const char *& p, bool single_expr = true)
{
string temp(p);
@@ -92,6 +113,13 @@ namespace {
}
return expr;
}
+
+ inline expr_t::ptr_op_t ident_node(const string& ident)
+ {
+ expr_t::ptr_op_t node(new expr_t::op_t(expr_t::op_t::IDENT));
+ node->set_ident(ident);
+ return node;
+ }
}
format_t::element_t * format_t::parse_elements(const string& fmt,
@@ -172,122 +200,168 @@ format_t::element_t * format_t::parse_elements(const string& fmt,
current->min_width = current->max_width;
}
- switch (*p) {
- case '%':
- current->type = element_t::STRING;
- current->data = string("%");
- break;
+ if (std::isalpha(*p)) {
+ bool found = false;
+ for (std::size_t i = 0; i < (sizeof(single_letter_mappings) /
+ sizeof(format_mapping_t)); i++) {
+ if (*p == single_letter_mappings[i].letter) {
+ std::ostringstream expr;
+ for (const char * ptr = single_letter_mappings[i].expr; *ptr; ){
+ if (*ptr == '$') {
+ const char * beg = ++ptr;
+ while (*ptr && std::isalpha(*ptr))
+ ++ptr;
+ string::size_type klen = static_cast<string::size_type>(ptr - beg);
+ string keyword(beg, 0, klen);
+ if (keyword == "min")
+ expr << (current->min_width > 0 ?
+ static_cast<int>(current->min_width) : -1);
+ else if (keyword == "max")
+ expr << (current->max_width > 0 ?
+ static_cast<int>(current->max_width) : -1);
+ else if (keyword == "left")
+ expr << (current->has_flags(ELEMENT_ALIGN_LEFT) ? "false" : "true");
+ else
+ assert("Unrecognized format substitution keyword" == NULL);
+ } else {
+ expr << *ptr++;
+ }
+ }
+ current->type = element_t::EXPR;
+ current->data = expr_t(expr.str());
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ } else {
+ switch (*p) {
+ case '%':
+ current->type = element_t::STRING;
+ current->data = string("%");
+ break;
- case '$': {
- if (! tmpl)
- throw_(format_error, _("Prior field reference, but no template"));
+ case '$': {
+ if (! tmpl)
+ throw_(format_error, _("Prior field reference, but no template"));
- p++;
- if (*p == '0' || (! std::isdigit(*p) &&
- *p != 'A' && *p != 'B' && *p != 'C' &&
- *p != 'D' && *p != 'E' && *p != 'F'))
- throw_(format_error, _("%$ field reference must be a digit from 1-9"));
+ p++;
+ if (*p == '0' || (! std::isdigit(*p) &&
+ *p != 'A' && *p != 'B' && *p != 'C' &&
+ *p != 'D' && *p != 'E' && *p != 'F'))
+ throw_(format_error, _("%$ field reference must be a digit from 1-9"));
- int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10);
- element_t * tmpl_elem = tmpl->elements.get();
+ int index = std::isdigit(*p) ? *p - '0' : (*p - 'A' + 10);
+ element_t * tmpl_elem = tmpl->elements.get();
- for (int i = 1; i < index && tmpl_elem; i++) {
- tmpl_elem = tmpl_elem->next.get();
- while (tmpl_elem && tmpl_elem->type != element_t::EXPR)
+ for (int i = 1; i < index && tmpl_elem; i++) {
tmpl_elem = tmpl_elem->next.get();
- }
+ while (tmpl_elem && tmpl_elem->type != element_t::EXPR)
+ tmpl_elem = tmpl_elem->next.get();
+ }
- if (! tmpl_elem)
- throw_(format_error, _("%$ reference to a non-existent prior field"));
+ if (! tmpl_elem)
+ throw_(format_error, _("%$ reference to a non-existent prior field"));
- *current = *tmpl_elem;
- break;
- }
+ *current = *tmpl_elem;
+ break;
+ }
- case '(':
- case '{': {
- bool format_amount = *p == '{';
- if (format_amount) p++;
+ case '(':
+ case '{': {
+ bool format_amount = *p == '{';
- current->type = element_t::EXPR;
- current->data = parse_single_expression(p, ! format_amount);
+ current->type = element_t::EXPR;
+ current->data = parse_single_expression(p);
- // Wrap the subexpression in calls to justify and scrub
- if (format_amount) {
- if (! *p || *(p + 1) != '}')
- throw_(format_error, _("Expected closing brace"));
- else
- p++;
+ // Wrap the subexpression in calls to justify and scrub
+ if (! format_amount)
+ break;
expr_t::ptr_op_t op = boost::get<expr_t>(current->data).get_op();
- expr_t::ptr_op_t amount_op;
- expr_t::ptr_op_t colorize_op;
- if (op->kind == expr_t::op_t::O_CONS) {
- amount_op = op->left();
- colorize_op = op->has_right() ? op->right() : NULL;
- } else {
- amount_op = op;
+ expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ {
+ call2_node->set_left(ident_node("justify"));
+
+ {
+ expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ {
+ expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL));
+ {
+ call1_node->set_left(ident_node("scrub"));
+ call1_node->set_right(op->kind == expr_t::op_t::O_CONS ? op->left() : op);
+ }
+
+ args3_node->set_left(call1_node);
+ }
+
+ expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ {
+ expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ arg1_node->set_value(current->min_width > 0 ?
+ long(current->min_width) : -1);
+
+ args2_node->set_left(arg1_node);
+ }
+
+ {
+ expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ {
+ expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ arg2_node->set_value(current->max_width > 0 ?
+ long(current->max_width) : -1);
+
+ args1_node->set_left(arg2_node);
+ }
+
+ {
+ expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE));
+ arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT));
+
+ args1_node->set_right(arg3_node);
+ }
+ }
+
+ args2_node->set_right(args1_node);
+ }
+
+ args3_node->set_right(args2_node);
+ }
+ }
+
+ call2_node->set_right(args3_node);
+ }
}
- expr_t::ptr_op_t scrub_node(new expr_t::op_t(expr_t::op_t::IDENT));
- scrub_node->set_ident("scrub");
-
- expr_t::ptr_op_t call1_node(new expr_t::op_t(expr_t::op_t::O_CALL));
- call1_node->set_left(scrub_node);
- call1_node->set_right(amount_op);
-
- expr_t::ptr_op_t arg1_node(new expr_t::op_t(expr_t::op_t::VALUE));
- expr_t::ptr_op_t arg2_node(new expr_t::op_t(expr_t::op_t::VALUE));
- expr_t::ptr_op_t arg3_node(new expr_t::op_t(expr_t::op_t::VALUE));
-
- arg1_node->set_value(current->min_width > 0 ?
- long(current->min_width) : -1);
- arg2_node->set_value(current->max_width > 0 ?
- long(current->max_width) : -1);
- arg3_node->set_value(! current->has_flags(ELEMENT_ALIGN_LEFT));
-
current->min_width = 0;
current->max_width = 0;
- expr_t::ptr_op_t args1_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args1_node->set_left(arg2_node);
- args1_node->set_right(arg3_node);
-
- expr_t::ptr_op_t args2_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args2_node->set_left(arg1_node);
- args2_node->set_right(args1_node);
-
- expr_t::ptr_op_t args3_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args3_node->set_left(call1_node);
- args3_node->set_right(args2_node);
-
- expr_t::ptr_op_t seq1_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
- seq1_node->set_left(args3_node);
-
- expr_t::ptr_op_t justify_node(new expr_t::op_t(expr_t::op_t::IDENT));
- justify_node->set_ident("justify");
-
- expr_t::ptr_op_t call2_node(new expr_t::op_t(expr_t::op_t::O_CALL));
- call2_node->set_left(justify_node);
- call2_node->set_right(seq1_node);
-
string prev_expr = boost::get<expr_t>(current->data).text();
- if (colorize_op) {
- expr_t::ptr_op_t ansify_if_node(new expr_t::op_t(expr_t::op_t::IDENT));
- ansify_if_node->set_ident("ansify_if");
-
- expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS));
- args4_node->set_left(call2_node);
- args4_node->set_right(colorize_op);
-
- expr_t::ptr_op_t seq2_node(new expr_t::op_t(expr_t::op_t::O_SEQ));
- seq2_node->set_left(args4_node);
+ expr_t::ptr_op_t colorize_op;
+ if (op->kind == expr_t::op_t::O_CONS)
+ colorize_op = op->has_right() ? op->right() : NULL;
+ if (colorize_op) {
expr_t::ptr_op_t call3_node(new expr_t::op_t(expr_t::op_t::O_CALL));
- call3_node->set_left(ansify_if_node);
- call3_node->set_right(seq2_node);
+ {
+ call3_node->set_left(ident_node("ansify_if"));
+
+ {
+ expr_t::ptr_op_t args4_node(new expr_t::op_t(expr_t::op_t::O_CONS));
+ {
+ args4_node->set_left(call2_node); // from above
+ args4_node->set_right(colorize_op);
+ }
+
+ call3_node->set_right(args4_node);
+ }
+ }
current->data = expr_t(call3_node);
} else {
@@ -295,12 +369,12 @@ format_t::element_t * format_t::parse_elements(const string& fmt,
}
boost::get<expr_t>(current->data).set_text(prev_expr);
+ break;
}
- break;
- }
- default:
- throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ default:
+ throw_(format_error, _("Unrecognized formatting character: %1") << *p);
+ }
}
}