Rework 'and' and 'or' to be "job decorators"

This promotes "and" and "or" from a type of statement to "job
decorators," as a possible prefix on a job. The point is to rationalize
how they interact with && and ||.

In the new world 'and' and 'or' apply to a entire job conjunction, i.e.
they have "lower precedence." Example:

if [ $age -ge 0 ] && [ $age -le 18 ]
   or [ $age -ge 75 ] && [ $age -le 100 ]
   echo "Child or senior"
end
This commit is contained in:
ridiculousfish
2018-03-02 18:09:16 -08:00
parent e1dafeab01
commit 357d3b8c6d
12 changed files with 151 additions and 148 deletions

View File

@@ -1072,17 +1072,15 @@ static bool detect_errors_in_backgrounded_job(tnode_t<grammar::job> job,
"Expected first job to be the node we found");
(void)first_jconj;
// Try getting the next job as a boolean statement.
tnode_t<g::job> next_job = jlist.next_in_list<g::job_conjunction>().child<0>();
tnode_t<g::statement> next_stmt = next_job.child<0>();
if (auto bool_stmt = next_stmt.try_get_child<g::boolean_statement, 0>()) {
// Try getting the next job's decorator.
if (auto next_job_dec = jlist.next_in_list<g::job_decorator>()) {
// The next job is indeed a boolean statement.
parse_bool_statement_type_t bool_type = bool_statement_type(bool_stmt);
if (bool_type == parse_bool_and) { // this is not allowed
errored = append_syntax_error(parse_errors, bool_stmt.source_range()->start,
parse_bool_statement_type_t bool_type = bool_statement_type(next_job_dec);
if (bool_type == parse_bool_and) {
errored = append_syntax_error(parse_errors, next_job_dec.source_range()->start,
BOOL_AFTER_BACKGROUND_ERROR_MSG, L"and");
} else if (bool_type == parse_bool_or) { // this is not allowed
errored = append_syntax_error(parse_errors, bool_stmt.source_range()->start,
} else if (bool_type == parse_bool_or) {
errored = append_syntax_error(parse_errors, next_job_dec.source_range()->start,
BOOL_AFTER_BACKGROUND_ERROR_MSG, L"or");
}
}
@@ -1100,7 +1098,8 @@ static bool detect_errors_in_plain_statement(const wcstring &buff_src,
// In a few places below, we want to know if we are in a pipeline.
tnode_t<statement> st = pst.try_get_parent<decorated_statement>().try_get_parent<statement>();
const bool is_in_pipeline = statement_is_in_pipeline(st, true /* count first */);
pipeline_position_t pipe_pos = get_pipeline_position(st);
bool is_in_pipeline = (pipe_pos != pipeline_position_t::none);
// We need to know the decoration.
const enum parse_statement_decoration_t decoration = get_decoration(pst);
@@ -1110,6 +1109,19 @@ static bool detect_errors_in_plain_statement(const wcstring &buff_src,
errored = append_syntax_error(parse_errors, source_start, EXEC_ERR_MSG, L"exec");
}
// This is a somewhat stale check that 'and' and 'or' are not in pipelines, except at the
// beginning. We can't disallow them as commands entirely because we need to support 'and
// --help', etc.
if (pipe_pos == pipeline_position_t::subsequent) {
// check if our command is 'and' or 'or'. This is very clumsy; we don't catch e.g. quoted
// commands.
wcstring command = pst.child<0>().get_source(buff_src);
if (command == L"and" || command == L"or") {
errored =
append_syntax_error(parse_errors, source_start, EXEC_ERR_MSG, command.c_str());
}
}
if (maybe_t<wcstring> mcommand = command_for_plain_statement(pst, buff_src)) {
wcstring command = std::move(*mcommand);
// Check that we can expand the command.
@@ -1254,16 +1266,9 @@ parser_test_error_bits_t parse_util_detect_errors(const wcstring &buff_src,
has_unclosed_block = true;
} else if (node.type == symbol_statement && !node.has_source()) {
// Check for a statement without source in a pipeline, i.e. unterminated pipeline.
has_unclosed_pipe |= statement_is_in_pipeline({&node_tree, &node}, false);
} else if (node.type == symbol_boolean_statement) {
// 'or' and 'and' can be in a pipeline, as long as they're first.
tnode_t<g::boolean_statement> gbs{&node_tree, &node};
parse_bool_statement_type_t type = bool_statement_type(gbs);
if ((type == parse_bool_and || type == parse_bool_or) &&
statement_is_in_pipeline(gbs.try_get_parent<g::statement>(),
false /* don't count first */)) {
errored = append_syntax_error(&parse_errors, node.source_start, EXEC_ERR_MSG,
(type == parse_bool_and) ? L"and" : L"or");
auto pipe_pos = get_pipeline_position({&node_tree, &node});
if (pipe_pos != pipeline_position_t::none) {
has_unclosed_pipe = true;
}
} else if (node.type == symbol_argument) {
tnode_t<g::argument> arg{&node_tree, &node};