mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 21:03:12 +00:00
[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:
parent
b9a3938a2b
commit
7b9c75094c
3 changed files with 81 additions and 26 deletions
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue