From aa2df9277d2ab8a006c128e0180623fc48c76f8a Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Mon, 7 Sep 2020 20:49:53 +0430 Subject: [PATCH] Shell: Allow control structures to appear in pipe sequences This makes commands like the following to be possible: ```sh $ ls && for $(seq 10) { echo $it } $ ls | for $(seq 1) { cat > foobar } ``` --- Shell/AST.cpp | 33 ++++++++++++++++++++------------ Shell/AST.h | 5 ++++- Shell/Parser.cpp | 23 +++++++++++----------- Shell/Parser.h | 3 ++- Shell/Shell.cpp | 50 +++++++++++++++++++++++++++++++++--------------- 5 files changed, 73 insertions(+), 41 deletions(-) diff --git a/Shell/AST.cpp b/Shell/AST.cpp index ebbc783a9d..2dca0594b6 100644 --- a/Shell/AST.cpp +++ b/Shell/AST.cpp @@ -107,6 +107,18 @@ void Node::for_each_entry(RefPtr shell, Function Node::to_lazy_evaluated_commands(RefPtr shell) +{ + if (would_execute()) { + // Wrap the node in a "should immediately execute next" command. + return { + Command { {}, {}, true, false, true, true, {}, { NodeWithAction(*this, NodeWithAction::Sequence) } } + }; + } + + return run(shell)->resolve_as_commands(shell); +} + void Node::dump(int level) const { print_indented(String::format("%s at %d:%d", class_name().characters(), m_position.start_offset, m_position.end_offset), level); @@ -184,7 +196,7 @@ void And::dump(int level) const RefPtr And::run(RefPtr shell) { - auto commands = m_left->run(shell)->resolve_as_commands(shell); + auto commands = m_left->to_lazy_evaluated_commands(shell); commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And }); return create(move(commands)); } @@ -340,10 +352,7 @@ RefPtr Background::run(RefPtr shell) // as it runs the node, which means nodes likes And and Or will evaluate // all but their last subnode before yielding to this, causing a command // like `foo && bar&` to effectively be `foo && (bar&)`. - auto value = m_command->run(shell)->resolve_without_cast(shell); - ASSERT(!value->is_job()); - - auto commands = value->resolve_as_commands(shell); + auto commands = m_command->to_lazy_evaluated_commands(shell); for (auto& command : commands) command.should_wait = false; @@ -1207,8 +1216,8 @@ void Join::dump(int level) const RefPtr Join::run(RefPtr shell) { - auto left = m_left->run(shell)->resolve_as_commands(shell); - auto right = m_right->run(shell)->resolve_as_commands(shell); + auto left = m_left->to_lazy_evaluated_commands(shell); + auto right = m_right->to_lazy_evaluated_commands(shell); return create(join_commands(move(left), move(right))); } @@ -1263,7 +1272,7 @@ void Or::dump(int level) const RefPtr Or::run(RefPtr shell) { - auto commands = m_left->run(shell)->resolve_as_commands(shell); + auto commands = m_left->to_lazy_evaluated_commands(shell); commands.last().next_chain.empend(*m_right, NodeWithAction::Or); return create(move(commands)); } @@ -1315,8 +1324,8 @@ void Pipe::dump(int level) const RefPtr Pipe::run(RefPtr shell) { - auto left = m_left->run(shell)->resolve_as_commands(shell); - auto right = m_right->run(shell)->resolve_as_commands(shell); + auto left = m_left->to_lazy_evaluated_commands(shell); + auto right = m_right->to_lazy_evaluated_commands(shell); auto last_in_left = left.take_last(); auto first_in_right = right.take_first(); @@ -1512,7 +1521,7 @@ RefPtr Sequence::run(RefPtr shell) return execute_node->run(shell); } - auto left = m_left->run(shell)->resolve_as_commands(shell); + auto left = m_left->to_lazy_evaluated_commands(shell); // This could happen if a comment is next to a command. if (left.size() == 1) { auto& command = left.first(); @@ -1523,7 +1532,7 @@ RefPtr Sequence::run(RefPtr shell) if (left.last().should_wait) left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence }); else - left.append(m_right->run(shell)->resolve_as_commands(shell)); + left.append(m_right->to_lazy_evaluated_commands(shell)); return create(move(left)); } diff --git a/Shell/AST.h b/Shell/AST.h index ce8d7358b4..f336b819d9 100644 --- a/Shell/AST.h +++ b/Shell/AST.h @@ -196,6 +196,7 @@ struct Command { bool should_wait { true }; bool is_pipe_source { false }; bool should_notify_if_in_background { true }; + bool should_immediately_execute_next { false }; mutable RefPtr pipeline; Vector next_chain; @@ -233,7 +234,7 @@ public: } CommandValue(Vector argv) - : m_command({ move(argv), {}, true, false, true, nullptr, {} }) + : m_command({ move(argv), {}, true, false, true, false, nullptr, {} }) { } @@ -410,6 +411,8 @@ public: virtual RefPtr leftmost_trivial_literal() const { return nullptr; } + Vector to_lazy_evaluated_commands(RefPtr shell); + protected: Position m_position; bool m_is_syntax_error { false }; diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp index 650a78cd8d..c40944ca47 100644 --- a/Shell/Parser.cpp +++ b/Shell/Parser.cpp @@ -167,12 +167,7 @@ RefPtr Parser::parse_sequence() break; } - RefPtr first = nullptr; - if (auto control_structure = parse_control_structure()) - first = control_structure; - - if (!first) - first = parse_or_logical_sequence(); + auto first = parse_or_logical_sequence(); if (!first) return var_decls; @@ -311,23 +306,27 @@ RefPtr Parser::parse_and_logical_sequence() RefPtr Parser::parse_pipe_sequence() { auto rule_start = push_start(); - auto command = parse_command(); - if (!command) - return nullptr; + auto left = parse_control_structure(); + if (!left) { + if (auto cmd = parse_command()) + left = cmd; + else + return nullptr; + } consume_while(is_whitespace); if (peek() != '|') - return command; + return left; consume(); if (auto pipe_seq = parse_pipe_sequence()) { - return create(move(command), move(pipe_seq)); // Pipe + return create(move(left), move(pipe_seq)); // Pipe } putback(); - return command; + return left; } RefPtr Parser::parse_command() diff --git a/Shell/Parser.h b/Shell/Parser.h index 24f9e63ec4..4d20a200d4 100644 --- a/Shell/Parser.h +++ b/Shell/Parser.h @@ -107,7 +107,6 @@ toplevel :: sequence? sequence :: variable_decls? or_logical_sequence terminator sequence | variable_decls? or_logical_sequence '&' sequence - | variable_decls? control_structure terminator sequence | variable_decls? or_logical_sequence | variable_decls? terminator sequence @@ -125,6 +124,8 @@ variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '* pipe_sequence :: command '|' pipe_sequence | command + | control_structure '|' pipe_sequence + | control_structure control_structure :: for_expr | if_expr diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp index a598cffc1f..610cf1074f 100644 --- a/Shell/Shell.cpp +++ b/Shell/Shell.cpp @@ -439,7 +439,7 @@ RefPtr Shell::run_command(const AST::Command& command) } // If the command is empty, store the redirections and apply them to all later commands. - if (command.argv.is_empty()) { + if (command.argv.is_empty() && !command.should_immediately_execute_next) { m_global_redirections.append(command.redirections); return nullptr; } @@ -481,6 +481,25 @@ RefPtr Shell::run_command(const AST::Command& command) return IterationDecision::Continue; }; + auto apply_rewirings = [&] { + for (auto& rewiring : rewirings) { +#ifdef SH_DEBUG + dbgprintf("in %s<%d>, dup2(%d, %d)\n", command.argv.is_empty() ? "()" : command.argv[0].characters(), getpid(), rewiring.dest_fd, rewiring.source_fd); +#endif + int rc = dup2(rewiring.dest_fd, rewiring.source_fd); + if (rc < 0) { + perror("dup2(run)"); + return IterationDecision::Break; + } + // dest_fd is closed via the `fds` collector, but rewiring.other_pipe_end->dest_fd + // isn't yet in that collector when the first child spawns. + if (rewiring.other_pipe_end && close(rewiring.other_pipe_end->dest_fd) < 0) + perror("close other pipe end"); + } + + return IterationDecision::Continue; + }; + for (auto& redirection : m_global_redirections) { if (resolve_redirection(redirection) == IterationDecision::Break) return nullptr; @@ -491,6 +510,19 @@ RefPtr Shell::run_command(const AST::Command& command) return nullptr; } + if (command.should_immediately_execute_next) { + ASSERT(command.argv.is_empty()); + + SavedFileDescriptors fds { rewirings }; + if (apply_rewirings() == IterationDecision::Break) + return nullptr; + + for (auto& next_in_chain : command.next_chain) + run_tail(next_in_chain, 0); + + return nullptr; + } + int retval = 0; if (run_builtin(command, rewirings, retval)) { for (auto& next_in_chain : command.next_chain) @@ -524,20 +556,8 @@ RefPtr Shell::run_command(const AST::Command& command) tcsetattr(0, TCSANOW, &default_termios); - for (auto& rewiring : rewirings) { -#ifdef SH_DEBUG - dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), rewiring.dest_fd, rewiring.source_fd); -#endif - int rc = dup2(rewiring.dest_fd, rewiring.source_fd); - if (rc < 0) { - perror("dup2(run)"); - return nullptr; - } - // dest_fd is closed via the `fds` collector, but rewiring.other_pipe_end->dest_fd - // isn't yet in that collector when the first child spawns. - if (rewiring.other_pipe_end && close(rewiring.other_pipe_end->dest_fd) < 0) - perror("close other pipe end"); - } + if (apply_rewirings() == IterationDecision::Break) + return nullptr; fds.collect();