mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:42:44 +00:00 
			
		
		
		
	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 }
```
			
			
This commit is contained in:
		
							parent
							
								
									7b5ead64a5
								
							
						
					
					
						commit
						aa2df9277d
					
				
					 5 changed files with 73 additions and 41 deletions
				
			
		|  | @ -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)); | ||||
| } | ||||
|  |  | |||
|  | @ -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 }; | ||||
|  |  | |||
|  | @ -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) | ||||
|     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() | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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)"); | ||||
|         if (apply_rewirings() == IterationDecision::Break) | ||||
|             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"); | ||||
|         } | ||||
| 
 | ||||
|         fds.collect(); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 AnotherTest
						AnotherTest