diff --git a/builtin_test.cpp b/builtin_test.cpp index a130ab993..3ed79a298 100644 --- a/builtin_test.cpp +++ b/builtin_test.cpp @@ -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 combiners(1, center_token->tok); + std::vector 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; diff --git a/fish_tests.cpp b/fish_tests.cpp index 07ad1dc7b..33b2fdb70 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -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 */