From 1ee637afefb32a42f76bb9c361fc5d6efe6a7ce7 Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Sat, 23 Jan 2016 12:09:38 -0600 Subject: [PATCH] Changed error handling a bit. te_interp returns NaN for bad expressions. --- README.md | 5 +---- example2.c | 2 +- test.c | 50 ++++++++++++++++++++++++++++++++++++++++++-------- tinyexpr.c | 25 ++++++++++++++++--------- tinyexpr.h | 12 +++--------- 5 files changed, 63 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2160e74..6b9e482 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ line. It also does error checking and binds the variables *x* and *y*. printf("\t%*s^\nError near here", err-1, ""); } - /* te_free should always be called after te_compile. */ + /* te_free is safe to call on null. */ te_free(n); return 0; @@ -235,9 +235,6 @@ In addition, the following C math functions are also supported: - All functions/types start with the letters *te*. -- Remember to always call *te_free* on the result of *te_compile*, even if - there is an error. - - If there is an error, you can usually still evaluate the first part of the expression. This may or may not be useful to you. diff --git a/example2.c b/example2.c index b317f98..20b26f9 100644 --- a/example2.c +++ b/example2.c @@ -32,7 +32,7 @@ int main(int argc, char *argv[]) printf("\t%*s^\nError near here", err-1, ""); } - /* te_free should always be called after te_compile. */ + /* te_free is safe to call on null. */ te_free(n); return 0; diff --git a/test.c b/test.c index 152a703..41bdb09 100644 --- a/test.c +++ b/test.c @@ -122,7 +122,26 @@ test_case errors[] = { }; -void test1() { +const char *nans[] = { + "1+", + "1)", + "(1", + "1**1", + "1*2(+4", + "1*2(1+4", + "a+5", + "A+5", + "Aa+5", + "1^^5", + "1**5", + "sin(cos5", + "5+5error", + "5+5+error", +}; + + + +void test_results() { int i; for (i = 0; i < sizeof(cases) / sizeof(test_case); ++i) { const char *expr = cases[i].expr; @@ -136,7 +155,7 @@ void test1() { } -void test2() { +void test_syntax() { int i; for (i = 0; i < sizeof(errors) / sizeof(test_case); ++i) { const char *expr = errors[i].expr; @@ -150,7 +169,7 @@ void test2() { -void test3() { +void test_variables() { double x, y; te_variable lookup[] = {{"x", &x}, {"y", &y}}; @@ -185,6 +204,7 @@ void test3() { } + #define cross_check(a, b) do {\ expr = te_compile((a), &lookup, 1, &err);\ lfequal(te_eval(expr), (b));\ @@ -192,7 +212,7 @@ void test3() { te_free(expr);\ }while(0) -void test4() { +void test_functions() { double x; te_variable lookup = {"x", &x}; @@ -221,12 +241,26 @@ void test4() { } +void test_nans() { + int i; + for (i = 0; i < sizeof(nans) / sizeof(const char*); ++i) { + const char *expr = nans[i]; + + int err; + const double r = te_interp(expr, &err); + lok(err); + lok(r != r); + } +} + + int main(int argc, char *argv[]) { - lrun("Results", test1); - lrun("Syntax", test2); - lrun("Bind", test3); - lrun("Functions", test4); + lrun("Results", test_results); + lrun("Syntax", test_syntax); + lrun("Variables", test_variables); + lrun("Functions", test_functions); + lrun("NaNs", test_nans); lresults(); return lfails != 0; diff --git a/tinyexpr.c b/tinyexpr.c index 8364ef1..ac56cd3 100644 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -56,6 +56,7 @@ static te_expr *new_expr(te_expr *l, te_expr *r) { void te_free(te_expr *n) { + if (!n) return; if (n->left) te_free(n->left); if (n->right) te_free(n->right); free(n); @@ -232,7 +233,7 @@ static te_expr *base(state *s) { default: ret = new_expr(0, 0); s->type = TOK_ERROR; - ret->value = 1.0/0.0; + ret->value = 0.0/0.0; break; } @@ -358,24 +359,30 @@ te_expr *te_compile(const char *expression, const te_variable *lookup, int looku next_token(&s); te_expr *root = expr(&s); - if (s.type != TOK_END) { - if (error) *error = (s.next - s.start); - if (*error == 0) *error = 1; + te_free(root); + if (error) { + *error = (s.next - s.start); + if (*error == 0) *error = 1; + } + return 0; } else { optimize(root); if (error) *error = 0; + return root; } - - - return root; } double te_interp(const char *expression, int *error) { te_expr *n = te_compile(expression, 0, 0, error); - double ret = te_eval(n); - free(n); + double ret; + if (n) { + ret = te_eval(n); + free(n); + } else { + ret = 0.0/0.0; + } return ret; } diff --git a/tinyexpr.h b/tinyexpr.h index dabffd9..95963e2 100644 --- a/tinyexpr.h +++ b/tinyexpr.h @@ -48,29 +48,23 @@ typedef struct { } te_variable; -/* Note on error handling: - * If the parser encounters an error, it will still return - * an expression up to that point (which may be worthless or useful. - * If the error pointer parameter is passed in and not null, the - * parser will set it to roughly the index of the error in the - * input expression. If there is no error, the parse sets - * the error pointer to 0. - */ - /* Parses the input expression, evaluates it, and frees it. */ double te_interp(const char *expression, int *error); /* Parses the input expression and binds variables. */ +/* Returns NULL on error. */ te_expr *te_compile(const char *expression, const te_variable *lookup, int lookup_len, int *error); /* Evaluates the expression. */ +/* Returns NaN on error. */ double te_eval(const te_expr *n); /* Prints debugging information on the syntax tree. */ void te_print(const te_expr *n); /* Frees the expression. */ +/* This is safe to call on NULL pointers. */ void te_free(te_expr *n);