Teach builtin_test about the special precedence rules in IEEE 1003.1

https://github.com/fish-shell/fish-shell/issues/601
This commit is contained in:
ridiculousfish 2013-03-03 13:22:00 -08:00
parent cbcc439e03
commit 1d75c232ff
2 changed files with 139 additions and 42 deletions

View file

@ -177,6 +177,8 @@ public:
{ }
expression *parse_expression(unsigned int start, unsigned int end);
expression *parse_3_arg_expression(unsigned int start, unsigned int end);
expression *parse_4_arg_expression(unsigned int start, unsigned int end);
expression *parse_combining_expression(unsigned int start, unsigned int end);
expression *parse_unary_expression(unsigned int start, unsigned int end);
@ -539,14 +541,102 @@ expression *test_parser::parse_primary(unsigned int start, unsigned int end)
return expr;
}
// See IEEE 1003.1 breakdown of the behavior for different parameter counts
expression *test_parser::parse_3_arg_expression(unsigned int start, unsigned int end)
{
assert(end - start == 3);
expression *result = NULL;
const token_info_t *center_token = token_for_string(arg(start + 1));
if (center_token->flags & BINARY_PRIMARY)
{
result = parse_binary_primary(start, end);
}
else if (center_token->tok == test_combine_and || center_token->tok == test_combine_or)
{
expr_ref_t left(parse_unary_expression(start, start + 1));
expr_ref_t right(parse_unary_expression(start + 2, start + 3));
if (left.get() && right.get())
{
// Transfer ownership to the vector of subjects
std::vector<token_t> combiners(1, center_token->tok);
std::vector<expression *> subjects;
subjects.push_back(left.release());
subjects.push_back(right.release());
result = new combining_expression(center_token->tok, range_t(start, end), subjects, combiners);
}
}
else
{
result = parse_unary_expression(start, end);
}
return result;
}
expression *test_parser::parse_4_arg_expression(unsigned int start, unsigned int end)
{
assert(end - start == 4);
expression *result = NULL;
token_t first_token = token_for_string(arg(start))->tok;
if (first_token == test_bang)
{
expr_ref_t subject(parse_3_arg_expression(start + 1, end));
if (subject.get())
{
result = new unary_operator(first_token, range_t(start, subject->range.end), subject);
}
}
else if (first_token == test_paren_open)
{
result = parse_parenthentical(start, end);
}
else
{
result = parse_combining_expression(start, end);
}
return result;
}
expression *test_parser::parse_expression(unsigned int start, unsigned int end)
{
if (start >= end)
{
return error(L"Missing argument at index %u", start);
}
return parse_combining_expression(start, end);
unsigned int argc = end - start;
switch (argc)
{
case 0:
assert(0); //should have been caught by the above test
return NULL;
case 1:
{
return error(L"Missing argument at index %u", start + 1);
}
case 2:
{
return parse_unary_expression(start, end);
}
case 3:
{
return parse_3_arg_expression(start, end);
}
case 4:
{
return parse_4_arg_expression(start, end);
}
default:
{
return parse_combining_expression(start, end);
}
}
}
expression *test_parser::parse_args(const wcstring_list_t &args, wcstring &err)
@ -798,18 +888,18 @@ static bool unary_primary_evaluate(test_expressions::token_t token, const wcstri
int builtin_test(parser_t &parser, wchar_t **argv)
{
using namespace test_expressions;
/* The first argument should be the name of the command ('test') */
if (! argv[0])
return BUILTIN_TEST_FAIL;
/* Whether we are invoked with bracket '[' or not */
const bool is_bracket = ! wcscmp(argv[0], L"[");
size_t argc = 0;
while (argv[argc + 1])
argc++;
/* If we're bracket, the last argument ought to be ]; we ignore it. Note that argc is the number of arguments after the command name; thus argv[argc] is the last argument. */
if (is_bracket)
{
@ -823,54 +913,57 @@ int builtin_test(parser_t &parser, wchar_t **argv)
builtin_show_error(L"[: the last argument must be ']'\n");
return BUILTIN_TEST_FAIL;
}
}
/* Collect the arguments into a list */
const wcstring_list_t args(argv + 1, argv + 1 + argc);
if (argc == 0)
switch (argc)
{
// Per 1003.1, exit false
return BUILTIN_TEST_FAIL;
}
else if (argc == 1)
{
// Per 1003.1, exit true if the arg is non-empty
return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS;
}
else
{
// Try parsing. If expr is not nil, we are responsible for deleting it.
wcstring err;
expression *expr = test_parser::parse_args(args, err);
if (! expr)
case 0:
{
#if 0
printf("Oops! test was given args:\n");
for (size_t i=0; i < argc; i++)
{
printf("\t%ls\n", args.at(i).c_str());
}
printf("and returned parse error: %ls\n", err.c_str());
#endif
builtin_show_error(err);
// Per 1003.1, exit false
return BUILTIN_TEST_FAIL;
}
else
case 1:
{
wcstring_list_t eval_errors;
bool result = expr->evaluate(eval_errors);
if (! eval_errors.empty())
// Per 1003.1, exit true if the arg is non-empty
return args.at(0).empty() ? BUILTIN_TEST_FAIL : BUILTIN_TEST_SUCCESS;
}
default:
{
// Try parsing. If expr is not nil, we are responsible for deleting it.
wcstring err;
expression *expr = test_parser::parse_args(args, err);
if (! expr)
{
printf("test returned eval errors:\n");
for (size_t i=0; i < eval_errors.size(); i++)
#if 0
printf("Oops! test was given args:\n");
for (size_t i=0; i < argc; i++)
{
printf("\t%ls\n", eval_errors.at(i).c_str());
printf("\t%ls\n", args.at(i).c_str());
}
printf("and returned parse error: %ls\n", err.c_str());
#endif
builtin_show_error(err);
return BUILTIN_TEST_FAIL;
}
else
{
wcstring_list_t eval_errors;
bool result = expr->evaluate(eval_errors);
if (! eval_errors.empty())
{
printf("test returned eval errors:\n");
for (size_t i=0; i < eval_errors.size(); i++)
{
printf("\t%ls\n", eval_errors.at(i).c_str());
}
}
delete expr;
return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL;
}
delete expr;
return result ? BUILTIN_TEST_SUCCESS : BUILTIN_TEST_FAIL;
}
}
return 1;

View file

@ -936,6 +936,10 @@ static void test_test()
/* This crashed */
assert(run_test_test(1, L"1 = 1 -a = 1"));
/* Make sure we can treat -S as a parameter instead of an operator. https://github.com/fish-shell/fish-shell/issues/601 */
assert(run_test_test(0, L"-S = -S"));
assert(run_test_test(1, L"! ! ! A"));
}
/** Testing colors */