Implement support for bare vars by math

This change allows you to type `math x + 3` rather than `math $x + 3`.

Another step to resolving issue #3157.
This commit is contained in:
Kurtis Rader 2017-08-23 20:38:40 -07:00
parent d10decabda
commit c95b9f06e1
4 changed files with 77 additions and 2 deletions

View file

@ -108,22 +108,72 @@ static const wchar_t *math_get_arg_argv(int *argidx, wchar_t **argv) {
/// Get the arguments from argv or stdin based on the execution context. This mimics how builtin /// Get the arguments from argv or stdin based on the execution context. This mimics how builtin
/// `string` does it. /// `string` does it.
static const wchar_t *math_get_arg(int *argidx, wchar_t **argv, wcstring *storage, static const wchar_t *math_get_arg(int *argidx, wchar_t **argv, wcstring *storage,
const io_streams_t &streams) { const io_streams_t &streams) {
if (math_args_from_stdin(streams)) { if (math_args_from_stdin(streams)) {
return math_get_arg_stdin(storage, streams); return math_get_arg_stdin(storage, streams);
} }
return math_get_arg_argv(argidx, argv); return math_get_arg_argv(argidx, argv);
} }
// The MuParser mechanism for dynamic lookup of variables requires that we return a unique address
// for each variable. The following limit is arbitrary but anyone writing a math expression in fish
// that references more than one hundred unique variables is abusing fish.
#define MAX_RESULTS 100
static double double_results[MAX_RESULTS];
static int next_result = 0;
/// Return a fish var converted to a double. This allows the user to use a bar var name in the
/// expression. That is `math a + 1` rather than `math $a + 1`.
static double *retrieve_var(const wchar_t *var_name, void *user_data) {
UNUSED(user_data);
static double zero_result = 0.0;
env_var_t var = env_get(var_name, ENV_DEFAULT);
if (var.missing()) {
// We could report an error but we normally don't treat missing vars as a fatal error.
// throw mu::ParserError(L"Var '%ls' does not exist.");
return &zero_result;
}
if (var.empty()) {
return &zero_result;
}
const wchar_t *first_val = var.as_const_list()[0].c_str();
wchar_t *endptr;
errno = 0;
double result = wcstod(first_val, &endptr);
if (*endptr != L'\0' || errno) {
wchar_t errmsg[500];
swprintf(errmsg, sizeof(errmsg) / sizeof(wchar_t),
_(L"Var '%ls' not a valid floating point number: '%ls'."), var_name, first_val);
throw mu::ParserError(errmsg);
}
// We need to return a unique address for the var. If we used a `static double` var and returned
// it's address then multiple vars in the expression would all refer to the same value.
if (next_result == MAX_RESULTS - 1) {
wchar_t errmsg[500];
swprintf(errmsg, sizeof(errmsg) / sizeof(wchar_t),
_(L"More than %d var names in math expression."), MAX_RESULTS);
throw mu::ParserError(errmsg);
}
double_results[next_result++] = result;
return double_results + next_result - 1;
}
/// Implement integer modulo math operator. /// Implement integer modulo math operator.
static double moduloOperator(double v, double w) { return (int)v % std::max(1, (int)w); }; static double moduloOperator(double v, double w) { return (int)v % std::max(1, (int)w); };
/// Evaluate math expressions.
static int evaluate_expression(wchar_t *cmd, parser_t &parser, io_streams_t &streams, static int evaluate_expression(wchar_t *cmd, parser_t &parser, io_streams_t &streams,
math_cmd_opts_t &opts, wcstring &expression) { math_cmd_opts_t &opts, wcstring &expression) {
UNUSED(parser); UNUSED(parser);
next_result = 0;
try { try {
mu::Parser p; mu::Parser p;
// Setup callback so variables can be retrieved dynamically.
p.SetVarFactory(retrieve_var, nullptr);
// MuParser doesn't implement the modulo operator so we add it ourselves since there are // MuParser doesn't implement the modulo operator so we add it ourselves since there are
// likely users of our old math wrapper around bc that expect it to be available. // likely users of our old math wrapper around bc that expect it to be available.
p.DefineOprtChars(L"%"); p.DefineOprtChars(L"%");
@ -139,7 +189,6 @@ static int evaluate_expression(wchar_t *cmd, parser_t &parser, io_streams_t &str
streams.out.append_format(L"%.*lf\n", opts.scale, v[i]); streams.out.append_format(L"%.*lf\n", opts.scale, v[i]);
} }
} }
return STATUS_CMD_OK; return STATUS_CMD_OK;
} catch (mu::Parser::exception_type &e) { } catch (mu::Parser::exception_type &e) {
streams.err.append_format(_(L"%ls: Invalid expression: %ls\n"), cmd, e.GetMsg().c_str()); streams.err.append_format(_(L"%ls: Invalid expression: %ls\n"), cmd, e.GetMsg().c_str());

View file

@ -0,0 +1,6 @@
####################
# Validate basic expressions
####################
# Validate how bare variables in an epxression are handled

View file

@ -1,3 +1,4 @@
logmsg Validate basic expressions
math 3 / 2 math 3 / 2
math 10/6 math 10/6
math -s0 10 / 6 math -s0 10 / 6
@ -12,3 +13,12 @@ math '-2 * -2'
math 5 \* -2 math 5 \* -2
math -- -4 / 2 math -- -4 / 2
math -- '-4 * 2' math -- '-4 * 2'
logmsg Validate how bare variables in an epxression are handled
math x + 1
set x 1
math x + 1
set x 3
set y 1.5
math '-x * y'
math -s1 '-x * y'

View file

@ -1,3 +1,6 @@
####################
# Validate basic expressions
1 1
1 1
1 1
@ -12,3 +15,10 @@
-10 -10
-2 -2
-8 -8
####################
# Validate how bare variables in an epxression are handled
1
2
-4
-4.5