Przeglądaj źródła

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 }
```
AnotherTest 4 lat temu
rodzic
commit
aa2df9277d
5 zmienionych plików z 73 dodań i 41 usunięć
  1. 21 12
      Shell/AST.cpp
  2. 4 1
      Shell/AST.h
  3. 11 12
      Shell/Parser.cpp
  4. 2 1
      Shell/Parser.h
  5. 35 15
      Shell/Shell.cpp

+ 21 - 12
Shell/AST.cpp

@@ -107,6 +107,18 @@ void Node::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(RefPtr
     }
 }
 
+Vector<Command> Node::to_lazy_evaluated_commands(RefPtr<Shell> 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<Value> And::run(RefPtr<Shell> 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<CommandSequenceValue>(move(commands));
 }
@@ -340,10 +352,7 @@ RefPtr<Value> Background::run(RefPtr<Shell> 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<Value> Join::run(RefPtr<Shell> 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<CommandSequenceValue>(join_commands(move(left), move(right)));
 }
@@ -1263,7 +1272,7 @@ void Or::dump(int level) const
 
 RefPtr<Value> Or::run(RefPtr<Shell> 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<CommandSequenceValue>(move(commands));
 }
@@ -1315,8 +1324,8 @@ void Pipe::dump(int level) const
 
 RefPtr<Value> Pipe::run(RefPtr<Shell> 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<Value> Sequence::run(RefPtr<Shell> 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<Value> Sequence::run(RefPtr<Shell> 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<CommandSequenceValue>(move(left));
 }

+ 4 - 1
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> pipeline;
     Vector<NodeWithAction> next_chain;
@@ -233,7 +234,7 @@ public:
     }
 
     CommandValue(Vector<String> 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<Node> leftmost_trivial_literal() const { return nullptr; }
 
+    Vector<Command> to_lazy_evaluated_commands(RefPtr<Shell> shell);
+
 protected:
     Position m_position;
     bool m_is_syntax_error { false };

+ 11 - 12
Shell/Parser.cpp

@@ -167,12 +167,7 @@ RefPtr<AST::Node> Parser::parse_sequence()
         break;
     }
 
-    RefPtr<AST::Node> 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<AST::Node> Parser::parse_and_logical_sequence()
 RefPtr<AST::Node> 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<AST::Pipe>(move(command), move(pipe_seq)); // Pipe
+        return create<AST::Pipe>(move(left), move(pipe_seq)); // Pipe
     }
 
     putback();
-    return command;
+    return left;
 }
 
 RefPtr<AST::Node> Parser::parse_command()

+ 2 - 1
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

+ 35 - 15
Shell/Shell.cpp

@@ -439,7 +439,7 @@ RefPtr<Job> 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<Job> 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() ? "(<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<Job> 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<Job> 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();