[tinyexpr] Add error handling

This turns a bunch of ifs on their heads.

We often see this pattern in te:

```c
if (s->type != SOME_TYPE) {
   // error handling
}  else {
   // normal code
}
```

Only, since we want to return the first error, we do

```c
if (s->type == SOME_TYPE) {
    // normal code
} else if (s->type != TOK_ERROR) {
    // Add a new error - if it already has type error
    // this should already be handled.
}
```

One big issue is the comma operator, that means arity-1 functions can
take an arbitrary number of arguments. E.g.

```fish
math "sin(5,9)"
```

will return the value of sin for _9_, since this is read as "5 COMMA
9".
This commit is contained in:
Fabian Homborg 2018-02-12 00:02:47 +01:00
parent b9a3938a2b
commit 7b9c75094c
3 changed files with 81 additions and 26 deletions

View file

@ -113,12 +113,28 @@ static const wchar_t *math_get_arg(int *argidx, wchar_t **argv, wcstring *storag
return math_get_arg_argv(argidx, argv);
}
wcstring math_describe_error(te_error_t& error) {
if (error.position == 0) return L"";
assert(error.type != TE_ERROR_NONE && L"Error has no position");
switch(error.type) {
case TE_ERROR_UNKNOWN_VARIABLE: return _(L"Unknown variable");
case TE_ERROR_MISSING_CLOSING_PAREN: return _(L"Missing closing parenthesis");
case TE_ERROR_MISSING_OPENING_PAREN: return _(L"Missing opening parenthesis");
case TE_ERROR_TOO_FEW_ARGS: return _(L"Too few arguments");
case TE_ERROR_TOO_MANY_ARGS: return _(L"Too many arguments");
case TE_ERROR_MISSING_OPERATOR: return _(L"Missing operator");
case TE_ERROR_BOGUS: return _(L"Expression is bogus");
default: return L"";
}
}
/// Evaluate math expressions.
static int evaluate_expression(const wchar_t *cmd, parser_t &parser, io_streams_t &streams,
math_cmd_opts_t &opts, wcstring &expression) {
UNUSED(parser);
int error;
te_error_t error;
char *narrow_str = wcs2str(expression);
// Switch locale while computing stuff.
// This means that the "." is always the radix character,
@ -128,7 +144,7 @@ static int evaluate_expression(const wchar_t *cmd, parser_t &parser, io_streams_
double v = te_interp(narrow_str, &error);
setlocale(LC_NUMERIC, saved_locale);
if (error == 0) {
if (error.position == 0) {
if (opts.scale == 0) {
streams.out.append_format(L"%ld\n", static_cast<long>(v));
} else {
@ -136,7 +152,11 @@ static int evaluate_expression(const wchar_t *cmd, parser_t &parser, io_streams_
}
} else {
// TODO: Better error reporting!
streams.err.append_format(L"'%ls': Error at token %d\n", expression.c_str(), error - 1);
streams.err.append(L"math: Error in expression\n");
streams.err.append_format(L"'%ls'\n", expression.c_str());
streams.err.append_format(L"%*lc^\n", error.position - 1, L' ');
streams.err.append(math_describe_error(error));
streams.err.append(L"\n");
}
free(narrow_str);
free(saved_locale);

View file

@ -50,6 +50,7 @@ typedef struct state {
const te_variable *lookup;
int lookup_len;
int error;
} state;
@ -226,9 +227,7 @@ void next_token(state *s) {
const te_variable *var = find_lookup(s, start, s->next - start);
if (!var) var = find_builtin(start, s->next - start);
if (!var) {
s->type = TOK_ERROR;
} else {
if (var) {
switch(TYPE_MASK(var->type))
{
case TE_VARIABLE:
@ -246,6 +245,10 @@ void next_token(state *s) {
s->function = var->address;
break;
}
} else if (s->type != TOK_ERROR) {
// TODO: Better error - "Not a variable"?
s->type = TOK_ERROR;
s->error = TE_ERROR_UNKNOWN_VARIABLE;
}
} else {
@ -261,7 +264,7 @@ void next_token(state *s) {
case ')': s->type = TOK_CLOSE; break;
case ',': s->type = TOK_SEP; break;
case ' ': case '\t': case '\n': case '\r': break;
default: s->type = TOK_ERROR; break;
default: s->type = TOK_ERROR; s->error = TE_ERROR_MISSING_OPERATOR; break;
}
}
}
@ -299,10 +302,12 @@ static te_expr *base(state *s) {
next_token(s);
if (s->type == TOK_OPEN) {
next_token(s);
if (s->type != TOK_CLOSE) {
s->type = TOK_ERROR;
} else {
if (s->type == TOK_CLOSE) {
next_token(s);
} else if (s->type != TOK_ERROR) {
// TODO: Better error - "Missing closing parenthesis"?
s->type = TOK_ERROR;
s->error = TE_ERROR_MISSING_CLOSING_PAREN;
}
}
break;
@ -327,9 +332,7 @@ static te_expr *base(state *s) {
if (IS_CLOSURE(s->type)) ret->parameters[arity] = s->context;
next_token(s);
if (s->type != TOK_OPEN) {
s->type = TOK_ERROR;
} else {
if (s->type == TOK_OPEN) {
int i;
for(i = 0; i < arity; i++) {
next_token(s);
@ -338,11 +341,19 @@ static te_expr *base(state *s) {
break;
}
}
if(s->type != TOK_CLOSE || i != arity - 1) {
s->type = TOK_ERROR;
} else {
if(s->type == TOK_CLOSE && i == arity - 1) {
next_token(s);
} else if (s->type != TOK_ERROR) {
// TODO: Either a closing paren was needed,
// or too few arguments were given?
s->type = TOK_ERROR;
s->error = i < arity ? TE_ERROR_TOO_FEW_ARGS
: TE_ERROR_TOO_MANY_ARGS;
}
} else if (s->type != TOK_ERROR) {
// TODO: Better error - "Expected opening parenthesis"?
s->type = TOK_ERROR;
s->error = TE_ERROR_MISSING_OPENING_PAREN;
}
break;
@ -350,16 +361,20 @@ static te_expr *base(state *s) {
case TOK_OPEN:
next_token(s);
ret = list(s);
if (s->type != TOK_CLOSE) {
s->type = TOK_ERROR;
} else {
if (s->type == TOK_CLOSE) {
next_token(s);
} else if (s->type != TOK_ERROR) {
// TODO: Error - missing closing paren?
s->type = TOK_ERROR;
s->error = TE_ERROR_MISSING_CLOSING_PAREN;
}
break;
default:
ret = new_expr(0, 0);
// TODO: Error - expression is bogus?
s->type = TOK_ERROR;
s->error = TE_ERROR_BOGUS;
ret->value = NAN;
break;
}
@ -520,7 +535,7 @@ static void optimize(te_expr *n) {
}
te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error) {
te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, te_error_t *error) {
state s;
s.start = s.next = expression;
s.lookup = variables;
@ -532,19 +547,19 @@ te_expr *te_compile(const char *expression, const te_variable *variables, int va
if (s.type != TOK_END) {
te_free(root);
if (error) {
*error = (s.next - s.start);
if (*error == 0) *error = 1;
error->position = (s.next - s.start) + 1;
error->type = s.error;
}
return 0;
} else {
optimize(root);
if (error) *error = 0;
if (error) error->position = 0;
return root;
}
}
double te_interp(const char *expression, int *error) {
double te_interp(const char *expression, te_error_t *error) {
te_expr *n = te_compile(expression, 0, 0, error);
double ret;
if (n) {

View file

@ -22,6 +22,8 @@
* 3. This notice may not be removed or altered from any source distribution.
*/
// This version was altered for inclusion in fish.
#ifndef __TINYEXPR_H__
#define __TINYEXPR_H__
@ -31,6 +33,24 @@ extern "C" {
#endif
enum {
TE_ERROR_NONE = 0,
TE_ERROR_UNKNOWN_VARIABLE = 1,
TE_ERROR_MISSING_CLOSING_PAREN = 2,
TE_ERROR_MISSING_OPENING_PAREN = 3,
TE_ERROR_TOO_FEW_ARGS = 4,
TE_ERROR_TOO_MANY_ARGS = 5,
TE_ERROR_MISSING_OPERATOR = 6,
TE_ERROR_BOGUS = 7
};
typedef int te_error_type_t;
typedef struct te_error_t {
te_error_type_t type;
int position;
} te_error_t;
typedef struct te_expr {
int type;
@ -62,11 +82,11 @@ typedef struct te_variable {
/* Parses the input expression, evaluates it, and frees it. */
/* Returns NaN on error. */
double te_interp(const char *expression, int *error);
double te_interp(const char *expression, te_error_t *error);
/* Parses the input expression and binds variables. */
/* Returns NULL on error. */
te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, int *error);
te_expr *te_compile(const char *expression, const te_variable *variables, int var_count, te_error_t *error);
/* Evaluates the expression. */
double te_eval(const te_expr *n);