mirror of
https://github.com/RGBCube/serenity
synced 2025-05-16 20:15:07 +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
|
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);
|
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)
|
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 });
|
commands.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::And });
|
||||||
return create<CommandSequenceValue>(move(commands));
|
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
|
// 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
|
// all but their last subnode before yielding to this, causing a command
|
||||||
// like `foo && bar&` to effectively be `foo && (bar&)`.
|
// like `foo && bar&` to effectively be `foo && (bar&)`.
|
||||||
auto value = m_command->run(shell)->resolve_without_cast(shell);
|
auto commands = m_command->to_lazy_evaluated_commands(shell);
|
||||||
ASSERT(!value->is_job());
|
|
||||||
|
|
||||||
auto commands = value->resolve_as_commands(shell);
|
|
||||||
for (auto& command : commands)
|
for (auto& command : commands)
|
||||||
command.should_wait = false;
|
command.should_wait = false;
|
||||||
|
|
||||||
|
@ -1207,8 +1216,8 @@ void Join::dump(int level) const
|
||||||
|
|
||||||
RefPtr<Value> Join::run(RefPtr<Shell> shell)
|
RefPtr<Value> Join::run(RefPtr<Shell> shell)
|
||||||
{
|
{
|
||||||
auto left = m_left->run(shell)->resolve_as_commands(shell);
|
auto left = m_left->to_lazy_evaluated_commands(shell);
|
||||||
auto right = m_right->run(shell)->resolve_as_commands(shell);
|
auto right = m_right->to_lazy_evaluated_commands(shell);
|
||||||
|
|
||||||
return create<CommandSequenceValue>(join_commands(move(left), move(right)));
|
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)
|
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);
|
commands.last().next_chain.empend(*m_right, NodeWithAction::Or);
|
||||||
return create<CommandSequenceValue>(move(commands));
|
return create<CommandSequenceValue>(move(commands));
|
||||||
}
|
}
|
||||||
|
@ -1315,8 +1324,8 @@ void Pipe::dump(int level) const
|
||||||
|
|
||||||
RefPtr<Value> Pipe::run(RefPtr<Shell> shell)
|
RefPtr<Value> Pipe::run(RefPtr<Shell> shell)
|
||||||
{
|
{
|
||||||
auto left = m_left->run(shell)->resolve_as_commands(shell);
|
auto left = m_left->to_lazy_evaluated_commands(shell);
|
||||||
auto right = m_right->run(shell)->resolve_as_commands(shell);
|
auto right = m_right->to_lazy_evaluated_commands(shell);
|
||||||
|
|
||||||
auto last_in_left = left.take_last();
|
auto last_in_left = left.take_last();
|
||||||
auto first_in_right = right.take_first();
|
auto first_in_right = right.take_first();
|
||||||
|
@ -1512,7 +1521,7 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> shell)
|
||||||
return execute_node->run(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.
|
// This could happen if a comment is next to a command.
|
||||||
if (left.size() == 1) {
|
if (left.size() == 1) {
|
||||||
auto& command = left.first();
|
auto& command = left.first();
|
||||||
|
@ -1523,7 +1532,7 @@ RefPtr<Value> Sequence::run(RefPtr<Shell> shell)
|
||||||
if (left.last().should_wait)
|
if (left.last().should_wait)
|
||||||
left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence });
|
left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence });
|
||||||
else
|
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));
|
return create<CommandSequenceValue>(move(left));
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ struct Command {
|
||||||
bool should_wait { true };
|
bool should_wait { true };
|
||||||
bool is_pipe_source { false };
|
bool is_pipe_source { false };
|
||||||
bool should_notify_if_in_background { true };
|
bool should_notify_if_in_background { true };
|
||||||
|
bool should_immediately_execute_next { false };
|
||||||
|
|
||||||
mutable RefPtr<Pipeline> pipeline;
|
mutable RefPtr<Pipeline> pipeline;
|
||||||
Vector<NodeWithAction> next_chain;
|
Vector<NodeWithAction> next_chain;
|
||||||
|
@ -233,7 +234,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandValue(Vector<String> argv)
|
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; }
|
virtual RefPtr<Node> leftmost_trivial_literal() const { return nullptr; }
|
||||||
|
|
||||||
|
Vector<Command> to_lazy_evaluated_commands(RefPtr<Shell> shell);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Position m_position;
|
Position m_position;
|
||||||
bool m_is_syntax_error { false };
|
bool m_is_syntax_error { false };
|
||||||
|
|
|
@ -167,12 +167,7 @@ RefPtr<AST::Node> Parser::parse_sequence()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<AST::Node> first = nullptr;
|
auto first = parse_or_logical_sequence();
|
||||||
if (auto control_structure = parse_control_structure())
|
|
||||||
first = control_structure;
|
|
||||||
|
|
||||||
if (!first)
|
|
||||||
first = parse_or_logical_sequence();
|
|
||||||
|
|
||||||
if (!first)
|
if (!first)
|
||||||
return var_decls;
|
return var_decls;
|
||||||
|
@ -311,23 +306,27 @@ RefPtr<AST::Node> Parser::parse_and_logical_sequence()
|
||||||
RefPtr<AST::Node> Parser::parse_pipe_sequence()
|
RefPtr<AST::Node> Parser::parse_pipe_sequence()
|
||||||
{
|
{
|
||||||
auto rule_start = push_start();
|
auto rule_start = push_start();
|
||||||
auto command = parse_command();
|
auto left = parse_control_structure();
|
||||||
if (!command)
|
if (!left) {
|
||||||
return nullptr;
|
if (auto cmd = parse_command())
|
||||||
|
left = cmd;
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
consume_while(is_whitespace);
|
consume_while(is_whitespace);
|
||||||
|
|
||||||
if (peek() != '|')
|
if (peek() != '|')
|
||||||
return command;
|
return left;
|
||||||
|
|
||||||
consume();
|
consume();
|
||||||
|
|
||||||
if (auto pipe_seq = parse_pipe_sequence()) {
|
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();
|
putback();
|
||||||
return command;
|
return left;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<AST::Node> Parser::parse_command()
|
RefPtr<AST::Node> Parser::parse_command()
|
||||||
|
|
|
@ -107,7 +107,6 @@ toplevel :: sequence?
|
||||||
|
|
||||||
sequence :: variable_decls? or_logical_sequence terminator sequence
|
sequence :: variable_decls? or_logical_sequence terminator sequence
|
||||||
| variable_decls? or_logical_sequence '&' sequence
|
| variable_decls? or_logical_sequence '&' sequence
|
||||||
| variable_decls? control_structure terminator sequence
|
|
||||||
| variable_decls? or_logical_sequence
|
| variable_decls? or_logical_sequence
|
||||||
| variable_decls? terminator sequence
|
| variable_decls? terminator sequence
|
||||||
|
|
||||||
|
@ -125,6 +124,8 @@ variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
|
||||||
|
|
||||||
pipe_sequence :: command '|' pipe_sequence
|
pipe_sequence :: command '|' pipe_sequence
|
||||||
| command
|
| command
|
||||||
|
| control_structure '|' pipe_sequence
|
||||||
|
| control_structure
|
||||||
|
|
||||||
control_structure :: for_expr
|
control_structure :: for_expr
|
||||||
| if_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 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);
|
m_global_redirections.append(command.redirections);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -481,6 +481,25 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
|
||||||
return IterationDecision::Continue;
|
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) {
|
for (auto& redirection : m_global_redirections) {
|
||||||
if (resolve_redirection(redirection) == IterationDecision::Break)
|
if (resolve_redirection(redirection) == IterationDecision::Break)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -491,6 +510,19 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
|
||||||
return nullptr;
|
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;
|
int retval = 0;
|
||||||
if (run_builtin(command, rewirings, retval)) {
|
if (run_builtin(command, rewirings, retval)) {
|
||||||
for (auto& next_in_chain : command.next_chain)
|
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);
|
tcsetattr(0, TCSANOW, &default_termios);
|
||||||
|
|
||||||
for (auto& rewiring : rewirings) {
|
if (apply_rewirings() == IterationDecision::Break)
|
||||||
#ifdef SH_DEBUG
|
return nullptr;
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
fds.collect();
|
fds.collect();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue