Accept &&/and for &, ||/or for |, and not for !. Also improved error
reporting in the tokenizer.
This commit is contained in:
parent
0b9f22b4d2
commit
fac5a95a48
2 changed files with 119 additions and 48 deletions
158
src/token.cc
158
src/token.cc
|
|
@ -34,46 +34,77 @@
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
|
int expr_t::token_t::parse_reserved_word(std::istream& in)
|
||||||
|
{
|
||||||
|
char c = in.peek();
|
||||||
|
|
||||||
|
if (c == 'a' || c == 'f' || c == 'o' || c == 'n' || c == 't') {
|
||||||
|
length = 0;
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
READ_INTO_(in, buf, 255, c, length,
|
||||||
|
std::isalnum(c) || c == '_' || c == '.' || c == '-');
|
||||||
|
|
||||||
|
switch (buf[0]) {
|
||||||
|
case 'a':
|
||||||
|
if (std::strcmp(buf, "and") == 0) {
|
||||||
|
symbol[0] = '&';
|
||||||
|
symbol[1] = '\0';
|
||||||
|
kind = KW_AND;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
if (std::strcmp(buf, "false") == 0) {
|
||||||
|
kind = VALUE;
|
||||||
|
value = false;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
if (std::strcmp(buf, "or") == 0) {
|
||||||
|
symbol[0] = '|';
|
||||||
|
symbol[1] = '\0';
|
||||||
|
kind = KW_OR;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
if (std::strcmp(buf, "not") == 0) {
|
||||||
|
symbol[0] = '!';
|
||||||
|
symbol[1] = '\0';
|
||||||
|
kind = EXCLAM;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
if (std::strcmp(buf, "true") == 0) {
|
||||||
|
kind = VALUE;
|
||||||
|
value = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
void expr_t::token_t::parse_ident(std::istream& in)
|
void expr_t::token_t::parse_ident(std::istream& in)
|
||||||
{
|
{
|
||||||
if (in.eof()) {
|
|
||||||
kind = TOK_EOF;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(in.good());
|
|
||||||
|
|
||||||
char c = peek_next_nonws(in);
|
|
||||||
|
|
||||||
if (in.eof()) {
|
|
||||||
kind = TOK_EOF;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(in.good());
|
|
||||||
|
|
||||||
kind = IDENT;
|
kind = IDENT;
|
||||||
length = 0;
|
length = 0;
|
||||||
|
|
||||||
char buf[256];
|
char c, buf[256];
|
||||||
READ_INTO_(in, buf, 255, c, length,
|
READ_INTO_(in, buf, 255, c, length,
|
||||||
std::isalnum(c) || c == '_' || c == '.' || c == '-');
|
std::isalnum(c) || c == '_' || c == '.' || c == '-');
|
||||||
|
|
||||||
switch (buf[0]) {
|
value.set_string(buf);
|
||||||
case 'f':
|
|
||||||
if (std::strcmp(buf, "false") == 0) {
|
|
||||||
kind = VALUE;
|
|
||||||
value = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 't':
|
|
||||||
if (std::strcmp(buf, "true") == 0) {
|
|
||||||
kind = VALUE;
|
|
||||||
value = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kind == IDENT)
|
|
||||||
value.set_string(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
|
|
@ -82,7 +113,8 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
kind = TOK_EOF;
|
kind = TOK_EOF;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(in.good());
|
if (! in.good())
|
||||||
|
throw_(parse_error, "Input stream no longer valid");
|
||||||
|
|
||||||
char c = peek_next_nonws(in);
|
char c = peek_next_nonws(in);
|
||||||
|
|
||||||
|
|
@ -90,7 +122,8 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
kind = TOK_EOF;
|
kind = TOK_EOF;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
assert(in.good());
|
if (! in.good())
|
||||||
|
throw_(parse_error, "Input stream no longer valid");
|
||||||
|
|
||||||
symbol[0] = c;
|
symbol[0] = c;
|
||||||
symbol[1] = '\0';
|
symbol[1] = '\0';
|
||||||
|
|
@ -100,10 +133,22 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '&':
|
case '&':
|
||||||
in.get(c);
|
in.get(c);
|
||||||
|
if (c == '&') {
|
||||||
|
in.get(c);
|
||||||
|
kind = KW_AND;
|
||||||
|
length = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
kind = KW_AND;
|
kind = KW_AND;
|
||||||
break;
|
break;
|
||||||
case '|':
|
case '|':
|
||||||
in.get(c);
|
in.get(c);
|
||||||
|
if (c == '|') {
|
||||||
|
in.get(c);
|
||||||
|
kind = KW_OR;
|
||||||
|
length = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
kind = KW_OR;
|
kind = KW_OR;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -201,7 +246,6 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
in.get(c);
|
in.get(c);
|
||||||
kind = QUERY;
|
kind = QUERY;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ':':
|
case ':':
|
||||||
in.get(c);
|
in.get(c);
|
||||||
kind = COLON;
|
kind = COLON;
|
||||||
|
|
@ -234,6 +278,14 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
length = 2;
|
length = 2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else if (c == '=') {
|
||||||
|
in.get(c);
|
||||||
|
symbol[1] = c;
|
||||||
|
symbol[2] = '\0';
|
||||||
|
kind = EQUAL;
|
||||||
|
length = 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
kind = EQUAL;
|
kind = EQUAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -269,29 +321,46 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
amount_t temp;
|
istream_pos_type pos = in.tellg();
|
||||||
istream_pos_type pos = 0;
|
|
||||||
|
|
||||||
// When in relaxed parsing mode, we want to migrate commodity
|
// First, check to see if it's a reserved word, such as: and or not
|
||||||
// flags so that any precision specified by the user updates the
|
int result = parse_reserved_word(in);
|
||||||
// current maximum displayed precision.
|
if (std::isalpha(c) && result == 1)
|
||||||
pos = in.tellg();
|
break;
|
||||||
|
|
||||||
|
// If not, rewind back to the beginning of the word to scan it
|
||||||
|
// again. If the result was -1, it means no identifier was scanned
|
||||||
|
// so we don't have to rewind.
|
||||||
|
if (result == 0) {
|
||||||
|
in.clear();
|
||||||
|
in.seekg(pos, std::ios::beg);
|
||||||
|
if (in.fail())
|
||||||
|
throw_(parse_error, "Failed to reset input stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When in relaxed parsing mode, we want to migrate commodity flags
|
||||||
|
// so that any precision specified by the user updates the current
|
||||||
|
// maximum displayed precision.
|
||||||
amount_t::flags_t parse_flags = 0;
|
amount_t::flags_t parse_flags = 0;
|
||||||
if (pflags & EXPR_PARSE_NO_MIGRATE)
|
if (pflags & EXPR_PARSE_NO_MIGRATE)
|
||||||
parse_flags |= AMOUNT_PARSE_NO_MIGRATE;
|
parse_flags |= AMOUNT_PARSE_NO_MIGRATE;
|
||||||
if (pflags & EXPR_PARSE_NO_REDUCE)
|
if (pflags & EXPR_PARSE_NO_REDUCE)
|
||||||
parse_flags |= AMOUNT_PARSE_NO_REDUCE;
|
parse_flags |= AMOUNT_PARSE_NO_REDUCE;
|
||||||
|
|
||||||
|
amount_t temp;
|
||||||
if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) {
|
if (! temp.parse(in, parse_flags | AMOUNT_PARSE_SOFT_FAIL)) {
|
||||||
// If the amount had no commodity, it must be an unambiguous
|
// If the amount had no commodity, it must be an unambiguous
|
||||||
// variable reference
|
// variable reference
|
||||||
|
|
||||||
in.clear();
|
in.clear();
|
||||||
in.seekg(pos, std::ios::beg);
|
in.seekg(pos, std::ios::beg);
|
||||||
|
if (in.fail())
|
||||||
|
throw_(parse_error, "Failed to reset input stream");
|
||||||
|
|
||||||
c = in.peek();
|
c = in.peek();
|
||||||
assert(! (std::isdigit(c) || c == '.'));
|
if (std::isdigit(c) || c == '.')
|
||||||
|
expected('\0', c);
|
||||||
|
|
||||||
parse_ident(in);
|
parse_ident(in);
|
||||||
} else {
|
} else {
|
||||||
kind = VALUE;
|
kind = VALUE;
|
||||||
|
|
@ -304,8 +373,9 @@ void expr_t::token_t::next(std::istream& in, const uint_least8_t pflags)
|
||||||
|
|
||||||
void expr_t::token_t::rewind(std::istream& in)
|
void expr_t::token_t::rewind(std::istream& in)
|
||||||
{
|
{
|
||||||
for (unsigned int i = 0; i < length; i++)
|
in.seekg(- length, std::ios::cur);
|
||||||
in.unget();
|
if (in.fail())
|
||||||
|
throw_(parse_error, "Failed to rewind input stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,9 @@ struct expr_t::token_t : public noncopyable
|
||||||
STAR, // *
|
STAR, // *
|
||||||
KW_DIV, // /
|
KW_DIV, // /
|
||||||
|
|
||||||
EXCLAM, // !
|
EXCLAM, // !, not
|
||||||
KW_AND, // &
|
KW_AND, // &, &&, and
|
||||||
KW_OR, // |
|
KW_OR, // |, ||, or
|
||||||
KW_MOD, // %
|
KW_MOD, // %
|
||||||
|
|
||||||
QUERY, // ?
|
QUERY, // ?
|
||||||
|
|
@ -90,7 +90,7 @@ struct expr_t::token_t : public noncopyable
|
||||||
token_t& operator=(const token_t& other) {
|
token_t& operator=(const token_t& other) {
|
||||||
if (&other == this)
|
if (&other == this)
|
||||||
return *this;
|
return *this;
|
||||||
assert(false);
|
assert(false); // only one token object is used at a time
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,6 +104,7 @@ struct expr_t::token_t : public noncopyable
|
||||||
symbol[2] = '\0';
|
symbol[2] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int parse_reserved_word(std::istream& in);
|
||||||
void parse_ident(std::istream& in);
|
void parse_ident(std::istream& in);
|
||||||
void next(std::istream& in, const uint_least8_t flags);
|
void next(std::istream& in, const uint_least8_t flags);
|
||||||
void rewind(std::istream& in);
|
void rewind(std::istream& in);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue