From c28d828d8e24fe637a74674bedc9bc0cbdabca1c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Tue, 17 Nov 2009 21:30:07 -0500 Subject: Renamed date_interval_t::end to finish --- src/precmd.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/precmd.cc') diff --git a/src/precmd.cc b/src/precmd.cc index 92483dc8..c4d7cbaa 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -173,8 +173,8 @@ value_t period_command(call_scope_t& args) out << _(" start: ") << format_date(*interval.start) << std::endl; else out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; - if (interval.end) - out << _(" end: ") << format_date(*interval.end) << std::endl; + if (interval.finish) + out << _(" finish: ") << format_date(*interval.finish) << std::endl; if (interval.skip_duration) out << _(" skip: ") << *interval.skip_duration << std::endl; @@ -191,8 +191,8 @@ value_t period_command(call_scope_t& args) if (interval.start) out << _(" start: ") << format_date(*interval.start) << std::endl; - if (interval.end) - out << _(" end: ") << format_date(*interval.end) << std::endl; + if (interval.finish) + out << _(" finish: ") << format_date(*interval.finish) << std::endl; if (interval.skip_duration) out << _(" skip: ") << *interval.skip_duration << std::endl; -- cgit v1.2.3 From 7fe369eb492f737f570d0ccf4aaf5db68f900279 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 04:11:14 -0500 Subject: The new period parser is implemented, but untested --- src/precmd.cc | 53 +--- src/times.cc | 959 +++++++++++++++++++++++++++++++++++++++------------------- src/times.h | 500 ++++++++++++++---------------- 3 files changed, 883 insertions(+), 629 deletions(-) (limited to 'src/precmd.cc') diff --git a/src/precmd.cc b/src/precmd.cc index c4d7cbaa..0e024a39 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -165,57 +165,12 @@ value_t period_command(call_scope_t& args) report_t& report(find_scope(args)); std::ostream& out(report.output_stream); + show_period_tokens(out, arg); + out << std::endl; + date_interval_t interval(arg); + interval.dump(out); - out << _("global details => ") << std::endl << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - else - out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - if (interval.find_period(interval.start ? - *interval.start : CURRENT_DATE())) { - out << std::endl - << _("after finding first period => ") << std::endl - << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - out << std::endl; - - for (int i = 0; i < 20 && interval; i++, ++interval) { - out << std::right; - out.width(2); - - out << i << "): " << format_date(*interval.start); - if (interval.end_of_duration) - out << " -- " << format_date(*interval.inclusive_end()); - out << std::endl; - - if (! interval.skip_duration) - break; - } - } return NULL_VALUE; } diff --git a/src/times.cc b/src/times.cc index 076cef3d..97fc3ab8 100644 --- a/src/times.cc +++ b/src/times.cc @@ -361,6 +361,8 @@ std::ostream& operator<<(std::ostream& out, out << duration.length << " week(s)"; else if (duration.quantum == date_duration_t::MONTHS) out << duration.length << " month(s)"; + else if (duration.quantum == date_duration_t::QUARTERS) + out << duration.length << " quarter(s)"; else { assert(duration.quantum == date_duration_t::YEARS); out << duration.length << " year(s)"; @@ -368,6 +370,537 @@ std::ostream& operator<<(std::ostream& out, return out; } +class date_parser_t +{ + friend void show_period_tokens(std::ostream& out, const string& arg); + + class lexer_t + { + friend class date_parser_t; + + string::const_iterator begin; + string::const_iterator end; + + public: + struct token_t + { + enum kind_t { + UNKNOWN, + + TOK_DATE, + TOK_INT, + TOK_SLASH, + TOK_DASH, + TOK_DOT, + + TOK_A_YEAR, + TOK_A_MONTH, + TOK_A_WDAY, + + TOK_SINCE, + TOK_UNTIL, + TOK_IN, + TOK_THIS, + TOK_NEXT, + TOK_LAST, + TOK_EVERY, + + TOK_TODAY, + TOK_TOMORROW, + TOK_YESTERDAY, + + TOK_YEAR, + TOK_QUARTER, + TOK_MONTH, + TOK_WEEK, + TOK_DAY, + + TOK_YEARLY, + TOK_QUARTERLY, + TOK_BIMONTHLY, + TOK_MONTHLY, + TOK_BIWEEKLY, + TOK_WEEKLY, + TOK_DAILY, + + TOK_YEARS, + TOK_QUARTERS, + TOK_MONTHS, + TOK_WEEKS, + TOK_DAYS, + + END_REACHED + + } kind; + + typedef variant content_t; + + optional value; + + explicit token_t(kind_t _kind = UNKNOWN, + const optional& _value = none) + : kind(_kind), value(_value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); + } + token_t(const token_t& tok) + : kind(tok.kind), value(tok.value) { + TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy"); + } + ~token_t() throw() { + TRACE_DTOR(date_parser_t::lexer_t::token_t); + } + + token_t& operator=(const token_t& tok) { + if (this != &tok) { + kind = tok.kind; + value = tok.value; + } + return *this; + } + + operator bool() const { + return kind != END_REACHED; + } + + string to_string() const { + switch (kind) { + case UNKNOWN: return "UNKNOWN"; + case TOK_DATE: return "TOK_DATE"; + case TOK_INT: return "TOK_INT"; + case TOK_SLASH: return "TOK_SLASH"; + case TOK_DASH: return "TOK_DASH"; + case TOK_DOT: return "TOK_DOT"; + case TOK_A_YEAR: return "TOK_A_YEAR"; + case TOK_A_MONTH: return "TOK_A_MONTH"; + case TOK_A_WDAY: return "TOK_A_WDAY"; + case TOK_SINCE: return "TOK_SINCE"; + case TOK_UNTIL: return "TOK_UNTIL"; + case TOK_IN: return "TOK_IN"; + case TOK_THIS: return "TOK_THIS"; + case TOK_NEXT: return "TOK_NEXT"; + case TOK_LAST: return "TOK_LAST"; + case TOK_EVERY: return "TOK_EVERY"; + case TOK_TODAY: return "TOK_EVERY"; + case TOK_TOMORROW: return "TOK_TOMORROW"; + case TOK_YESTERDAY: return "TOK_YESTERDAY"; + case TOK_YEAR: return "TOK_YEAR"; + case TOK_QUARTER: return "TOK_QUARTER"; + case TOK_MONTH: return "TOK_MONTH"; + case TOK_WEEK: return "TOK_WEEK"; + case TOK_DAY: return "TOK_DAY"; + case TOK_YEARLY: return "TOK_YEARLY"; + case TOK_QUARTERLY: return "TOK_QUARTERLY"; + case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; + case TOK_MONTHLY: return "TOK_MONTHLY"; + case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; + case TOK_WEEKLY: return "TOK_WEEKLY"; + case TOK_DAILY: return "TOK_DAILY"; + case TOK_YEARS: return "TOK_YEARS"; + case TOK_QUARTERS: return "TOK_QUARTERS"; + case TOK_MONTHS: return "TOK_MONTHS"; + case TOK_WEEKS: return "TOK_WEEKS"; + case TOK_DAYS: return "TOK_DAYS"; + case END_REACHED: return "END_REACHED"; + } + assert(false); + return empty_string; + } + + void unexpected(); + static void expected(char wanted, char c = '\0'); + }; + + token_t token_cache; + + lexer_t(string::const_iterator _begin, + string::const_iterator _end) + : begin(_begin), end(_end) + { + TRACE_CTOR(date_parser_t::lexer_t, ""); + } + lexer_t(const lexer_t& lexer) + : begin(lexer.begin), end(lexer.end), + token_cache(lexer.token_cache) + { + TRACE_CTOR(date_parser_t::lexer_t, "copy"); + } + ~lexer_t() throw() { + TRACE_DTOR(date_parser_t::lexer_t); + } + + token_t next_token(); + void push_token(token_t tok) { + assert(token_cache.kind == token_t::UNKNOWN); + token_cache = tok; + } + token_t peek_token() { + if (token_cache.kind == token_t::UNKNOWN) + token_cache = next_token(); + return token_cache; + } + }; + + string arg; + lexer_t lexer; + +public: + date_parser_t(const string& _arg) + : arg(_arg), lexer(arg.begin(), arg.end()) { + TRACE_CTOR(date_parser_t, ""); + } + date_parser_t(const date_parser_t& parser) + : arg(parser.arg), lexer(parser.lexer) { + TRACE_CTOR(date_parser_t, "copy"); + } + ~date_parser_t() throw() { + TRACE_DTOR(date_parser_t); + } + + date_interval_t parse(); + +private: + void determine_when(lexer_t::token_t& tok, date_specifier_t& specifier); +}; + +void date_parser_t::determine_when(date_parser_t::lexer_t::token_t& tok, + date_specifier_t& specifier) +{ + switch (tok.kind) { + case lexer_t::token_t::TOK_DATE: + specifier = boost::get(*tok.value); + break; + + case lexer_t::token_t::TOK_INT: + specifier.day = + date_specifier_t::day_type(boost::get(*tok.value)); + break; + case lexer_t::token_t::TOK_A_YEAR: + specifier.year = boost::get(*tok.value); + break; + case lexer_t::token_t::TOK_A_MONTH: + specifier.month = + date_specifier_t::month_type + (boost::get(*tok.value)); + break; + case lexer_t::token_t::TOK_A_WDAY: + specifier.wday = + date_specifier_t::day_of_week_type + (boost::get(*tok.value)); + break; + + default: + tok.unexpected(); + break; + } +} + +date_interval_t date_parser_t::parse() +{ + optional since_specifier; + optional until_specifier; + optional inclusion_specifier; + + date_interval_t period; + date_t today = CURRENT_DATE(); + bool end_inclusive = false; + + for (lexer_t::token_t tok = lexer.next_token(); + tok.kind != lexer_t::token_t::END_REACHED; + tok = lexer.next_token()) { + switch (tok.kind) { +#if 0 + case lexer_t::token_t::TOK_INT: + // jww (2009-11-18): NYI + assert(! "Need to allow for expressions like \"4 months ago\""); + tok.unexpected(); + break; +#endif + + case lexer_t::token_t::TOK_DATE: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_INT: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_YEAR: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_MONTH: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_A_WDAY: + if (! inclusion_specifier) + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + break; + + case lexer_t::token_t::TOK_DASH: + if (inclusion_specifier) { + since_specifier = inclusion_specifier; + until_specifier = date_specifier_t(); + inclusion_specifier = none; + + tok = lexer.next_token(); + determine_when(tok, *until_specifier); + + // The dash operator is special: it has an _inclusive_ end. + end_inclusive = true; + } else { + tok.unexpected(); + } + break; + + case lexer_t::token_t::TOK_SINCE: + if (since_specifier) { + tok.unexpected(); + } else { + since_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *since_specifier); + } + break; + + case lexer_t::token_t::TOK_UNTIL: + if (until_specifier) { + tok.unexpected(); + } else { + until_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *until_specifier); + } + break; + + case lexer_t::token_t::TOK_IN: + if (inclusion_specifier) { + tok.unexpected(); + } else { + inclusion_specifier = date_specifier_t(); + tok = lexer.next_token(); + determine_when(tok, *inclusion_specifier); + } + break; + + case lexer_t::token_t::TOK_THIS: + case lexer_t::token_t::TOK_NEXT: + case lexer_t::token_t::TOK_LAST: { + int8_t adjust = 0; + if (tok.kind == lexer_t::token_t::TOK_NEXT) + adjust = 1; + else if (tok.kind == lexer_t::token_t::TOK_LAST) + adjust = -1; + + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_INT: + // jww (2009-11-18): Allow things like "last 5 weeks" + assert(! "Need to allow for expressions like \"last 5 weeks\""); + tok.unexpected(); + break; + + case lexer_t::token_t::TOK_A_MONTH: { + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + + date_t temp(today.year(), *inclusion_specifier->month, 1); + temp += gregorian::years(adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year()), + temp.month()); + break; + } + + case lexer_t::token_t::TOK_A_WDAY: { + inclusion_specifier = date_specifier_t(); + determine_when(tok, *inclusion_specifier); + + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::WEEKS); + while (temp.day_of_week() != inclusion_specifier->wday) + temp += gregorian::days(1); + temp += gregorian::days(7 * adjust); + inclusion_specifier = date_specifier_t(temp); + break; + } + + case lexer_t::token_t::TOK_YEAR: { + date_t temp(today); + temp += gregorian::years(adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year())); + break; + } + + case lexer_t::token_t::TOK_QUARTER: { + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::QUARTERS); + temp += gregorian::months(3 * adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year()), + temp.month()); + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + } + + case lexer_t::token_t::TOK_MONTH: { + date_t temp(today); + temp += gregorian::months(adjust); + inclusion_specifier = + date_specifier_t(static_cast(temp.year()), + temp.month()); + break; + } + + case lexer_t::token_t::TOK_WEEK: { + date_t temp = + date_duration_t::find_nearest(today, date_duration_t::WEEKS); + temp += gregorian::days(7 * adjust); + inclusion_specifier = date_specifier_t(today); + period.duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + } + + case lexer_t::token_t::TOK_DAY: { + date_t temp(today); + temp += gregorian::days(adjust); + inclusion_specifier = date_specifier_t(temp); + break; + } + + default: + tok.unexpected(); + break; + } + break; + } + + case lexer_t::token_t::TOK_TODAY: + inclusion_specifier = date_specifier_t(today); + break; + case lexer_t::token_t::TOK_TOMORROW: + inclusion_specifier = date_specifier_t(today + gregorian::days(1)); + break; + case lexer_t::token_t::TOK_YESTERDAY: + inclusion_specifier = date_specifier_t(today - gregorian::days(1)); + break; + + case lexer_t::token_t::TOK_EVERY: + tok = lexer.next_token(); + if (tok == lexer_t::token_t::TOK_INT) { + int quantity = boost::get(*tok.value); + tok = lexer.next_token(); + switch (tok.kind) { + case lexer_t::token_t::TOK_YEARS: + period.skip_duration = date_duration_t(date_duration_t::YEARS, quantity); + break; + case lexer_t::token_t::TOK_QUARTERS: + period.skip_duration = date_duration_t(date_duration_t::QUARTERS, quantity); + break; + case lexer_t::token_t::TOK_MONTHS: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, quantity); + break; + case lexer_t::token_t::TOK_WEEKS: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, quantity); + break; + case lexer_t::token_t::TOK_DAYS: + period.skip_duration = date_duration_t(date_duration_t::DAYS, quantity); + break; + default: + tok.unexpected(); + break; + } + } else { + switch (tok.kind) { + case lexer_t::token_t::TOK_YEAR: + period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTER: + period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_MONTH: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_WEEK: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAY: + period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + break; + default: + tok.unexpected(); + break; + } + } + break; + + case lexer_t::token_t::TOK_YEARLY: + period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + break; + case lexer_t::token_t::TOK_QUARTERLY: + period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + break; + case lexer_t::token_t::TOK_BIMONTHLY: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, 2); + break; + case lexer_t::token_t::TOK_MONTHLY: + period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + break; + case lexer_t::token_t::TOK_BIWEEKLY: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, 2); + break; + case lexer_t::token_t::TOK_WEEKLY: + period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + break; + case lexer_t::token_t::TOK_DAILY: + period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + break; + + default: + tok.unexpected(); + break; + } + } + + if (! period.duration && inclusion_specifier) + period.duration = inclusion_specifier->implied_duration(); + + if (since_specifier || until_specifier) { + date_range_t range(since_specifier, until_specifier); + range.end_inclusive = end_inclusive; + + period.range = date_specifier_or_range_t(range); + } + else if (inclusion_specifier) { + period.range = date_specifier_or_range_t(*inclusion_specifier); + } + else { + /* otherwise, it's something like "monthly", with no date reference */ + } + + return period; +} + +void date_interval_t::parse(const string& str) +{ + date_parser_t parser(str); + *this = parser.parse(); +} + void date_interval_t::resolve_end() { if (start && ! end_of_duration) { @@ -395,6 +928,40 @@ void date_interval_t::resolve_end() } } +date_t date_duration_t::find_nearest(const date_t& date, skip_quantum_t skip) +{ + date_t result; + + switch (skip) { + case date_duration_t::YEARS: + result = date_t(date.year(), gregorian::Jan, 1); + break; + case date_duration_t::QUARTERS: + result = date_t(date.year(), date.month(), 1); + while (result.month() != gregorian::Jan && + result.month() != gregorian::Apr && + result.month() != gregorian::Jul && + result.month() != gregorian::Oct) + result -= gregorian::months(1); + break; + case date_duration_t::MONTHS: + result = date_t(date.year(), date.month(), 1); + break; + case date_duration_t::WEEKS: + result = date; + while (result.day_of_week() != start_of_week) + result -= gregorian::days(1); + break; + case date_duration_t::DAYS: + result = date; + break; + default: + assert(false); + break; + } + return result; +} + void date_interval_t::stabilize(const optional& date) { #if defined(DEBUG_ON) @@ -430,20 +997,15 @@ void date_interval_t::stabilize(const optional& date) date_t when = start ? *start : *date; if (duration->quantum == date_duration_t::MONTHS || + duration->quantum == date_duration_t::QUARTERS || duration->quantum == date_duration_t::YEARS) { - DEBUG("times.interval", "stabilize: monthly or yearly duration"); - - start = date_t(when.year(), gregorian::Jan, 1); + DEBUG("times.interval", + "stabilize: monthly, quarterly or yearly duration"); + start = date_duration_t::find_nearest(when, duration->quantum); } else { DEBUG("times.interval", "stabilize: daily or weekly duration"); - - start = date_t(when - gregorian::days(400)); - - if (duration->quantum == date_duration_t::WEEKS) { - // Move it to a Sunday - while (start->day_of_week() != start_of_week) - *start += gregorian::days(1); - } + start = date_duration_t::find_nearest(when - gregorian::days(400), + duration->quantum); } DEBUG("times.interval", @@ -475,6 +1037,14 @@ void date_interval_t::stabilize(const optional& date) DEBUG("times.interval", "stabilize: finish reset to initial finish"); } } + else if (range) { + start = range->begin(); + finish = range->end(); + + if (start && finish) + duration = date_duration_t(date_duration_t::DAYS, + static_cast((*finish - *start).days())); + } aligned = true; } @@ -546,7 +1116,7 @@ bool date_interval_t::find_period(const date_t& date) return true; } - scan = skip_duration->add(scan); + scan = skip_duration->add(scan); end_of_scan = duration->add(scan); } @@ -581,214 +1151,57 @@ date_interval_t& date_interval_t::operator++() return *this; } -namespace { - void parse_inclusion_specifier(const string& word, - date_t * begin, - date_t * end) - { - date_traits_t traits; - date_t when = parse_date_mask(word.c_str(), none, &traits); - - if (when.is_not_a_date()) - throw_(date_error, _("Could not parse date mask: %1") << word); - - if (begin) { - *begin = when; - - if (end) { - if (traits.has_day) - *end = *begin + gregorian::days(1); - else if (traits.has_month) - *end = *begin + gregorian::months(1); - else - *end = *begin + gregorian::years(1); - } - } - else if (end) { - *end = when; - } - } - - inline void read_lower_word(std::istream& in, string& word) { - in >> word; - for (string::size_type i = 0, l = word.length(); i < l; i++) - word[i] = static_cast(std::tolower(word[i])); - } - - void parse_date_words(std::istream& in, - string& word, - date_interval_t& interval, - bool look_for_start = true, - bool look_for_finish = true) - { - string type; - - if (word == _("this") || word == _("last") || word == _("next")) { - type = word; - if (! in.eof()) - read_lower_word(in, word); - else - word = _("month"); - } else { - type = _("this"); - } - - date_t start = CURRENT_DATE(); - date_t finish; - bool parse_specifier = false; - - optional duration; - - assert(look_for_start || look_for_finish); - - if (word == _("year")) { - duration = date_duration_t(date_duration_t::YEARS, 1); - start = gregorian::date(start.year(), 1, 1); - } - else if (word == _("month")) { - duration = date_duration_t(date_duration_t::MONTHS, 1); - start = gregorian::date(start.year(), start.month(), 1); - } - else if (word == _("today") || word == _("day")) { - duration = date_duration_t(date_duration_t::DAYS, 1); - } - else { - parse_specifier = true; - } - - if (parse_specifier) - parse_inclusion_specifier(word, &start, &finish); - else - finish = duration->add(start); - - if (type == _("last") && duration) { - start = duration->subtract(start); - finish = duration->subtract(finish); - } - else if (type == _("next") && duration) { - start = duration->add(start); - finish = duration->add(finish); - } - - if (look_for_start && is_valid(start)) interval.start = start; - if (look_for_finish && is_valid(finish)) interval.finish = finish; - } -} - -void date_interval_t::parse(std::istream& in) +void date_interval_t::dump(std::ostream& out) { - string word; - - optional mon; - optional wday; - optional year; - - while (! in.eof()) { - read_lower_word(in, word); - if (word == _("every")) { - read_lower_word(in, word); - if (std::isdigit(word[0])) { - int quantity = lexical_cast(word); - read_lower_word(in, word); - if (word == _("days")) - duration = date_duration_t(date_duration_t::DAYS, quantity); - else if (word == _("weeks")) - duration = date_duration_t(date_duration_t::WEEKS, quantity); - else if (word == _("months")) - duration = date_duration_t(date_duration_t::MONTHS, quantity); - else if (word == _("quarters")) - duration = date_duration_t(date_duration_t::MONTHS, 3 * quantity); - else if (word == _("years")) - duration = date_duration_t(date_duration_t::YEARS, quantity); - } - else if (word == _("day")) - duration = date_duration_t(date_duration_t::DAYS, 1); - else if (word == _("week")) - duration = date_duration_t(date_duration_t::WEEKS, 1); - else if (word == _("month")) - duration = date_duration_t(date_duration_t::MONTHS, 1); - else if (word == _("quarter")) - duration = date_duration_t(date_duration_t::MONTHS, 3); - else if (word == _("year")) - duration = date_duration_t(date_duration_t::YEARS, 1); - } - else if (word == _("daily")) - duration = date_duration_t(date_duration_t::DAYS, 1); - else if (word == _("weekly")) - duration = date_duration_t(date_duration_t::WEEKS, 1); - else if (word == _("biweekly")) - duration = date_duration_t(date_duration_t::WEEKS, 2); - else if (word == _("monthly")) - duration = date_duration_t(date_duration_t::MONTHS, 1); - else if (word == _("bimonthly")) - duration = date_duration_t(date_duration_t::MONTHS, 2); - else if (word == _("quarterly")) - duration = date_duration_t(date_duration_t::MONTHS, 3); - else if (word == _("yearly")) - duration = date_duration_t(date_duration_t::YEARS, 1); - else if (word == _("this") || word == _("last") || word == _("next") || - word == _("today")) { - parse_date_words(in, word, *this); - } - else if (word == _("in")) { - read_lower_word(in, word); - parse_date_words(in, word, *this); - } - else if (word == _("from") || word == _("since")) { - read_lower_word(in, word); - parse_date_words(in, word, *this, true, false); - } - else if (word == _("to") || word == _("until")) { - read_lower_word(in, word); - parse_date_words(in, word, *this, false, true); - } - else if (optional - m = string_to_month_of_year(word)) { - mon = m; - } - else if (optional - d = string_to_day_of_week(word)) { - wday = d; - } - else if (all(word, is_digit())) { - year = lexical_cast(word); - } - else { - // otherwise, it should be an explicit date - date_t s, f; - parse_inclusion_specifier(word, &s, &f); - start = s; - finish = f; - } - } - - if (year || mon || wday) { - if (! start) - start = CURRENT_DATE(); - - if (wday) { - while (start->day_of_week() != *wday) - *start -= gregorian::days(1); - - if (! finish) - finish = *start + gregorian::days(1); - } else { - bool overwrite_finish = false; - - if (year) { - start = date_t(*year, 1, 1); - if (! finish) { - finish = *start + gregorian::years(1); - overwrite_finish = true; - } - } + out << _("--- Before stabilization ---") << std::endl; + + if (range) + out << _(" range: ") << range->to_string() << std::endl; + if (start) + out << _(" start: ") << format_date(*start) << std::endl; + if (finish) + out << _(" finish: ") << format_date(*finish) << std::endl; + + if (skip_duration) + out << _(" skip: ") << skip_duration->to_string() << std::endl; + if (factor) + out << _(" factor: ") << factor << std::endl; + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; + + stabilize(begin()); + + out << std::endl + << _("--- After stabilization ---") << std::endl; + + if (range) + out << _(" range: ") << range->to_string() << std::endl; + if (start) + out << _(" start: ") << format_date(*start) << std::endl; + if (finish) + out << _(" finish: ") << format_date(*finish) << std::endl; + + if (skip_duration) + out << _(" skip: ") << skip_duration->to_string() << std::endl; + if (factor) + out << _(" factor: ") << factor << std::endl; + if (duration) + out << _("duration: ") << duration->to_string() << std::endl; + + out << std::endl + << _("--- Sample dates in range (max. 20) ---") << std::endl; + + for (int i = 0; i < 20 && *this; i++, ++*this) { + out << std::right; + out.width(2); + + out << (i + 1) << ": " << format_date(*start); + if (skip_duration) + out << " -- " << format_date(*inclusive_skip_end()); + out << std::endl; - if (mon) { - start = date_t(start->year(), *mon, 1); - if (! finish || overwrite_finish) - finish = *start + gregorian::months(1); - } - } + if (! skip_duration) + break; } } @@ -846,22 +1259,22 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() if (! term.empty()) { if (std::isdigit(term[0])) { - return token_t(term.length() == 4 ? - token_t::TOK_A_YEAR : token_t::TOK_INT, - token_t::content_t(lexical_cast(term))); + if (term.length() == 4) + return token_t(token_t::TOK_A_YEAR, + token_t::content_t + (lexical_cast(term))); + else + return token_t(token_t::TOK_INT, + token_t::content_t(lexical_cast(term))); } else if (std::isalpha(term[0])) { if (optional month = string_to_month_of_year(term)) { - date_specifier_t specifier; - specifier.month = static_cast(*month); - return token_t(token_t::TOK_A_MONTH, token_t::content_t(specifier)); + return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month)); } else if (optional wday = string_to_day_of_week(term)) { - date_specifier_t specifier; - specifier.wday = static_cast(*wday); - return token_t(token_t::TOK_A_WDAY, token_t::content_t(specifier)); + return token_t(token_t::TOK_A_WDAY, token_t::content_t(*wday)); } else if (term == _("from") || term == _("since")) return token_t(token_t::TOK_SINCE); @@ -875,6 +1288,14 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() return token_t(token_t::TOK_NEXT); else if (term == _("last")) return token_t(token_t::TOK_LAST); + else if (term == _("every")) + return token_t(token_t::TOK_EVERY); + else if (term == _("today")) + return token_t(token_t::TOK_TODAY); + else if (term == _("tomorrow")) + return token_t(token_t::TOK_TOMORROW); + else if (term == _("yesterday")) + return token_t(token_t::TOK_YESTERDAY); else if (term == _("year")) return token_t(token_t::TOK_YEAR); else if (term == _("quarter")) @@ -950,13 +1371,6 @@ void date_parser_t::lexer_t::token_t::expected(char wanted, char c) } } -date_interval_t date_parser_t::parse_date_expr() -{ - date_interval_t interval; - - return interval; -} - namespace { typedef std::map datetime_io_map; typedef std::map date_io_map; @@ -1084,85 +1498,14 @@ void show_period_tokens(std::ostream& out, const string& arg) { date_parser_t::lexer_t lexer(arg.begin(), arg.end()); + out << _("--- Period expression tokens ---") << std::endl; + date_parser_t::lexer_t::token_t token; do { token = lexer.next_token(); - out << _("token: ") << token.to_string() << std::endl; + out << token.to_string() << std::endl; } while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); } -void analyze_period(std::ostream& out, const string& arg) -{ - date_parser_t date_parser(arg); - - date_interval_t interval = date_parser.parse(); - - out << _("global details => ") << std::endl << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - else - out << _(" start: TODAY: ") << format_date(CURRENT_DATE()) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - if (interval.find_period(interval.start ? - *interval.start : CURRENT_DATE())) { - out << std::endl - << _("after finding first period => ") << std::endl - << std::endl; - - if (interval.start) - out << _(" start: ") << format_date(*interval.start) << std::endl; - if (interval.finish) - out << _(" finish: ") << format_date(*interval.finish) << std::endl; - - if (interval.skip_duration) - out << _(" skip: ") << *interval.skip_duration << std::endl; - if (interval.factor) - out << _(" factor: ") << interval.factor << std::endl; - if (interval.duration) - out << _("duration: ") << *interval.duration << std::endl; - - out << std::endl; - - for (int i = 0; i < 20 && interval; i++, ++interval) { - out << std::right; - out.width(2); - - out << i << "): " << format_date(*interval.start); - if (interval.end_of_duration) - out << " -- " << format_date(*interval.inclusive_end()); - out << std::endl; - - if (! interval.skip_duration) - break; - } - } -} - } // namespace ledger - -#if defined(TIMES_HARNESS) - -int main(int argc, char *argv[]) -{ - if (argc > 1) { - ledger::times_initialize(); - ledger::analyze_period(std::cout, argv[1]); - ledger::times_shutdown(); - } else { - std::cerr << "Usage: times " << std::endl; - } - return 0; -} - -#endif // TIMES_HARNESS diff --git a/src/times.h b/src/times.h index 94ae4752..d378dd81 100644 --- a/src/times.h +++ b/src/times.h @@ -146,12 +146,18 @@ struct date_traits_t date_traits_t(bool _has_year = false, bool _has_month = false, bool _has_day = false) - : has_year(_has_year), has_month(_has_month), has_day(_has_day) {} - + : has_year(_has_year), has_month(_has_month), has_day(_has_day) { + TRACE_CTOR(date_traits_t, "bool, bool, bool"); + } date_traits_t(const date_traits_t& traits) : has_year(traits.has_year), has_month(traits.has_month), - has_day(traits.has_day) {} + has_day(traits.has_day) { + TRACE_CTOR(date_traits_t, "copy"); + } + ~date_traits_t() throw() { + TRACE_DTOR(date_traits_t); + } date_traits_t& operator=(const date_traits_t& traits) { has_year = traits.has_year; @@ -181,6 +187,100 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +struct date_duration_t +{ + enum skip_quantum_t { + DAYS, WEEKS, MONTHS, QUARTERS, YEARS + } quantum; + int length; + + date_duration_t() : quantum(DAYS), length(0) { + TRACE_CTOR(date_duration_t, ""); + } + date_duration_t(skip_quantum_t _quantum, int _length) + : quantum(_quantum), length(_length) { + TRACE_CTOR(date_duration_t, "skip_quantum_t, int"); + } + date_duration_t(const date_duration_t& dur) + : quantum(dur.quantum), length(dur.length) { + TRACE_CTOR(date_duration_t, "copy"); + } + ~date_duration_t() throw() { + TRACE_DTOR(date_duration_t); + } + + date_t add(const date_t& date) const { + switch (quantum) { + case DAYS: + return date + gregorian::days(length); + case WEEKS: + return date + gregorian::weeks(length); + case MONTHS: + return date + gregorian::months(length); + case QUARTERS: + return date + gregorian::months(length * 3); + case YEARS: + return date + gregorian::years(length); + default: + assert(false); return date_t(); + } + } + + date_t subtract(const date_t& date) const { + switch (quantum) { + case DAYS: + return date - gregorian::days(length); + case WEEKS: + return date - gregorian::weeks(length); + case MONTHS: + return date - gregorian::months(length); + case QUARTERS: + return date - gregorian::months(length * 3); + case YEARS: + return date - gregorian::years(length); + default: + assert(false); return date_t(); + } + } + + string to_string() const { + std::ostringstream out; + + out << length << ' '; + + switch (quantum) { + case DAYS: out << "day"; break; + case WEEKS: out << "week"; break; + case MONTHS: out << "month"; break; + case QUARTERS: out << "quarter"; break; + case YEARS: out << "year"; break; + default: + assert(false); + break; + } + + if (length > 1) + out << 's'; + + return out.str(); + } + + static date_t find_nearest(const date_t& date, skip_quantum_t skip); + +#if defined(HAVE_BOOST_SERIALIZATION) +private: + /** Serialization. */ + + friend class boost::serialization::access; + + template + void serialize(Archive& ar, const unsigned int /* version */) { + ar & quantum; + ar & length; + } +#endif // HAVE_BOOST_SERIALIZATION +}; + class date_specifier_t { friend class date_parser_t; @@ -200,15 +300,32 @@ class date_specifier_t optional wday; public: - date_specifier_t() {} - date_specifier_t(const date_t& date, const date_traits_t& traits) { - if (traits.has_year) + date_specifier_t(const optional& _year = none, + const optional& _month = none, + const optional& _day = none, + const optional& _wday = none) + : year(_year), month(_month), day(_day), wday(_wday) { + TRACE_CTOR(date_specifier_t, + "year_type, month_type, day_type, day_of_week_type"); + } + date_specifier_t(const date_t& date, + const optional& traits = none) { + TRACE_CTOR(date_specifier_t, "date_t, date_traits_t"); + if (! traits || traits->has_year) year = date.year(); - if (traits.has_month) + if (! traits || traits->has_month) month = date.month(); - if (traits.has_day) + if (! traits || traits->has_day) day = date.day(); } + date_specifier_t(const date_specifier_t& other) + : year(other.year), month(other.month), + day(other.day), wday(other.wday) { + TRACE_CTOR(date_specifier_t, "copy"); + } + ~date_specifier_t() throw() { + TRACE_DTOR(date_specifier_t); + } date_t begin(const optional_year& current_year = none) const; date_t end(const optional_year& current_year = none) const; @@ -218,6 +335,32 @@ public: return date >= begin(current_year) && date < end(current_year); } + optional implied_duration() const { + if (day || wday) + return date_duration_t(date_duration_t::DAYS, 1); + else if (month) + return date_duration_t(date_duration_t::MONTHS, 1); + else if (year) + return date_duration_t(date_duration_t::YEARS, 1); + else + return none; + } + + string to_string() const { + std::ostringstream out; + + if (year) + out << " year " << *year; + if (month) + out << " month " << *month; + if (day) + out << " day " << *day; + if (wday) + out << " wday " << *wday; + + return out.str(); + } + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -241,7 +384,24 @@ class date_range_t optional range_begin; optional range_end; + bool end_inclusive; + public: + date_range_t(const optional& _range_begin = none, + const optional& _range_end = none) + : range_begin(_range_begin), range_end(_range_end), + end_inclusive(false) { + TRACE_CTOR(date_range_t, "date_specifier_t, date_specifier_t"); + } + date_range_t(const date_range_t& other) + : range_begin(other.range_begin), range_end(other.range_end), + end_inclusive(other.end_inclusive) { + TRACE_CTOR(date_range_t, "date_range_t"); + } + ~date_range_t() throw() { + TRACE_DTOR(date_range_t); + } + optional begin(const optional_year& current_year = none) const { if (range_begin) return range_begin->begin(current_year); @@ -249,10 +409,14 @@ public: return none; } optional end(const optional_year& current_year = none) const { - if (range_end) - return range_end->end(current_year); - else + if (range_end) { + if (end_inclusive) + return range_end->end(current_year); + else + return range_end->begin(current_year); + } else { return none; + } } bool is_within(const date_t& date, @@ -264,70 +428,15 @@ public: return after_begin && before_end; } -#if defined(HAVE_BOOST_SERIALIZATION) -private: - /** Serialization. */ - - friend class boost::serialization::access; - - template - void serialize(Archive& ar, const unsigned int /* version */) { - ar & range_begin; - ar & range_end; - } -#endif // HAVE_BOOST_SERIALIZATION -}; - -struct date_duration_t -{ - enum skip_quantum_t { - DAYS, WEEKS, MONTHS, YEARS - } quantum; - int length; + string to_string() const { + std::ostringstream out; - date_duration_t() : quantum(DAYS), length(0) { - TRACE_CTOR(date_duration_t, ""); - } - date_duration_t(skip_quantum_t _quantum, int _length) - : quantum(_quantum), length(_length) { - TRACE_CTOR(date_duration_t, "skip_quantum_t, int"); - } - date_duration_t(const date_duration_t& dur) - : quantum(dur.quantum), length(dur.length) { - TRACE_CTOR(date_duration_t, "copy"); - } - ~date_duration_t() throw() { - TRACE_DTOR(date_duration_t); - } - - date_t add(const date_t& date) const { - switch (quantum) { - case DAYS: - return date + gregorian::days(length); - case WEEKS: - return date + gregorian::weeks(length); - case MONTHS: - return date + gregorian::months(length); - case YEARS: - return date + gregorian::years(length); - default: - assert(false); return date_t(); - } - } - - date_t subtract(const date_t& date) const { - switch (quantum) { - case DAYS: - return date - gregorian::days(length); - case WEEKS: - return date - gregorian::weeks(length); - case MONTHS: - return date - gregorian::months(length); - case YEARS: - return date - gregorian::years(length); - default: - assert(false); return date_t(); - } + if (range_begin) + out << "from" << range_begin->to_string(); + if (range_end) + out << " to" << range_end->to_string(); + + return out.str(); } #if defined(HAVE_BOOST_SERIALIZATION) @@ -338,8 +447,8 @@ private: template void serialize(Archive& ar, const unsigned int /* version */) { - ar & quantum; - ar & length; + ar & range_begin; + ar & range_end; } #endif // HAVE_BOOST_SERIALIZATION }; @@ -351,6 +460,25 @@ class date_specifier_or_range_t value_type specifier_or_range; public: + date_specifier_or_range_t() { + TRACE_CTOR(date_specifier_or_range_t, ""); + } + date_specifier_or_range_t(const date_specifier_or_range_t& other) + : specifier_or_range(other.specifier_or_range) { + TRACE_CTOR(date_specifier_or_range_t, "copy"); + } + date_specifier_or_range_t(const date_specifier_t& specifier) + : specifier_or_range(specifier) { + TRACE_CTOR(date_specifier_or_range_t, "date_specifier_t"); + } + date_specifier_or_range_t(const date_range_t& range) + : specifier_or_range(range) { + TRACE_CTOR(date_specifier_or_range_t, "date_range_t"); + } + ~date_specifier_or_range_t() throw() { + TRACE_DTOR(date_specifier_or_range_t); + } + optional begin(const optional_year& current_year = none) const { if (specifier_or_range.type() == typeid(date_specifier_t)) return boost::get(specifier_or_range).begin(current_year); @@ -368,6 +496,18 @@ public: return none; } + + string to_string() const { + std::ostringstream out; + + if (specifier_or_range.type() == typeid(date_specifier_t)) + out << "in" << boost::get(specifier_or_range).to_string(); + else if (specifier_or_range.type() == typeid(date_range_t)) + out << boost::get(specifier_or_range).to_string(); + + return out.str(); + } + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -439,17 +579,12 @@ public: return finish ? finish : (range ? range->end(current_year) : none); } - void parse(std::istream& in); - - void parse(const string& str) { - std::istringstream in(str); - parse(in); - } + void parse(const string& str); - void resolve_end(); - void stabilize(const optional& date = none); + void resolve_end(); + void stabilize(const optional& date = none); - bool is_valid() const { + bool is_valid() const { return start; } @@ -458,6 +593,12 @@ public: containing date, or false if no such period can be found. */ bool find_period(const date_t& date); + optional inclusive_skip_end() const { + if (skip_duration) + return skip_duration->add(*start) - gregorian::days(1); + else + return none; + } optional inclusive_end() const { if (end_of_duration) return *end_of_duration - gregorian::days(1); @@ -467,6 +608,8 @@ public: date_interval_t& operator++(); + void dump(std::ostream& out); + #if defined(HAVE_BOOST_SERIALIZATION) private: /** Serialization. */ @@ -488,197 +631,10 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; -class date_parser_t -{ - friend void show_period_tokens(std::ostream& out, const string& arg); - - class lexer_t - { - friend class date_parser_t; - - string::const_iterator begin; - string::const_iterator end; - - public: - struct token_t - { - enum kind_t { - UNKNOWN, - - TOK_DATE, - TOK_INT, - TOK_SLASH, - TOK_DASH, - TOK_DOT, - - TOK_A_YEAR, - TOK_A_MONTH, - TOK_A_DAY, - TOK_A_WDAY, - - TOK_SINCE, - TOK_UNTIL, - TOK_IN, - TOK_THIS, - TOK_NEXT, - TOK_LAST, - - TOK_YEAR, - TOK_QUARTER, - TOK_MONTH, - TOK_WEEK, - TOK_DAY, - - TOK_YEARLY, - TOK_QUARTERLY, - TOK_BIMONTHLY, - TOK_MONTHLY, - TOK_BIWEEKLY, - TOK_WEEKLY, - TOK_DAILY, - - TOK_YEARS, - TOK_QUARTERS, - TOK_MONTHS, - TOK_WEEKS, - TOK_DAYS, - - END_REACHED - - } kind; - - typedef variant content_t; - - optional value; - - explicit token_t(kind_t _kind = UNKNOWN, - const optional& _value = none) - : kind(_kind), value(_value) { - TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); - } - token_t(const token_t& tok) - : kind(tok.kind), value(tok.value) { - TRACE_CTOR(date_parser_t::lexer_t::token_t, "copy"); - } - ~token_t() throw() { - TRACE_DTOR(date_parser_t::lexer_t::token_t); - } - - token_t& operator=(const token_t& tok) { - if (this != &tok) { - kind = tok.kind; - value = tok.value; - } - return *this; - } - - operator bool() const { - return kind != END_REACHED; - } - - string to_string() const { - switch (kind) { - case UNKNOWN: return "UNKNOWN"; - case TOK_DATE: return "TOK_DATE"; - case TOK_INT: return "TOK_INT"; - case TOK_SLASH: return "TOK_SLASH"; - case TOK_DASH: return "TOK_DASH"; - case TOK_DOT: return "TOK_DOT"; - case TOK_A_YEAR: return "TOK_A_YEAR"; - case TOK_A_MONTH: return "TOK_A_MONTH"; - case TOK_A_DAY: return "TOK_A_DAY"; - case TOK_A_WDAY: return "TOK_A_WDAY"; - case TOK_SINCE: return "TOK_SINCE"; - case TOK_UNTIL: return "TOK_UNTIL"; - case TOK_IN: return "TOK_IN"; - case TOK_THIS: return "TOK_THIS"; - case TOK_NEXT: return "TOK_NEXT"; - case TOK_LAST: return "TOK_LAST"; - case TOK_YEAR: return "TOK_YEAR"; - case TOK_QUARTER: return "TOK_QUARTER"; - case TOK_MONTH: return "TOK_MONTH"; - case TOK_WEEK: return "TOK_WEEK"; - case TOK_DAY: return "TOK_DAY"; - case TOK_YEARLY: return "TOK_YEARLY"; - case TOK_QUARTERLY: return "TOK_QUARTERLY"; - case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; - case TOK_MONTHLY: return "TOK_MONTHLY"; - case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; - case TOK_WEEKLY: return "TOK_WEEKLY"; - case TOK_DAILY: return "TOK_DAILY"; - case TOK_YEARS: return "TOK_YEARS"; - case TOK_QUARTERS: return "TOK_QUARTERS"; - case TOK_MONTHS: return "TOK_MONTHS"; - case TOK_WEEKS: return "TOK_WEEKS"; - case TOK_DAYS: return "TOK_DAYS"; - case END_REACHED: return "END_REACHED"; - } - assert(false); - return empty_string; - } - - void unexpected(); - static void expected(char wanted, char c = '\0'); - }; - - token_t token_cache; - - lexer_t(string::const_iterator _begin, - string::const_iterator _end) - : begin(_begin), end(_end) - { - TRACE_CTOR(date_parser_t::lexer_t, ""); - } - lexer_t(const lexer_t& lexer) - : begin(lexer.begin), end(lexer.end), - token_cache(lexer.token_cache) - { - TRACE_CTOR(date_parser_t::lexer_t, "copy"); - } - ~lexer_t() throw() { - TRACE_DTOR(date_parser_t::lexer_t); - } - - token_t next_token(); - void push_token(token_t tok) { - assert(token_cache.kind == token_t::UNKNOWN); - token_cache = tok; - } - token_t peek_token() { - if (token_cache.kind == token_t::UNKNOWN) - token_cache = next_token(); - return token_cache; - } - }; - - string arg; - lexer_t lexer; - - date_interval_t parse_date_expr(); - -public: - date_parser_t(const string& _arg) - : arg(_arg), lexer(arg.begin(), arg.end()) { - TRACE_CTOR(date_parser_t, ""); - } - date_parser_t(const date_parser_t& parser) - : arg(parser.arg), lexer(parser.lexer) { - TRACE_CTOR(date_parser_t, "copy"); - } - ~date_parser_t() throw() { - TRACE_DTOR(date_parser_t); - } - - date_interval_t parse() { - return date_interval_t(); - } -}; - void times_initialize(); void times_shutdown(); void show_period_tokens(std::ostream& out, const string& arg); -void analyze_period(std::ostream& out, const string& arg); std::ostream& operator<<(std::ostream& out, const date_duration_t& duration); -- cgit v1.2.3 From e4b3f0bb3a74b799f0f67d8b2f1efeedad5e2021 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 05:45:48 -0500 Subject: The new period parser is passing all tests --- src/archive.cc | 2 +- src/filters.cc | 14 ++- src/precmd.cc | 2 +- src/report.cc | 13 ++- src/report.h | 31 ++--- src/times.cc | 275 ++++++++++++++++++++++++++++----------------- src/times.h | 18 +-- src/token.cc | 5 +- test/regress/7F3650FD.test | 69 ++++++++++-- test/regress/BBFA1759.test | 14 ++- 10 files changed, 283 insertions(+), 160 deletions(-) (limited to 'src/precmd.cc') diff --git a/src/archive.cc b/src/archive.cc index d36712ca..7306f8d3 100644 --- a/src/archive.cc +++ b/src/archive.cc @@ -43,7 +43,7 @@ #include "xact.h" #define LEDGER_MAGIC 0x4c454447 -#define ARCHIVE_VERSION 0x03000005 +#define ARCHIVE_VERSION 0x03000006 //BOOST_IS_ABSTRACT(ledger::scope_t) BOOST_CLASS_EXPORT(ledger::scope_t) diff --git a/src/filters.cc b/src/filters.cc index 5a92421b..47e7f66d 100644 --- a/src/filters.cc +++ b/src/filters.cc @@ -515,12 +515,14 @@ void subtotal_posts::report_subtotal(const char * spec_fmt, optional range_start = interval ? interval->start : none; optional range_finish = interval ? interval->inclusive_end() : none; - foreach (post_t * post, component_posts) { - date_t date = post->date(); - if (! range_start || date < *range_start) - range_start = date; - if (! range_finish || date > *range_finish) - range_finish = date; + if (! range_start || ! range_finish) { + foreach (post_t * post, component_posts) { + date_t date = post->date(); + if (! range_start || date < *range_start) + range_start = date; + if (! range_finish || date > *range_finish) + range_finish = date; + } } component_posts.clear(); diff --git a/src/precmd.cc b/src/precmd.cc index 0e024a39..516b90dd 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -169,7 +169,7 @@ value_t period_command(call_scope_t& args) out << std::endl; date_interval_t interval(arg); - interval.dump(out); + interval.dump(out, report.session.current_year); return NULL_VALUE; } diff --git a/src/report.cc b/src/report.cc index efe162e2..78ed05c0 100644 --- a/src/report.cc +++ b/src/report.cc @@ -120,14 +120,15 @@ void report_t::normalize_options(const string& verb) date_interval_t interval(HANDLER(period_).str()); - if (! HANDLED(begin_) && interval.start) { - string predicate = - "date>=[" + to_iso_extended_string(*interval.start) + "]"; + optional begin = interval.begin(session.current_year); + optional end = interval.end(session.current_year); + + if (! HANDLED(begin_) && begin) { + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } - if (! HANDLED(end_) && interval.finish) { - string predicate = - "date<[" + to_iso_extended_string(*interval.finish) + "]"; + if (! HANDLED(end_) && end) { + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; HANDLER(limit_).on(string("?normalize"), predicate); } diff --git a/src/report.h b/src/report.h index 93f6e9e0..0b1baff1 100644 --- a/src/report.h +++ b/src/report.h @@ -50,6 +50,7 @@ #include "option.h" #include "commodity.h" #include "annotate.h" +#include "session.h" #include "format.h" namespace ledger { @@ -352,7 +353,7 @@ public: set_expr(args[0].to_string(), args[1].to_string()); }); - OPTION(report_t, amount_data); + OPTION(report_t, amount_data); // -j OPTION(report_t, anon); OPTION_(report_t, average, DO() { // -A @@ -377,14 +378,14 @@ public: }); OPTION_(report_t, begin_, DO_(args) { // -b - date_interval_t interval(args[1].to_string()); - if (! interval.start) + date_interval_t interval(args[1].to_string()); + optional begin = interval.begin(parent->session.current_year); + if (! begin) throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") << args[1].to_string()); - string predicate = - "date>=[" + to_iso_extended_string(*interval.start) + "]"; + string predicate = "date>=[" + to_iso_extended_string(*begin) + "]"; parent->HANDLER(limit_).on(string("--begin"), predicate); }); @@ -524,17 +525,19 @@ public: OPTION(report_t, empty); // -E OPTION_(report_t, end_, DO_(args) { // -e - date_interval_t interval(args[1].to_string()); - if (! interval.start) + date_interval_t interval(args[1].to_string()); + // Use begin() here so that if the user says --end=2008, we end on + // 2008/01/01 instead of 2009/01/01 (which is what end() would return). + optional end = interval.begin(parent->session.current_year); + if (! end) throw_(std::invalid_argument, _("Could not determine end of period '%1'") << args[1].to_string()); - string predicate = - "date<[" + to_iso_extended_string(*interval.start) + "]"; + string predicate = "date<[" + to_iso_extended_string(*end) + "]"; parent->HANDLER(limit_).on(string("--end"), predicate); - parent->terminus = datetime_t(*interval.start); + parent->terminus = datetime_t(*end); }); OPTION(report_t, equity); @@ -622,11 +625,13 @@ public: OPTION_(report_t, now_, DO_(args) { date_interval_t interval(args[1].to_string()); - if (! interval.start) + optional begin = interval.begin(parent->session.current_year); + if (! begin) throw_(std::invalid_argument, _("Could not determine beginning of period '%1'") << args[1].to_string()); - ledger::epoch = datetime_t(*interval.start); + ledger::epoch = parent->terminus = datetime_t(*begin); + parent->session.current_year = ledger::epoch->date().year(); }); OPTION__ @@ -844,7 +849,7 @@ public: set_expr(args[0].to_string(), args[1].to_string()); }); - OPTION(report_t, total_data); + OPTION(report_t, total_data); // -J OPTION_(report_t, truncate_, DO_(args) { string style(args[1].to_string()); diff --git a/src/times.cc b/src/times.cc index 97fc3ab8..646f2a84 100644 --- a/src/times.cc +++ b/src/times.cc @@ -434,6 +434,7 @@ class date_parser_t } kind; typedef variant value; explicit token_t(kind_t _kind = UNKNOWN, - const optional& _value = none) + const optional& _value = + content_t(empty_string)) : kind(_kind), value(_value) { TRACE_CTOR(date_parser_t::lexer_t::token_t, ""); } @@ -467,47 +469,110 @@ class date_parser_t } string to_string() const { + std::ostringstream out; + + switch (kind) { + case UNKNOWN: + out << boost::get(*value); + break; + case TOK_DATE: + return boost::get(*value).to_string(); + case TOK_INT: + out << boost::get(*value); + break; + case TOK_SLASH: return "/"; + case TOK_DASH: return "-"; + case TOK_DOT: return "."; + case TOK_A_YEAR: + out << boost::get(*value); + break; + case TOK_A_MONTH: + out << date_specifier_t::month_type + (boost::get(*value)); + break; + case TOK_A_WDAY: + out << date_specifier_t::day_of_week_type + (boost::get(*value)); + break; + case TOK_SINCE: return "since"; + case TOK_UNTIL: return "until"; + case TOK_IN: return "in"; + case TOK_THIS: return "this"; + case TOK_NEXT: return "next"; + case TOK_LAST: return "last"; + case TOK_EVERY: return "every"; + case TOK_TODAY: return "today"; + case TOK_TOMORROW: return "tomorrow"; + case TOK_YESTERDAY: return "yesterday"; + case TOK_YEAR: return "year"; + case TOK_QUARTER: return "quarter"; + case TOK_MONTH: return "month"; + case TOK_WEEK: return "week"; + case TOK_DAY: return "day"; + case TOK_YEARLY: return "yearly"; + case TOK_QUARTERLY: return "quarterly"; + case TOK_BIMONTHLY: return "bimonthly"; + case TOK_MONTHLY: return "monthly"; + case TOK_BIWEEKLY: return "biweekly"; + case TOK_WEEKLY: return "weekly"; + case TOK_DAILY: return "daily"; + case TOK_YEARS: return "years"; + case TOK_QUARTERS: return "quarters"; + case TOK_MONTHS: return "months"; + case TOK_WEEKS: return "weeks"; + case TOK_DAYS: return "days"; + case END_REACHED: return ""; + default: + assert(false); + return empty_string; + } + + return out.str(); + } + + void dump(std::ostream& out) const { switch (kind) { - case UNKNOWN: return "UNKNOWN"; - case TOK_DATE: return "TOK_DATE"; - case TOK_INT: return "TOK_INT"; - case TOK_SLASH: return "TOK_SLASH"; - case TOK_DASH: return "TOK_DASH"; - case TOK_DOT: return "TOK_DOT"; - case TOK_A_YEAR: return "TOK_A_YEAR"; - case TOK_A_MONTH: return "TOK_A_MONTH"; - case TOK_A_WDAY: return "TOK_A_WDAY"; - case TOK_SINCE: return "TOK_SINCE"; - case TOK_UNTIL: return "TOK_UNTIL"; - case TOK_IN: return "TOK_IN"; - case TOK_THIS: return "TOK_THIS"; - case TOK_NEXT: return "TOK_NEXT"; - case TOK_LAST: return "TOK_LAST"; - case TOK_EVERY: return "TOK_EVERY"; - case TOK_TODAY: return "TOK_EVERY"; - case TOK_TOMORROW: return "TOK_TOMORROW"; - case TOK_YESTERDAY: return "TOK_YESTERDAY"; - case TOK_YEAR: return "TOK_YEAR"; - case TOK_QUARTER: return "TOK_QUARTER"; - case TOK_MONTH: return "TOK_MONTH"; - case TOK_WEEK: return "TOK_WEEK"; - case TOK_DAY: return "TOK_DAY"; - case TOK_YEARLY: return "TOK_YEARLY"; - case TOK_QUARTERLY: return "TOK_QUARTERLY"; - case TOK_BIMONTHLY: return "TOK_BIMONTHLY"; - case TOK_MONTHLY: return "TOK_MONTHLY"; - case TOK_BIWEEKLY: return "TOK_BIWEEKLY"; - case TOK_WEEKLY: return "TOK_WEEKLY"; - case TOK_DAILY: return "TOK_DAILY"; - case TOK_YEARS: return "TOK_YEARS"; - case TOK_QUARTERS: return "TOK_QUARTERS"; - case TOK_MONTHS: return "TOK_MONTHS"; - case TOK_WEEKS: return "TOK_WEEKS"; - case TOK_DAYS: return "TOK_DAYS"; - case END_REACHED: return "END_REACHED"; + case UNKNOWN: out << "UNKNOWN"; break; + case TOK_DATE: out << "TOK_DATE"; break; + case TOK_INT: out << "TOK_INT"; break; + case TOK_SLASH: out << "TOK_SLASH"; break; + case TOK_DASH: out << "TOK_DASH"; break; + case TOK_DOT: out << "TOK_DOT"; break; + case TOK_A_YEAR: out << "TOK_A_YEAR"; break; + case TOK_A_MONTH: out << "TOK_A_MONTH"; break; + case TOK_A_WDAY: out << "TOK_A_WDAY"; break; + case TOK_SINCE: out << "TOK_SINCE"; break; + case TOK_UNTIL: out << "TOK_UNTIL"; break; + case TOK_IN: out << "TOK_IN"; break; + case TOK_THIS: out << "TOK_THIS"; break; + case TOK_NEXT: out << "TOK_NEXT"; break; + case TOK_LAST: out << "TOK_LAST"; break; + case TOK_EVERY: out << "TOK_EVERY"; break; + case TOK_TODAY: out << "TOK_TODAY"; break; + case TOK_TOMORROW: out << "TOK_TOMORROW"; break; + case TOK_YESTERDAY: out << "TOK_YESTERDAY"; break; + case TOK_YEAR: out << "TOK_YEAR"; break; + case TOK_QUARTER: out << "TOK_QUARTER"; break; + case TOK_MONTH: out << "TOK_MONTH"; break; + case TOK_WEEK: out << "TOK_WEEK"; break; + case TOK_DAY: out << "TOK_DAY"; break; + case TOK_YEARLY: out << "TOK_YEARLY"; break; + case TOK_QUARTERLY: out << "TOK_QUARTERLY"; break; + case TOK_BIMONTHLY: out << "TOK_BIMONTHLY"; break; + case TOK_MONTHLY: out << "TOK_MONTHLY"; break; + case TOK_BIWEEKLY: out << "TOK_BIWEEKLY"; break; + case TOK_WEEKLY: out << "TOK_WEEKLY"; break; + case TOK_DAILY: out << "TOK_DAILY"; break; + case TOK_YEARS: out << "TOK_YEARS"; break; + case TOK_QUARTERS: out << "TOK_QUARTERS"; break; + case TOK_MONTHS: out << "TOK_MONTHS"; break; + case TOK_WEEKS: out << "TOK_WEEKS"; break; + case TOK_DAYS: out << "TOK_DAYS"; break; + case END_REACHED: out << "END_REACHED"; break; + default: + assert(false); + break; } - assert(false); - return empty_string; } void unexpected(); @@ -753,7 +818,9 @@ date_interval_t date_parser_t::parse() inclusion_specifier = date_specifier_t(static_cast(temp.year()), temp.month()); +#if 0 period.duration = date_duration_t(date_duration_t::QUARTERS, 1); +#endif break; } @@ -771,7 +838,9 @@ date_interval_t date_parser_t::parse() date_duration_t::find_nearest(today, date_duration_t::WEEKS); temp += gregorian::days(7 * adjust); inclusion_specifier = date_specifier_t(today); +#if 0 period.duration = date_duration_t(date_duration_t::WEEKS, 1); +#endif break; } @@ -806,19 +875,19 @@ date_interval_t date_parser_t::parse() tok = lexer.next_token(); switch (tok.kind) { case lexer_t::token_t::TOK_YEARS: - period.skip_duration = date_duration_t(date_duration_t::YEARS, quantity); + period.duration = date_duration_t(date_duration_t::YEARS, quantity); break; case lexer_t::token_t::TOK_QUARTERS: - period.skip_duration = date_duration_t(date_duration_t::QUARTERS, quantity); + period.duration = date_duration_t(date_duration_t::QUARTERS, quantity); break; case lexer_t::token_t::TOK_MONTHS: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, quantity); + period.duration = date_duration_t(date_duration_t::MONTHS, quantity); break; case lexer_t::token_t::TOK_WEEKS: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, quantity); + period.duration = date_duration_t(date_duration_t::WEEKS, quantity); break; case lexer_t::token_t::TOK_DAYS: - period.skip_duration = date_duration_t(date_duration_t::DAYS, quantity); + period.duration = date_duration_t(date_duration_t::DAYS, quantity); break; default: tok.unexpected(); @@ -827,19 +896,19 @@ date_interval_t date_parser_t::parse() } else { switch (tok.kind) { case lexer_t::token_t::TOK_YEAR: - period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + period.duration = date_duration_t(date_duration_t::YEARS, 1); break; case lexer_t::token_t::TOK_QUARTER: - period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); break; case lexer_t::token_t::TOK_MONTH: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + period.duration = date_duration_t(date_duration_t::MONTHS, 1); break; case lexer_t::token_t::TOK_WEEK: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + period.duration = date_duration_t(date_duration_t::WEEKS, 1); break; case lexer_t::token_t::TOK_DAY: - period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + period.duration = date_duration_t(date_duration_t::DAYS, 1); break; default: tok.unexpected(); @@ -849,25 +918,25 @@ date_interval_t date_parser_t::parse() break; case lexer_t::token_t::TOK_YEARLY: - period.skip_duration = date_duration_t(date_duration_t::YEARS, 1); + period.duration = date_duration_t(date_duration_t::YEARS, 1); break; case lexer_t::token_t::TOK_QUARTERLY: - period.skip_duration = date_duration_t(date_duration_t::QUARTERS, 1); + period.duration = date_duration_t(date_duration_t::QUARTERS, 1); break; case lexer_t::token_t::TOK_BIMONTHLY: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, 2); + period.duration = date_duration_t(date_duration_t::MONTHS, 2); break; case lexer_t::token_t::TOK_MONTHLY: - period.skip_duration = date_duration_t(date_duration_t::MONTHS, 1); + period.duration = date_duration_t(date_duration_t::MONTHS, 1); break; case lexer_t::token_t::TOK_BIWEEKLY: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, 2); + period.duration = date_duration_t(date_duration_t::WEEKS, 2); break; case lexer_t::token_t::TOK_WEEKLY: - period.skip_duration = date_duration_t(date_duration_t::WEEKS, 1); + period.duration = date_duration_t(date_duration_t::WEEKS, 1); break; case lexer_t::token_t::TOK_DAILY: - period.skip_duration = date_duration_t(date_duration_t::DAYS, 1); + period.duration = date_duration_t(date_duration_t::DAYS, 1); break; default: @@ -876,8 +945,10 @@ date_interval_t date_parser_t::parse() } } +#if 0 if (! period.duration && inclusion_specifier) period.duration = inclusion_specifier->implied_duration(); +#endif if (since_specifier || until_specifier) { date_range_t range(since_specifier, until_specifier); @@ -915,16 +986,9 @@ void date_interval_t::resolve_end() "stabilize: end_of_duration reset to end: " << *end_of_duration); } - if (! skip_duration) { - skip_duration = duration; - DEBUG("times.interval", - "stabilize: skip_duration set to: " << *skip_duration); - } - if (start && ! next) { - next = skip_duration->add(*start); - DEBUG("times.interval", - "stabilize: next set to: " << *next); + next = end_of_duration; + DEBUG("times.interval", "stabilize: next set to: " << *next); } } @@ -988,10 +1052,10 @@ void date_interval_t::stabilize(const optional& date) #if defined(DEBUG_ON) if (initial_start) DEBUG("times.interval", - "stabilize: initial_start = " << *initial_start); + "stabilize: initial_start = " << *initial_start); if (initial_finish) DEBUG("times.interval", - "stabilize: initial_finish = " << *initial_finish); + "stabilize: initial_finish = " << *initial_finish); #endif date_t when = start ? *start : *date; @@ -1024,9 +1088,10 @@ void date_interval_t::stabilize(const optional& date) } } - DEBUG("times.interval", "stabilize: final start date = " << *start); + DEBUG("times.interval", "stabilize: proposed start date = " << *start); if (initial_start && (! start || *start < *initial_start)) { + // Using the discovered start, find the end of the period resolve_end(); start = initial_start; @@ -1036,14 +1101,20 @@ void date_interval_t::stabilize(const optional& date) finish = initial_finish; DEBUG("times.interval", "stabilize: finish reset to initial finish"); } + + if (start) + DEBUG("times.interval", "stabilize: final start = " << *start); + if (finish) + DEBUG("times.interval", "stabilize: final finish = " << *finish); } else if (range) { - start = range->begin(); - finish = range->end(); - - if (start && finish) - duration = date_duration_t(date_duration_t::DAYS, - static_cast((*finish - *start).days())); + if (date) { + start = range->begin(date->year()); + finish = range->end(date->year()); + } else { + start = range->begin(); + finish = range->end(); + } } aligned = true; } @@ -1116,7 +1187,7 @@ bool date_interval_t::find_period(const date_t& date) return true; } - scan = skip_duration->add(scan); + scan = duration->add(scan); end_of_scan = duration->add(scan); } @@ -1130,7 +1201,7 @@ date_interval_t& date_interval_t::operator++() stabilize(); - if (! skip_duration || ! duration) + if (! duration) throw_(date_error, _("Cannot increment a date interval without a duration")); @@ -1140,10 +1211,8 @@ date_interval_t& date_interval_t::operator++() start = none; } else { start = *next; - end_of_duration = duration->add(*start); } - next = none; resolve_end(); @@ -1151,7 +1220,7 @@ date_interval_t& date_interval_t::operator++() return *this; } -void date_interval_t::dump(std::ostream& out) +void date_interval_t::dump(std::ostream& out, optional_year current_year) { out << _("--- Before stabilization ---") << std::endl; @@ -1162,14 +1231,10 @@ void date_interval_t::dump(std::ostream& out) if (finish) out << _(" finish: ") << format_date(*finish) << std::endl; - if (skip_duration) - out << _(" skip: ") << skip_duration->to_string() << std::endl; - if (factor) - out << _(" factor: ") << factor << std::endl; if (duration) out << _("duration: ") << duration->to_string() << std::endl; - stabilize(begin()); + stabilize(begin(current_year)); out << std::endl << _("--- After stabilization ---") << std::endl; @@ -1181,27 +1246,30 @@ void date_interval_t::dump(std::ostream& out) if (finish) out << _(" finish: ") << format_date(*finish) << std::endl; - if (skip_duration) - out << _(" skip: ") << skip_duration->to_string() << std::endl; - if (factor) - out << _(" factor: ") << factor << std::endl; if (duration) out << _("duration: ") << duration->to_string() << std::endl; out << std::endl << _("--- Sample dates in range (max. 20) ---") << std::endl; - for (int i = 0; i < 20 && *this; i++, ++*this) { + date_t last_date; + + for (int i = 0; i < 20 && *this; ++i, ++*this) { out << std::right; out.width(2); + if (! last_date.is_not_a_date() && last_date == *start) + break; + out << (i + 1) << ": " << format_date(*start); - if (skip_duration) - out << " -- " << format_date(*inclusive_skip_end()); + if (duration) + out << " -- " << format_date(*inclusive_end()); out << std::endl; - if (! skip_duration) + if (! duration) break; + + last_date = *start; } } @@ -1268,6 +1336,8 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() token_t::content_t(lexical_cast(term))); } else if (std::isalpha(term[0])) { + to_lower(term); + if (optional month = string_to_month_of_year(term)) { return token_t(token_t::TOK_A_MONTH, token_t::content_t(*month)); @@ -1339,20 +1409,20 @@ date_parser_t::lexer_t::token_t date_parser_t::lexer_t::next_token() token_t::expected('\0', *begin); } - return token_t(token_t::UNKNOWN); + return token_t(token_t::UNKNOWN, token_t::content_t(term)); } void date_parser_t::lexer_t::token_t::unexpected() { - kind_t prev_kind = kind; - - kind = UNKNOWN; - - switch (prev_kind) { + switch (kind) { case END_REACHED: + kind = UNKNOWN; throw_(date_error, _("Unexpected end of expression")); - default: - throw_(date_error, _("Unexpected token '%1'") << to_string()); + default: { + string desc = to_string(); + kind = UNKNOWN; + throw_(date_error, _("Unexpected date period token '%1'") << desc); + } } } @@ -1503,7 +1573,8 @@ void show_period_tokens(std::ostream& out, const string& arg) date_parser_t::lexer_t::token_t token; do { token = lexer.next_token(); - out << token.to_string() << std::endl; + token.dump(out); + out << ": " << token.to_string() << std::endl; } while (token.kind != date_parser_t::lexer_t::token_t::END_REACHED); } diff --git a/src/times.h b/src/times.h index d378dd81..1c9d812e 100644 --- a/src/times.h +++ b/src/times.h @@ -534,16 +534,14 @@ public: optional start; // the real start, after adjustment optional finish; // the real end, likewise bool aligned; - optional skip_duration; - std::size_t factor; optional next; optional duration; optional end_of_duration; - explicit date_interval_t() : aligned(false), factor(1) { + explicit date_interval_t() : aligned(false) { TRACE_CTOR(date_interval_t, ""); } - date_interval_t(const string& str) : aligned(false), factor(1) { + date_interval_t(const string& str) : aligned(false) { TRACE_CTOR(date_interval_t, "const string&"); parse(str); } @@ -552,8 +550,6 @@ public: start(other.start), finish(other.finish), aligned(other.aligned), - skip_duration(other.skip_duration), - factor(other.factor), next(other.next), duration(other.duration), end_of_duration(other.end_of_duration) { @@ -593,12 +589,6 @@ public: containing date, or false if no such period can be found. */ bool find_period(const date_t& date); - optional inclusive_skip_end() const { - if (skip_duration) - return skip_duration->add(*start) - gregorian::days(1); - else - return none; - } optional inclusive_end() const { if (end_of_duration) return *end_of_duration - gregorian::days(1); @@ -608,7 +598,7 @@ public: date_interval_t& operator++(); - void dump(std::ostream& out); + void dump(std::ostream& out, optional_year current_year = none); #if defined(HAVE_BOOST_SERIALIZATION) private: @@ -622,8 +612,6 @@ private: ar & start; ar & finish; ar & aligned; - ar & skip_duration; - ar & factor; ar & next; ar & duration; ar & end_of_duration; diff --git a/src/token.cc b/src/token.cc index 3df072a7..81c54a82 100644 --- a/src/token.cc +++ b/src/token.cc @@ -206,11 +206,12 @@ void expr_t::token_t::next(std::istream& in, const parse_flags_t& pflags) length++; date_interval_t timespan(buf); - if (! timespan) + optional begin = timespan.begin(); + if (! begin) throw_(parse_error, _("Date specifier does not refer to a starting date")); kind = VALUE; - value = *timespan.start; + value = *begin; break; } diff --git a/test/regress/7F3650FD.test b/test/regress/7F3650FD.test index dce5233f..f7154eb8 100644 --- a/test/regress/7F3650FD.test +++ b/test/regress/7F3650FD.test @@ -1,50 +1,95 @@ period --now=2010/11/01 12/01 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: month Dec day 1 +END_REACHED: - start: 09-Dec-01 - finish: 09-Dec-02 - factor: 1 +--- Before stabilization --- + range: in month Dec day 1 + +--- After stabilization --- + range: in month Dec day 1 + start: 10-Dec-01 + finish: 10-Dec-02 + +--- Sample dates in range (max. 20) --- + 1: 10-Dec-01 >>>2 === 0 period --now=2010/11/01 10/01 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: month Oct day 1 +END_REACHED: + +--- Before stabilization --- + range: in month Oct day 1 +--- After stabilization --- + range: in month Oct day 1 start: 10-Oct-01 finish: 10-Oct-02 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 10-Oct-01 >>>2 === 0 period --now=2010/11/01 2009/10 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: year 2009 month Oct +END_REACHED: + +--- Before stabilization --- + range: in year 2009 month Oct +--- After stabilization --- + range: in year 2009 month Oct start: 09-Oct-01 finish: 09-Nov-01 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 09-Oct-01 >>>2 === 0 period --now=2010/11/01 2009/10/01 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_DATE: year 2009 month Oct day 1 +END_REACHED: + +--- Before stabilization --- + range: in year 2009 month Oct day 1 +--- After stabilization --- + range: in year 2009 month Oct day 1 start: 09-Oct-01 finish: 09-Oct-02 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 09-Oct-01 >>>2 === 0 period --now=2010/11/01 2009 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_A_YEAR: 2009 +END_REACHED: +--- Before stabilization --- + range: in year 2009 + +--- After stabilization --- + range: in year 2009 start: 09-Jan-01 finish: 10-Jan-01 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 09-Jan-01 >>>2 === 0 diff --git a/test/regress/BBFA1759.test b/test/regress/BBFA1759.test index b109f868..cd5990fc 100644 --- a/test/regress/BBFA1759.test +++ b/test/regress/BBFA1759.test @@ -1,10 +1,20 @@ period june 2008 <<< >>>1 -global details => +--- Period expression tokens --- +TOK_A_MONTH: Jun +TOK_A_YEAR: 2008 +END_REACHED: +--- Before stabilization --- + range: in year 2008 month Jun + +--- After stabilization --- + range: in year 2008 month Jun start: 08-Jun-01 finish: 08-Jul-01 - factor: 1 + +--- Sample dates in range (max. 20) --- + 1: 08-Jun-01 >>>2 === 0 -- cgit v1.2.3 From e27ba3e1ff77537ade8287f1d66cf6fe626c8962 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Wed, 18 Nov 2009 23:15:22 -0500 Subject: It's OK for a report query to be empty It's always possible the user only specified a display predicate. --- src/precmd.cc | 24 ++++++++++-------------- src/report.cc | 24 ++++++++++-------------- 2 files changed, 20 insertions(+), 28 deletions(-) (limited to 'src/precmd.cc') diff --git a/src/precmd.cc b/src/precmd.cc index 516b90dd..31249016 100644 --- a/src/precmd.cc +++ b/src/precmd.cc @@ -184,14 +184,12 @@ value_t args_command(call_scope_t& args) out << std::endl << std::endl; query_t query(args.value(), report.what_to_keep()); - if (! query) - throw_(std::runtime_error, - _("Invalid query predicate: %1") << join_args(args)); + if (query) { + call_scope_t sub_args(static_cast(args)); + sub_args.push_back(string_value(query.text())); - call_scope_t sub_args(static_cast(args)); - sub_args.push_back(string_value(query.text())); - - parse_command(sub_args); + parse_command(sub_args); + } if (query.tokens_remaining()) { out << std::endl << _("====== Display predicate ======") @@ -199,14 +197,12 @@ value_t args_command(call_scope_t& args) query.parse_again(); - if (! query) - throw_(std::runtime_error, - _("Invalid display predicate: %1") << join_args(args)); + if (query) { + call_scope_t disp_sub_args(static_cast(args)); + disp_sub_args.push_back(string_value(query.text())); - call_scope_t disp_sub_args(static_cast(args)); - disp_sub_args.push_back(string_value(query.text())); - - parse_command(disp_sub_args); + parse_command(disp_sub_args); + } } return NULL_VALUE; diff --git a/src/report.cc b/src/report.cc index 78ed05c0..ed3238bc 100644 --- a/src/report.cc +++ b/src/report.cc @@ -222,25 +222,21 @@ void report_t::normalize_options(const string& verb) void report_t::parse_query_args(const value_t& args, const string& whence) { query_t query(args, what_to_keep()); - if (! query) - throw_(std::runtime_error, - _("Invalid query predicate: %1") << query.text()); + if (query) { + HANDLER(limit_).on(whence, query.text()); - HANDLER(limit_).on(whence, query.text()); - - DEBUG("report.predicate", - "Predicate = " << HANDLER(limit_).str()); + DEBUG("report.predicate", + "Predicate = " << HANDLER(limit_).str()); + } if (query.tokens_remaining()) { query.parse_again(); - if (! query) - throw_(std::runtime_error, - _("Invalid display predicate: %1") << query.text()); + if (query) { + HANDLER(display_).on(whence, query.text()); - HANDLER(display_).on(whence, query.text()); - - DEBUG("report.predicate", - "Display predicate = " << HANDLER(display_).str()); + DEBUG("report.predicate", + "Display predicate = " << HANDLER(display_).str()); + } } } -- cgit v1.2.3