Skip to content

Commit

Permalink
Shell: Allow control structures to appear in pipe sequences
Browse files Browse the repository at this point in the history
This makes commands like the following to be possible:
```sh
$ ls && for $(seq 10) { echo $it }
$ ls | for $(seq 1) { cat > foobar }
```
  • Loading branch information
alimpfard authored and awesomekling committed Sep 9, 2020
1 parent 7b5ead6 commit aa2df92
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 41 deletions.
33 changes: 21 additions & 12 deletions Shell/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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)));
}
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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));
}
Expand Down
5 changes: 4 additions & 1 deletion Shell/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -233,7 +234,7 @@ class CommandValue final : public Value {
}

CommandValue(Vector<String> argv)
: m_command({ move(argv), {}, true, false, true, nullptr, {} })
: m_command({ move(argv), {}, true, false, true, false, nullptr, {} })
{
}

Expand Down Expand Up @@ -410,6 +411,8 @@ class Node : public RefCounted<Node> {

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 };
Expand Down
23 changes: 11 additions & 12 deletions Shell/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 2 additions & 1 deletion Shell/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
50 changes: 35 additions & 15 deletions Shell/Shell.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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();

Expand Down

0 comments on commit aa2df92

Please sign in to comment.