From bcaaaebdde43e26a8d27c53590f34bf29eb56406 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sat, 1 Jan 2022 11:10:05 -0800 Subject: [PATCH] lexer: New functions for parsing real numbers in specified ranges. These will be more useful in the future. --- src/language/lexer/lexer.c | 230 +++++++++++++++++++++++++++++ src/language/lexer/lexer.h | 6 + src/language/stats/examine.c | 9 +- src/language/stats/logistic.c | 13 +- src/language/stats/quick-cluster.c | 7 +- 5 files changed, 242 insertions(+), 23 deletions(-) diff --git a/src/language/lexer/lexer.c b/src/language/lexer/lexer.c index 329003406b..908556a7ee 100644 --- a/src/language/lexer/lexer.c +++ b/src/language/lexer/lexer.c @@ -966,6 +966,236 @@ lex_force_num (struct lexer *lexer) return false; } +/* If the current token is an number in the closed range [MIN,MAX], does + nothing and returns true. Otherwise, reports an error and returns false. + If NAME is nonnull, then it is used in the error message. */ +bool +lex_force_num_range_closed (struct lexer *lexer, const char *name, + double min, double max) +{ + bool is_number = lex_is_number (lexer); + bool too_small = is_number && lex_number (lexer) < min; + bool too_big = is_number && lex_number (lexer) > max; + if (is_number && !too_small && !too_big) + return true; + + if (min > max) + { + /* Weird, maybe a bug in the caller. Just report that we needed an + number. */ + if (name) + lex_error (lexer, _("Number expected for %s."), name); + else + lex_error (lexer, _("Number expected.")); + } + else if (min == max) + { + if (name) + lex_error (lexer, _("Expected %g for %s."), min, name); + else + lex_error (lexer, _("Expected %g."), min); + } + else + { + bool report_lower_bound = min > -DBL_MAX || too_small; + bool report_upper_bound = max < DBL_MAX || too_big; + + if (report_lower_bound && report_upper_bound) + { + if (name) + lex_error (lexer, + _("Expected number between %g and %g for %s."), + min, max, name); + else + lex_error (lexer, _("Expected number between %g and %g."), + min, max); + } + else if (report_lower_bound) + { + if (min == 0) + { + if (name) + lex_error (lexer, _("Expected non-negative number for %s."), + name); + else + lex_error (lexer, _("Expected non-negative number.")); + } + else + { + if (name) + lex_error (lexer, _("Expected number %g or greater for %s."), + min, name); + else + lex_error (lexer, _("Expected number %g or greater."), min); + } + } + else if (report_upper_bound) + { + if (name) + lex_error (lexer, + _("Expected number less than or equal to %g for %s."), + max, name); + else + lex_error (lexer, _("Expected number less than or equal to %g."), + max); + } + else + { + if (name) + lex_error (lexer, _("Number expected for %s."), name); + else + lex_error (lexer, _("Number expected.")); + } + } + return false; +} + +/* If the current token is an number in the half-open range [MIN,MAX), does + nothing and returns true. Otherwise, reports an error and returns false. + If NAME is nonnull, then it is used in the error message. */ +bool +lex_force_num_range_halfopen (struct lexer *lexer, const char *name, + double min, double max) +{ + bool is_number = lex_is_number (lexer); + bool too_small = is_number && lex_number (lexer) < min; + bool too_big = is_number && lex_number (lexer) >= max; + if (is_number && !too_small && !too_big) + return true; + + if (min >= max) + { + /* Weird, maybe a bug in the caller. Just report that we needed an + number. */ + if (name) + lex_error (lexer, _("Number expected for %s."), name); + else + lex_error (lexer, _("Number expected.")); + } + else + { + bool report_lower_bound = min > -DBL_MAX || too_small; + bool report_upper_bound = max < DBL_MAX || too_big; + + if (report_lower_bound && report_upper_bound) + { + if (name) + lex_error (lexer, _("Expected number in [%g,%g) for %s."), + min, max, name); + else + lex_error (lexer, _("Expected number in [%g,%g)."), + min, max); + } + else if (report_lower_bound) + { + if (min == 0) + { + if (name) + lex_error (lexer, _("Expected non-negative number for %s."), + name); + else + lex_error (lexer, _("Expected non-negative number.")); + } + else + { + if (name) + lex_error (lexer, _("Expected number %g or greater for %s."), + min, name); + else + lex_error (lexer, _("Expected number %g or greater."), min); + } + } + else if (report_upper_bound) + { + if (name) + lex_error (lexer, + _("Expected number less than %g for %s."), max, name); + else + lex_error (lexer, _("Expected number less than %g."), max); + } + else + { + if (name) + lex_error (lexer, _("Number expected for %s."), name); + else + lex_error (lexer, _("Number expected.")); + } + } + return false; +} + +/* If the current token is an number in the open range (MIN,MAX], does + nothing and returns true. Otherwise, reports an error and returns false. + If NAME is nonnull, then it is used in the error message. */ +bool +lex_force_num_range_open (struct lexer *lexer, const char *name, + double min, double max) +{ + bool is_number = lex_is_number (lexer); + bool too_small = is_number && lex_number (lexer) <= min; + bool too_big = is_number && lex_number (lexer) >= max; + if (is_number && !too_small && !too_big) + return true; + + if (min >= max) + { + /* Weird, maybe a bug in the caller. Just report that we needed an + number. */ + if (name) + lex_error (lexer, _("Number expected for %s."), name); + else + lex_error (lexer, _("Number expected.")); + } + else + { + bool report_lower_bound = min > -DBL_MAX || too_small; + bool report_upper_bound = max < DBL_MAX || too_big; + + if (report_lower_bound && report_upper_bound) + { + if (name) + lex_error (lexer, _("Expected number in (%g,%g) for %s."), + min, max, name); + else + lex_error (lexer, _("Expected number in (%g,%g)."), min, max); + } + else if (report_lower_bound) + { + if (min == 0) + { + if (name) + lex_error (lexer, _("Expected positive number for %s."), name); + else + lex_error (lexer, _("Expected positive number.")); + } + else + { + if (name) + lex_error (lexer, _("Expected number greater than %g for %s."), + min, name); + else + lex_error (lexer, _("Expected number greater than %g."), min); + } + } + else if (report_upper_bound) + { + if (name) + lex_error (lexer, _("Expected number less than %g for %s."), + max, name); + else + lex_error (lexer, _("Expected number less than %g."), max); + } + else + { + if (name) + lex_error (lexer, _("Number expected for %s."), name); + else + lex_error (lexer, _("Number expected.")); + } + } + return false; +} + /* If the current token is an identifier, does nothing and returns true. Otherwise, reports an error and returns false. */ bool diff --git a/src/language/lexer/lexer.h b/src/language/lexer/lexer.h index 5493db9b6a..764da74b19 100644 --- a/src/language/lexer/lexer.h +++ b/src/language/lexer/lexer.h @@ -134,6 +134,12 @@ bool lex_force_int (struct lexer *) WARN_UNUSED_RESULT; bool lex_force_int_range (struct lexer *, const char *name, long min, long max) WARN_UNUSED_RESULT; bool lex_force_num (struct lexer *) WARN_UNUSED_RESULT; +bool lex_force_num_range_closed (struct lexer *, const char *name, + double min, double max) WARN_UNUSED_RESULT; +bool lex_force_num_range_halfopen (struct lexer *, const char *name, + double min, double max) WARN_UNUSED_RESULT; +bool lex_force_num_range_open (struct lexer *, const char *name, + double min, double max) WARN_UNUSED_RESULT; bool lex_force_id (struct lexer *) WARN_UNUSED_RESULT; bool lex_force_string (struct lexer *) WARN_UNUSED_RESULT; bool lex_force_string_or_id (struct lexer *) WARN_UNUSED_RESULT; diff --git a/src/language/stats/examine.c b/src/language/stats/examine.c index ad8a4126ff..4dbafce320 100644 --- a/src/language/stats/examine.c +++ b/src/language/stats/examine.c @@ -1568,15 +1568,10 @@ cmd_examine (struct lexer *lexer, struct dataset *ds) { while (lex_is_number (lexer)) { + if (!lex_force_num_range_open (lexer, "PERCENTILES", 0, 100)) + goto error; double p = lex_number (lexer); - if (p <= 0 || p >= 100.0) - { - lex_error (lexer, - _("Percentiles must lie in the range (0, 100)")); - goto error; - } - examine.n_percentiles++; examine.ptiles = xrealloc (examine.ptiles, diff --git a/src/language/stats/logistic.c b/src/language/stats/logistic.c index 556b060841..75f1d2a13f 100644 --- a/src/language/stats/logistic.c +++ b/src/language/stats/logistic.c @@ -982,18 +982,11 @@ cmd_logistic (struct lexer *lexer, struct dataset *ds) { if (lex_force_match (lexer, T_LPAREN)) { - if (! lex_force_num (lexer)) - { - lex_error (lexer, NULL); - goto error; - } + if (!lex_force_num_range_closed (lexer, "CUT", 0, 1)) + goto error; + cp = lex_number (lexer); - if (cp < 0 || cp > 1.0) - { - msg (ME, _("Cut point value must be in the range [0,1]")); - goto error; - } lex_get (lexer); if (! lex_force_match (lexer, T_RPAREN)) { diff --git a/src/language/stats/quick-cluster.c b/src/language/stats/quick-cluster.c index 9dd07f591b..0cd08d02a9 100644 --- a/src/language/stats/quick-cluster.c +++ b/src/language/stats/quick-cluster.c @@ -931,14 +931,9 @@ quick_cluster_parse (struct lexer *lexer, struct qc *qc) else if (lex_match_id (lexer, "CONVERGE")) { if (lex_force_match (lexer, T_LPAREN) && - lex_force_num (lexer)) + lex_force_num_range_open (lexer, "CONVERGE", 0, DBL_MAX)) { qc->epsilon = lex_number (lexer); - if (qc->epsilon <= 0) - { - lex_error (lexer, _("The convergence criterion must be positive")); - return false; - } lex_get (lexer); if (!lex_force_match (lexer, T_RPAREN)) return false; -- 2.30.2