diff options
Diffstat (limited to 'Shell')
-rw-r--r-- | Shell/AST.cpp | 2996 | ||||
-rw-r--r-- | Shell/AST.h | 1324 | ||||
-rw-r--r-- | Shell/Builtin.cpp | 929 | ||||
-rw-r--r-- | Shell/CMakeLists.txt | 19 | ||||
-rw-r--r-- | Shell/Execution.h | 65 | ||||
-rw-r--r-- | Shell/Formatter.cpp | 755 | ||||
-rw-r--r-- | Shell/Formatter.h | 129 | ||||
-rw-r--r-- | Shell/Forward.h | 85 | ||||
-rw-r--r-- | Shell/Job.cpp | 124 | ||||
-rw-r--r-- | Shell/Job.h | 136 | ||||
-rw-r--r-- | Shell/NodeVisitor.cpp | 245 | ||||
-rw-r--r-- | Shell/NodeVisitor.h | 76 | ||||
-rw-r--r-- | Shell/Parser.cpp | 1555 | ||||
-rw-r--r-- | Shell/Parser.h | 262 | ||||
-rw-r--r-- | Shell/Shell.cpp | 1939 | ||||
-rw-r--r-- | Shell/Shell.h | 351 | ||||
-rw-r--r-- | Shell/Tests/backgrounding.sh | 55 | ||||
-rw-r--r-- | Shell/Tests/brace-exp.sh | 21 | ||||
-rw-r--r-- | Shell/Tests/builtin-redir.sh | 17 | ||||
-rw-r--r-- | Shell/Tests/control-structure-as-command.sh | 39 | ||||
-rw-r--r-- | Shell/Tests/function.sh | 41 | ||||
-rw-r--r-- | Shell/Tests/if.sh | 50 | ||||
-rw-r--r-- | Shell/Tests/loop.sh | 77 | ||||
-rw-r--r-- | Shell/Tests/match.sh | 83 | ||||
-rw-r--r-- | Shell/Tests/sigpipe.sh | 13 | ||||
-rw-r--r-- | Shell/Tests/special-vars.sh | 15 | ||||
-rw-r--r-- | Shell/Tests/subshell.sh | 28 | ||||
-rw-r--r-- | Shell/Tests/valid.sh | 92 | ||||
-rw-r--r-- | Shell/main.cpp | 194 |
29 files changed, 0 insertions, 11715 deletions
diff --git a/Shell/AST.cpp b/Shell/AST.cpp deleted file mode 100644 index 992452f503..0000000000 --- a/Shell/AST.cpp +++ /dev/null @@ -1,2996 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "AST.h" -#include "Shell.h" -#include <AK/MemoryStream.h> -#include <AK/ScopeGuard.h> -#include <AK/String.h> -#include <AK/StringBuilder.h> -#include <AK/URL.h> -#include <LibCore/EventLoop.h> -#include <LibCore/File.h> -#include <signal.h> - -//#define EXECUTE_DEBUG - -void AK::Formatter<Shell::AST::Command>::format(FormatBuilder& builder, const Shell::AST::Command& value) -{ - if (m_sign_mode != FormatBuilder::SignMode::Default) - ASSERT_NOT_REACHED(); - if (m_alternative_form) - ASSERT_NOT_REACHED(); - if (m_zero_pad) - ASSERT_NOT_REACHED(); - if (m_mode != Mode::Default && m_mode != Mode::String) - ASSERT_NOT_REACHED(); - if (m_width.has_value()) - ASSERT_NOT_REACHED(); - if (m_precision.has_value()) - ASSERT_NOT_REACHED(); - - if (value.argv.is_empty()) { - builder.put_literal("(ShellInternal)"); - } else { - bool first = true; - for (auto& arg : value.argv) { - if (!first) - builder.put_literal(" "); - first = false; - builder.put_literal(arg); - } - } - - for (auto& redir : value.redirections) { - builder.put_padding(' ', 1); - if (redir.is_path_redirection()) { - auto path_redir = (const Shell::AST::PathRedirection*)&redir; - builder.put_i64(path_redir->fd); - switch (path_redir->direction) { - case Shell::AST::PathRedirection::Read: - builder.put_literal("<"); - break; - case Shell::AST::PathRedirection::Write: - builder.put_literal(">"); - break; - case Shell::AST::PathRedirection::WriteAppend: - builder.put_literal(">>"); - break; - case Shell::AST::PathRedirection::ReadWrite: - builder.put_literal("<>"); - break; - } - builder.put_literal(path_redir->path); - } else if (redir.is_fd_redirection()) { - auto* fdredir = (const Shell::AST::FdRedirection*)&redir; - builder.put_i64(fdredir->new_fd); - builder.put_literal(">"); - builder.put_i64(fdredir->old_fd); - } else if (redir.is_close_redirection()) { - auto close_redir = (const Shell::AST::CloseRedirection*)&redir; - builder.put_i64(close_redir->fd); - builder.put_literal(">&-"); - } else { - ASSERT_NOT_REACHED(); - } - } - - if (!value.next_chain.is_empty()) { - for (auto& command : value.next_chain) { - switch (command.action) { - case Shell::AST::NodeWithAction::And: - builder.put_literal(" && "); - break; - case Shell::AST::NodeWithAction::Or: - builder.put_literal(" || "); - break; - case Shell::AST::NodeWithAction::Sequence: - builder.put_literal("; "); - break; - } - - builder.put_literal("("); - builder.put_literal(command.node->class_name()); - builder.put_literal("...)"); - } - } - if (!value.should_wait) - builder.put_literal("&"); -} - -namespace Shell::AST { - -static inline void print_indented(const String& str, int indent) -{ - for (auto i = 0; i < indent; ++i) - dbgprintf(" "); - dbgprintf("%s\n", str.characters()); -} - -static inline Optional<Position> merge_positions(const Optional<Position>& left, const Optional<Position>& right) -{ - if (!left.has_value()) - return right; - - if (!right.has_value()) - return left; - - return Position { - .start_offset = left->start_offset, - .end_offset = right->end_offset, - .start_line = left->start_line, - .end_line = right->end_line, - }; -} - -static inline Vector<Command> join_commands(Vector<Command> left, Vector<Command> right) -{ - Command command; - - auto last_in_left = left.take_last(); - auto first_in_right = right.take_first(); - - command.argv.append(last_in_left.argv); - command.argv.append(first_in_right.argv); - - command.redirections.append(last_in_left.redirections); - command.redirections.append(first_in_right.redirections); - - command.should_wait = first_in_right.should_wait && last_in_left.should_wait; - command.is_pipe_source = first_in_right.is_pipe_source; - command.should_notify_if_in_background = first_in_right.should_notify_if_in_background || last_in_left.should_notify_if_in_background; - - command.position = merge_positions(last_in_left.position, first_in_right.position); - - Vector<Command> commands; - commands.append(left); - commands.append(command); - commands.append(right); - - return commands; -} - -void Node::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(NonnullRefPtr<Value>)> callback) -{ - auto value = run(shell)->resolve_without_cast(shell); - if (value->is_job()) { - callback(value); - return; - } - - if (value->is_list_without_resolution()) { - auto list = value->resolve_without_cast(shell); - for (auto& element : static_cast<ListValue*>(list.ptr())->values()) { - if (callback(element) == IterationDecision::Break) - break; - } - return; - } - - auto list = value->resolve_as_list(shell); - for (auto& element : list) { - if (callback(create<StringValue>(move(element))) == IterationDecision::Break) - break; - } -} - -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) }, position() } - }; - } - - return run(shell)->resolve_as_commands(shell); -} - -void Node::dump(int level) const -{ - print_indented(String::formatted("{} at {}:{} (from {}.{} to {}.{})", - class_name().characters(), - m_position.start_offset, - m_position.end_offset, - m_position.start_line.line_number, - m_position.start_line.line_column, - m_position.end_line.line_number, - m_position.end_line.line_column), - level); -} - -Node::Node(Position position) - : m_position(position) -{ -} - -Vector<Line::CompletionSuggestion> Node::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (matching_node) { - if (matching_node->is_bareword()) { - auto* node = static_cast<BarewordLiteral*>(matching_node.ptr()); - auto corrected_offset = find_offset_into_node(node->text(), offset - matching_node->position().start_offset); - - if (corrected_offset > node->text().length()) - return {}; - auto& text = node->text(); - - // If the literal isn't an option, treat it as a path. - if (!(text.starts_with("-") || text == "--" || text == "-")) - return shell.complete_path("", text, corrected_offset); - - // If the literal is an option, we have to know the program name - // should we have no way to get that, bail early. - - if (!hit_test_result.closest_command_node) - return {}; - - auto program_name_node = hit_test_result.closest_command_node->leftmost_trivial_literal(); - if (!program_name_node) - return {}; - - String program_name; - if (program_name_node->is_bareword()) - program_name = static_cast<BarewordLiteral*>(program_name_node.ptr())->text(); - else - program_name = static_cast<StringLiteral*>(program_name_node.ptr())->text(); - - return shell.complete_option(program_name, text, corrected_offset); - } - return {}; - } - auto result = hit_test_position(offset); - if (!result.matching_node) - return {}; - auto node = result.matching_node; - if (node->is_bareword() || node != result.closest_node_with_semantic_meaning) - node = result.closest_node_with_semantic_meaning; - - if (!node) - return {}; - - return node->complete_for_editor(shell, offset, result); -} - -Vector<Line::CompletionSuggestion> Node::complete_for_editor(Shell& shell, size_t offset) -{ - return Node::complete_for_editor(shell, offset, { nullptr, nullptr, nullptr }); -} - -Node::~Node() -{ -} - -void And::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> And::run(RefPtr<Shell> 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)); -} - -void And::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - metadata.is_first_in_list = true; - m_left->highlight_in_editor(editor, shell, metadata); - m_right->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult And::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (result.matching_node) { - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; - } - - result = m_right->hit_test_position(offset); - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; -} - -And::And(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position and_position) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) - , m_and_position(and_position) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -And::~And() -{ -} - -void ListConcatenate::dump(int level) const -{ - Node::dump(level); - for (auto& element : m_list) - element->dump(level + 1); -} - -RefPtr<Value> ListConcatenate::run(RefPtr<Shell> shell) -{ - RefPtr<Value> result = nullptr; - - for (auto& element : m_list) { - if (!result) { - result = create<ListValue>({ element->run(shell)->resolve_without_cast(shell) }); - continue; - } - auto element_value = element->run(shell)->resolve_without_cast(shell); - - if (result->is_command() || element_value->is_command()) { - auto joined_commands = join_commands(result->resolve_as_commands(shell), element_value->resolve_as_commands(shell)); - - if (joined_commands.size() == 1) { - auto& command = joined_commands[0]; - command.position = position(); - result = create<CommandValue>(command); - } else { - result = create<CommandSequenceValue>(move(joined_commands)); - } - } else { - NonnullRefPtrVector<Value> values; - - if (result->is_list_without_resolution()) { - values.append(static_cast<ListValue*>(result.ptr())->values()); - } else { - for (auto& result : result->resolve_as_list(shell)) - values.append(create<StringValue>(result)); - } - - values.append(element_value); - - result = create<ListValue>(move(values)); - } - } - if (!result) - return create<ListValue>({}); - - return result; -} - -void ListConcatenate::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - auto first = metadata.is_first_in_list; - metadata.is_first_in_list = false; - - metadata.is_first_in_list = first; - for (auto& element : m_list) { - element->highlight_in_editor(editor, shell, metadata); - metadata.is_first_in_list = false; - } -} - -HitTestResult ListConcatenate::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - bool first = true; - for (auto& element : m_list) { - auto result = element->hit_test_position(offset); - if (!result.closest_node_with_semantic_meaning && !first) - result.closest_node_with_semantic_meaning = this; - if (result.matching_node) - return result; - first = false; - } - - return {}; -} - -RefPtr<Node> ListConcatenate::leftmost_trivial_literal() const -{ - if (m_list.is_empty()) - return nullptr; - - return m_list.first()->leftmost_trivial_literal(); -} - -ListConcatenate::ListConcatenate(Position position, Vector<NonnullRefPtr<Node>> list) - : Node(move(position)) - , m_list(move(list)) -{ - for (auto& element : m_list) { - if (element->is_syntax_error()) { - set_is_syntax_error(element->syntax_error_node()); - break; - } - } -} - -ListConcatenate::~ListConcatenate() -{ -} - -void Background::dump(int level) const -{ - Node::dump(level); - m_command->dump(level + 1); -} - -RefPtr<Value> Background::run(RefPtr<Shell> shell) -{ - auto commands = m_command->to_lazy_evaluated_commands(shell); - for (auto& command : commands) - command.should_wait = false; - - return create<CommandSequenceValue>(move(commands)); -} - -void Background::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_command->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Background::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - return m_command->hit_test_position(offset); -} - -Background::Background(Position position, NonnullRefPtr<Node> command) - : Node(move(position)) - , m_command(move(command)) -{ - if (m_command->is_syntax_error()) - set_is_syntax_error(m_command->syntax_error_node()); -} - -Background::~Background() -{ -} - -void BarewordLiteral::dump(int level) const -{ - Node::dump(level); - print_indented(m_text, level + 1); -} - -RefPtr<Value> BarewordLiteral::run(RefPtr<Shell>) -{ - return create<StringValue>(m_text); -} - -void BarewordLiteral::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - if (metadata.is_first_in_list) { - if (shell.is_runnable(m_text)) { - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Bold }); - } else { - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Red) }); - } - - return; - } - - if (m_text.starts_with('-')) { - if (m_text == "--") { - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Green) }); - return; - } - if (m_text == "-") - return; - - if (m_text.starts_with("--")) { - auto index = m_text.index_of("=").value_or(m_text.length() - 1) + 1; - editor.stylize({ m_position.start_offset, m_position.start_offset + index }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) }); - } else { - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) }); - } - } - if (Core::File::exists(m_text)) { - auto realpath = shell.resolve_path(m_text); - auto url = URL::create_with_file_protocol(realpath); - url.set_host(shell.hostname); - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Hyperlink(url.to_string()) }); - } -} - -BarewordLiteral::BarewordLiteral(Position position, String text) - : Node(move(position)) - , m_text(move(text)) -{ -} - -BarewordLiteral::~BarewordLiteral() -{ -} - -void BraceExpansion::dump(int level) const -{ - Node::dump(level); - for (auto& entry : m_entries) - entry.dump(level + 1); -} - -RefPtr<Value> BraceExpansion::run(RefPtr<Shell> shell) -{ - NonnullRefPtrVector<Value> values; - for (auto& entry : m_entries) { - auto value = entry.run(shell); - if (value) - values.append(value.release_nonnull()); - } - - return create<ListValue>(move(values)); -} - -HitTestResult BraceExpansion::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - for (auto& entry : m_entries) { - auto result = entry.hit_test_position(offset); - if (result.matching_node) { - if (!result.closest_command_node) - result.closest_command_node = &entry; - return result; - } - } - - return {}; -} - -void BraceExpansion::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - for (auto& entry : m_entries) { - entry.highlight_in_editor(editor, shell, metadata); - metadata.is_first_in_list = false; - } -} - -BraceExpansion::BraceExpansion(Position position, NonnullRefPtrVector<Node> entries) - : Node(move(position)) - , m_entries(move(entries)) -{ - for (auto& entry : m_entries) { - if (entry.is_syntax_error()) { - set_is_syntax_error(entry.syntax_error_node()); - break; - } - } -} - -BraceExpansion::~BraceExpansion() -{ -} - -void CastToCommand::dump(int level) const -{ - Node::dump(level); - m_inner->dump(level + 1); -} - -RefPtr<Value> CastToCommand::run(RefPtr<Shell> shell) -{ - if (m_inner->is_command()) - return m_inner->run(shell); - - auto value = m_inner->run(shell)->resolve_without_cast(shell); - if (value->is_command()) - return value; - - auto argv = value->resolve_as_list(shell); - return create<CommandValue>(move(argv), position()); -} - -void CastToCommand::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_inner->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult CastToCommand::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_inner->hit_test_position(offset); - if (!result.closest_node_with_semantic_meaning) - result.closest_node_with_semantic_meaning = this; - return result; -} - -Vector<Line::CompletionSuggestion> CastToCommand::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (!matching_node || !matching_node->is_bareword()) - return {}; - - auto corrected_offset = offset - matching_node->position().start_offset; - auto* node = static_cast<BarewordLiteral*>(matching_node.ptr()); - - if (corrected_offset > node->text().length()) - return {}; - - return shell.complete_program_name(node->text(), corrected_offset); -} - -RefPtr<Node> CastToCommand::leftmost_trivial_literal() const -{ - return m_inner->leftmost_trivial_literal(); -} - -CastToCommand::CastToCommand(Position position, NonnullRefPtr<Node> inner) - : Node(move(position)) - , m_inner(move(inner)) -{ - if (m_inner->is_syntax_error()) - set_is_syntax_error(m_inner->syntax_error_node()); -} - -CastToCommand::~CastToCommand() -{ -} - -void CastToList::dump(int level) const -{ - Node::dump(level); - if (m_inner) - m_inner->dump(level + 1); - else - print_indented("(empty)", level + 1); -} - -RefPtr<Value> CastToList::run(RefPtr<Shell> shell) -{ - if (!m_inner) - return create<ListValue>({}); - - auto inner_value = m_inner->run(shell)->resolve_without_cast(shell); - - if (inner_value->is_command() || inner_value->is_list()) - return inner_value; - - auto values = inner_value->resolve_as_list(shell); - NonnullRefPtrVector<Value> cast_values; - for (auto& value : values) - cast_values.append(create<StringValue>(value)); - - return create<ListValue>(cast_values); -} - -void CastToList::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - if (m_inner) - m_inner->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult CastToList::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - if (!m_inner) - return {}; - - return m_inner->hit_test_position(offset); -} - -RefPtr<Node> CastToList::leftmost_trivial_literal() const -{ - return m_inner->leftmost_trivial_literal(); -} - -CastToList::CastToList(Position position, RefPtr<Node> inner) - : Node(move(position)) - , m_inner(move(inner)) -{ - if (m_inner && m_inner->is_syntax_error()) - set_is_syntax_error(m_inner->syntax_error_node()); -} - -CastToList::~CastToList() -{ -} - -void CloseFdRedirection::dump(int level) const -{ - Node::dump(level); - print_indented(String::format("%d -> Close", m_fd), level); -} - -RefPtr<Value> CloseFdRedirection::run(RefPtr<Shell>) -{ - Command command; - command.position = position(); - command.redirections.append(adopt(*new CloseRedirection(m_fd))); - return create<CommandValue>(move(command)); -} - -void CloseFdRedirection::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset - 1 }, { Line::Style::Foreground(0x87, 0x9b, 0xcd) }); // 25% Darkened Periwinkle - editor.stylize({ m_position.end_offset - 1, m_position.end_offset }, { Line::Style::Foreground(0xff, 0x7e, 0x00) }); // Amber -} - -CloseFdRedirection::CloseFdRedirection(Position position, int fd) - : Node(move(position)) - , m_fd(fd) -{ -} - -CloseFdRedirection::~CloseFdRedirection() -{ -} - -void CommandLiteral::dump(int level) const -{ - Node::dump(level); - print_indented("(Generated command literal)", level + 1); -} - -RefPtr<Value> CommandLiteral::run(RefPtr<Shell>) -{ - return create<CommandValue>(m_command); -} - -CommandLiteral::CommandLiteral(Position position, Command command) - : Node(move(position)) - , m_command(move(command)) -{ -} - -CommandLiteral::~CommandLiteral() -{ -} - -void Comment::dump(int level) const -{ - Node::dump(level); - print_indented(m_text, level + 1); -} - -RefPtr<Value> Comment::run(RefPtr<Shell>) -{ - return create<ListValue>({}); -} - -void Comment::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(150, 150, 150) }); // Light gray -} - -Comment::Comment(Position position, String text) - : Node(move(position)) - , m_text(move(text)) -{ -} - -Comment::~Comment() -{ -} - -void ContinuationControl::dump(int level) const -{ - Node::dump(level); - print_indented(m_kind == Continue ? "(Continue)" : "(Break)", level + 1); -} - -RefPtr<Value> ContinuationControl::run(RefPtr<Shell> shell) -{ - if (m_kind == Break) - shell->raise_error(Shell::ShellError::InternalControlFlowBreak, {}, position()); - else if (m_kind == Continue) - shell->raise_error(Shell::ShellError::InternalControlFlowContinue, {}, position()); - else - ASSERT_NOT_REACHED(); - return create<ListValue>({}); -} - -void ContinuationControl::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); -} - -void DoubleQuotedString::dump(int level) const -{ - Node::dump(level); - m_inner->dump(level + 1); -} - -RefPtr<Value> DoubleQuotedString::run(RefPtr<Shell> shell) -{ - StringBuilder builder; - auto values = m_inner->run(shell)->resolve_as_list(shell); - - builder.join("", values); - - return create<StringValue>(builder.to_string()); -} - -void DoubleQuotedString::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }; - if (metadata.is_first_in_list) - style.unify_with({ Line::Style::Bold }); - - editor.stylize({ m_position.start_offset, m_position.end_offset }, style); - metadata.is_first_in_list = false; - m_inner->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult DoubleQuotedString::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - return m_inner->hit_test_position(offset); -} - -DoubleQuotedString::DoubleQuotedString(Position position, RefPtr<Node> inner) - : Node(move(position)) - , m_inner(move(inner)) -{ - if (m_inner->is_syntax_error()) - set_is_syntax_error(m_inner->syntax_error_node()); -} - -DoubleQuotedString::~DoubleQuotedString() -{ -} - -void DynamicEvaluate::dump(int level) const -{ - Node::dump(level); - m_inner->dump(level + 1); -} - -RefPtr<Value> DynamicEvaluate::run(RefPtr<Shell> shell) -{ - auto result = m_inner->run(shell)->resolve_without_cast(shell); - // Dynamic Evaluation behaves differently between strings and lists. - // Strings are treated as variables, and Lists are treated as commands. - if (result->is_string()) { - auto name_part = result->resolve_as_list(shell); - ASSERT(name_part.size() == 1); - return create<SimpleVariableValue>(name_part[0]); - } - - // If it's anything else, we're just gonna cast it to a list. - auto list = result->resolve_as_list(shell); - return create<CommandValue>(move(list), position()); -} - -void DynamicEvaluate::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - m_inner->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult DynamicEvaluate::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - return m_inner->hit_test_position(offset); -} - -DynamicEvaluate::DynamicEvaluate(Position position, NonnullRefPtr<Node> inner) - : Node(move(position)) - , m_inner(move(inner)) -{ - if (m_inner->is_syntax_error()) - set_is_syntax_error(m_inner->syntax_error_node()); -} - -DynamicEvaluate::~DynamicEvaluate() -{ -} - -void Fd2FdRedirection::dump(int level) const -{ - Node::dump(level); - print_indented(String::format("%d -> %d", m_old_fd, m_new_fd), level); -} - -RefPtr<Value> Fd2FdRedirection::run(RefPtr<Shell>) -{ - Command command; - command.position = position(); - command.redirections.append(FdRedirection::create(m_new_fd, m_old_fd, Rewiring::Close::None)); - return create<CommandValue>(move(command)); -} - -void Fd2FdRedirection::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(0x87, 0x9b, 0xcd) }); // 25% Darkened Periwinkle -} - -Fd2FdRedirection::Fd2FdRedirection(Position position, int src, int dst) - : Node(move(position)) - , m_old_fd(src) - , m_new_fd(dst) -{ -} - -Fd2FdRedirection::~Fd2FdRedirection() -{ -} - -void FunctionDeclaration::dump(int level) const -{ - Node::dump(level); - print_indented(String::format("(name: %s)\n", m_name.name.characters()), level + 1); - print_indented("(argument namess)", level + 1); - for (auto& arg : m_arguments) - print_indented(String::format("(name: %s)\n", arg.name.characters()), level + 2); - - print_indented("(body)", level + 1); - if (m_block) - m_block->dump(level + 2); - else - print_indented("(null)", level + 2); -} - -RefPtr<Value> FunctionDeclaration::run(RefPtr<Shell> shell) -{ - Vector<String> args; - for (auto& arg : m_arguments) - args.append(arg.name); - - shell->define_function(m_name.name, move(args), m_block); - - return create<ListValue>({}); -} - -void FunctionDeclaration::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - editor.stylize({ m_name.position.start_offset, m_name.position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue) }); - - for (auto& arg : m_arguments) - editor.stylize({ arg.position.start_offset, arg.position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic }); - - metadata.is_first_in_list = true; - if (m_block) - m_block->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult FunctionDeclaration::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - if (!m_block) - return {}; - - auto result = m_block->hit_test_position(offset); - if (result.matching_node && result.matching_node->is_simple_variable()) - result.closest_node_with_semantic_meaning = this; - return result; -} - -Vector<Line::CompletionSuggestion> FunctionDeclaration::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (!matching_node) - return {}; - - if (!matching_node->is_simple_variable()) - return matching_node->complete_for_editor(shell, offset, hit_test_result); - - auto corrected_offset = offset - matching_node->position().start_offset - 1; // Skip the first '$' - auto* node = static_cast<SimpleVariable*>(matching_node.ptr()); - - auto name = node->name().substring_view(0, corrected_offset); - - Vector<Line::CompletionSuggestion> results; - for (auto& arg : m_arguments) { - if (arg.name.starts_with(name)) - results.append(arg.name); - } - - results.append(matching_node->complete_for_editor(shell, offset, hit_test_result)); - - return results; -} - -FunctionDeclaration::FunctionDeclaration(Position position, NameWithPosition name, Vector<NameWithPosition> arguments, RefPtr<AST::Node> body) - : Node(move(position)) - , m_name(move(name)) - , m_arguments(arguments) - , m_block(move(body)) -{ - if (m_block && m_block->is_syntax_error()) - set_is_syntax_error(m_block->syntax_error_node()); -} - -FunctionDeclaration::~FunctionDeclaration() -{ -} - -void ForLoop::dump(int level) const -{ - Node::dump(level); - print_indented(String::format("%s in\n", m_variable_name.characters()), level + 1); - if (m_iterated_expression) - m_iterated_expression->dump(level + 2); - else - print_indented("(ever)", level + 2); - print_indented("Running", level + 1); - if (m_block) - m_block->dump(level + 2); - else - print_indented("(null)", level + 2); -} - -RefPtr<Value> ForLoop::run(RefPtr<Shell> shell) -{ - if (!m_block) - return create<ListValue>({}); - - size_t consecutive_interruptions = 0; - auto run = [&](auto& block_value) { - if (shell->has_error(Shell::ShellError::InternalControlFlowBreak)) { - shell->take_error(); - return IterationDecision::Break; - } - - if (shell->has_error(Shell::ShellError::InternalControlFlowContinue)) { - shell->take_error(); - return IterationDecision::Continue; - } - - if (block_value->is_job()) { - auto job = static_cast<JobValue*>(block_value.ptr())->job(); - if (!job || job->is_running_in_background()) - return IterationDecision::Continue; - shell->block_on_job(job); - - if (job->signaled()) { - if (job->termination_signal() == SIGINT) - ++consecutive_interruptions; - else - return IterationDecision::Break; - } else { - consecutive_interruptions = 0; - } - } - return IterationDecision::Continue; - }; - - if (m_iterated_expression) { - m_iterated_expression->for_each_entry(shell, [&](auto value) { - if (consecutive_interruptions == 2) - return IterationDecision::Break; - - RefPtr<Value> block_value; - - { - auto frame = shell->push_frame(String::formatted("for ({})", this)); - shell->set_local_variable(m_variable_name, value, true); - - block_value = m_block->run(shell); - } - return run(block_value); - }); - } else { - for (;;) { - if (consecutive_interruptions == 2) - break; - - RefPtr<Value> block_value = m_block->run(shell); - if (run(block_value) == IterationDecision::Break) - break; - } - } - - return create<ListValue>({}); -} - -void ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - auto is_loop = m_iterated_expression.is_null(); - editor.stylize({ m_position.start_offset, m_position.start_offset + (is_loop ? 4 : 3) }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - if (!is_loop) { - if (m_in_kw_position.has_value()) - editor.stylize({ m_in_kw_position.value().start_offset, m_in_kw_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - - metadata.is_first_in_list = false; - m_iterated_expression->highlight_in_editor(editor, shell, metadata); - } - - metadata.is_first_in_list = true; - if (m_block) - m_block->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult ForLoop::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - if (m_iterated_expression) { - if (auto result = m_iterated_expression->hit_test_position(offset); result.matching_node) - return result; - } - - if (!m_block) - return {}; - - return m_block->hit_test_position(offset); -} - -ForLoop::ForLoop(Position position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position) - : Node(move(position)) - , m_variable_name(move(variable_name)) - , m_iterated_expression(move(iterated_expr)) - , m_block(move(block)) - , m_in_kw_position(move(in_kw_position)) -{ - if (m_iterated_expression && m_iterated_expression->is_syntax_error()) - set_is_syntax_error(m_iterated_expression->syntax_error_node()); - else if (m_block && m_block->is_syntax_error()) - set_is_syntax_error(m_block->syntax_error_node()); -} - -ForLoop::~ForLoop() -{ -} - -void Glob::dump(int level) const -{ - Node::dump(level); - print_indented(m_text, level + 1); -} - -RefPtr<Value> Glob::run(RefPtr<Shell>) -{ - return create<GlobValue>(m_text, position()); -} - -void Glob::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata) -{ - Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Cyan) }; - if (metadata.is_first_in_list) - style.unify_with({ Line::Style::Bold }); - editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style)); -} - -Glob::Glob(Position position, String text) - : Node(move(position)) - , m_text(move(text)) -{ -} - -Glob::~Glob() -{ -} - -void Execute::dump(int level) const -{ - Node::dump(level); - if (m_capture_stdout) - print_indented("(Capturing stdout)", level + 1); - m_command->dump(level + 1); -} - -void Execute::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(NonnullRefPtr<Value>)> callback) -{ - if (m_command->would_execute()) - return m_command->for_each_entry(shell, move(callback)); - - auto commands = shell->expand_aliases(m_command->run(shell)->resolve_as_commands(shell)); - - if (m_capture_stdout) { - int pipefd[2]; - int rc = pipe(pipefd); - if (rc < 0) { - dbgln("Error: cannot pipe(): {}", strerror(errno)); - return; - } - auto& last_in_commands = commands.last(); - - last_in_commands.redirections.prepend(FdRedirection::create(pipefd[1], STDOUT_FILENO, Rewiring::Close::Old)); - last_in_commands.should_wait = false; - last_in_commands.should_notify_if_in_background = false; - last_in_commands.is_pipe_source = false; - - Core::EventLoop loop; - - auto notifier = Core::Notifier::construct(pipefd[0], Core::Notifier::Read); - DuplexMemoryStream stream; - - enum { - Continue, - Break, - NothingLeft, - }; - auto check_and_call = [&] { - auto ifs = shell->local_variable_or("IFS", "\n"); - - if (auto offset = stream.offset_of(ifs.bytes()); offset.has_value()) { - auto line_end = offset.value(); - if (line_end == 0) { - auto rc = stream.discard_or_error(ifs.length()); - ASSERT(rc); - - if (shell->options.inline_exec_keep_empty_segments) - if (callback(create<StringValue>("")) == IterationDecision::Break) { - loop.quit(Break); - notifier->set_enabled(false); - return Break; - } - } else { - auto entry = ByteBuffer::create_uninitialized(line_end + ifs.length()); - auto rc = stream.read_or_error(entry); - ASSERT(rc); - - auto str = StringView(entry.data(), entry.size() - ifs.length()); - if (callback(create<StringValue>(str)) == IterationDecision::Break) { - loop.quit(Break); - notifier->set_enabled(false); - return Break; - } - } - - return Continue; - } - - return NothingLeft; - }; - - notifier->on_ready_to_read = [&] { - constexpr static auto buffer_size = 16; - u8 buffer[buffer_size]; - size_t remaining_size = buffer_size; - - for (;;) { - notifier->set_event_mask(Core::Notifier::None); - bool should_enable_notifier = false; - - ScopeGuard notifier_enabler { [&] { - if (should_enable_notifier) - notifier->set_event_mask(Core::Notifier::Read); - } }; - - if (check_and_call() == Break) { - loop.quit(Break); - return; - } - - auto read_size = read(pipefd[0], buffer, remaining_size); - if (read_size < 0) { - int saved_errno = errno; - if (saved_errno == EINTR) { - should_enable_notifier = true; - continue; - } - if (saved_errno == 0) - continue; - dbgln("read() failed: {}", strerror(saved_errno)); - break; - } - if (read_size == 0) - break; - - should_enable_notifier = true; - stream.write({ buffer, (size_t)read_size }); - } - - loop.quit(NothingLeft); - }; - - auto jobs = shell->run_commands(commands); - ScopeGuard kill_jobs_if_around { [&] { - for (auto& job : jobs) { - if (job.is_running_in_background() && !job.exited() && !job.signaled()) { - job.set_should_announce_signal(false); // We're explicitly killing it here. - shell->kill_job(&job, SIGTERM); - } - } - } }; - - auto exit_reason = loop.exec(); - - notifier->on_ready_to_read = nullptr; - - if (close(pipefd[0]) < 0) { - dbgln("close() failed: {}", strerror(errno)); - } - - if (exit_reason != Break && !stream.eof()) { - auto action = Continue; - do { - action = check_and_call(); - if (action == Break) - return; - } while (action == Continue); - - if (!stream.eof()) { - auto entry = ByteBuffer::create_uninitialized(stream.size()); - auto rc = stream.read_or_error(entry); - ASSERT(rc); - callback(create<StringValue>(String::copy(entry))); - } - } - - return; - } - - auto jobs = shell->run_commands(commands); - - if (!jobs.is_empty()) - callback(create<JobValue>(&jobs.last())); -} - -RefPtr<Value> Execute::run(RefPtr<Shell> shell) -{ - if (m_command->would_execute()) - return m_command->run(shell); - - NonnullRefPtrVector<Value> values; - for_each_entry(shell, [&](auto value) { - values.append(*value); - return IterationDecision::Continue; - }); - - if (values.size() == 1 && values.first().is_job()) - return values.first(); - - return create<ListValue>(move(values)); -} - -void Execute::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - if (m_capture_stdout) - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Green) }); - metadata.is_first_in_list = true; - m_command->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Execute::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_command->hit_test_position(offset); - if (!result.closest_node_with_semantic_meaning) - result.closest_node_with_semantic_meaning = this; - if (!result.closest_command_node) - result.closest_command_node = m_command; - return result; -} - -Vector<Line::CompletionSuggestion> Execute::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (!matching_node || !matching_node->is_bareword()) - return {}; - - auto corrected_offset = offset - matching_node->position().start_offset; - auto* node = static_cast<BarewordLiteral*>(matching_node.ptr()); - - if (corrected_offset > node->text().length()) - return {}; - - return shell.complete_program_name(node->text(), corrected_offset); -} - -Execute::Execute(Position position, NonnullRefPtr<Node> command, bool capture_stdout) - : Node(move(position)) - , m_command(move(command)) - , m_capture_stdout(capture_stdout) -{ - if (m_command->is_syntax_error()) - set_is_syntax_error(m_command->syntax_error_node()); -} - -Execute::~Execute() -{ -} - -void IfCond::dump(int level) const -{ - Node::dump(level); - print_indented("Condition", ++level); - m_condition->dump(level + 1); - print_indented("True Branch", level); - if (m_true_branch) - m_true_branch->dump(level + 1); - else - print_indented("(empty)", level + 1); - print_indented("False Branch", level); - if (m_false_branch) - m_false_branch->dump(level + 1); - else - print_indented("(empty)", level + 1); -} - -RefPtr<Value> IfCond::run(RefPtr<Shell> shell) -{ - auto cond = m_condition->run(shell)->resolve_without_cast(shell); - // The condition could be a builtin, in which case it has already run and exited. - if (cond && cond->is_job()) { - auto cond_job_value = static_cast<const JobValue*>(cond.ptr()); - auto cond_job = cond_job_value->job(); - - shell->block_on_job(cond_job); - - if (cond_job->signaled()) - return create<ListValue>({}); // Exit early. - } - if (shell->last_return_code == 0) { - if (m_true_branch) - return m_true_branch->run(shell); - } else { - if (m_false_branch) - return m_false_branch->run(shell); - } - - return create<ListValue>({}); -} - -void IfCond::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - metadata.is_first_in_list = true; - - editor.stylize({ m_position.start_offset, m_position.start_offset + 2 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - if (m_else_position.has_value()) - editor.stylize({ m_else_position.value().start_offset, m_else_position.value().start_offset + 4 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - - m_condition->highlight_in_editor(editor, shell, metadata); - if (m_true_branch) - m_true_branch->highlight_in_editor(editor, shell, metadata); - if (m_false_branch) - m_false_branch->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult IfCond::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - if (auto result = m_condition->hit_test_position(offset); result.matching_node) - return result; - - if (m_true_branch) { - if (auto result = m_true_branch->hit_test_position(offset); result.matching_node) - return result; - } - - if (m_false_branch) { - if (auto result = m_false_branch->hit_test_position(offset); result.matching_node) - return result; - } - - return {}; -} - -IfCond::IfCond(Position position, Optional<Position> else_position, NonnullRefPtr<Node> condition, RefPtr<Node> true_branch, RefPtr<Node> false_branch) - : Node(move(position)) - , m_condition(move(condition)) - , m_true_branch(move(true_branch)) - , m_false_branch(move(false_branch)) - , m_else_position(move(else_position)) -{ - if (m_condition->is_syntax_error()) - set_is_syntax_error(m_condition->syntax_error_node()); - else if (m_true_branch && m_true_branch->is_syntax_error()) - set_is_syntax_error(m_true_branch->syntax_error_node()); - else if (m_false_branch && m_false_branch->is_syntax_error()) - set_is_syntax_error(m_false_branch->syntax_error_node()); - - m_condition = create<AST::Execute>(m_condition->position(), m_condition); - - if (m_true_branch) { - auto true_branch = m_true_branch.release_nonnull(); - m_true_branch = create<AST::Execute>(true_branch->position(), true_branch); - } - - if (m_false_branch) { - auto false_branch = m_false_branch.release_nonnull(); - m_false_branch = create<AST::Execute>(false_branch->position(), false_branch); - } -} - -IfCond::~IfCond() -{ -} - -void Join::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> Join::run(RefPtr<Shell> 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))); -} - -void Join::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_left->highlight_in_editor(editor, shell, metadata); - if (m_left->is_list() || m_left->is_command()) - metadata.is_first_in_list = false; - m_right->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Join::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (result.matching_node) - return result; - - return m_right->hit_test_position(offset); -} - -RefPtr<Node> Join::leftmost_trivial_literal() const -{ - if (auto value = m_left->leftmost_trivial_literal()) - return value; - return m_right->leftmost_trivial_literal(); -} - -Join::Join(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -Join::~Join() -{ -} - -void MatchExpr::dump(int level) const -{ - Node::dump(level); - print_indented(String::formatted("(expression: {})", m_expr_name.characters()), level + 1); - m_matched_expr->dump(level + 2); - print_indented(String::formatted("(named: {})", m_expr_name.characters()), level + 1); - print_indented("(entries)", level + 1); - for (auto& entry : m_entries) { - StringBuilder builder; - builder.append("(match"); - if (entry.match_names.has_value()) { - builder.append(" to names ("); - bool first = true; - for (auto& name : entry.match_names.value()) { - if (!first) - builder.append(' '); - first = false; - builder.append(name); - } - builder.append("))"); - - } else { - builder.append(')'); - } - print_indented(builder.string_view(), level + 2); - for (auto& node : entry.options) - node.dump(level + 3); - print_indented("(execute)", level + 2); - if (entry.body) - entry.body->dump(level + 3); - else - print_indented("(nothing)", level + 3); - } -} - -RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell) -{ - auto value = m_matched_expr->run(shell)->resolve_without_cast(shell); - auto list = value->resolve_as_list(shell); - - auto list_matches = [&](auto&& pattern, auto& spans) { - if (pattern.size() != list.size()) - return false; - - for (size_t i = 0; i < pattern.size(); ++i) { - Vector<AK::MaskSpan> mask_spans; - if (!list[i].matches(pattern[i], mask_spans)) - return false; - for (auto& span : mask_spans) - spans.append(list[i].substring(span.start, span.length)); - } - - return true; - }; - - auto resolve_pattern = [&](auto& option) { - Vector<String> pattern; - if (option.is_glob()) { - pattern.append(static_cast<const Glob*>(&option)->text()); - } else if (option.is_bareword()) { - pattern.append(static_cast<const BarewordLiteral*>(&option)->text()); - } else { - auto list = option.run(shell); - option.for_each_entry(shell, [&](auto&& value) { - pattern.append(value->resolve_as_list(nullptr)); // Note: 'nullptr' incurs special behaviour, - // asking the node for a 'raw' value. - return IterationDecision::Continue; - }); - } - - return pattern; - }; - - auto frame = shell->push_frame(String::formatted("match ({})", this)); - if (!m_expr_name.is_empty()) - shell->set_local_variable(m_expr_name, value, true); - - for (auto& entry : m_entries) { - for (auto& option : entry.options) { - Vector<String> spans; - if (list_matches(resolve_pattern(option), spans)) { - if (entry.body) { - if (entry.match_names.has_value()) { - size_t i = 0; - for (auto& name : entry.match_names.value()) { - if (spans.size() > i) - shell->set_local_variable(name, create<AST::StringValue>(spans[i]), true); - ++i; - } - } - return entry.body->run(shell); - } else { - return create<AST::ListValue>({}); - } - } - } - } - - shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Non-exhaustive match rules!", position()); - return create<AST::ListValue>({}); -} - -void MatchExpr::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - editor.stylize({ m_position.start_offset, m_position.start_offset + 5 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - if (m_as_position.has_value()) - editor.stylize({ m_as_position.value().start_offset, m_as_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - - metadata.is_first_in_list = false; - m_matched_expr->highlight_in_editor(editor, shell, metadata); - - for (auto& entry : m_entries) { - metadata.is_first_in_list = false; - for (auto& option : entry.options) - option.highlight_in_editor(editor, shell, metadata); - - metadata.is_first_in_list = true; - if (entry.body) - entry.body->highlight_in_editor(editor, shell, metadata); - - for (auto& position : entry.pipe_positions) - editor.stylize({ position.start_offset, position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - - if (entry.match_as_position.has_value()) - editor.stylize({ entry.match_as_position.value().start_offset, entry.match_as_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - } -} - -HitTestResult MatchExpr::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_matched_expr->hit_test_position(offset); - if (result.matching_node) - return result; - - for (auto& entry : m_entries) { - if (!entry.body) - continue; - auto result = entry.body->hit_test_position(offset); - if (result.matching_node) - return result; - } - - return {}; -} - -MatchExpr::MatchExpr(Position position, NonnullRefPtr<Node> expr, String name, Optional<Position> as_position, Vector<MatchEntry> entries) - : Node(move(position)) - , m_matched_expr(move(expr)) - , m_expr_name(move(name)) - , m_as_position(move(as_position)) - , m_entries(move(entries)) -{ - if (m_matched_expr->is_syntax_error()) { - set_is_syntax_error(m_matched_expr->syntax_error_node()); - } else { - for (auto& entry : m_entries) { - if (!entry.body) - continue; - if (entry.body->is_syntax_error()) { - set_is_syntax_error(entry.body->syntax_error_node()); - break; - } - } - } -} - -MatchExpr::~MatchExpr() -{ -} - -void Or::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> Or::run(RefPtr<Shell> shell) -{ - auto commands = m_left->to_lazy_evaluated_commands(shell); - commands.last().next_chain.empend(*m_right, NodeWithAction::Or); - return create<CommandSequenceValue>(move(commands)); -} - -void Or::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_left->highlight_in_editor(editor, shell, metadata); - m_right->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Or::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (result.matching_node) { - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; - } - - result = m_right->hit_test_position(offset); - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; -} - -Or::Or(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position or_position) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) - , m_or_position(or_position) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -Or::~Or() -{ -} - -void Pipe::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> Pipe::run(RefPtr<Shell> 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(); - - auto pipe_read_end = FdRedirection::create(-1, STDIN_FILENO, Rewiring::Close::Old); - auto pipe_write_end = FdRedirection::create(-1, STDOUT_FILENO, pipe_read_end, Rewiring::Close::RefreshOld); - first_in_right.redirections.append(pipe_read_end); - last_in_left.redirections.append(pipe_write_end); - last_in_left.should_wait = false; - last_in_left.is_pipe_source = true; - - if (first_in_right.pipeline) { - last_in_left.pipeline = first_in_right.pipeline; - } else { - auto pipeline = create<Pipeline>(); - last_in_left.pipeline = pipeline; - first_in_right.pipeline = pipeline; - } - - Vector<Command> commands; - commands.append(left); - commands.append(last_in_left); - commands.append(first_in_right); - commands.append(right); - - return create<CommandSequenceValue>(move(commands)); -} - -void Pipe::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_left->highlight_in_editor(editor, shell, metadata); - m_right->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Pipe::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (result.matching_node) { - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; - } - - result = m_right->hit_test_position(offset); - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; -} - -Pipe::Pipe(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -Pipe::~Pipe() -{ -} - -PathRedirectionNode::PathRedirectionNode(Position position, int fd, NonnullRefPtr<Node> path) - : Node(move(position)) - , m_fd(fd) - , m_path(move(path)) -{ -} - -void PathRedirectionNode::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(0x87, 0x9b, 0xcd) }); // 25% Darkened Periwinkle - metadata.is_first_in_list = false; - m_path->highlight_in_editor(editor, shell, metadata); - if (m_path->is_bareword()) { - auto path_text = m_path->run(nullptr)->resolve_as_list(nullptr); - ASSERT(path_text.size() == 1); - // Apply a URL to the path. - auto& position = m_path->position(); - auto& path = path_text[0]; - if (!path.starts_with('/')) - path = String::format("%s/%s", shell.cwd.characters(), path.characters()); - auto url = URL::create_with_file_protocol(path); - url.set_host(shell.hostname); - editor.stylize({ position.start_offset, position.end_offset }, { Line::Style::Hyperlink(url.to_string()) }); - } -} - -HitTestResult PathRedirectionNode::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_path->hit_test_position(offset); - if (!result.closest_node_with_semantic_meaning) - result.closest_node_with_semantic_meaning = this; - return result; -} - -Vector<Line::CompletionSuggestion> PathRedirectionNode::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (!matching_node || !matching_node->is_bareword()) - return {}; - - auto corrected_offset = offset - matching_node->position().start_offset; - auto* node = static_cast<BarewordLiteral*>(matching_node.ptr()); - - if (corrected_offset > node->text().length()) - return {}; - - return shell.complete_path("", node->text(), corrected_offset); -} - -PathRedirectionNode::~PathRedirectionNode() -{ -} - -void Range::dump(int level) const -{ - Node::dump(level); - print_indented("(From)", level + 1); - m_start->dump(level + 2); - print_indented("(To)", level + 1); - m_end->dump(level + 2); -} - -RefPtr<Value> Range::run(RefPtr<Shell> shell) -{ - auto interpolate = [position = position()](RefPtr<Value> start, RefPtr<Value> end, RefPtr<Shell> shell) -> NonnullRefPtrVector<Value> { - NonnullRefPtrVector<Value> values; - - if (start->is_string() && end->is_string()) { - auto start_str = start->resolve_as_list(shell)[0]; - auto end_str = end->resolve_as_list(shell)[0]; - - Utf8View start_view { start_str }, end_view { end_str }; - if (start_view.validate() && end_view.validate()) { - if (start_view.length() == 1 && end_view.length() == 1) { - // Interpolate between two code points. - auto start_code_point = *start_view.begin(); - auto end_code_point = *end_view.begin(); - auto step = start_code_point > end_code_point ? -1 : 1; - StringBuilder builder; - for (u32 code_point = start_code_point; code_point != end_code_point; code_point += step) { - builder.clear(); - builder.append_code_point(code_point); - values.append(create<StringValue>(builder.to_string())); - } - // Append the ending code point too, most shells treat this as inclusive. - builder.clear(); - builder.append_code_point(end_code_point); - values.append(create<StringValue>(builder.to_string())); - } else { - // Could be two numbers? - auto start_int = start_str.to_int(); - auto end_int = end_str.to_int(); - if (start_int.has_value() && end_int.has_value()) { - auto start = start_int.value(); - auto end = end_int.value(); - auto step = start > end ? -1 : 1; - for (int value = start; value != end; value += step) - values.append(create<StringValue>(String::number(value))); - // Append the range end too, most shells treat this as inclusive. - values.append(create<StringValue>(String::number(end))); - } else { - goto yield_start_end; - } - } - } else { - yield_start_end:; - shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, String::formatted("Cannot interpolate between '{}' and '{}'!", start_str, end_str), position); - // We can't really interpolate between the two, so just yield both. - values.append(create<StringValue>(move(start_str))); - values.append(create<StringValue>(move(end_str))); - } - - return values; - } - - warnln("Shell: Cannot apply the requested interpolation"); - return values; - }; - - auto start_value = m_start->run(shell); - auto end_value = m_end->run(shell); - if (!start_value || !end_value) - return create<ListValue>({}); - - return create<ListValue>(interpolate(*start_value, *end_value, shell)); -} - -void Range::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_start->highlight_in_editor(editor, shell, metadata); - - // Highlight the '..' - editor.stylize({ m_start->position().end_offset, m_end->position().start_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); - - metadata.is_first_in_list = false; - m_end->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Range::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_start->hit_test_position(offset); - if (result.matching_node) { - if (!result.closest_command_node) - result.closest_command_node = m_start; - return result; - } - - result = m_end->hit_test_position(offset); - if (!result.closest_command_node) - result.closest_command_node = m_end; - return result; -} - -Range::Range(Position position, NonnullRefPtr<Node> start, NonnullRefPtr<Node> end) - : Node(move(position)) - , m_start(move(start)) - , m_end(move(end)) -{ - if (m_start->is_syntax_error()) - set_is_syntax_error(m_start->syntax_error_node()); - else if (m_end->is_syntax_error()) - set_is_syntax_error(m_end->syntax_error_node()); -} - -Range::~Range() -{ -} - -void ReadRedirection::dump(int level) const -{ - Node::dump(level); - m_path->dump(level + 1); - print_indented(String::format("To %d", m_fd), level + 1); -} - -RefPtr<Value> ReadRedirection::run(RefPtr<Shell> shell) -{ - Command command; - auto path_segments = m_path->run(shell)->resolve_as_list(shell); - StringBuilder builder; - builder.join(" ", path_segments); - - command.redirections.append(PathRedirection::create(builder.to_string(), m_fd, PathRedirection::Read)); - return create<CommandValue>(move(command)); -} - -ReadRedirection::ReadRedirection(Position position, int fd, NonnullRefPtr<Node> path) - : PathRedirectionNode(move(position), fd, move(path)) -{ -} - -ReadRedirection::~ReadRedirection() -{ -} - -void ReadWriteRedirection::dump(int level) const -{ - Node::dump(level); - m_path->dump(level + 1); - print_indented(String::format("To/From %d", m_fd), level + 1); -} - -RefPtr<Value> ReadWriteRedirection::run(RefPtr<Shell> shell) -{ - Command command; - auto path_segments = m_path->run(shell)->resolve_as_list(shell); - StringBuilder builder; - builder.join(" ", path_segments); - - command.redirections.append(PathRedirection::create(builder.to_string(), m_fd, PathRedirection::ReadWrite)); - return create<CommandValue>(move(command)); -} - -ReadWriteRedirection::ReadWriteRedirection(Position position, int fd, NonnullRefPtr<Node> path) - : PathRedirectionNode(move(position), fd, move(path)) -{ -} - -ReadWriteRedirection::~ReadWriteRedirection() -{ -} - -void Sequence::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> Sequence::run(RefPtr<Shell> 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(); - if (command.argv.is_empty() && command.redirections.is_empty() && command.next_chain.is_empty()) - return m_right->run(shell); - } - - if (left.last().should_wait) - left.last().next_chain.append(NodeWithAction { *m_right, NodeWithAction::Sequence }); - else - left.append(m_right->to_lazy_evaluated_commands(shell)); - - return create<CommandSequenceValue>(move(left)); -} - -void Sequence::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_left->highlight_in_editor(editor, shell, metadata); - m_right->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Sequence::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (result.matching_node) { - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; - } - - result = m_right->hit_test_position(offset); - if (!result.closest_command_node) - result.closest_command_node = m_right; - return result; -} - -Sequence::Sequence(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right, Position separator_position) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) - , m_separator_position(separator_position) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -Sequence::~Sequence() -{ -} - -void Subshell::dump(int level) const -{ - Node::dump(level); - if (m_block) - m_block->dump(level + 1); -} - -RefPtr<Value> Subshell::run(RefPtr<Shell> shell) -{ - if (!m_block) - return create<ListValue>({}); - - return create<AST::CommandSequenceValue>(m_block->to_lazy_evaluated_commands(shell)); -} - -void Subshell::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - metadata.is_first_in_list = true; - if (m_block) - m_block->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult Subshell::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - if (m_block) - return m_block->hit_test_position(offset); - - return {}; -} - -Subshell::Subshell(Position position, RefPtr<Node> block) - : Node(move(position)) - , m_block(block) -{ - if (m_block && m_block->is_syntax_error()) - set_is_syntax_error(m_block->syntax_error_node()); -} - -Subshell::~Subshell() -{ -} - -void SimpleVariable::dump(int level) const -{ - Node::dump(level); - print_indented(m_name, level + 1); -} - -RefPtr<Value> SimpleVariable::run(RefPtr<Shell>) -{ - return create<SimpleVariableValue>(m_name); -} - -void SimpleVariable::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata) -{ - Line::Style style { Line::Style::Foreground(214, 112, 214) }; - if (metadata.is_first_in_list) - style.unify_with({ Line::Style::Bold }); - editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style)); -} - -HitTestResult SimpleVariable::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - return { this, this, nullptr }; -} - -Vector<Line::CompletionSuggestion> SimpleVariable::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (!matching_node) - return {}; - - if (matching_node != this) - return {}; - - auto corrected_offset = offset - matching_node->position().start_offset - 1; - - if (corrected_offset > m_name.length() + 1) - return {}; - - return shell.complete_variable(m_name, corrected_offset); -} - -SimpleVariable::SimpleVariable(Position position, String name) - : Node(move(position)) - , m_name(move(name)) -{ -} - -SimpleVariable::~SimpleVariable() -{ -} - -void SpecialVariable::dump(int level) const -{ - Node::dump(level); - print_indented(String { &m_name, 1 }, level + 1); -} - -RefPtr<Value> SpecialVariable::run(RefPtr<Shell>) -{ - return create<SpecialVariableValue>(m_name); -} - -void SpecialVariable::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(214, 112, 214) }); -} - -Vector<Line::CompletionSuggestion> SpecialVariable::complete_for_editor(Shell&, size_t, const HitTestResult&) -{ - return {}; -} - -HitTestResult SpecialVariable::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - return { this, this, nullptr }; -} - -SpecialVariable::SpecialVariable(Position position, char name) - : Node(move(position)) - , m_name(name) -{ -} - -SpecialVariable::~SpecialVariable() -{ -} - -void Juxtaposition::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> Juxtaposition::run(RefPtr<Shell> shell) -{ - auto left_value = m_left->run(shell)->resolve_without_cast(shell); - auto right_value = m_right->run(shell)->resolve_without_cast(shell); - - auto left = left_value->resolve_as_list(shell); - auto right = right_value->resolve_as_list(shell); - - if (left_value->is_string() && right_value->is_string()) { - - ASSERT(left.size() == 1); - ASSERT(right.size() == 1); - - StringBuilder builder; - builder.append(left[0]); - builder.append(right[0]); - - return create<StringValue>(builder.to_string()); - } - - // Otherwise, treat them as lists and create a list product. - if (left.is_empty() || right.is_empty()) - return create<ListValue>({}); - - Vector<String> result; - result.ensure_capacity(left.size() * right.size()); - - StringBuilder builder; - for (auto& left_element : left) { - for (auto& right_element : right) { - builder.append(left_element); - builder.append(right_element); - result.append(builder.to_string()); - builder.clear(); - } - } - - return create<ListValue>(move(result)); -} - -void Juxtaposition::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_left->highlight_in_editor(editor, shell, metadata); - - // '~/foo/bar' is special, we have to actually resolve the tilde - // since that resolution is a pure operation, we can just go ahead - // and do it to get the value :) - if (m_right->is_bareword() && m_left->is_tilde()) { - auto tilde_value = m_left->run(shell)->resolve_as_list(shell)[0]; - auto bareword_value = m_right->run(shell)->resolve_as_list(shell)[0]; - - StringBuilder path_builder; - path_builder.append(tilde_value); - path_builder.append("/"); - path_builder.append(bareword_value); - auto path = path_builder.to_string(); - - if (Core::File::exists(path)) { - auto realpath = shell.resolve_path(path); - auto url = URL::create_with_file_protocol(realpath); - url.set_host(shell.hostname); - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Hyperlink(url.to_string()) }); - } - - } else { - m_right->highlight_in_editor(editor, shell, metadata); - } -} - -Vector<Line::CompletionSuggestion> Juxtaposition::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - // '~/foo/bar' is special, we have to actually resolve the tilde - // then complete the bareword with that path prefix. - if (m_right->is_bareword() && m_left->is_tilde()) { - auto tilde_value = m_left->run(shell)->resolve_as_list(shell)[0]; - - auto corrected_offset = offset - matching_node->position().start_offset; - auto* node = static_cast<BarewordLiteral*>(matching_node.ptr()); - - if (corrected_offset > node->text().length()) - return {}; - - auto text = node->text().substring(1, node->text().length() - 1); - - return shell.complete_path(tilde_value, text, corrected_offset - 1); - } - - return Node::complete_for_editor(shell, offset, hit_test_result); -} - -HitTestResult Juxtaposition::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (!result.closest_node_with_semantic_meaning) - result.closest_node_with_semantic_meaning = this; - if (result.matching_node) - return result; - - result = m_right->hit_test_position(offset); - if (!result.closest_node_with_semantic_meaning) - result.closest_node_with_semantic_meaning = this; - return result; -} - -Juxtaposition::Juxtaposition(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -Juxtaposition::~Juxtaposition() -{ -} - -void StringLiteral::dump(int level) const -{ - Node::dump(level); - print_indented(m_text, level + 1); -} - -RefPtr<Value> StringLiteral::run(RefPtr<Shell>) -{ - return create<StringValue>(m_text); -} - -void StringLiteral::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata) -{ - if (m_text.is_empty()) - return; - - Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }; - if (metadata.is_first_in_list) - style.unify_with({ Line::Style::Bold }); - editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style)); -} - -StringLiteral::StringLiteral(Position position, String text) - : Node(move(position)) - , m_text(move(text)) -{ -} - -StringLiteral::~StringLiteral() -{ -} - -void StringPartCompose::dump(int level) const -{ - Node::dump(level); - m_left->dump(level + 1); - m_right->dump(level + 1); -} - -RefPtr<Value> StringPartCompose::run(RefPtr<Shell> shell) -{ - auto left = m_left->run(shell)->resolve_as_list(shell); - auto right = m_right->run(shell)->resolve_as_list(shell); - - StringBuilder builder; - builder.join(" ", left); - builder.join(" ", right); - - return create<StringValue>(builder.to_string()); -} - -void StringPartCompose::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - m_left->highlight_in_editor(editor, shell, metadata); - m_right->highlight_in_editor(editor, shell, metadata); -} - -HitTestResult StringPartCompose::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - auto result = m_left->hit_test_position(offset); - if (result.matching_node) - return result; - return m_right->hit_test_position(offset); -} - -StringPartCompose::StringPartCompose(Position position, NonnullRefPtr<Node> left, NonnullRefPtr<Node> right) - : Node(move(position)) - , m_left(move(left)) - , m_right(move(right)) -{ - if (m_left->is_syntax_error()) - set_is_syntax_error(m_left->syntax_error_node()); - else if (m_right->is_syntax_error()) - set_is_syntax_error(m_right->syntax_error_node()); -} - -StringPartCompose::~StringPartCompose() -{ -} - -void SyntaxError::dump(int level) const -{ - Node::dump(level); - print_indented("(Error text)", level + 1); - print_indented(m_syntax_error_text, level + 2); - print_indented("(Can be recovered from)", level + 1); - print_indented(String::formatted("{}", m_is_continuable), level + 2); -} - -RefPtr<Value> SyntaxError::run(RefPtr<Shell> shell) -{ - shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, m_syntax_error_text, position()); - return create<StringValue>(""); -} - -void SyntaxError::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata) -{ - editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Red), Line::Style::Bold }); -} - -SyntaxError::SyntaxError(Position position, String error, bool is_continuable) - : Node(move(position)) - , m_syntax_error_text(move(error)) - , m_is_continuable(is_continuable) -{ - m_is_syntax_error = true; -} - -const SyntaxError& SyntaxError::syntax_error_node() const -{ - return *this; -} - -SyntaxError::~SyntaxError() -{ -} - -void Tilde::dump(int level) const -{ - Node::dump(level); - print_indented(m_username, level + 1); -} - -RefPtr<Value> Tilde::run(RefPtr<Shell>) -{ - return create<TildeValue>(m_username); -} - -void Tilde::highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata) -{ -} - -HitTestResult Tilde::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - return { this, this, nullptr }; -} - -Vector<Line::CompletionSuggestion> Tilde::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result) -{ - auto matching_node = hit_test_result.matching_node; - if (!matching_node) - return {}; - - if (matching_node != this) - return {}; - - auto corrected_offset = offset - matching_node->position().start_offset - 1; - - if (corrected_offset > m_username.length() + 1) - return {}; - - return shell.complete_user(m_username, corrected_offset); -} - -String Tilde::text() const -{ - StringBuilder builder; - builder.append('~'); - builder.append(m_username); - return builder.to_string(); -} - -Tilde::Tilde(Position position, String username) - : Node(move(position)) - , m_username(move(username)) -{ -} - -Tilde::~Tilde() -{ -} - -void WriteAppendRedirection::dump(int level) const -{ - Node::dump(level); - m_path->dump(level + 1); - print_indented(String::format("From %d", m_fd), level + 1); -} - -RefPtr<Value> WriteAppendRedirection::run(RefPtr<Shell> shell) -{ - Command command; - auto path_segments = m_path->run(shell)->resolve_as_list(shell); - StringBuilder builder; - builder.join(" ", path_segments); - - command.redirections.append(PathRedirection::create(builder.to_string(), m_fd, PathRedirection::WriteAppend)); - return create<CommandValue>(move(command)); -} - -WriteAppendRedirection::WriteAppendRedirection(Position position, int fd, NonnullRefPtr<Node> path) - : PathRedirectionNode(move(position), fd, move(path)) -{ -} - -WriteAppendRedirection::~WriteAppendRedirection() -{ -} - -void WriteRedirection::dump(int level) const -{ - Node::dump(level); - m_path->dump(level + 1); - print_indented(String::format("From %d", m_fd), level + 1); -} - -RefPtr<Value> WriteRedirection::run(RefPtr<Shell> shell) -{ - Command command; - auto path_segments = m_path->run(shell)->resolve_as_list(shell); - StringBuilder builder; - builder.join(" ", path_segments); - - command.redirections.append(PathRedirection::create(builder.to_string(), m_fd, PathRedirection::Write)); - return create<CommandValue>(move(command)); -} - -WriteRedirection::WriteRedirection(Position position, int fd, NonnullRefPtr<Node> path) - : PathRedirectionNode(move(position), fd, move(path)) -{ -} - -WriteRedirection::~WriteRedirection() -{ -} - -void VariableDeclarations::dump(int level) const -{ - Node::dump(level); - for (auto& var : m_variables) { - print_indented("Set", level + 1); - var.name->dump(level + 2); - var.value->dump(level + 2); - } -} - -RefPtr<Value> VariableDeclarations::run(RefPtr<Shell> shell) -{ - for (auto& var : m_variables) { - auto name_value = var.name->run(shell)->resolve_as_list(shell); - ASSERT(name_value.size() == 1); - auto name = name_value[0]; - auto value = var.value->run(shell); - if (value->is_list()) { - auto parts = value->resolve_as_list(shell); - shell->set_local_variable(name, adopt(*new ListValue(move(parts)))); - } else if (value->is_command()) { - shell->set_local_variable(name, value); - } else { - auto part = value->resolve_as_list(shell); - shell->set_local_variable(name, adopt(*new StringValue(part[0]))); - } - } - - return create<ListValue>({}); -} - -void VariableDeclarations::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) -{ - metadata.is_first_in_list = false; - for (auto& var : m_variables) { - var.name->highlight_in_editor(editor, shell, metadata); - // Highlight the '='. - editor.stylize({ var.name->position().end_offset - 1, var.name->position().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue) }); - var.value->highlight_in_editor(editor, shell, metadata); - } -} - -HitTestResult VariableDeclarations::hit_test_position(size_t offset) -{ - if (!position().contains(offset)) - return {}; - - for (auto decl : m_variables) { - auto result = decl.value->hit_test_position(offset); - if (result.matching_node) - return result; - } - - return { nullptr, nullptr, nullptr }; -} - -VariableDeclarations::VariableDeclarations(Position position, Vector<Variable> variables) - : Node(move(position)) - , m_variables(move(variables)) -{ - for (auto& decl : m_variables) { - if (decl.name->is_syntax_error()) { - set_is_syntax_error(decl.name->syntax_error_node()); - break; - } - if (decl.value->is_syntax_error()) { - set_is_syntax_error(decl.value->syntax_error_node()); - break; - } - } -} - -VariableDeclarations::~VariableDeclarations() -{ -} - -Value::~Value() -{ -} -Vector<AST::Command> Value::resolve_as_commands(RefPtr<Shell> shell) -{ - Command command; - command.argv = resolve_as_list(shell); - return { command }; -} - -ListValue::ListValue(Vector<String> values) -{ - m_contained_values.ensure_capacity(values.size()); - for (auto& str : values) - m_contained_values.append(adopt(*new StringValue(move(str)))); -} - -ListValue::~ListValue() -{ -} - -Vector<String> ListValue::resolve_as_list(RefPtr<Shell> shell) -{ - Vector<String> values; - for (auto& value : m_contained_values) - values.append(value.resolve_as_list(shell)); - - return values; -} - -NonnullRefPtr<Value> ListValue::resolve_without_cast(RefPtr<Shell> shell) -{ - NonnullRefPtrVector<Value> values; - for (auto& value : m_contained_values) - values.append(value.resolve_without_cast(shell)); - - return create<ListValue>(move(values)); -} - -CommandValue::~CommandValue() -{ -} - -CommandSequenceValue::~CommandSequenceValue() -{ -} - -Vector<String> CommandSequenceValue::resolve_as_list(RefPtr<Shell>) -{ - // TODO: Somehow raise an "error". - return {}; -} - -Vector<Command> CommandSequenceValue::resolve_as_commands(RefPtr<Shell>) -{ - return m_contained_values; -} - -Vector<String> CommandValue::resolve_as_list(RefPtr<Shell>) -{ - // TODO: Somehow raise an "error". - return {}; -} - -Vector<Command> CommandValue::resolve_as_commands(RefPtr<Shell>) -{ - return { m_command }; -} - -JobValue::~JobValue() -{ -} - -StringValue::~StringValue() -{ -} -Vector<String> StringValue::resolve_as_list(RefPtr<Shell>) -{ - if (is_list()) { - auto parts = StringView(m_string).split_view(m_split, m_keep_empty); - Vector<String> result; - result.ensure_capacity(parts.size()); - for (auto& part : parts) - result.append(part); - return result; - } - - return { m_string }; -} - -GlobValue::~GlobValue() -{ -} -Vector<String> GlobValue::resolve_as_list(RefPtr<Shell> shell) -{ - if (!shell) - return { m_glob }; - - auto results = shell->expand_globs(m_glob, shell->cwd); - if (results.is_empty()) - shell->raise_error(Shell::ShellError::InvalidGlobError, "Glob did not match anything!", m_generation_position); - return results; -} - -SimpleVariableValue::~SimpleVariableValue() -{ -} -Vector<String> SimpleVariableValue::resolve_as_list(RefPtr<Shell> shell) -{ - if (!shell) - return {}; - - if (auto value = resolve_without_cast(shell); value != this) - return value->resolve_as_list(shell); - - char* env_value = getenv(m_name.characters()); - if (env_value == nullptr) - return { "" }; - - Vector<String> res; - String str_env_value = String(env_value); - const auto& split_text = str_env_value.split_view(' '); - for (auto& part : split_text) - res.append(part); - return res; -} - -NonnullRefPtr<Value> SimpleVariableValue::resolve_without_cast(RefPtr<Shell> shell) -{ - ASSERT(shell); - - if (auto value = shell->lookup_local_variable(m_name)) - return value.release_nonnull(); - return *this; -} - -SpecialVariableValue::~SpecialVariableValue() -{ -} - -Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell) -{ - if (!shell) - return {}; - - switch (m_name) { - case '?': - return { String::number(shell->last_return_code) }; - case '$': - return { String::number(getpid()) }; - case '*': - if (auto argv = shell->lookup_local_variable("ARGV")) - return argv->resolve_as_list(shell); - return {}; - case '#': - if (auto argv = shell->lookup_local_variable("ARGV")) { - if (argv->is_list()) { - auto list_argv = static_cast<AST::ListValue*>(argv.ptr()); - return { String::number(list_argv->values().size()) }; - } - return { "1" }; - } - return { "0" }; - default: - return { "" }; - } -} - -TildeValue::~TildeValue() -{ -} -Vector<String> TildeValue::resolve_as_list(RefPtr<Shell> shell) -{ - StringBuilder builder; - builder.append("~"); - builder.append(m_username); - - if (!shell) - return { builder.to_string() }; - - return { shell->expand_tilde(builder.to_string()) }; -} - -Result<NonnullRefPtr<Rewiring>, String> CloseRedirection::apply() const -{ - return adopt(*new Rewiring(fd, fd, Rewiring::Close::ImmediatelyCloseNew)); -} - -CloseRedirection::~CloseRedirection() -{ -} - -Result<NonnullRefPtr<Rewiring>, String> PathRedirection::apply() const -{ - auto check_fd_and_return = [my_fd = this->fd](int fd, const String& path) -> Result<NonnullRefPtr<Rewiring>, String> { - if (fd < 0) { - String error = strerror(errno); - dbgln("open() failed for '{}' with {}", path, error); - return error; - } - return adopt(*new Rewiring(fd, my_fd, Rewiring::Close::Old)); - }; - switch (direction) { - case AST::PathRedirection::WriteAppend: - return check_fd_and_return(open(path.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666), path); - - case AST::PathRedirection::Write: - return check_fd_and_return(open(path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666), path); - - case AST::PathRedirection::Read: - return check_fd_and_return(open(path.characters(), O_RDONLY), path); - - case AST::PathRedirection::ReadWrite: - return check_fd_and_return(open(path.characters(), O_RDWR | O_CREAT, 0666), path); - } - - ASSERT_NOT_REACHED(); -} - -PathRedirection::~PathRedirection() -{ -} - -FdRedirection::~FdRedirection() -{ -} - -Redirection::~Redirection() -{ -} - -} diff --git a/Shell/AST.h b/Shell/AST.h deleted file mode 100644 index 713f604340..0000000000 --- a/Shell/AST.h +++ /dev/null @@ -1,1324 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "Forward.h" -#include "Job.h" -#include "NodeVisitor.h" -#include <AK/Format.h> -#include <AK/InlineLinkedList.h> -#include <AK/NonnullRefPtr.h> -#include <AK/RefCounted.h> -#include <AK/RefPtr.h> -#include <AK/String.h> -#include <AK/Types.h> -#include <AK/Vector.h> -#include <LibLine/Editor.h> - -namespace Shell::AST { - -template<typename T, typename... Args> -static inline NonnullRefPtr<T> create(Args... args) -{ - return adopt(*new T(args...)); -} - -template<typename T> -static inline NonnullRefPtr<T> create(std::initializer_list<NonnullRefPtr<Value>> arg) -{ - return adopt(*new T(arg)); -} - -struct HighlightMetadata { - bool is_first_in_list { true }; -}; - -struct Position { - size_t start_offset { 0 }; - size_t end_offset { 0 }; - struct Line { - size_t line_number { 0 }; - size_t line_column { 0 }; - - bool operator==(const Line& other) const - { - return line_number == other.line_number && line_column == other.line_column; - } - } start_line, end_line; - - bool contains(size_t offset) const { return start_offset <= offset && offset <= end_offset; } -}; - -struct FdRedirection; -struct Rewiring : public RefCounted<Rewiring> { - int old_fd { -1 }; - int new_fd { -1 }; - FdRedirection* other_pipe_end { nullptr }; - enum class Close { - None, - Old, - New, - RefreshNew, - RefreshOld, - ImmediatelyCloseNew, - } fd_action { Close::None }; - - Rewiring(int source, int dest, Close close = Close::None) - : old_fd(source) - , new_fd(dest) - , fd_action(close) - { - } - - Rewiring(int source, int dest, FdRedirection* other_end, Close close) - : old_fd(source) - , new_fd(dest) - , other_pipe_end(other_end) - , fd_action(close) - { - } -}; - -struct Redirection : public RefCounted<Redirection> { - virtual Result<NonnullRefPtr<Rewiring>, String> apply() const = 0; - virtual ~Redirection(); - virtual bool is_path_redirection() const { return false; } - virtual bool is_fd_redirection() const { return false; } - virtual bool is_close_redirection() const { return false; } -}; - -struct CloseRedirection : public Redirection { - int fd { -1 }; - - virtual Result<NonnullRefPtr<Rewiring>, String> apply() const override; - virtual ~CloseRedirection(); - CloseRedirection(int fd) - : fd(fd) - { - } - -private: - virtual bool is_close_redirection() const override { return true; } -}; - -struct PathRedirection : public Redirection { - String path; - int fd { -1 }; - enum { - Read, - Write, - WriteAppend, - ReadWrite, - } direction { Read }; - - static NonnullRefPtr<PathRedirection> create(String path, int fd, decltype(direction) direction) - { - return adopt(*new PathRedirection(move(path), fd, direction)); - } - - virtual Result<NonnullRefPtr<Rewiring>, String> apply() const override; - virtual ~PathRedirection(); - -private: - PathRedirection(String path, int fd, decltype(direction) direction) - : path(move(path)) - , fd(fd) - , direction(direction) - { - } - - virtual bool is_path_redirection() const override { return true; } -}; - -struct FdRedirection : public Redirection { -public: - static NonnullRefPtr<FdRedirection> create(int old_fd, int new_fd, Rewiring::Close close) - { - return adopt(*new FdRedirection(old_fd, new_fd, close)); - } - - static NonnullRefPtr<FdRedirection> create(int old_fd, int new_fd, FdRedirection* pipe_end, Rewiring::Close close) - { - return adopt(*new FdRedirection(old_fd, new_fd, pipe_end, close)); - } - - virtual ~FdRedirection(); - - virtual Result<NonnullRefPtr<Rewiring>, String> apply() const override - { - return adopt(*new Rewiring(old_fd, new_fd, other_pipe_end, action)); - } - - int old_fd { -1 }; - int new_fd { -1 }; - FdRedirection* other_pipe_end { nullptr }; - Rewiring::Close action { Rewiring::Close::None }; - -private: - FdRedirection(int source, int dest, Rewiring::Close close) - : FdRedirection(source, dest, nullptr, close) - { - } - - FdRedirection(int old_fd, int new_fd, FdRedirection* pipe_end, Rewiring::Close close) - : old_fd(old_fd) - , new_fd(new_fd) - , other_pipe_end(pipe_end) - , action(close) - { - } - - virtual bool is_fd_redirection() const override { return true; } -}; - -class Pipeline : public RefCounted<Pipeline> { -public: - pid_t pgid { -1 }; -}; - -struct NodeWithAction { - mutable NonnullRefPtr<Node> node; - enum Action { - And, - Or, - Sequence, - } action; - - NodeWithAction(Node& node, Action action) - : node(node) - , action(action) - { - } -}; - -struct Command { - Vector<String> argv; - NonnullRefPtrVector<Redirection> redirections; - 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; - Optional<Position> position; -}; - -struct HitTestResult { - RefPtr<Node> matching_node; - RefPtr<Node> closest_node_with_semantic_meaning; // This is used if matching_node is a bareword - RefPtr<Node> closest_command_node; // This is used if matching_node is a bareword, and it is not the first in a list -}; - -class Value : public RefCounted<Value> { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) = 0; - virtual Vector<Command> resolve_as_commands(RefPtr<Shell>); - virtual NonnullRefPtr<Value> resolve_without_cast(RefPtr<Shell>) { return *this; } - virtual ~Value(); - virtual bool is_command() const { return false; } - virtual bool is_glob() const { return false; } - virtual bool is_job() const { return false; } - virtual bool is_list() const { return false; } - virtual bool is_string() const { return false; } - virtual bool is_list_without_resolution() const { return false; } -}; - -class CommandValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual Vector<Command> resolve_as_commands(RefPtr<Shell>) override; - virtual ~CommandValue(); - virtual bool is_command() const override { return true; } - CommandValue(Command command) - : m_command(move(command)) - { - } - - CommandValue(Vector<String> argv, Position position) - : m_command({ move(argv), {}, true, false, true, false, nullptr, {}, move(position) }) - { - } - -private: - Command m_command; -}; - -class CommandSequenceValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual Vector<Command> resolve_as_commands(RefPtr<Shell>) override; - virtual ~CommandSequenceValue(); - virtual bool is_command() const override { return true; } - CommandSequenceValue(Vector<Command> commands) - : m_contained_values(move(commands)) - { - } - -private: - Vector<Command> m_contained_values; -}; - -class JobValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override { ASSERT_NOT_REACHED(); } - virtual Vector<Command> resolve_as_commands(RefPtr<Shell>) override { ASSERT_NOT_REACHED(); } - virtual ~JobValue(); - virtual bool is_job() const override { return true; } - JobValue(RefPtr<Job> job) - : m_job(move(job)) - { - } - - const RefPtr<Job> job() const { return m_job; } - -private: - RefPtr<Job> m_job; -}; - -class ListValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual NonnullRefPtr<Value> resolve_without_cast(RefPtr<Shell>) override; - virtual ~ListValue(); - virtual bool is_list() const override { return true; } - virtual bool is_list_without_resolution() const override { return true; } - ListValue(Vector<String> values); - ListValue(Vector<NonnullRefPtr<Value>> values) - : m_contained_values(move(static_cast<NonnullRefPtrVector<Value>&>(values))) - { - } - ListValue(NonnullRefPtrVector<Value> values) - : m_contained_values(move(values)) - { - } - - const NonnullRefPtrVector<Value>& values() const { return m_contained_values; } - NonnullRefPtrVector<Value>& values() { return m_contained_values; } - -private: - NonnullRefPtrVector<Value> m_contained_values; -}; - -class StringValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual ~StringValue(); - virtual bool is_string() const override { return m_split.is_null(); } - virtual bool is_list() const override { return !m_split.is_null(); } - StringValue(String string, String split_by = {}, bool keep_empty = false) - : m_string(move(string)) - , m_split(move(split_by)) - , m_keep_empty(keep_empty) - { - } - -private: - String m_string; - String m_split; - bool m_keep_empty { false }; -}; - -class GlobValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual ~GlobValue(); - virtual bool is_glob() const override { return true; } - GlobValue(String glob, Position position) - : m_glob(move(glob)) - , m_generation_position(move(position)) - { - } - -private: - String m_glob; - Position m_generation_position; -}; - -class SimpleVariableValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual NonnullRefPtr<Value> resolve_without_cast(RefPtr<Shell>) override; - virtual ~SimpleVariableValue(); - SimpleVariableValue(String name) - : m_name(move(name)) - { - } - -private: - String m_name; -}; - -class SpecialVariableValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual ~SpecialVariableValue(); - SpecialVariableValue(char name) - : m_name(name) - { - } - -private: - char m_name { -1 }; -}; - -class TildeValue final : public Value { -public: - virtual Vector<String> resolve_as_list(RefPtr<Shell>) override; - virtual ~TildeValue(); - virtual bool is_string() const override { return true; } - TildeValue(String name) - : m_username(move(name)) - { - } - -private: - String m_username; -}; - -class Node : public RefCounted<Node> { -public: - virtual void dump(int level) const = 0; - virtual void for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(NonnullRefPtr<Value>)> callback); - virtual RefPtr<Value> run(RefPtr<Shell>) = 0; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) = 0; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&); - Vector<Line::CompletionSuggestion> complete_for_editor(Shell& shell, size_t offset); - virtual HitTestResult hit_test_position(size_t offset) - { - if (m_position.contains(offset)) - return { this, nullptr, nullptr }; - return { nullptr, nullptr, nullptr }; - } - virtual String class_name() const { return "Node"; } - Node(Position); - virtual ~Node(); - - virtual bool is_bareword() const { return false; } - virtual bool is_command() const { return false; } - virtual bool is_execute() const { return false; } - virtual bool is_glob() const { return false; } - virtual bool is_tilde() const { return false; } - virtual bool is_variable_decls() const { return false; } - virtual bool is_simple_variable() const { return false; } - virtual bool is_syntax_error() const { return m_is_syntax_error; } - - virtual bool is_list() const { return false; } - virtual bool would_execute() const { return false; } - virtual bool should_override_execution_in_current_process() const { return false; } - - const Position& position() const { return m_position; } - void set_is_syntax_error(const SyntaxError& error_node) - { - if (!m_is_syntax_error) { - m_is_syntax_error = true; - m_syntax_error_node = error_node; - } - } - virtual const SyntaxError& syntax_error_node() const - { - ASSERT(is_syntax_error()); - return *m_syntax_error_node; - } - - virtual RefPtr<Node> leftmost_trivial_literal() const { return nullptr; } - - Vector<Command> to_lazy_evaluated_commands(RefPtr<Shell> shell); - - virtual void visit(NodeVisitor&) { ASSERT_NOT_REACHED(); } - virtual void visit(NodeVisitor& visitor) const { const_cast<Node*>(this)->visit(visitor); } - - enum class Kind : u32 { - And, - ListConcatenate, - Background, - BarewordLiteral, - BraceExpansion, - CastToCommand, - CastToList, - CloseFdRedirection, - CommandLiteral, - Comment, - ContinuationControl, - DynamicEvaluate, - DoubleQuotedString, - Fd2FdRedirection, - FunctionDeclaration, - ForLoop, - Glob, - Execute, - IfCond, - Join, - MatchExpr, - Or, - Pipe, - Range, - ReadRedirection, - ReadWriteRedirection, - Sequence, - Subshell, - SimpleVariable, - SpecialVariable, - Juxtaposition, - StringLiteral, - StringPartCompose, - SyntaxError, - Tilde, - VariableDeclarations, - WriteAppendRedirection, - WriteRedirection, - __Count, - }; - - virtual Kind kind() const = 0; - -protected: - Position m_position; - bool m_is_syntax_error { false }; - RefPtr<const SyntaxError> m_syntax_error_node; -}; - -#define NODE(name) \ - virtual String class_name() const override { return #name; } \ - virtual Kind kind() const override { return Kind::name; } - -class PathRedirectionNode : public Node { -public: - PathRedirectionNode(Position, int, NonnullRefPtr<Node>); - virtual ~PathRedirectionNode(); - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual HitTestResult hit_test_position(size_t offset) override; - virtual bool is_command() const override { return true; } - virtual bool is_list() const override { return true; } - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& path() const { return m_path; } - int fd() const { return m_fd; } - -protected: - int m_fd { -1 }; - NonnullRefPtr<Node> m_path; -}; - -class And final : public Node { -public: - And(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>, Position and_position); - virtual ~And(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - const Position& and_position() const { return m_and_position; } - -private: - NODE(And); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; - Position m_and_position; -}; - -class ListConcatenate final : public Node { -public: - ListConcatenate(Position, Vector<NonnullRefPtr<Node>>); - virtual ~ListConcatenate(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - const Vector<NonnullRefPtr<Node>> list() const { return m_list; } - -private: - NODE(ListConcatenate); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_list() const override { return true; } - virtual RefPtr<Node> leftmost_trivial_literal() const override; - - Vector<NonnullRefPtr<Node>> m_list; -}; - -class Background final : public Node { -public: - Background(Position, NonnullRefPtr<Node>); - virtual ~Background(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& command() const { return m_command; } - -private: - NODE(Background); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - NonnullRefPtr<Node> m_command; -}; - -class BarewordLiteral final : public Node { -public: - BarewordLiteral(Position, String); - virtual ~BarewordLiteral(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const String& text() const { return m_text; } - -private: - NODE(BarewordLiteral); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual bool is_bareword() const override { return true; } - virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; } - - String m_text; -}; - -class BraceExpansion final : public Node { -public: - BraceExpansion(Position, NonnullRefPtrVector<Node>); - virtual ~BraceExpansion(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtrVector<Node>& entries() const { return m_entries; } - -private: - NODE(BraceExpansion); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - NonnullRefPtrVector<Node> m_entries; -}; - -class CastToCommand final : public Node { -public: - CastToCommand(Position, NonnullRefPtr<Node>); - virtual ~CastToCommand(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& inner() const { return m_inner; } - -private: - NODE(CastToCommand); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual bool is_command() const override { return true; } - virtual bool is_list() const override { return true; } - virtual RefPtr<Node> leftmost_trivial_literal() const override; - - NonnullRefPtr<Node> m_inner; -}; - -class CastToList final : public Node { -public: - CastToList(Position, RefPtr<Node>); - virtual ~CastToList(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const RefPtr<Node>& inner() const { return m_inner; } - -private: - NODE(CastToList); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_list() const override { return true; } - virtual RefPtr<Node> leftmost_trivial_literal() const override; - - RefPtr<Node> m_inner; -}; - -class CloseFdRedirection final : public Node { -public: - CloseFdRedirection(Position, int); - virtual ~CloseFdRedirection(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - int fd() const { return m_fd; } - -private: - NODE(CloseFdRedirection); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual bool is_command() const override { return true; } - - int m_fd { -1 }; -}; - -class CommandLiteral final : public Node { -public: - CommandLiteral(Position, Command); - virtual ~CommandLiteral(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const Command& command() const { return m_command; } - -private: - NODE(CommandLiteral); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override { ASSERT_NOT_REACHED(); } - virtual bool is_command() const override { return true; } - virtual bool is_list() const override { return true; } - - Command m_command; -}; - -class Comment : public Node { -public: - Comment(Position, String); - virtual ~Comment(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const String& text() const { return m_text; } - -private: - NODE(Comment); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - - String m_text; -}; - -class ContinuationControl final : public Node { -public: - enum ContinuationKind { - Break, - Continue, - }; - ContinuationControl(Position position, ContinuationKind kind) - : Node(move(position)) - , m_kind(kind) - { - } - virtual ~ContinuationControl() { } - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - ContinuationKind continuation_kind() const { return m_kind; } - -private: - NODE(ContinuationControl); - - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - - ContinuationKind m_kind { ContinuationKind::Break }; -}; - -class DynamicEvaluate final : public Node { -public: - DynamicEvaluate(Position, NonnullRefPtr<Node>); - virtual ~DynamicEvaluate(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& inner() const { return m_inner; } - -private: - NODE(DynamicEvaluate); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - virtual bool is_bareword() const override { return m_inner->is_bareword(); } - virtual bool is_command() const override { return is_list(); } - virtual bool is_execute() const override { return true; } - virtual bool is_glob() const override { return m_inner->is_glob(); } - virtual bool is_list() const override - { - return m_inner->is_list() || m_inner->is_command() || m_inner->is_glob(); // Anything that generates a list. - } - - NonnullRefPtr<Node> m_inner; -}; - -class DoubleQuotedString final : public Node { -public: - DoubleQuotedString(Position, RefPtr<Node>); - virtual ~DoubleQuotedString(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const RefPtr<Node>& inner() const { return m_inner; } - -private: - NODE(DoubleQuotedString); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - RefPtr<Node> m_inner; -}; - -class Fd2FdRedirection final : public Node { -public: - Fd2FdRedirection(Position, int, int); - virtual ~Fd2FdRedirection(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - int source_fd() const { return m_old_fd; } - int dest_fd() const { return m_new_fd; } - -private: - NODE(Fd2FdRedirection); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual bool is_command() const override { return true; } - - int m_old_fd { -1 }; - int m_new_fd { -1 }; -}; - -class FunctionDeclaration final : public Node { -public: - struct NameWithPosition { - String name; - Position position; - }; - FunctionDeclaration(Position, NameWithPosition name, Vector<NameWithPosition> argument_names, RefPtr<AST::Node> body); - virtual ~FunctionDeclaration(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NameWithPosition& name() const { return m_name; } - const Vector<NameWithPosition> arguments() const { return m_arguments; } - const RefPtr<Node>& block() const { return m_block; } - -private: - NODE(FunctionDeclaration); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual bool would_execute() const override { return true; } - virtual bool should_override_execution_in_current_process() const override { return true; } - - NameWithPosition m_name; - Vector<NameWithPosition> m_arguments; - RefPtr<AST::Node> m_block; -}; - -class ForLoop final : public Node { -public: - ForLoop(Position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position = {}); - virtual ~ForLoop(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const String& variable_name() const { return m_variable_name; } - const RefPtr<Node>& iterated_expression() const { return m_iterated_expression; } - const RefPtr<Node>& block() const { return m_block; } - const Optional<Position> in_keyword_position() const { return m_in_kw_position; } - -private: - NODE(ForLoop); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool would_execute() const override { return true; } - - String m_variable_name; - RefPtr<AST::Node> m_iterated_expression; - RefPtr<AST::Node> m_block; - Optional<Position> m_in_kw_position; -}; - -class Glob final : public Node { -public: - Glob(Position, String); - virtual ~Glob(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const String& text() const { return m_text; } - -private: - NODE(Glob); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual bool is_glob() const override { return true; } - virtual bool is_list() const override { return true; } - - String m_text; -}; - -class Execute final : public Node { -public: - Execute(Position, NonnullRefPtr<Node>, bool capture_stdout = false); - virtual ~Execute(); - void capture_stdout() { m_capture_stdout = true; } - NonnullRefPtr<Node>& command() { return m_command; } - virtual void for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(NonnullRefPtr<Value>)> callback) override; - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& command() const { return m_command; } - bool does_capture_stdout() const { return m_capture_stdout; } - -private: - NODE(Execute); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual bool is_execute() const override { return true; } - virtual bool would_execute() const override { return true; } - - NonnullRefPtr<Node> m_command; - bool m_capture_stdout { false }; -}; - -class IfCond final : public Node { -public: - IfCond(Position, Optional<Position> else_position, NonnullRefPtr<AST::Node> cond_expr, RefPtr<AST::Node> true_branch, RefPtr<AST::Node> false_branch); - virtual ~IfCond(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& condition() const { return m_condition; } - const RefPtr<Node>& true_branch() const { return m_true_branch; } - const RefPtr<Node>& false_branch() const { return m_false_branch; } - const Optional<Position> else_position() const { return m_else_position; } - -private: - NODE(IfCond); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool would_execute() const override { return true; } - - NonnullRefPtr<AST::Node> m_condition; - RefPtr<AST::Node> m_true_branch; - RefPtr<AST::Node> m_false_branch; - - Optional<Position> m_else_position; -}; - -class Join final : public Node { -public: - Join(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>); - virtual ~Join(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - -private: - NODE(Join); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_command() const override { return true; } - virtual bool is_list() const override { return true; } - virtual RefPtr<Node> leftmost_trivial_literal() const override; - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; -}; - -struct MatchEntry { - NonnullRefPtrVector<Node> options; - Optional<Vector<String>> match_names; - Optional<Position> match_as_position; - Vector<Position> pipe_positions; - RefPtr<Node> body; -}; - -class MatchExpr final : public Node { -public: - MatchExpr(Position, NonnullRefPtr<Node> expr, String name, Optional<Position> as_position, Vector<MatchEntry> entries); - virtual ~MatchExpr(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& matched_expr() const { return m_matched_expr; } - const String& expr_name() const { return m_expr_name; } - const Vector<MatchEntry>& entries() const { return m_entries; } - const Optional<Position>& as_position() const { return m_as_position; } - -private: - NODE(MatchExpr); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool would_execute() const override { return true; } - - NonnullRefPtr<Node> m_matched_expr; - String m_expr_name; - Optional<Position> m_as_position; - Vector<MatchEntry> m_entries; -}; - -class Or final : public Node { -public: - Or(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>, Position or_position); - virtual ~Or(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - const Position& or_position() const { return m_or_position; } - -private: - NODE(Or); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; - Position m_or_position; -}; - -class Pipe final : public Node { -public: - Pipe(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>); - virtual ~Pipe(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - -private: - NODE(Pipe); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_command() const override { return true; } - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; -}; - -class Range final : public Node { -public: - Range(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>); - virtual ~Range(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& start() const { return m_start; } - const NonnullRefPtr<Node>& end() const { return m_end; } - -private: - NODE(Range); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - NonnullRefPtr<Node> m_start; - NonnullRefPtr<Node> m_end; -}; - -class ReadRedirection final : public PathRedirectionNode { -public: - ReadRedirection(Position, int, NonnullRefPtr<Node>); - virtual ~ReadRedirection(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - -private: - NODE(ReadRedirection); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; -}; - -class ReadWriteRedirection final : public PathRedirectionNode { -public: - ReadWriteRedirection(Position, int, NonnullRefPtr<Node>); - virtual ~ReadWriteRedirection(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - -private: - NODE(ReadWriteRedirection); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; -}; - -class Sequence final : public Node { -public: - Sequence(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>, Position separator_position); - virtual ~Sequence(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - - const Position& separator_position() const { return m_separator_position; } - -private: - NODE(Sequence); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_list() const override { return true; } - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; - Position m_separator_position; -}; - -class Subshell final : public Node { -public: - Subshell(Position, RefPtr<Node> block); - virtual ~Subshell(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const RefPtr<Node>& block() const { return m_block; } - -private: - NODE(Subshell); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool would_execute() const override { return false; } - - RefPtr<AST::Node> m_block; -}; - -class SimpleVariable final : public Node { -public: - SimpleVariable(Position, String); - virtual ~SimpleVariable(); - - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - const String& name() const { return m_name; } - -private: - NODE(SimpleVariable); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_simple_variable() const override { return true; } - - String m_name; -}; - -class SpecialVariable final : public Node { -public: - SpecialVariable(Position, char); - virtual ~SpecialVariable(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - char name() const { return m_name; } - -private: - NODE(SpecialVariable); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual HitTestResult hit_test_position(size_t) override; - - char m_name { -1 }; -}; - -class Juxtaposition final : public Node { -public: - Juxtaposition(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>); - virtual ~Juxtaposition(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - -private: - NODE(Juxtaposition); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; -}; - -class StringLiteral final : public Node { -public: - StringLiteral(Position, String); - virtual ~StringLiteral(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const String& text() const { return m_text; } - -private: - NODE(StringLiteral); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; }; - - String m_text; -}; - -class StringPartCompose final : public Node { -public: - StringPartCompose(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>); - virtual ~StringPartCompose(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const NonnullRefPtr<Node>& left() const { return m_left; } - const NonnullRefPtr<Node>& right() const { return m_right; } - -private: - NODE(StringPartCompose); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - - NonnullRefPtr<Node> m_left; - NonnullRefPtr<Node> m_right; -}; - -class SyntaxError final : public Node { -public: - SyntaxError(Position, String, bool is_continuable = false); - virtual ~SyntaxError(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const String& error_text() const { return m_syntax_error_text; } - bool is_continuable() const { return m_is_continuable; } - -private: - NODE(SyntaxError); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override { return { nullptr, nullptr, nullptr }; } - virtual bool is_syntax_error() const override { return true; } - virtual const SyntaxError& syntax_error_node() const override; - - String m_syntax_error_text; - bool m_is_continuable { false }; -}; - -class Tilde final : public Node { -public: - Tilde(Position, String); - virtual ~Tilde(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - String text() const; - -private: - NODE(Tilde); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_tilde() const override { return true; } - - String m_username; -}; - -class VariableDeclarations final : public Node { -public: - struct Variable { - NonnullRefPtr<Node> name; - NonnullRefPtr<Node> value; - }; - VariableDeclarations(Position, Vector<Variable> variables); - virtual ~VariableDeclarations(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - - const Vector<Variable>& variables() const { return m_variables; } - -private: - NODE(VariableDeclarations); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; - virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; - virtual HitTestResult hit_test_position(size_t) override; - virtual bool is_variable_decls() const override { return true; } - - Vector<Variable> m_variables; -}; - -class WriteAppendRedirection final : public PathRedirectionNode { -public: - WriteAppendRedirection(Position, int, NonnullRefPtr<Node>); - virtual ~WriteAppendRedirection(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - -private: - NODE(WriteAppendRedirection); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; -}; - -class WriteRedirection final : public PathRedirectionNode { -public: - WriteRedirection(Position, int, NonnullRefPtr<Node>); - virtual ~WriteRedirection(); - virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } - -private: - NODE(WriteRedirection); - virtual void dump(int level) const override; - virtual RefPtr<Value> run(RefPtr<Shell>) override; -}; - -} - -namespace AK { - -template<> -struct Formatter<Shell::AST::Command> : StandardFormatter { - Formatter() { } - explicit Formatter(StandardFormatter formatter) - : StandardFormatter(formatter) - { - } - - void format(FormatBuilder&, const Shell::AST::Command& value); -}; - -} diff --git a/Shell/Builtin.cpp b/Shell/Builtin.cpp deleted file mode 100644 index d3a48b5089..0000000000 --- a/Shell/Builtin.cpp +++ /dev/null @@ -1,929 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "AST.h" -#include "Shell.h" -#include <AK/LexicalPath.h> -#include <AK/ScopeGuard.h> -#include <LibCore/ArgsParser.h> -#include <LibCore/EventLoop.h> -#include <LibCore/File.h> -#include <inttypes.h> -#include <signal.h> -#include <sys/wait.h> -#include <unistd.h> - -extern char** environ; - -namespace Shell { - -int Shell::builtin_alias(int argc, const char** argv) -{ - Vector<const char*> arguments; - - Core::ArgsParser parser; - parser.add_positional_argument(arguments, "List of name[=values]'s", "name[=value]", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (arguments.is_empty()) { - for (auto& alias : m_aliases) - printf("%s=%s\n", escape_token(alias.key).characters(), escape_token(alias.value).characters()); - return 0; - } - - bool fail = false; - for (auto& argument : arguments) { - auto parts = String { argument }.split_limit('=', 2, true); - if (parts.size() == 1) { - auto alias = m_aliases.get(parts[0]); - if (alias.has_value()) { - printf("%s=%s\n", escape_token(parts[0]).characters(), escape_token(alias.value()).characters()); - } else { - fail = true; - } - } else { - m_aliases.set(parts[0], parts[1]); - add_entry_to_cache(parts[0]); - } - } - - return fail ? 1 : 0; -} - -int Shell::builtin_bg(int argc, const char** argv) -{ - int job_id = -1; - - Core::ArgsParser parser; - parser.add_positional_argument(job_id, "Job ID to run in background", "job-id", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (job_id == -1 && !jobs.is_empty()) - job_id = find_last_job_id(); - - auto* job = const_cast<Job*>(find_job(job_id)); - - if (!job) { - if (job_id == -1) { - fprintf(stderr, "bg: no current job\n"); - } else { - fprintf(stderr, "bg: job with id %d not found\n", job_id); - } - return 1; - } - - job->set_running_in_background(true); - job->set_should_announce_exit(true); - job->set_is_suspended(false); - - dbgln("Resuming {} ({})", job->pid(), job->cmd()); - warnln("Resuming job {} - {}", job->job_id(), job->cmd().characters()); - - // Try using the PGID, but if that fails, just use the PID. - if (killpg(job->pgid(), SIGCONT) < 0) { - if (kill(job->pid(), SIGCONT) < 0) { - perror("kill"); - return 1; - } - } - - return 0; -} - -int Shell::builtin_cd(int argc, const char** argv) -{ - const char* arg_path = nullptr; - - Core::ArgsParser parser; - parser.add_positional_argument(arg_path, "Path to change to", "path", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - String new_path; - - if (!arg_path) { - new_path = home; - } else { - if (strcmp(arg_path, "-") == 0) { - char* oldpwd = getenv("OLDPWD"); - if (oldpwd == nullptr) - return 1; - new_path = oldpwd; - } else if (arg_path[0] == '/') { - new_path = argv[1]; - } else { - StringBuilder builder; - builder.append(cwd); - builder.append('/'); - builder.append(arg_path); - new_path = builder.to_string(); - } - } - - auto real_path = Core::File::real_path_for(new_path); - if (real_path.is_empty()) { - fprintf(stderr, "Invalid path '%s'\n", new_path.characters()); - return 1; - } - - if (cd_history.is_empty() || cd_history.last() != real_path) - cd_history.enqueue(real_path); - - const char* path = real_path.characters(); - - int rc = chdir(path); - if (rc < 0) { - if (errno == ENOTDIR) { - fprintf(stderr, "Not a directory: %s\n", path); - } else { - fprintf(stderr, "chdir(%s) failed: %s\n", path, strerror(errno)); - } - return 1; - } - setenv("OLDPWD", cwd.characters(), 1); - cwd = real_path; - setenv("PWD", cwd.characters(), 1); - return 0; -} - -int Shell::builtin_cdh(int argc, const char** argv) -{ - int index = -1; - - Core::ArgsParser parser; - parser.add_positional_argument(index, "Index of the cd history entry (leave out for a list)", "index", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (index == -1) { - if (cd_history.is_empty()) { - fprintf(stderr, "cdh: no history available\n"); - return 0; - } - - for (ssize_t i = cd_history.size() - 1; i >= 0; --i) - printf("%lu: %s\n", cd_history.size() - i, cd_history.at(i).characters()); - return 0; - } - - if (index < 1 || (size_t)index > cd_history.size()) { - fprintf(stderr, "cdh: history index out of bounds: %d not in (0, %zu)\n", index, cd_history.size()); - return 1; - } - - const char* path = cd_history.at(cd_history.size() - index).characters(); - const char* cd_args[] = { "cd", path, nullptr }; - return Shell::builtin_cd(2, cd_args); -} - -int Shell::builtin_dirs(int argc, const char** argv) -{ - // The first directory in the stack is ALWAYS the current directory - directory_stack.at(0) = cwd.characters(); - - bool clear = false; - bool print = false; - bool number_when_printing = false; - char separator = ' '; - - Vector<const char*> paths; - - Core::ArgsParser parser; - parser.add_option(clear, "Clear the directory stack", "clear", 'c'); - parser.add_option(print, "Print directory entries one per line", "print", 'p'); - parser.add_option(number_when_printing, "Number the directories in the stack when printing", "number", 'v'); - parser.add_positional_argument(paths, "Extra paths to put on the stack", "path", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - // -v implies -p - print = print || number_when_printing; - - if (print) { - if (!paths.is_empty()) { - fprintf(stderr, "dirs: 'print' and 'number' are not allowed when any path is specified"); - return 1; - } - separator = '\n'; - } - - if (clear) { - for (size_t i = 1; i < directory_stack.size(); i++) - directory_stack.remove(i); - } - - for (auto& path : paths) - directory_stack.append(path); - - if (print || (!clear && paths.is_empty())) { - int index = 0; - for (auto& directory : directory_stack) { - if (number_when_printing) - printf("%d ", index++); - print_path(directory); - fputc(separator, stdout); - } - } - - return 0; -} - -int Shell::builtin_exec(int argc, const char** argv) -{ - if (argc < 2) { - fprintf(stderr, "Shell: No command given to exec\n"); - return 1; - } - - Vector<const char*> argv_vector; - argv_vector.append(argv + 1, argc - 1); - argv_vector.append(nullptr); - - execute_process(move(argv_vector)); -} - -int Shell::builtin_exit(int argc, const char** argv) -{ - int exit_code = 0; - Core::ArgsParser parser; - parser.add_positional_argument(exit_code, "Exit code", "code", Core::ArgsParser::Required::No); - if (!parser.parse(argc, const_cast<char**>(argv))) - return 1; - - if (m_is_interactive) { - if (!jobs.is_empty()) { - if (!m_should_ignore_jobs_on_next_exit) { - fprintf(stderr, "Shell: You have %zu active job%s, run 'exit' again to really exit.\n", jobs.size(), jobs.size() > 1 ? "s" : ""); - m_should_ignore_jobs_on_next_exit = true; - return 1; - } - } - } - stop_all_jobs(); - m_editor->save_history(get_history_path()); - if (m_is_interactive) - printf("Good-bye!\n"); - exit(exit_code); - return 0; -} - -int Shell::builtin_export(int argc, const char** argv) -{ - Vector<const char*> vars; - - Core::ArgsParser parser; - parser.add_positional_argument(vars, "List of variable[=value]'s", "values", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (vars.is_empty()) { - for (size_t i = 0; environ[i]; ++i) - puts(environ[i]); - return 0; - } - - for (auto& value : vars) { - auto parts = String { value }.split_limit('=', 2); - - if (parts.size() == 1) { - auto value = lookup_local_variable(parts[0]); - if (value) { - auto values = value->resolve_as_list(*this); - StringBuilder builder; - builder.join(" ", values); - parts.append(builder.to_string()); - } else { - // Ignore the export. - continue; - } - } - - int setenv_return = setenv(parts[0].characters(), parts[1].characters(), 1); - - if (setenv_return != 0) { - perror("setenv"); - return 1; - } - - if (parts[0] == "PATH") - cache_path(); - } - - return 0; -} - -int Shell::builtin_glob(int argc, const char** argv) -{ - Vector<const char*> globs; - Core::ArgsParser parser; - parser.add_positional_argument(globs, "Globs to resolve", "glob"); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - for (auto& glob : globs) { - for (auto& expanded : expand_globs(glob, cwd)) - outln("{}", expanded); - } - - return 0; -} - -int Shell::builtin_fg(int argc, const char** argv) -{ - int job_id = -1; - - Core::ArgsParser parser; - parser.add_positional_argument(job_id, "Job ID to bring to foreground", "job-id", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (job_id == -1 && !jobs.is_empty()) - job_id = find_last_job_id(); - - RefPtr<Job> job = find_job(job_id); - - if (!job) { - if (job_id == -1) { - fprintf(stderr, "fg: no current job\n"); - } else { - fprintf(stderr, "fg: job with id %d not found\n", job_id); - } - return 1; - } - - job->set_running_in_background(false); - job->set_is_suspended(false); - - dbgln("Resuming {} ({})", job->pid(), job->cmd()); - warnln("Resuming job {} - {}", job->job_id(), job->cmd().characters()); - - tcsetpgrp(STDOUT_FILENO, job->pgid()); - tcsetpgrp(STDIN_FILENO, job->pgid()); - - // Try using the PGID, but if that fails, just use the PID. - if (killpg(job->pgid(), SIGCONT) < 0) { - if (kill(job->pid(), SIGCONT) < 0) { - perror("kill"); - return 1; - } - } - - block_on_job(job); - - if (job->exited()) - return job->exit_code(); - else - return 0; -} - -int Shell::builtin_disown(int argc, const char** argv) -{ - Vector<const char*> str_job_ids; - - Core::ArgsParser parser; - parser.add_positional_argument(str_job_ids, "Id of the jobs to disown (omit for current job)", "job_ids", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - Vector<size_t> job_ids; - for (auto& job_id : str_job_ids) { - auto id = StringView(job_id).to_uint(); - if (id.has_value()) - job_ids.append(id.value()); - else - fprintf(stderr, "disown: Invalid job id %s\n", job_id); - } - - if (job_ids.is_empty()) - job_ids.append(find_last_job_id()); - - Vector<const Job*> jobs_to_disown; - - for (auto id : job_ids) { - auto job = find_job(id); - if (!job) - fprintf(stderr, "disown: job with id %zu not found\n", id); - else - jobs_to_disown.append(job); - } - - if (jobs_to_disown.is_empty()) { - if (str_job_ids.is_empty()) - fprintf(stderr, "disown: no current job\n"); - // An error message has already been printed about the nonexistence of each listed job. - return 1; - } - - for (auto job : jobs_to_disown) { - job->deactivate(); - - if (!job->is_running_in_background()) - fprintf(stderr, "disown warning: job %" PRIu64 " is currently not running, 'kill -%d %d' to make it continue\n", job->job_id(), SIGCONT, job->pid()); - - jobs.remove(job->pid()); - } - - return 0; -} - -int Shell::builtin_history(int, const char**) -{ - for (size_t i = 0; i < m_editor->history().size(); ++i) { - printf("%6zu %s\n", i, m_editor->history()[i].entry.characters()); - } - return 0; -} - -int Shell::builtin_jobs(int argc, const char** argv) -{ - bool list = false, show_pid = false; - - Core::ArgsParser parser; - parser.add_option(list, "List all information about jobs", "list", 'l'); - parser.add_option(show_pid, "Display the PID of the jobs", "pid", 'p'); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - Job::PrintStatusMode mode = Job::PrintStatusMode::Basic; - - if (show_pid) - mode = Job::PrintStatusMode::OnlyPID; - - if (list) - mode = Job::PrintStatusMode::ListAll; - - for (auto& it : jobs) { - if (!it.value->print_status(mode)) - return 1; - } - - return 0; -} - -int Shell::builtin_popd(int argc, const char** argv) -{ - if (directory_stack.size() <= 1) { - fprintf(stderr, "Shell: popd: directory stack empty\n"); - return 1; - } - - bool should_not_switch = false; - String path = directory_stack.take_last(); - - Core::ArgsParser parser; - parser.add_option(should_not_switch, "Do not switch dirs", "no-switch", 'n'); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - bool should_switch = !should_not_switch; - - // When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory. - if (argc == 1) { - int rc = chdir(path.characters()); - if (rc < 0) { - fprintf(stderr, "chdir(%s) failed: %s\n", path.characters(), strerror(errno)); - return 1; - } - - cwd = path; - return 0; - } - - LexicalPath lexical_path(path.characters()); - if (!lexical_path.is_valid()) { - fprintf(stderr, "LexicalPath failed to canonicalize '%s'\n", path.characters()); - return 1; - } - - const char* real_path = lexical_path.string().characters(); - - struct stat st; - int rc = stat(real_path, &st); - if (rc < 0) { - fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno)); - return 1; - } - - if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "Not a directory: %s\n", real_path); - return 1; - } - - if (should_switch) { - int rc = chdir(real_path); - if (rc < 0) { - fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno)); - return 1; - } - - cwd = lexical_path.string(); - } - - return 0; -} - -int Shell::builtin_pushd(int argc, const char** argv) -{ - StringBuilder path_builder; - bool should_switch = true; - - // From the BASH reference manual: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html - // With no arguments, pushd exchanges the top two directories and makes the new top the current directory. - if (argc == 1) { - if (directory_stack.size() < 2) { - fprintf(stderr, "pushd: no other directory\n"); - return 1; - } - - String dir1 = directory_stack.take_first(); - String dir2 = directory_stack.take_first(); - directory_stack.insert(0, dir2); - directory_stack.insert(1, dir1); - - int rc = chdir(dir2.characters()); - if (rc < 0) { - fprintf(stderr, "chdir(%s) failed: %s\n", dir2.characters(), strerror(errno)); - return 1; - } - - cwd = dir2; - - return 0; - } - - // Let's assume the user's typed in 'pushd <dir>' - if (argc == 2) { - directory_stack.append(cwd.characters()); - if (argv[1][0] == '/') { - path_builder.append(argv[1]); - } else { - path_builder.appendf("%s/%s", cwd.characters(), argv[1]); - } - } else if (argc == 3) { - directory_stack.append(cwd.characters()); - for (int i = 1; i < argc; i++) { - const char* arg = argv[i]; - - if (arg[0] != '-') { - if (arg[0] == '/') { - path_builder.append(arg); - } else - path_builder.appendf("%s/%s", cwd.characters(), arg); - } - - if (!strcmp(arg, "-n")) - should_switch = false; - } - } - - LexicalPath lexical_path(path_builder.to_string()); - if (!lexical_path.is_valid()) { - fprintf(stderr, "LexicalPath failed to canonicalize '%s'\n", path_builder.to_string().characters()); - return 1; - } - - const char* real_path = lexical_path.string().characters(); - - struct stat st; - int rc = stat(real_path, &st); - if (rc < 0) { - fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno)); - return 1; - } - - if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "Not a directory: %s\n", real_path); - return 1; - } - - if (should_switch) { - int rc = chdir(real_path); - if (rc < 0) { - fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno)); - return 1; - } - - cwd = lexical_path.string(); - } - - return 0; -} - -int Shell::builtin_pwd(int, const char**) -{ - print_path(cwd); - fputc('\n', stdout); - return 0; -} - -int Shell::builtin_setopt(int argc, const char** argv) -{ - if (argc == 1) { -#define __ENUMERATE_SHELL_OPTION(name, default_, description) \ - if (options.name) \ - fprintf(stderr, #name "\n"); - - ENUMERATE_SHELL_OPTIONS(); - -#undef __ENUMERATE_SHELL_OPTION - } - - Core::ArgsParser parser; -#define __ENUMERATE_SHELL_OPTION(name, default_, description) \ - bool name = false; \ - bool not_##name = false; \ - parser.add_option(name, "Enable: " description, #name, '\0'); \ - parser.add_option(not_##name, "Disable: " description, "no_" #name, '\0'); - - ENUMERATE_SHELL_OPTIONS(); - -#undef __ENUMERATE_SHELL_OPTION - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - -#define __ENUMERATE_SHELL_OPTION(name, default_, description) \ - if (name) \ - options.name = true; \ - if (not_##name) \ - options.name = false; - - ENUMERATE_SHELL_OPTIONS(); - -#undef __ENUMERATE_SHELL_OPTION - - return 0; -} - -int Shell::builtin_shift(int argc, const char** argv) -{ - int count = 1; - - Core::ArgsParser parser; - parser.add_positional_argument(count, "Shift count", "count", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (count < 1) - return 0; - - auto argv_ = lookup_local_variable("ARGV"); - if (!argv_) { - fprintf(stderr, "shift: ARGV is unset\n"); - return 1; - } - - if (!argv_->is_list()) - argv_ = adopt(*new AST::ListValue({ argv_.release_nonnull() })); - - auto& values = static_cast<AST::ListValue*>(argv_.ptr())->values(); - if ((size_t)count > values.size()) { - fprintf(stderr, "shift: shift count must not be greater than %zu\n", values.size()); - return 1; - } - - for (auto i = 0; i < count; ++i) - values.take_first(); - - return 0; -} - -int Shell::builtin_source(int argc, const char** argv) -{ - const char* file_to_source = nullptr; - Vector<const char*> args; - - Core::ArgsParser parser; - parser.add_positional_argument(file_to_source, "File to read commands from", "path"); - parser.add_positional_argument(args, "ARGV for the sourced file", "args", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv))) - return 1; - - Vector<String> string_argv; - for (auto& arg : args) - string_argv.append(arg); - - auto previous_argv = lookup_local_variable("ARGV"); - ScopeGuard guard { [&] { - if (!args.is_empty()) - set_local_variable("ARGV", move(previous_argv)); - } }; - - if (!args.is_empty()) - set_local_variable("ARGV", AST::create<AST::ListValue>(move(string_argv))); - - if (!run_file(file_to_source, true)) - return 126; - - return 0; -} - -int Shell::builtin_time(int argc, const char** argv) -{ - Vector<const char*> args; - - Core::ArgsParser parser; - parser.add_positional_argument(args, "Command to execute with arguments", "command", Core::ArgsParser::Required::Yes); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - AST::Command command; - for (auto& arg : args) - command.argv.append(arg); - - auto commands = expand_aliases({ move(command) }); - - Core::ElapsedTimer timer; - int exit_code = 1; - timer.start(); - for (auto& job : run_commands(commands)) { - block_on_job(job); - exit_code = job.exit_code(); - } - fprintf(stderr, "Time: %d ms\n", timer.elapsed()); - return exit_code; -} - -int Shell::builtin_umask(int argc, const char** argv) -{ - const char* mask_text = nullptr; - - Core::ArgsParser parser; - parser.add_positional_argument(mask_text, "New mask (omit to get current mask)", "octal-mask", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - if (!mask_text) { - mode_t old_mask = umask(0); - printf("%#o\n", old_mask); - umask(old_mask); - return 0; - } - - unsigned mask; - int matches = sscanf(mask_text, "%o", &mask); - if (matches == 1) { - umask(mask); - return 0; - } - - fprintf(stderr, "umask: Invalid mask '%s'\n", mask_text); - return 1; -} - -int Shell::builtin_wait(int argc, const char** argv) -{ - Vector<const char*> job_ids; - - Core::ArgsParser parser; - parser.add_positional_argument(job_ids, "Job IDs to wait for, defaults to all jobs if missing", "jobs", Core::ArgsParser::Required::No); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - Vector<NonnullRefPtr<Job>> jobs_to_wait_for; - - if (job_ids.is_empty()) { - for (auto it : jobs) - jobs_to_wait_for.append(it.value); - } else { - for (String id_s : job_ids) { - auto id_opt = id_s.to_uint(); - if (id_opt.has_value()) { - if (auto job = find_job(id_opt.value())) { - jobs_to_wait_for.append(*job); - continue; - } - } - - warnln("wait: invalid or nonexistent job id {}", id_s); - return 1; - } - } - - for (auto& job : jobs_to_wait_for) { - job->set_running_in_background(false); - block_on_job(job); - } - - return 0; -} - -int Shell::builtin_unset(int argc, const char** argv) -{ - Vector<const char*> vars; - - Core::ArgsParser parser; - parser.add_positional_argument(vars, "List of variables", "variables", Core::ArgsParser::Required::Yes); - - if (!parser.parse(argc, const_cast<char**>(argv), false)) - return 1; - - for (auto& value : vars) { - if (lookup_local_variable(value)) { - unset_local_variable(value); - } else { - unsetenv(value); - } - } - - return 0; -} - -bool Shell::run_builtin(const AST::Command& command, const NonnullRefPtrVector<AST::Rewiring>& rewirings, int& retval) -{ - if (command.argv.is_empty()) - return false; - - if (!has_builtin(command.argv.first())) - return false; - - Vector<const char*> argv; - for (auto& arg : command.argv) - argv.append(arg.characters()); - - argv.append(nullptr); - - StringView name = command.argv.first(); - - SavedFileDescriptors fds { rewirings }; - - for (auto& rewiring : rewirings) { - int rc = dup2(rewiring.old_fd, rewiring.new_fd); - if (rc < 0) { - perror("dup2(run)"); - return false; - } - } - - Core::EventLoop loop; - setup_signals(); - -#define __ENUMERATE_SHELL_BUILTIN(builtin) \ - if (name == #builtin) { \ - retval = builtin_##builtin(argv.size() - 1, argv.data()); \ - if (!has_error(ShellError::None)) \ - raise_error(m_error, m_error_description, command.position); \ - return true; \ - } - - ENUMERATE_SHELL_BUILTINS(); - -#undef __ENUMERATE_SHELL_BUILTIN - return false; -} - -bool Shell::has_builtin(const StringView& name) const -{ -#define __ENUMERATE_SHELL_BUILTIN(builtin) \ - if (name == #builtin) { \ - return true; \ - } - - ENUMERATE_SHELL_BUILTINS(); - -#undef __ENUMERATE_SHELL_BUILTIN - return false; -} - -} diff --git a/Shell/CMakeLists.txt b/Shell/CMakeLists.txt deleted file mode 100644 index 706bc5eae1..0000000000 --- a/Shell/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -set(SOURCES - AST.cpp - Builtin.cpp - Formatter.cpp - Job.cpp - NodeVisitor.cpp - Parser.cpp - Shell.cpp -) - -serenity_lib(LibShell shell) -target_link_libraries(LibShell LibCore LibLine) - -set(SOURCES - main.cpp -) - -serenity_bin(Shell) -target_link_libraries(Shell LibShell) diff --git a/Shell/Execution.h b/Shell/Execution.h deleted file mode 100644 index 2d3bf0d12a..0000000000 --- a/Shell/Execution.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "Forward.h" -#include <AK/Forward.h> -#include <AK/NonnullRefPtrVector.h> -#include <AK/String.h> -#include <AK/Vector.h> -#include <LibCore/ElapsedTimer.h> - -namespace Shell { - -class FileDescriptionCollector { -public: - FileDescriptionCollector() { } - ~FileDescriptionCollector(); - - void collect(); - void add(int fd); - -private: - Vector<int, 32> m_fds; -}; - -class SavedFileDescriptors { -public: - SavedFileDescriptors(const NonnullRefPtrVector<AST::Rewiring>&); - ~SavedFileDescriptors(); - -private: - struct SavedFileDescriptor { - int original { -1 }; - int saved { -1 }; - }; - - Vector<SavedFileDescriptor> m_saves; - FileDescriptionCollector m_collector; -}; - -} diff --git a/Shell/Formatter.cpp b/Shell/Formatter.cpp deleted file mode 100644 index e12b7b9551..0000000000 --- a/Shell/Formatter.cpp +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "Formatter.h" -#include "AST.h" -#include "Parser.h" -#include <AK/TemporaryChange.h> - -namespace Shell { - -String Formatter::format() -{ - auto node = Parser(m_source).parse(); - if (m_cursor >= 0) - m_output_cursor = m_cursor; - - if (!node) - return String(); - - if (node->is_syntax_error()) - return m_source; - - if (m_cursor >= 0) { - auto hit_test = node->hit_test_position(m_cursor); - if (hit_test.matching_node) - m_hit_node = hit_test.matching_node.ptr(); - else - m_hit_node = nullptr; - } - - m_parent_node = nullptr; - - node->visit(*this); - - auto string = m_builder.string_view(); - - if (!string.ends_with(" ")) - m_builder.append(m_trivia); - - return m_builder.to_string(); -} - -void Formatter::with_added_indent(int indent, Function<void()> callback) -{ - TemporaryChange indent_change { m_current_indent, m_current_indent + indent }; - callback(); -} - -void Formatter::in_new_block(Function<void()> callback) -{ - current_builder().append('{'); - - with_added_indent(1, [&] { - insert_separator(); - callback(); - }); - - insert_separator(); - current_builder().append('}'); -} - -void Formatter::test_and_update_output_cursor(const AST::Node* node) -{ - if (!node) - return; - - if (node != m_hit_node) - return; - - m_output_cursor = current_builder().length() + m_cursor - node->position().start_offset; -} - -void Formatter::visited(const AST::Node* node) -{ - m_last_visited_node = node; -} - -void Formatter::will_visit(const AST::Node* node) -{ - if (!m_last_visited_node) - return; - - if (!node) - return; - - auto direct_sequence_child = !m_parent_node || m_parent_node->kind() == AST::Node::Kind::Sequence; - - if (direct_sequence_child && node->kind() != AST::Node::Kind::Sequence) { - // Collapse more than one empty line to a single one. - if (node->position().start_line.line_number - m_last_visited_node->position().end_line.line_number > 1) - current_builder().append('\n'); - } -} - -void Formatter::insert_separator() -{ - current_builder().append('\n'); - insert_indent(); -} - -void Formatter::insert_indent() -{ - for (size_t i = 0; i < m_current_indent; ++i) - current_builder().append(" "); -} - -void Formatter::visit(const AST::PathRedirectionNode* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::And* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::And; - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - with_added_indent(should_indent ? 1 : 0, [&] { - node->left()->visit(*this); - - current_builder().append(" \\"); - insert_separator(); - current_builder().append("&& "); - - node->right()->visit(*this); - }); - visited(node); -} - -void Formatter::visit(const AST::ListConcatenate* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - auto first = true; - for (auto& subnode : node->list()) { - if (!first) - current_builder().append(' '); - first = false; - subnode->visit(*this); - } - visited(node); -} - -void Formatter::visit(const AST::Background* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - current_builder().append(" &"); - visited(node); -} - -void Formatter::visit(const AST::BarewordLiteral* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append(node->text()); - visited(node); -} - -void Formatter::visit(const AST::BraceExpansion* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append('{'); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - bool first = true; - for (auto& entry : node->entries()) { - if (!first) - current_builder().append(','); - first = false; - entry.visit(*this); - } - - current_builder().append('}'); - visited(node); -} - -void Formatter::visit(const AST::CastToCommand* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - if (m_options.explicit_parentheses) - current_builder().append('('); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - - if (m_options.explicit_parentheses) - current_builder().append(')'); - visited(node); -} - -void Formatter::visit(const AST::CastToList* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append('('); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - - current_builder().append(')'); - visited(node); -} - -void Formatter::visit(const AST::CloseFdRedirection* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - current_builder().appendf(" %d>&-", node->fd()); - visited(node); -} - -void Formatter::visit(const AST::CommandLiteral*) -{ - ASSERT_NOT_REACHED(); -} - -void Formatter::visit(const AST::Comment* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append("#"); - current_builder().append(node->text()); - visited(node); -} - -void Formatter::visit(const AST::ContinuationControl* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - if (node->continuation_kind() == AST::ContinuationControl::Break) - current_builder().append("break"); - else if (node->continuation_kind() == AST::ContinuationControl::Continue) - current_builder().append("continue"); - else - ASSERT_NOT_REACHED(); - visited(node); -} - -void Formatter::visit(const AST::DynamicEvaluate* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append('$'); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::DoubleQuotedString* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append("\""); - - TemporaryChange quotes { m_options.in_double_quotes, true }; - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - NodeVisitor::visit(node); - - current_builder().append("\""); - visited(node); -} - -void Formatter::visit(const AST::Fd2FdRedirection* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - current_builder().appendf(" %d>&%d", node->source_fd(), node->dest_fd()); - if (m_hit_node == node) - ++m_output_cursor; - visited(node); -} - -void Formatter::visit(const AST::FunctionDeclaration* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append(node->name().name); - current_builder().append('('); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - auto first = true; - for (auto& arg : node->arguments()) { - if (!first) - current_builder().append(' '); - first = false; - current_builder().append(arg.name); - } - - current_builder().append(") "); - - in_new_block([&] { - if (node->block()) - node->block()->visit(*this); - }); - visited(node); -} - -void Formatter::visit(const AST::ForLoop* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - auto is_loop = node->iterated_expression().is_null(); - current_builder().append(is_loop ? "loop" : "for "); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - if (!is_loop) { - if (node->variable_name() != "it") { - current_builder().append(node->variable_name()); - current_builder().append(" in "); - } - - node->iterated_expression()->visit(*this); - } - - current_builder().append(' '); - in_new_block([&] { - if (node->block()) - node->block()->visit(*this); - }); - visited(node); -} - -void Formatter::visit(const AST::Glob* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append(node->text()); - visited(node); -} - -void Formatter::visit(const AST::Execute* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - auto& builder = current_builder(); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - ScopedValueRollback options_rollback { m_options }; - - if (node->does_capture_stdout()) { - builder.append("$"); - m_options.explicit_parentheses = true; - } - - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::IfCond* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - - current_builder().append("if "); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - node->condition()->visit(*this); - - current_builder().append(' '); - - in_new_block([&] { - if (node->true_branch()) - node->true_branch()->visit(*this); - }); - - if (node->false_branch()) { - current_builder().append(" else "); - if (node->false_branch()->kind() != AST::Node::Kind::IfCond) { - in_new_block([&] { - node->false_branch()->visit(*this); - }); - } else { - node->false_branch()->visit(*this); - } - } else if (node->else_position().has_value()) { - current_builder().append(" else "); - } - visited(node); -} - -void Formatter::visit(const AST::Join* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - auto should_parenthesise = m_options.explicit_parentheses; - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - TemporaryChange parens { m_options.explicit_parentheses, false }; - - if (should_parenthesise) - current_builder().append('('); - - NodeVisitor::visit(node); - - if (should_parenthesise) - current_builder().append(')'); - visited(node); -} - -void Formatter::visit(const AST::MatchExpr* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append("match "); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - node->matched_expr()->visit(*this); - - if (!node->expr_name().is_empty()) { - current_builder().append(" as "); - current_builder().append(node->expr_name()); - } - - current_builder().append(' '); - in_new_block([&] { - auto first_entry = true; - for (auto& entry : node->entries()) { - if (!first_entry) - insert_separator(); - first_entry = false; - auto first = true; - for (auto& option : entry.options) { - if (!first) - current_builder().append(" | "); - first = false; - option.visit(*this); - } - - current_builder().append(' '); - if (entry.match_names.has_value() && !entry.match_names.value().is_empty()) { - current_builder().append("as ("); - auto first = true; - for (auto& name : entry.match_names.value()) { - if (!first) - current_builder().append(' '); - first = false; - current_builder().append(name); - } - current_builder().append(") "); - } - in_new_block([&] { - if (entry.body) - entry.body->visit(*this); - }); - } - }); - visited(node); -} - -void Formatter::visit(const AST::Or* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::Or; - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - with_added_indent(should_indent ? 1 : 0, [&] { - node->left()->visit(*this); - - current_builder().append(" \\"); - insert_separator(); - current_builder().append("|| "); - - node->right()->visit(*this); - }); - visited(node); -} - -void Formatter::visit(const AST::Pipe* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::Pipe; - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - node->left()->visit(*this); - current_builder().append(" \\"); - - with_added_indent(should_indent ? 1 : 0, [&] { - insert_separator(); - current_builder().append("| "); - - node->right()->visit(*this); - }); - visited(node); -} - -void Formatter::visit(const AST::Range* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append('{'); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - node->start()->visit(*this); - current_builder().append(".."); - node->end()->visit(*this); - - current_builder().append('}'); - visited(node); -} - -void Formatter::visit(const AST::ReadRedirection* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - if (node->fd() != 0) - current_builder().appendf(" %d<", node->fd()); - else - current_builder().append(" <"); - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::ReadWriteRedirection* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - if (node->fd() != 0) - current_builder().appendf(" %d<>", node->fd()); - else - current_builder().append(" <>"); - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::Sequence* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - node->left()->visit(*this); - insert_separator(); - - node->right()->visit(*this); - visited(node); -} - -void Formatter::visit(const AST::Subshell* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - in_new_block([&] { - insert_separator(); - NodeVisitor::visit(node); - insert_separator(); - }); - visited(node); -} - -void Formatter::visit(const AST::SimpleVariable* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append('$'); - current_builder().append(node->name()); - visited(node); -} - -void Formatter::visit(const AST::SpecialVariable* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append('$'); - current_builder().append(node->name()); - visited(node); -} - -void Formatter::visit(const AST::Juxtaposition* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::StringLiteral* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - if (!m_options.in_double_quotes) - current_builder().append("'"); - - if (m_options.in_double_quotes) { - for (auto ch : node->text()) { - switch (ch) { - case '"': - case '\\': - case '$': - current_builder().append('\\'); - break; - case '\n': - current_builder().append("\\n"); - continue; - case '\r': - current_builder().append("\\r"); - continue; - case '\t': - current_builder().append("\\t"); - continue; - case '\v': - current_builder().append("\\v"); - continue; - case '\f': - current_builder().append("\\f"); - continue; - case '\a': - current_builder().append("\\a"); - continue; - case '\e': - current_builder().append("\\e"); - continue; - default: - break; - } - current_builder().append(ch); - } - } else { - current_builder().append(node->text()); - } - - if (!m_options.in_double_quotes) - current_builder().append("'"); - visited(node); -} - -void Formatter::visit(const AST::StringPartCompose* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::SyntaxError* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::Tilde* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - current_builder().append(node->text()); - visited(node); -} - -void Formatter::visit(const AST::VariableDeclarations* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - auto first = true; - for (auto& entry : node->variables()) { - if (!first) - current_builder().append(' '); - first = false; - entry.name->visit(*this); - current_builder().append('='); - - if (entry.value->is_command()) - current_builder().append('('); - - entry.value->visit(*this); - - if (entry.value->is_command()) - current_builder().append(')'); - } - visited(node); -} - -void Formatter::visit(const AST::WriteAppendRedirection* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - if (node->fd() != 1) - current_builder().appendf(" %d>>", node->fd()); - else - current_builder().append(" >>"); - NodeVisitor::visit(node); - visited(node); -} - -void Formatter::visit(const AST::WriteRedirection* node) -{ - will_visit(node); - test_and_update_output_cursor(node); - TemporaryChange<const AST::Node*> parent { m_parent_node, node }; - - if (node->fd() != 1) - current_builder().appendf(" %d>", node->fd()); - else - current_builder().append(" >"); - NodeVisitor::visit(node); - visited(node); -} - -} diff --git a/Shell/Formatter.h b/Shell/Formatter.h deleted file mode 100644 index a4e0318b8c..0000000000 --- a/Shell/Formatter.h +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "NodeVisitor.h" -#include <AK/Forward.h> -#include <AK/StringBuilder.h> -#include <AK/StringView.h> -#include <AK/Types.h> -#include <ctype.h> - -namespace Shell { - -class Formatter final : public AST::NodeVisitor { -public: - Formatter(const StringView& source, ssize_t cursor = -1) - : m_builder(round_up_to_power_of_two(source.length(), 16)) - , m_source(source) - , m_cursor(cursor) - { - size_t offset = 0; - for (auto ptr = m_source.end() - 1; ptr >= m_source.begin() && isspace(*ptr); --ptr) - ++offset; - - m_trivia = m_source.substring_view(m_source.length() - offset, offset); - } - - String format(); - size_t cursor() const { return m_output_cursor; } - -private: - virtual void visit(const AST::PathRedirectionNode*) override; - virtual void visit(const AST::And*) override; - virtual void visit(const AST::ListConcatenate*) override; - virtual void visit(const AST::Background*) override; - virtual void visit(const AST::BarewordLiteral*) override; - virtual void visit(const AST::BraceExpansion*) override; - virtual void visit(const AST::CastToCommand*) override; - virtual void visit(const AST::CastToList*) override; - virtual void visit(const AST::CloseFdRedirection*) override; - virtual void visit(const AST::CommandLiteral*) override; - virtual void visit(const AST::Comment*) override; - virtual void visit(const AST::ContinuationControl*) override; - virtual void visit(const AST::DynamicEvaluate*) override; - virtual void visit(const AST::DoubleQuotedString*) override; - virtual void visit(const AST::Fd2FdRedirection*) override; - virtual void visit(const AST::FunctionDeclaration*) override; - virtual void visit(const AST::ForLoop*) override; - virtual void visit(const AST::Glob*) override; - virtual void visit(const AST::Execute*) override; - virtual void visit(const AST::IfCond*) override; - virtual void visit(const AST::Join*) override; - virtual void visit(const AST::MatchExpr*) override; - virtual void visit(const AST::Or*) override; - virtual void visit(const AST::Pipe*) override; - virtual void visit(const AST::Range*) override; - virtual void visit(const AST::ReadRedirection*) override; - virtual void visit(const AST::ReadWriteRedirection*) override; - virtual void visit(const AST::Sequence*) override; - virtual void visit(const AST::Subshell*) override; - virtual void visit(const AST::SimpleVariable*) override; - virtual void visit(const AST::SpecialVariable*) override; - virtual void visit(const AST::Juxtaposition*) override; - virtual void visit(const AST::StringLiteral*) override; - virtual void visit(const AST::StringPartCompose*) override; - virtual void visit(const AST::SyntaxError*) override; - virtual void visit(const AST::Tilde*) override; - virtual void visit(const AST::VariableDeclarations*) override; - virtual void visit(const AST::WriteAppendRedirection*) override; - virtual void visit(const AST::WriteRedirection*) override; - - void test_and_update_output_cursor(const AST::Node*); - void visited(const AST::Node*); - void will_visit(const AST::Node*); - void insert_separator(); - void insert_indent(); - - ALWAYS_INLINE void with_added_indent(int indent, Function<void()>); - ALWAYS_INLINE void in_new_block(Function<void()>); - - StringBuilder& current_builder() { return m_builder; } - - struct Options { - size_t max_line_length_hint { 80 }; - bool explicit_parentheses { false }; - bool explicit_braces { false }; - bool in_double_quotes { false }; - } m_options; - - size_t m_current_indent { 0 }; - - StringBuilder m_builder; - - StringView m_source; - size_t m_output_cursor { 0 }; - ssize_t m_cursor { -1 }; - AST::Node* m_hit_node { nullptr }; - - const AST::Node* m_parent_node { nullptr }; - const AST::Node* m_last_visited_node { nullptr }; - - StringView m_trivia; -}; - -} diff --git a/Shell/Forward.h b/Shell/Forward.h deleted file mode 100644 index b30448da52..0000000000 --- a/Shell/Forward.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -namespace Shell::AST { - -struct Command; -class Node; -class Value; -class SyntaxError; -class Pipeline; -struct Rewiring; -class NodeVisitor; - -class PathRedirectionNode; -class And; -class ListConcatenate; -class Background; -class BarewordLiteral; -class BraceExpansion; -class CastToCommand; -class CastToList; -class CloseFdRedirection; -class CommandLiteral; -class Comment; -class ContinuationControl; -class DynamicEvaluate; -class DoubleQuotedString; -class Fd2FdRedirection; -class FunctionDeclaration; -class ForLoop; -class Glob; -class Execute; -class IfCond; -class Join; -class MatchExpr; -class Or; -class Pipe; -class Range; -class ReadRedirection; -class ReadWriteRedirection; -class Sequence; -class Subshell; -class SimpleVariable; -class SpecialVariable; -class Juxtaposition; -class StringLiteral; -class StringPartCompose; -class SyntaxError; -class Tilde; -class VariableDeclarations; -class WriteAppendRedirection; -class WriteRedirection; - -} - -namespace Shell { - -class Shell; - -} diff --git a/Shell/Job.cpp b/Shell/Job.cpp deleted file mode 100644 index de662033fb..0000000000 --- a/Shell/Job.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "Job.h" -#include "AST.h" -#include "Shell.h" -#include <inttypes.h> -#include <stdio.h> -#include <sys/wait.h> - -namespace Shell { - -bool Job::print_status(PrintStatusMode mode) -{ - int wstatus; - auto rc = waitpid(m_pid, &wstatus, WNOHANG); - auto status = "running"; - - if (rc > 0) { - if (WIFEXITED(wstatus)) - status = "exited"; - - if (WIFSTOPPED(wstatus)) - status = "stopped"; - - if (WIFSIGNALED(wstatus)) - status = "signaled"; - } else if (rc < 0) { - // We couldn't waitpid() it, probably because we're not the parent shell. - // just use the old information. - if (exited()) - status = "exited"; - else if (m_is_suspended) - status = "stopped"; - else if (signaled()) - status = "signaled"; - } - - char background_indicator = '-'; - - if (is_running_in_background()) - background_indicator = '+'; - - const AST::Command& command = *m_command; - - switch (mode) { - case PrintStatusMode::Basic: - outln("[{}] {} {} {}", m_job_id, background_indicator, status, command); - break; - case PrintStatusMode::OnlyPID: - outln("[{}] {} {} {} {}", m_job_id, background_indicator, m_pid, status, command); - break; - case PrintStatusMode::ListAll: - outln("[{}] {} {} {} {} {}", m_job_id, background_indicator, m_pid, m_pgid, status, command); - break; - } - fflush(stdout); - - return true; -} - -Job::Job(pid_t pid, unsigned pgid, String cmd, u64 job_id, AST::Command&& command) - : m_pgid(pgid) - , m_pid(pid) - , m_job_id(job_id) - , m_cmd(move(cmd)) -{ - m_command = make<AST::Command>(move(command)); - - set_running_in_background(false); - m_command_timer.start(); -} - -void Job::set_has_exit(int exit_code) -{ - if (m_exited) - return; - m_exit_code = exit_code; - m_exited = true; - if (on_exit) - on_exit(*this); -} - -void Job::set_signalled(int sig) -{ - if (m_exited) - return; - m_exited = true; - m_exit_code = 126; - m_term_sig = sig; - if (on_exit) - on_exit(*this); -} - -void Job::unblock() const -{ - if (!m_exited && on_exit) - on_exit(*this); -} - -} diff --git a/Shell/Job.h b/Shell/Job.h deleted file mode 100644 index 1be90d3bf7..0000000000 --- a/Shell/Job.h +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "Execution.h" -#include "Forward.h" -#include <AK/Function.h> -#include <AK/JsonObject.h> -#include <AK/JsonValue.h> -#include <AK/OwnPtr.h> -#include <AK/String.h> -#include <LibCore/ElapsedTimer.h> -#include <LibCore/Object.h> - -#define JOB_TIME_INFO -#ifndef __serenity__ -# undef JOB_TIME_INFO -#endif - -namespace Shell { - -struct LocalFrame; - -class Job : public RefCounted<Job> { -public: - static NonnullRefPtr<Job> create(pid_t pid, pid_t pgid, String cmd, u64 job_id, AST::Command&& command) { return adopt(*new Job(pid, pgid, move(cmd), job_id, move(command))); } - - ~Job() - { -#ifdef JOB_TIME_INFO - if (m_active) { - auto elapsed = m_command_timer.elapsed(); - // Don't mistake this for the command! - dbg() << "Job entry \"" << m_cmd << "\" deleted in " << elapsed << " ms"; - } -#endif - } - - Function<void(RefPtr<Job>)> on_exit; - - pid_t pgid() const { return m_pgid; } - pid_t pid() const { return m_pid; } - const String& cmd() const { return m_cmd; } - const AST::Command& command() const { return *m_command; } - AST::Command* command_ptr() { return m_command; } - u64 job_id() const { return m_job_id; } - bool exited() const { return m_exited; } - bool signaled() const { return m_term_sig != -1; } - int exit_code() const - { - ASSERT(exited()); - return m_exit_code; - } - int termination_signal() const - { - ASSERT(signaled()); - return m_term_sig; - } - bool should_be_disowned() const { return m_should_be_disowned; } - void disown() { m_should_be_disowned = true; } - bool is_running_in_background() const { return m_running_in_background; } - bool should_announce_exit() const { return m_should_announce_exit; } - bool should_announce_signal() const { return m_should_announce_signal; } - bool is_suspended() const { return m_is_suspended; } - void unblock() const; - - Core::ElapsedTimer& timer() { return m_command_timer; } - - void set_has_exit(int exit_code); - void set_signalled(int sig); - - void set_is_suspended(bool value) const { m_is_suspended = value; } - - void set_running_in_background(bool running_in_background) - { - m_running_in_background = running_in_background; - } - - void set_should_announce_exit(bool value) { m_should_announce_exit = value; } - void set_should_announce_signal(bool value) { m_should_announce_signal = value; } - - void deactivate() const { m_active = false; } - - enum class PrintStatusMode { - Basic, - OnlyPID, - ListAll, - }; - - bool print_status(PrintStatusMode); - -private: - Job(pid_t pid, unsigned pgid, String cmd, u64 job_id, AST::Command&& command); - - pid_t m_pgid { 0 }; - pid_t m_pid { 0 }; - u64 m_job_id { 0 }; - String m_cmd; - bool m_exited { false }; - bool m_running_in_background { false }; - bool m_should_announce_exit { false }; - bool m_should_announce_signal { true }; - int m_exit_code { -1 }; - int m_term_sig { -1 }; - Core::ElapsedTimer m_command_timer; - mutable bool m_active { true }; - mutable bool m_is_suspended { false }; - bool m_should_be_disowned { false }; - OwnPtr<AST::Command> m_command; -}; - -} diff --git a/Shell/NodeVisitor.cpp b/Shell/NodeVisitor.cpp deleted file mode 100644 index af55be40d1..0000000000 --- a/Shell/NodeVisitor.cpp +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "NodeVisitor.h" -#include "AST.h" - -namespace Shell::AST { - -void NodeVisitor::visit(const AST::PathRedirectionNode* node) -{ - node->path()->visit(*this); -} - -void NodeVisitor::visit(const AST::And* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::ListConcatenate* node) -{ - for (auto& subnode : node->list()) - subnode->visit(*this); -} - -void NodeVisitor::visit(const AST::Background* node) -{ - node->command()->visit(*this); -} - -void NodeVisitor::visit(const AST::BarewordLiteral*) -{ -} - -void NodeVisitor::visit(const AST::BraceExpansion* node) -{ - for (auto& entry : node->entries()) - entry.visit(*this); -} - -void NodeVisitor::visit(const AST::CastToCommand* node) -{ - node->inner()->visit(*this); -} - -void NodeVisitor::visit(const AST::CastToList* node) -{ - if (node->inner()) - node->inner()->visit(*this); -} - -void NodeVisitor::visit(const AST::CloseFdRedirection*) -{ -} - -void NodeVisitor::visit(const AST::CommandLiteral*) -{ -} - -void NodeVisitor::visit(const AST::Comment*) -{ -} - -void NodeVisitor::visit(const AST::ContinuationControl*) -{ -} - -void NodeVisitor::visit(const AST::DynamicEvaluate* node) -{ - node->inner()->visit(*this); -} - -void NodeVisitor::visit(const AST::DoubleQuotedString* node) -{ - if (node->inner()) - node->inner()->visit(*this); -} - -void NodeVisitor::visit(const AST::Fd2FdRedirection*) -{ -} - -void NodeVisitor::visit(const AST::FunctionDeclaration* node) -{ - if (node->block()) - node->block()->visit(*this); -} - -void NodeVisitor::visit(const AST::ForLoop* node) -{ - if (node->iterated_expression()) - node->iterated_expression()->visit(*this); - if (node->block()) - node->block()->visit(*this); -} - -void NodeVisitor::visit(const AST::Glob*) -{ -} - -void NodeVisitor::visit(const AST::Execute* node) -{ - node->command()->visit(*this); -} - -void NodeVisitor::visit(const AST::IfCond* node) -{ - node->condition()->visit(*this); - if (node->true_branch()) - node->true_branch()->visit(*this); - if (node->false_branch()) - node->false_branch()->visit(*this); -} - -void NodeVisitor::visit(const AST::Join* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::MatchExpr* node) -{ - node->matched_expr()->visit(*this); - for (auto& entry : node->entries()) { - for (auto& option : entry.options) - option.visit(*this); - if (entry.body) - entry.body->visit(*this); - } -} - -void NodeVisitor::visit(const AST::Or* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::Pipe* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::Range* node) -{ - node->start()->visit(*this); - node->end()->visit(*this); -} - -void NodeVisitor::visit(const AST::ReadRedirection* node) -{ - visit(static_cast<const AST::PathRedirectionNode*>(node)); -} - -void NodeVisitor::visit(const AST::ReadWriteRedirection* node) -{ - visit(static_cast<const AST::PathRedirectionNode*>(node)); -} - -void NodeVisitor::visit(const AST::Sequence* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::Subshell* node) -{ - if (node->block()) - node->block()->visit(*this); -} - -void NodeVisitor::visit(const AST::SimpleVariable*) -{ -} - -void NodeVisitor::visit(const AST::SpecialVariable*) -{ -} - -void NodeVisitor::visit(const AST::Juxtaposition* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::StringLiteral*) -{ -} - -void NodeVisitor::visit(const AST::StringPartCompose* node) -{ - node->left()->visit(*this); - node->right()->visit(*this); -} - -void NodeVisitor::visit(const AST::SyntaxError*) -{ -} - -void NodeVisitor::visit(const AST::Tilde*) -{ -} - -void NodeVisitor::visit(const AST::VariableDeclarations* node) -{ - for (auto& entry : node->variables()) { - entry.name->visit(*this); - entry.value->visit(*this); - } -} - -void NodeVisitor::visit(const AST::WriteAppendRedirection* node) -{ - visit(static_cast<const AST::PathRedirectionNode*>(node)); -} - -void NodeVisitor::visit(const AST::WriteRedirection* node) -{ - visit(static_cast<const AST::PathRedirectionNode*>(node)); -} - -} diff --git a/Shell/NodeVisitor.h b/Shell/NodeVisitor.h deleted file mode 100644 index 4f08c92721..0000000000 --- a/Shell/NodeVisitor.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "Forward.h" - -namespace Shell::AST { - -class NodeVisitor { -public: - virtual void visit(const AST::PathRedirectionNode*); - virtual void visit(const AST::And*); - virtual void visit(const AST::ListConcatenate*); - virtual void visit(const AST::Background*); - virtual void visit(const AST::BarewordLiteral*); - virtual void visit(const AST::BraceExpansion*); - virtual void visit(const AST::CastToCommand*); - virtual void visit(const AST::CastToList*); - virtual void visit(const AST::CloseFdRedirection*); - virtual void visit(const AST::CommandLiteral*); - virtual void visit(const AST::Comment*); - virtual void visit(const AST::ContinuationControl*); - virtual void visit(const AST::DynamicEvaluate*); - virtual void visit(const AST::DoubleQuotedString*); - virtual void visit(const AST::Fd2FdRedirection*); - virtual void visit(const AST::FunctionDeclaration*); - virtual void visit(const AST::ForLoop*); - virtual void visit(const AST::Glob*); - virtual void visit(const AST::Execute*); - virtual void visit(const AST::IfCond*); - virtual void visit(const AST::Join*); - virtual void visit(const AST::MatchExpr*); - virtual void visit(const AST::Or*); - virtual void visit(const AST::Pipe*); - virtual void visit(const AST::Range*); - virtual void visit(const AST::ReadRedirection*); - virtual void visit(const AST::ReadWriteRedirection*); - virtual void visit(const AST::Sequence*); - virtual void visit(const AST::Subshell*); - virtual void visit(const AST::SimpleVariable*); - virtual void visit(const AST::SpecialVariable*); - virtual void visit(const AST::Juxtaposition*); - virtual void visit(const AST::StringLiteral*); - virtual void visit(const AST::StringPartCompose*); - virtual void visit(const AST::SyntaxError*); - virtual void visit(const AST::Tilde*); - virtual void visit(const AST::VariableDeclarations*); - virtual void visit(const AST::WriteAppendRedirection*); - virtual void visit(const AST::WriteRedirection*); -}; - -} diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp deleted file mode 100644 index 993b6df478..0000000000 --- a/Shell/Parser.cpp +++ /dev/null @@ -1,1555 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "Parser.h" -#include <AK/TemporaryChange.h> -#include <ctype.h> -#include <stdio.h> -#include <unistd.h> - -namespace Shell { - -Parser::SavedOffset Parser::save_offset() const -{ - return { m_offset, m_line }; -} - -char Parser::peek() -{ - if (at_end()) - return 0; - - ASSERT(m_offset < m_input.length()); - - auto ch = m_input[m_offset]; - if (ch == '\\' && m_input.length() > m_offset + 1 && m_input[m_offset + 1] == '\n') { - m_offset += 2; - ++m_line.line_number; - m_line.line_column = 0; - return peek(); - } - - return ch; -} - -char Parser::consume() -{ - if (at_end()) - return 0; - - auto ch = peek(); - ++m_offset; - - if (ch == '\n') { - ++m_line.line_number; - m_line.line_column = 0; - } else { - ++m_line.line_column; - } - - return ch; -} - -bool Parser::expect(char ch) -{ - return expect(StringView { &ch, 1 }); -} - -bool Parser::expect(const StringView& expected) -{ - auto offset_at_start = m_offset; - auto line_at_start = line(); - - if (expected.length() + m_offset > m_input.length()) - return false; - - for (size_t i = 0; i < expected.length(); ++i) { - if (peek() != expected[i]) { - restore_to(offset_at_start, line_at_start); - return false; - } - - consume(); - } - - return true; -} - -template<typename A, typename... Args> -NonnullRefPtr<A> Parser::create(Args... args) -{ - return adopt(*new A(AST::Position { m_rule_start_offsets.last(), m_offset, m_rule_start_lines.last(), line() }, args...)); -} - -[[nodiscard]] OwnPtr<Parser::ScopedOffset> Parser::push_start() -{ - return make<ScopedOffset>(m_rule_start_offsets, m_rule_start_lines, m_offset, m_line.line_number, m_line.line_column); -} - -static constexpr bool is_whitespace(char c) -{ - return c == ' ' || c == '\t'; -} - -static constexpr bool is_word_character(char c) -{ - return (c <= '9' && c >= '0') || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '_'; -} - -static constexpr bool is_digit(char c) -{ - return c <= '9' && c >= '0'; -} - -static constexpr auto is_not(char c) -{ - return [c](char ch) { return ch != c; }; -} - -static inline char to_byte(char a, char b) -{ - char buf[3] { a, b, 0 }; - return strtol(buf, nullptr, 16); -} - -RefPtr<AST::Node> Parser::parse() -{ - m_offset = 0; - m_line = { 0, 0 }; - - auto toplevel = parse_toplevel(); - - if (m_offset < m_input.length()) { - // Parsing stopped midway, this is a syntax error. - auto error_start = push_start(); - while (!at_end()) - consume(); - auto syntax_error_node = create<AST::SyntaxError>("Unexpected tokens past the end"); - if (!toplevel) - toplevel = move(syntax_error_node); - else if (!toplevel->is_syntax_error()) - toplevel->set_is_syntax_error(*syntax_error_node); - } - - return toplevel; -} - -RefPtr<AST::Node> Parser::parse_toplevel() -{ - auto rule_start = push_start(); - - if (auto sequence = parse_sequence()) - return create<AST::Execute>(sequence.release_nonnull()); - - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_sequence() -{ - consume_while(is_any_of(" \t\n;")); // ignore whitespaces or terminators without effect. - - auto rule_start = push_start(); - auto var_decls = parse_variable_decls(); - - auto pos_before_seps = save_offset(); - - switch (peek()) { - case '}': - return var_decls; - case ';': - case '\n': { - if (!var_decls) - break; - - consume_while(is_any_of("\n;")); - - auto pos_after_seps = save_offset(); - - auto rest = parse_sequence(); - if (rest) - return create<AST::Sequence>( - var_decls.release_nonnull(), - rest.release_nonnull(), - AST::Position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line }); - return var_decls; - } - default: - break; - } - - auto first = parse_function_decl(); - - if (!first) - first = parse_or_logical_sequence(); - - if (!first) - return var_decls; - - if (var_decls) - first = create<AST::Sequence>( - var_decls.release_nonnull(), - first.release_nonnull(), - AST::Position { pos_before_seps.offset, pos_before_seps.offset, pos_before_seps.line, pos_before_seps.line }); - - consume_while(is_whitespace); - - pos_before_seps = save_offset(); - switch (peek()) { - case ';': - case '\n': { - consume_while(is_any_of("\n;")); - auto pos_after_seps = save_offset(); - - if (auto expr = parse_sequence()) { - return create<AST::Sequence>( - first.release_nonnull(), - expr.release_nonnull(), - AST::Position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_after_seps.line }); // Sequence - } - return first; - } - case '&': { - consume(); - auto pos_after_seps = save_offset(); - auto bg = create<AST::Background>(first.release_nonnull()); // Execute Background - if (auto rest = parse_sequence()) - return create<AST::Sequence>( - move(bg), - rest.release_nonnull(), - AST::Position { pos_before_seps.offset, pos_after_seps.offset, pos_before_seps.line, pos_before_seps.line }); // Sequence Background Sequence - - return bg; - } - default: - return first; - } -} - -RefPtr<AST::Node> Parser::parse_variable_decls() -{ - auto rule_start = push_start(); - - consume_while(is_whitespace); - - auto pos_before_name = save_offset(); - auto var_name = consume_while(is_word_character); - if (var_name.is_empty()) - return nullptr; - - if (!expect('=')) { - restore_to(pos_before_name.offset, pos_before_name.line); - return nullptr; - } - - auto name_expr = create<AST::BarewordLiteral>(move(var_name)); - - auto start = push_start(); - auto expression = parse_expression(); - if (!expression || expression->is_syntax_error()) { - restore_to(*start); - if (peek() == '(') { - consume(); - auto command = parse_pipe_sequence(); - if (!command) - restore_to(*start); - else if (!expect(')')) - command->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren", true)); - expression = command; - } - } - if (!expression) { - if (is_whitespace(peek())) { - auto string_start = push_start(); - expression = create<AST::StringLiteral>(""); - } else { - restore_to(pos_before_name.offset, pos_before_name.line); - return nullptr; - } - } - - Vector<AST::VariableDeclarations::Variable> variables; - variables.append({ move(name_expr), expression.release_nonnull() }); - - if (consume_while(is_whitespace).is_empty()) - return create<AST::VariableDeclarations>(move(variables)); - - auto rest = parse_variable_decls(); - if (!rest) - return create<AST::VariableDeclarations>(move(variables)); - - ASSERT(rest->is_variable_decls()); - auto* rest_decl = static_cast<AST::VariableDeclarations*>(rest.ptr()); - - variables.append(rest_decl->variables()); - - return create<AST::VariableDeclarations>(move(variables)); -} - -RefPtr<AST::Node> Parser::parse_function_decl() -{ - auto rule_start = push_start(); - - auto restore = [&] { - restore_to(*rule_start); - return nullptr; - }; - - consume_while(is_whitespace); - auto pos_before_name = save_offset(); - auto function_name = consume_while(is_word_character); - auto pos_after_name = save_offset(); - if (function_name.is_empty()) - return restore(); - - if (!expect('(')) - return restore(); - - Vector<AST::FunctionDeclaration::NameWithPosition> arguments; - for (;;) { - consume_while(is_whitespace); - - if (expect(')')) - break; - - auto name_offset = m_offset; - auto start_line = line(); - auto arg_name = consume_while(is_word_character); - if (arg_name.is_empty()) { - // FIXME: Should this be a syntax error, or just return? - return restore(); - } - arguments.append({ arg_name, { name_offset, m_offset, start_line, line() } }); - } - - consume_while(is_whitespace); - - { - RefPtr<AST::Node> syntax_error; - { - auto obrace_error_start = push_start(); - syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a function body", true); - } - if (!expect('{')) { - return create<AST::FunctionDeclaration>( - AST::FunctionDeclaration::NameWithPosition { - move(function_name), - { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } }, - move(arguments), - move(syntax_error)); - } - } - - TemporaryChange controls { m_continuation_controls_allowed, false }; - auto body = parse_toplevel(); - - { - RefPtr<AST::SyntaxError> syntax_error; - { - auto cbrace_error_start = push_start(); - syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a function body", true); - } - if (!expect('}')) { - if (body) - body->set_is_syntax_error(*syntax_error); - else - body = move(syntax_error); - - return create<AST::FunctionDeclaration>( - AST::FunctionDeclaration::NameWithPosition { - move(function_name), - { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } }, - move(arguments), - move(body)); - } - } - - return create<AST::FunctionDeclaration>( - AST::FunctionDeclaration::NameWithPosition { - move(function_name), - { pos_before_name.offset, pos_after_name.offset, pos_before_name.line, pos_after_name.line } }, - move(arguments), - move(body)); -} - -RefPtr<AST::Node> Parser::parse_or_logical_sequence() -{ - consume_while(is_whitespace); - auto rule_start = push_start(); - auto and_sequence = parse_and_logical_sequence(); - if (!and_sequence) - return nullptr; - - consume_while(is_whitespace); - auto pos_before_or = save_offset(); - if (!expect("||")) - return and_sequence; - auto pos_after_or = save_offset(); - - auto right_and_sequence = parse_and_logical_sequence(); - if (!right_and_sequence) - right_and_sequence = create<AST::SyntaxError>("Expected an expression after '||'", true); - - return create<AST::Or>( - and_sequence.release_nonnull(), - right_and_sequence.release_nonnull(), - AST::Position { pos_before_or.offset, pos_after_or.offset, pos_before_or.line, pos_after_or.line }); -} - -RefPtr<AST::Node> Parser::parse_and_logical_sequence() -{ - consume_while(is_whitespace); - auto rule_start = push_start(); - auto pipe_sequence = parse_pipe_sequence(); - if (!pipe_sequence) - return nullptr; - - consume_while(is_whitespace); - auto pos_before_and = save_offset(); - if (!expect("&&")) - return pipe_sequence; - auto pos_after_end = save_offset(); - - auto right_and_sequence = parse_and_logical_sequence(); - if (!right_and_sequence) - right_and_sequence = create<AST::SyntaxError>("Expected an expression after '&&'", true); - - return create<AST::And>( - pipe_sequence.release_nonnull(), - right_and_sequence.release_nonnull(), - AST::Position { pos_before_and.offset, pos_after_end.offset, pos_before_and.line, pos_after_end.line }); -} - -RefPtr<AST::Node> Parser::parse_pipe_sequence() -{ - auto rule_start = push_start(); - auto left = parse_control_structure(); - if (!left) { - if (auto cmd = parse_command()) - left = cmd; - else - return nullptr; - } - - consume_while(is_whitespace); - - if (peek() != '|') - return left; - - auto before_pipe = save_offset(); - consume(); - - if (auto pipe_seq = parse_pipe_sequence()) { - return create<AST::Pipe>(left.release_nonnull(), pipe_seq.release_nonnull()); // Pipe - } - - restore_to(before_pipe.offset, before_pipe.line); - return left; -} - -RefPtr<AST::Node> Parser::parse_command() -{ - auto rule_start = push_start(); - consume_while(is_whitespace); - - auto redir = parse_redirection(); - if (!redir) { - auto list_expr = parse_list_expression(); - if (!list_expr) - return nullptr; - - auto cast = create<AST::CastToCommand>(list_expr.release_nonnull()); // Cast List Command - - auto next_command = parse_command(); - if (!next_command) - return cast; - - return create<AST::Join>(move(cast), next_command.release_nonnull()); // Join List Command - } - - auto command = parse_command(); - if (!command) - return redir; - - return create<AST::Join>(redir.release_nonnull(), command.release_nonnull()); // Join Command Command -} - -RefPtr<AST::Node> Parser::parse_control_structure() -{ - auto rule_start = push_start(); - consume_while(is_whitespace); - if (auto control = parse_continuation_control()) - return control; - - if (auto for_loop = parse_for_loop()) - return for_loop; - - if (auto loop = parse_loop_loop()) - return loop; - - if (auto if_expr = parse_if_expr()) - return if_expr; - - if (auto subshell = parse_subshell()) - return subshell; - - if (auto match = parse_match_expr()) - return match; - - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_continuation_control() -{ - if (!m_continuation_controls_allowed) - return nullptr; - - auto rule_start = push_start(); - - if (expect("break")) { - { - auto break_end = push_start(); - if (consume_while(is_any_of(" \t\n;")).is_empty()) { - restore_to(*rule_start); - return nullptr; - } - restore_to(*break_end); - } - return create<AST::ContinuationControl>(AST::ContinuationControl::Break); - } - - if (expect("continue")) { - { - auto continue_end = push_start(); - if (consume_while(is_any_of(" \t\n;")).is_empty()) { - restore_to(*rule_start); - return nullptr; - } - restore_to(*continue_end); - } - return create<AST::ContinuationControl>(AST::ContinuationControl::Continue); - } - - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_for_loop() -{ - auto rule_start = push_start(); - if (!expect("for")) - return nullptr; - - if (consume_while(is_any_of(" \t\n")).is_empty()) { - restore_to(*rule_start); - return nullptr; - } - - auto variable_name = consume_while(is_word_character); - Optional<AST::Position> in_start_position; - if (variable_name.is_empty()) { - variable_name = "it"; - } else { - consume_while(is_whitespace); - auto in_error_start = push_start(); - if (!expect("in")) { - auto syntax_error = create<AST::SyntaxError>("Expected 'in' after a variable name in a 'for' loop", true); - return create<AST::ForLoop>(move(variable_name), move(syntax_error), nullptr); // ForLoop Var Iterated Block - } - in_start_position = AST::Position { in_error_start->offset, m_offset, in_error_start->line, line() }; - } - - consume_while(is_whitespace); - RefPtr<AST::Node> iterated_expression; - { - auto iter_error_start = push_start(); - iterated_expression = parse_expression(); - if (!iterated_expression) - iterated_expression = create<AST::SyntaxError>("Expected an expression in 'for' loop", true); - } - - consume_while(is_any_of(" \t\n")); - { - auto obrace_error_start = push_start(); - if (!expect('{')) { - auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'for' loop body", true); - return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(syntax_error), move(in_start_position)); // ForLoop Var Iterated Block - } - } - - TemporaryChange controls { m_continuation_controls_allowed, true }; - auto body = parse_toplevel(); - - { - auto cbrace_error_start = push_start(); - if (!expect('}')) { - auto error_start = push_start(); - auto syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a 'for' loop body", true); - if (body) - body->set_is_syntax_error(*syntax_error); - else - body = syntax_error; - } - } - - return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block -} - -RefPtr<AST::Node> Parser::parse_loop_loop() -{ - auto rule_start = push_start(); - if (!expect("loop")) - return nullptr; - - if (consume_while(is_any_of(" \t\n")).is_empty()) { - restore_to(*rule_start); - return nullptr; - } - - { - auto obrace_error_start = push_start(); - if (!expect('{')) { - auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'loop' loop body", true); - return create<AST::ForLoop>(String::empty(), nullptr, move(syntax_error), Optional<AST::Position> {}); // ForLoop null null Block - } - } - - TemporaryChange controls { m_continuation_controls_allowed, true }; - auto body = parse_toplevel(); - - { - auto cbrace_error_start = push_start(); - if (!expect('}')) { - auto error_start = push_start(); - auto syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a 'loop' loop body", true); - if (body) - body->set_is_syntax_error(*syntax_error); - else - body = syntax_error; - } - } - - return create<AST::ForLoop>(String::empty(), nullptr, move(body), Optional<AST::Position> {}); // ForLoop null null Block -} - -RefPtr<AST::Node> Parser::parse_if_expr() -{ - auto rule_start = push_start(); - if (!expect("if")) - return nullptr; - - if (consume_while(is_any_of(" \t\n")).is_empty()) { - restore_to(*rule_start); - return nullptr; - } - - RefPtr<AST::Node> condition; - { - auto cond_error_start = push_start(); - condition = parse_or_logical_sequence(); - if (!condition) - condition = create<AST::SyntaxError>("Expected a logical sequence after 'if'", true); - } - - auto parse_braced_toplevel = [&]() -> RefPtr<AST::Node> { - RefPtr<AST::Node> body; - { - auto obrace_error_start = push_start(); - if (!expect('{')) { - body = create<AST::SyntaxError>("Expected an open brace '{' to start an 'if' true branch", true); - } - } - - if (!body) - body = parse_toplevel(); - - { - auto cbrace_error_start = push_start(); - if (!expect('}')) { - auto error_start = push_start(); - RefPtr<AST::SyntaxError> syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end an 'if' true branch", true); - if (body) - body->set_is_syntax_error(*syntax_error); - else - body = syntax_error; - } - } - - return body; - }; - - consume_while(is_whitespace); - auto true_branch = parse_braced_toplevel(); - - consume_while(is_whitespace); - Optional<AST::Position> else_position; - { - auto else_start = push_start(); - if (expect("else")) - else_position = AST::Position { else_start->offset, m_offset, else_start->line, line() }; - } - - if (else_position.has_value()) { - consume_while(is_whitespace); - if (peek() == '{') { - auto false_branch = parse_braced_toplevel(); - return create<AST::IfCond>(else_position, condition.release_nonnull(), move(true_branch), move(false_branch)); // If expr true_branch Else false_branch - } - - auto else_if_branch = parse_if_expr(); - return create<AST::IfCond>(else_position, condition.release_nonnull(), move(true_branch), move(else_if_branch)); // If expr true_branch Else If ... - } - - return create<AST::IfCond>(else_position, condition.release_nonnull(), move(true_branch), nullptr); // If expr true_branch -} - -RefPtr<AST::Node> Parser::parse_subshell() -{ - auto rule_start = push_start(); - if (!expect('{')) - return nullptr; - - auto body = parse_toplevel(); - - { - auto cbrace_error_start = push_start(); - if (!expect('}')) { - auto error_start = push_start(); - RefPtr<AST::SyntaxError> syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a subshell", true); - if (body) - body->set_is_syntax_error(*syntax_error); - else - body = syntax_error; - } - } - - return create<AST::Subshell>(move(body)); -} - -RefPtr<AST::Node> Parser::parse_match_expr() -{ - auto rule_start = push_start(); - if (!expect("match")) - return nullptr; - - if (consume_while(is_whitespace).is_empty()) { - restore_to(*rule_start); - return nullptr; - } - - auto match_expression = parse_expression(); - if (!match_expression) { - return create<AST::MatchExpr>( - create<AST::SyntaxError>("Expected an expression after 'match'", true), - String {}, Optional<AST::Position> {}, Vector<AST::MatchEntry> {}); - } - - consume_while(is_any_of(" \t\n")); - - String match_name; - Optional<AST::Position> as_position; - auto as_start = m_offset; - auto as_line = line(); - if (expect("as")) { - as_position = AST::Position { as_start, m_offset, as_line, line() }; - - if (consume_while(is_any_of(" \t\n")).is_empty()) { - auto node = create<AST::MatchExpr>( - match_expression.release_nonnull(), - String {}, move(as_position), Vector<AST::MatchEntry> {}); - node->set_is_syntax_error(create<AST::SyntaxError>("Expected whitespace after 'as' in 'match'", true)); - return node; - } - - match_name = consume_while(is_word_character); - if (match_name.is_empty()) { - auto node = create<AST::MatchExpr>( - match_expression.release_nonnull(), - String {}, move(as_position), Vector<AST::MatchEntry> {}); - node->set_is_syntax_error(create<AST::SyntaxError>("Expected an identifier after 'as' in 'match'", true)); - return node; - } - } - - consume_while(is_any_of(" \t\n")); - - if (!expect('{')) { - auto node = create<AST::MatchExpr>( - match_expression.release_nonnull(), - move(match_name), move(as_position), Vector<AST::MatchEntry> {}); - node->set_is_syntax_error(create<AST::SyntaxError>("Expected an open brace '{' to start a 'match' entry list", true)); - return node; - } - - consume_while(is_any_of(" \t\n")); - - Vector<AST::MatchEntry> entries; - for (;;) { - auto entry = parse_match_entry(); - consume_while(is_any_of(" \t\n")); - if (entry.options.is_empty()) - break; - - entries.append(entry); - } - - consume_while(is_any_of(" \t\n")); - - if (!expect('}')) { - auto node = create<AST::MatchExpr>( - match_expression.release_nonnull(), - move(match_name), move(as_position), move(entries)); - node->set_is_syntax_error(create<AST::SyntaxError>("Expected a close brace '}' to end a 'match' entry list", true)); - return node; - } - - return create<AST::MatchExpr>(match_expression.release_nonnull(), move(match_name), move(as_position), move(entries)); -} - -AST::MatchEntry Parser::parse_match_entry() -{ - auto rule_start = push_start(); - - NonnullRefPtrVector<AST::Node> patterns; - Vector<AST::Position> pipe_positions; - Optional<Vector<String>> match_names; - Optional<AST::Position> match_as_position; - - auto pattern = parse_match_pattern(); - if (!pattern) - return { {}, {}, {}, {}, create<AST::SyntaxError>("Expected a pattern in 'match' body", true) }; - - patterns.append(pattern.release_nonnull()); - - consume_while(is_any_of(" \t\n")); - - auto previous_pipe_start_position = m_offset; - auto previous_pipe_start_line = line(); - RefPtr<AST::SyntaxError> error; - while (expect('|')) { - pipe_positions.append({ previous_pipe_start_position, m_offset, previous_pipe_start_line, line() }); - consume_while(is_any_of(" \t\n")); - auto pattern = parse_match_pattern(); - if (!pattern) { - error = create<AST::SyntaxError>("Expected a pattern to follow '|' in 'match' body", true); - break; - } - consume_while(is_any_of(" \t\n")); - - patterns.append(pattern.release_nonnull()); - - previous_pipe_start_line = line(); - previous_pipe_start_position = m_offset; - } - - consume_while(is_any_of(" \t\n")); - - auto as_start_position = m_offset; - auto as_start_line = line(); - if (expect("as")) { - match_as_position = AST::Position { as_start_position, m_offset, as_start_line, line() }; - consume_while(is_any_of(" \t\n")); - if (!expect('(')) { - if (!error) - error = create<AST::SyntaxError>("Expected an explicit list of identifiers after a pattern 'as'"); - } else { - match_names = Vector<String>(); - for (;;) { - consume_while(is_whitespace); - auto name = consume_while(is_word_character); - if (name.is_empty()) - break; - match_names.value().append(move(name)); - } - - if (!expect(')')) { - if (!error) - error = create<AST::SyntaxError>("Expected a close paren ')' to end the identifier list of pattern 'as'", true); - } - } - consume_while(is_any_of(" \t\n")); - } - - if (!expect('{')) { - if (!error) - error = create<AST::SyntaxError>("Expected an open brace '{' to start a match entry body", true); - } - - auto body = parse_toplevel(); - - if (!expect('}')) { - if (!error) - error = create<AST::SyntaxError>("Expected a close brace '}' to end a match entry body", true); - } - - if (body && error) - body->set_is_syntax_error(*error); - else if (error) - body = error; - - return { move(patterns), move(match_names), move(match_as_position), move(pipe_positions), move(body) }; -} - -RefPtr<AST::Node> Parser::parse_match_pattern() -{ - return parse_expression(); -} - -RefPtr<AST::Node> Parser::parse_redirection() -{ - auto rule_start = push_start(); - auto pipe_fd = 0; - auto number = consume_while(is_digit); - if (number.is_empty()) { - pipe_fd = -1; - } else { - auto fd = number.to_int(); - pipe_fd = fd.value_or(-1); - } - - switch (peek()) { - case '>': { - consume(); - if (peek() == '>') { - consume(); - consume_while(is_whitespace); - pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO; - auto path = parse_expression(); - if (!path) { - if (!at_end()) { - // Eat a character and hope the problem goes away - consume(); - } - path = create<AST::SyntaxError>("Expected a path after redirection", true); - } - return create<AST::WriteAppendRedirection>(pipe_fd, path.release_nonnull()); // Redirection WriteAppend - } - if (peek() == '&') { - consume(); - // FIXME: 'fd>&-' Syntax not the best. needs discussion. - if (peek() == '-') { - consume(); - pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO; - return create<AST::CloseFdRedirection>(pipe_fd); // Redirection CloseFd - } - int dest_pipe_fd = 0; - auto number = consume_while(is_digit); - pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO; - if (number.is_empty()) { - dest_pipe_fd = -1; - } else { - auto fd = number.to_int(); - dest_pipe_fd = fd.value_or(-1); - } - auto redir = create<AST::Fd2FdRedirection>(pipe_fd, dest_pipe_fd); // Redirection Fd2Fd - if (dest_pipe_fd == -1) - redir->set_is_syntax_error(*create<AST::SyntaxError>("Expected a file descriptor")); - return redir; - } - consume_while(is_whitespace); - pipe_fd = pipe_fd >= 0 ? pipe_fd : STDOUT_FILENO; - auto path = parse_expression(); - if (!path) { - if (!at_end()) { - // Eat a character and hope the problem goes away - consume(); - } - path = create<AST::SyntaxError>("Expected a path after redirection", true); - } - return create<AST::WriteRedirection>(pipe_fd, path.release_nonnull()); // Redirection Write - } - case '<': { - consume(); - enum { - Read, - ReadWrite, - } mode { Read }; - - if (peek() == '>') { - mode = ReadWrite; - consume(); - } - - consume_while(is_whitespace); - pipe_fd = pipe_fd >= 0 ? pipe_fd : STDIN_FILENO; - auto path = parse_expression(); - if (!path) { - if (!at_end()) { - // Eat a character and hope the problem goes away - consume(); - } - path = create<AST::SyntaxError>("Expected a path after redirection", true); - } - if (mode == Read) - return create<AST::ReadRedirection>(pipe_fd, path.release_nonnull()); // Redirection Read - - return create<AST::ReadWriteRedirection>(pipe_fd, path.release_nonnull()); // Redirection ReadWrite - } - default: - restore_to(*rule_start); - return nullptr; - } -} - -RefPtr<AST::Node> Parser::parse_list_expression() -{ - consume_while(is_whitespace); - - auto rule_start = push_start(); - Vector<NonnullRefPtr<AST::Node>> nodes; - - do { - auto expr = parse_expression(); - if (!expr) - break; - nodes.append(expr.release_nonnull()); - } while (!consume_while(is_whitespace).is_empty()); - - if (nodes.is_empty()) - return nullptr; - - return create<AST::ListConcatenate>(move(nodes)); // Concatenate List -} - -RefPtr<AST::Node> Parser::parse_expression() -{ - auto rule_start = push_start(); - if (m_rule_start_offsets.size() > max_allowed_nested_rule_depth) - return create<AST::SyntaxError>(String::formatted("Expression nested too deep (max allowed is {})", max_allowed_nested_rule_depth)); - - auto starting_char = peek(); - - auto read_concat = [&](auto&& expr) -> NonnullRefPtr<AST::Node> { - if (is_whitespace(peek())) - return move(expr); - - if (auto next_expr = parse_expression()) - return create<AST::Juxtaposition>(move(expr), next_expr.release_nonnull()); - - return move(expr); - }; - - if (strchr("&|)} ;<>\n", starting_char) != nullptr) - return nullptr; - - if (m_is_in_brace_expansion_spec && starting_char == ',') - return nullptr; - - if (m_is_in_brace_expansion_spec && next_is("..")) - return nullptr; - - if (isdigit(starting_char)) { - ScopedValueRollback offset_rollback { m_offset }; - - auto redir = parse_redirection(); - if (redir) - return nullptr; - } - - if (starting_char == '$') { - if (auto variable = parse_variable()) - return read_concat(variable.release_nonnull()); - - if (auto inline_exec = parse_evaluate()) - return read_concat(inline_exec.release_nonnull()); - } - - if (starting_char == '#') - return parse_comment(); - - if (starting_char == '(') { - consume(); - auto list = parse_list_expression(); - if (!expect(')')) { - restore_to(*rule_start); - return nullptr; - } - return read_concat(create<AST::CastToList>(move(list))); // Cast To List - } - - if (auto composite = parse_string_composite()) - return read_concat(composite.release_nonnull()); - - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_string_composite() -{ - auto rule_start = push_start(); - if (auto string = parse_string()) { - if (auto next_part = parse_string_composite()) - return create<AST::Juxtaposition>(string.release_nonnull(), next_part.release_nonnull()); // Concatenate String StringComposite - - return string; - } - - if (auto variable = parse_variable()) { - if (auto next_part = parse_string_composite()) - return create<AST::Juxtaposition>(variable.release_nonnull(), next_part.release_nonnull()); // Concatenate Variable StringComposite - - return variable; - } - - if (auto glob = parse_glob()) { - if (auto next_part = parse_string_composite()) - return create<AST::Juxtaposition>(glob.release_nonnull(), next_part.release_nonnull()); // Concatenate Glob StringComposite - - return glob; - } - - if (auto expansion = parse_brace_expansion()) { - if (auto next_part = parse_string_composite()) - return create<AST::Juxtaposition>(expansion.release_nonnull(), next_part.release_nonnull()); // Concatenate BraceExpansion StringComposite - - return expansion; - } - - if (auto bareword = parse_bareword()) { - if (auto next_part = parse_string_composite()) - return create<AST::Juxtaposition>(bareword.release_nonnull(), next_part.release_nonnull()); // Concatenate Bareword StringComposite - - return bareword; - } - - if (auto inline_command = parse_evaluate()) { - if (auto next_part = parse_string_composite()) - return create<AST::Juxtaposition>(inline_command.release_nonnull(), next_part.release_nonnull()); // Concatenate Execute StringComposite - - return inline_command; - } - - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_string() -{ - auto rule_start = push_start(); - if (at_end()) - return nullptr; - - if (peek() == '"') { - consume(); - auto inner = parse_doublequoted_string_inner(); - if (!inner) - inner = create<AST::SyntaxError>("Unexpected EOF in string", true); - if (!expect('"')) { - inner = create<AST::DoubleQuotedString>(move(inner)); - inner->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating double quote", true)); - return inner; - } - return create<AST::DoubleQuotedString>(move(inner)); // Double Quoted String - } - - if (peek() == '\'') { - consume(); - auto text = consume_while(is_not('\'')); - bool is_error = false; - if (!expect('\'')) - is_error = true; - auto result = create<AST::StringLiteral>(move(text)); // String Literal - if (is_error) - result->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating single quote", true)); - return move(result); - } - - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_doublequoted_string_inner() -{ - auto rule_start = push_start(); - if (at_end()) - return nullptr; - - StringBuilder builder; - while (!at_end() && peek() != '"') { - if (peek() == '\\') { - consume(); - if (at_end()) { - break; - } - auto ch = consume(); - switch (ch) { - case '\\': - default: - builder.append(ch); - break; - case 'x': { - if (m_input.length() <= m_offset + 2) - break; - auto first_nibble = tolower(consume()); - auto second_nibble = tolower(consume()); - if (!isxdigit(first_nibble) || !isxdigit(second_nibble)) { - builder.append(first_nibble); - builder.append(second_nibble); - break; - } - builder.append(to_byte(first_nibble, second_nibble)); - break; - } - case 'a': - builder.append('\a'); - break; - case 'b': - builder.append('\b'); - break; - case 'e': - builder.append('\x1b'); - break; - case 'f': - builder.append('\f'); - break; - case 'r': - builder.append('\r'); - break; - case 'n': - builder.append('\n'); - break; - } - continue; - } - if (peek() == '$') { - auto string_literal = create<AST::StringLiteral>(builder.to_string()); // String Literal - if (auto variable = parse_variable()) { - auto inner = create<AST::StringPartCompose>( - move(string_literal), - variable.release_nonnull()); // Compose String Variable - - if (auto string = parse_doublequoted_string_inner()) { - return create<AST::StringPartCompose>(move(inner), string.release_nonnull()); // Compose Composition Composition - } - - return inner; - } - - if (auto evaluate = parse_evaluate()) { - auto composition = create<AST::StringPartCompose>( - move(string_literal), - evaluate.release_nonnull()); // Compose String Sequence - - if (auto string = parse_doublequoted_string_inner()) { - return create<AST::StringPartCompose>(move(composition), string.release_nonnull()); // Compose Composition Composition - } - - return composition; - } - } - - builder.append(consume()); - } - - return create<AST::StringLiteral>(builder.to_string()); // String Literal -} - -RefPtr<AST::Node> Parser::parse_variable() -{ - auto rule_start = push_start(); - if (at_end()) - return nullptr; - - if (peek() != '$') - return nullptr; - - consume(); - switch (peek()) { - case '$': - case '?': - case '*': - case '#': - return create<AST::SpecialVariable>(consume()); // Variable Special - default: - break; - } - - auto name = consume_while(is_word_character); - - if (name.length() == 0) { - restore_to(rule_start->offset, rule_start->line); - return nullptr; - } - - return create<AST::SimpleVariable>(move(name)); // Variable Simple -} - -RefPtr<AST::Node> Parser::parse_evaluate() -{ - auto rule_start = push_start(); - if (at_end()) - return nullptr; - - if (peek() != '$') - return nullptr; - - consume(); - if (peek() == '(') { - consume(); - auto inner = parse_pipe_sequence(); - if (!inner) - inner = create<AST::SyntaxError>("Unexpected EOF in list", true); - if (!expect(')')) - inner->set_is_syntax_error(*create<AST::SyntaxError>("Expected a terminating close paren", true)); - - return create<AST::Execute>(inner.release_nonnull(), true); - } - auto inner = parse_expression(); - - if (!inner) { - inner = create<AST::SyntaxError>("Expected a command", true); - } else { - if (inner->is_list()) { - auto execute_inner = create<AST::Execute>(inner.release_nonnull(), true); - inner = move(execute_inner); - } else { - auto dyn_inner = create<AST::DynamicEvaluate>(inner.release_nonnull()); - inner = move(dyn_inner); - } - } - - return inner; -} - -RefPtr<AST::Node> Parser::parse_comment() -{ - if (at_end()) - return nullptr; - - if (peek() != '#') - return nullptr; - - consume(); - auto text = consume_while(is_not('\n')); - return create<AST::Comment>(move(text)); // Comment -} - -RefPtr<AST::Node> Parser::parse_bareword() -{ - auto rule_start = push_start(); - StringBuilder builder; - auto is_acceptable_bareword_character = [&](char c) { - return strchr("\\\"'*$&#|(){} ?;<>\n", c) == nullptr - && ((m_is_in_brace_expansion_spec && c != ',') || !m_is_in_brace_expansion_spec); - }; - while (!at_end()) { - char ch = peek(); - if (ch == '\\') { - consume(); - if (!at_end()) { - ch = consume(); - if (is_acceptable_bareword_character(ch)) - builder.append('\\'); - } - builder.append(ch); - continue; - } - - if (m_is_in_brace_expansion_spec && next_is("..")) { - // Don't eat '..' in a brace expansion spec. - break; - } - - if (is_acceptable_bareword_character(ch)) { - builder.append(consume()); - continue; - } - - break; - } - - if (builder.is_empty()) - return nullptr; - - auto current_end = m_offset; - auto current_line = line(); - auto string = builder.to_string(); - if (string.starts_with('~')) { - String username; - RefPtr<AST::Node> tilde, text; - - auto first_slash_index = string.index_of("/"); - if (first_slash_index.has_value()) { - username = string.substring_view(1, first_slash_index.value() - 1); - string = string.substring_view(first_slash_index.value(), string.length() - first_slash_index.value()); - } else { - username = string.substring_view(1, string.length() - 1); - string = ""; - } - - // Synthesize a Tilde Node with the correct positioning information. - { - restore_to(rule_start->offset, rule_start->line); - auto ch = consume(); - ASSERT(ch == '~'); - tilde = create<AST::Tilde>(move(username)); - } - - if (string.is_empty()) - return tilde; - - // Synthesize a BarewordLiteral Node with the correct positioning information. - { - auto text_start = push_start(); - restore_to(current_end, current_line); - text = create<AST::BarewordLiteral>(move(string)); - } - - return create<AST::Juxtaposition>(tilde.release_nonnull(), text.release_nonnull()); // Juxtaposition Variable Bareword - } - - if (string.starts_with("\\~")) { - // Un-escape the tilde, but only at the start (where it would be an expansion) - string = string.substring(1, string.length() - 1); - } - - return create<AST::BarewordLiteral>(move(string)); // Bareword Literal -} - -RefPtr<AST::Node> Parser::parse_glob() -{ - auto rule_start = push_start(); - auto bareword_part = parse_bareword(); - - if (at_end()) - return bareword_part; - - char ch = peek(); - if (ch == '*' || ch == '?') { - auto saved_offset = save_offset(); - consume(); - StringBuilder textbuilder; - if (bareword_part) { - StringView text; - if (bareword_part->is_bareword()) { - auto bareword = static_cast<AST::BarewordLiteral*>(bareword_part.ptr()); - text = bareword->text(); - } else { - // FIXME: Allow composition of tilde+bareword with globs: '~/foo/bar/baz*' - restore_to(saved_offset.offset, saved_offset.line); - bareword_part->set_is_syntax_error(*create<AST::SyntaxError>(String::format("Unexpected %s inside a glob", bareword_part->class_name().characters()))); - return bareword_part; - } - textbuilder.append(text); - } - - textbuilder.append(ch); - - auto glob_after = parse_glob(); - if (glob_after) { - if (glob_after->is_glob()) { - auto glob = static_cast<AST::Glob*>(glob_after.ptr()); - textbuilder.append(glob->text()); - } else if (glob_after->is_bareword()) { - auto bareword = static_cast<AST::BarewordLiteral*>(glob_after.ptr()); - textbuilder.append(bareword->text()); - } else if (glob_after->is_tilde()) { - auto bareword = static_cast<AST::Tilde*>(glob_after.ptr()); - textbuilder.append("~"); - textbuilder.append(bareword->text()); - } else { - return create<AST::SyntaxError>(String::formatted("Invalid node '{}' in glob position, escape shell special characters", glob_after->class_name())); - } - } - - return create<AST::Glob>(textbuilder.to_string()); // Glob - } - - return bareword_part; -} - -RefPtr<AST::Node> Parser::parse_brace_expansion() -{ - auto rule_start = push_start(); - - if (!expect('{')) - return nullptr; - - if (auto spec = parse_brace_expansion_spec()) { - if (!expect('}')) - spec->set_is_syntax_error(create<AST::SyntaxError>("Expected a close brace '}' to end a brace expansion", true)); - - return spec; - } - - restore_to(*rule_start); - return nullptr; -} - -RefPtr<AST::Node> Parser::parse_brace_expansion_spec() -{ - TemporaryChange is_in_brace_expansion { m_is_in_brace_expansion_spec, true }; - auto rule_start = push_start(); - auto start_expr = parse_expression(); - if (start_expr) { - if (expect("..")) { - if (auto end_expr = parse_expression()) { - if (end_expr->position().start_offset != start_expr->position().end_offset + 2) - end_expr->set_is_syntax_error(create<AST::SyntaxError>("Expected no whitespace between '..' and the following expression in brace expansion")); - - return create<AST::Range>(start_expr.release_nonnull(), end_expr.release_nonnull()); - } - - return create<AST::Range>(start_expr.release_nonnull(), create<AST::SyntaxError>("Expected an expression to end range brace expansion with", true)); - } - } - - NonnullRefPtrVector<AST::Node> subexpressions; - if (start_expr) - subexpressions.append(start_expr.release_nonnull()); - - while (expect(',')) { - auto expr = parse_expression(); - if (expr) { - subexpressions.append(expr.release_nonnull()); - } else { - subexpressions.append(create<AST::StringLiteral>("")); - } - } - - if (subexpressions.is_empty()) - return nullptr; - - return create<AST::BraceExpansion>(move(subexpressions)); -} - -StringView Parser::consume_while(Function<bool(char)> condition) -{ - if (at_end()) - return {}; - - auto start_offset = m_offset; - - while (!at_end() && condition(peek())) - consume(); - - return m_input.substring_view(start_offset, m_offset - start_offset); -} - -bool Parser::next_is(const StringView& next) -{ - auto start = push_start(); - auto res = expect(next); - restore_to(*start); - return res; -} - -} diff --git a/Shell/Parser.h b/Shell/Parser.h deleted file mode 100644 index c0d92e3a55..0000000000 --- a/Shell/Parser.h +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "AST.h" -#include <AK/Function.h> -#include <AK/RefPtr.h> -#include <AK/String.h> -#include <AK/StringBuilder.h> -#include <AK/Vector.h> - -namespace Shell { - -class Parser { -public: - Parser(StringView input) - : m_input(move(input)) - { - } - - RefPtr<AST::Node> parse(); - - struct SavedOffset { - size_t offset; - AST::Position::Line line; - }; - SavedOffset save_offset() const; - -private: - constexpr static size_t max_allowed_nested_rule_depth = 2048; - RefPtr<AST::Node> parse_toplevel(); - RefPtr<AST::Node> parse_sequence(); - RefPtr<AST::Node> parse_function_decl(); - RefPtr<AST::Node> parse_and_logical_sequence(); - RefPtr<AST::Node> parse_or_logical_sequence(); - RefPtr<AST::Node> parse_variable_decls(); - RefPtr<AST::Node> parse_pipe_sequence(); - RefPtr<AST::Node> parse_command(); - RefPtr<AST::Node> parse_control_structure(); - RefPtr<AST::Node> parse_continuation_control(); - RefPtr<AST::Node> parse_for_loop(); - RefPtr<AST::Node> parse_loop_loop(); - RefPtr<AST::Node> parse_if_expr(); - RefPtr<AST::Node> parse_subshell(); - RefPtr<AST::Node> parse_match_expr(); - AST::MatchEntry parse_match_entry(); - RefPtr<AST::Node> parse_match_pattern(); - RefPtr<AST::Node> parse_redirection(); - RefPtr<AST::Node> parse_list_expression(); - RefPtr<AST::Node> parse_expression(); - RefPtr<AST::Node> parse_string_composite(); - RefPtr<AST::Node> parse_string(); - RefPtr<AST::Node> parse_doublequoted_string_inner(); - RefPtr<AST::Node> parse_variable(); - RefPtr<AST::Node> parse_evaluate(); - RefPtr<AST::Node> parse_comment(); - RefPtr<AST::Node> parse_bareword(); - RefPtr<AST::Node> parse_glob(); - RefPtr<AST::Node> parse_brace_expansion(); - RefPtr<AST::Node> parse_brace_expansion_spec(); - - template<typename A, typename... Args> - NonnullRefPtr<A> create(Args... args); - - bool at_end() const { return m_input.length() <= m_offset; } - char peek(); - char consume(); - bool expect(char); - bool expect(const StringView&); - bool next_is(const StringView&); - - void restore_to(size_t offset, AST::Position::Line line) - { - m_offset = offset; - m_line = move(line); - } - - AST::Position::Line line() const { return m_line; } - - StringView consume_while(Function<bool(char)>); - - struct ScopedOffset { - ScopedOffset(Vector<size_t>& offsets, Vector<AST::Position::Line>& lines, size_t offset, size_t lineno, size_t linecol) - : offsets(offsets) - , lines(lines) - , offset(offset) - , line({ lineno, linecol }) - { - offsets.append(offset); - lines.append(line); - } - ~ScopedOffset() - { - auto last = offsets.take_last(); - ASSERT(last == offset); - - auto last_line = lines.take_last(); - ASSERT(last_line == line); - } - - Vector<size_t>& offsets; - Vector<AST::Position::Line>& lines; - size_t offset; - AST::Position::Line line; - }; - - void restore_to(const ScopedOffset& offset) { restore_to(offset.offset, offset.line); } - - OwnPtr<ScopedOffset> push_start(); - - StringView m_input; - size_t m_offset { 0 }; - - AST::Position::Line m_line { 0, 0 }; - - Vector<size_t> m_rule_start_offsets; - Vector<AST::Position::Line> m_rule_start_lines; - - bool m_is_in_brace_expansion_spec { false }; - bool m_continuation_controls_allowed { false }; -}; - -#if 0 -constexpr auto the_grammar = R"( -toplevel :: sequence? - -sequence :: variable_decls? or_logical_sequence terminator sequence - | variable_decls? or_logical_sequence '&' sequence - | variable_decls? or_logical_sequence - | variable_decls? function_decl (terminator sequence)? - | variable_decls? terminator sequence - -function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' [!c] toplevel '}' - -or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence - | and_logical_sequence - -and_logical_sequence :: pipe_sequence '&' '&' and_logical_sequence - | pipe_sequence - -terminator :: ';' - | '\n' - -variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '* - | identifier '=' '(' pipe_sequence ')' (' '+ variable_decls)? ' '* - -pipe_sequence :: command '|' pipe_sequence - | command - | control_structure '|' pipe_sequence - | control_structure - -control_structure[c] :: for_expr - | loop_expr - | if_expr - | subshell - | match_expr - | ?c: continuation_control - -continuation_control :: 'break' - | 'continue' - -for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}' - -loop_expr :: 'loop' ws* '{' [c] toplevel '}' - -if_expr :: 'if' ws+ or_logical_sequence ws+ '{' toplevel '}' else_clause? - -else_clause :: else '{' toplevel '}' - | else if_expr - -subshell :: '{' toplevel '}' - -match_expr :: 'match' ws+ expression ws* ('as' ws+ identifier)? '{' match_entry* '}' - -match_entry :: match_pattern ws* (as identifier_list)? '{' toplevel '}' - -identifier_list :: '(' (identifier ws*)* ')' - -match_pattern :: expression (ws* '|' ws* expression)* - -command :: redirection command - | list_expression command? - -redirection :: number? '>'{1,2} ' '* string_composite - | number? '<' ' '* string_composite - | number? '>' '&' number - | number? '>' '&' '-' - -list_expression :: ' '* expression (' '+ list_expression)? - -expression :: evaluate expression? - | string_composite expression? - | comment expression? - | '(' list_expression ')' expression? - -evaluate :: '$' '(' pipe_sequence ')' - | '$' expression {eval / dynamic resolve} - -string_composite :: string string_composite? - | variable string_composite? - | bareword string_composite? - | glob string_composite? - | brace_expansion string_composite? - -string :: '"' dquoted_string_inner '"' - | "'" [^']* "'" - -dquoted_string_inner :: '\' . dquoted_string_inner? {concat} - | variable dquoted_string_inner? {compose} - | . dquoted_string_inner? - | '\' 'x' digit digit dquoted_string_inner? - | '\' [abefrn] dquoted_string_inner? - -variable :: '$' identifier - | '$' '$' - | '$' '?' - | '$' '*' - | '$' '#' - | ... - -comment :: '#' [^\n]* - -bareword :: [^"'*$&#|()[\]{} ?;<>] bareword? - | '\' [^"'*$&#|()[\]{} ?;<>] bareword? - -bareword_with_tilde_expansion :: '~' bareword? - -glob :: [*?] bareword? - | bareword [*?] - -brace_expansion :: '{' brace_expansion_spec '}' - -brace_expansion_spec :: expression? (',' expression?)* - | expression '..' expression -)"; -#endif - -} diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp deleted file mode 100644 index 5be7baea2d..0000000000 --- a/Shell/Shell.cpp +++ /dev/null @@ -1,1939 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "Shell.h" -#include "Execution.h" -#include "Formatter.h" -#include <AK/Function.h> -#include <AK/LexicalPath.h> -#include <AK/ScopeGuard.h> -#include <AK/StringBuilder.h> -#include <AK/TemporaryChange.h> -#include <LibCore/ArgsParser.h> -#include <LibCore/DirIterator.h> -#include <LibCore/Event.h> -#include <LibCore/EventLoop.h> -#include <LibCore/File.h> -#include <LibLine/Editor.h> -#include <errno.h> -#include <fcntl.h> -#include <inttypes.h> -#include <pwd.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/utsname.h> -#include <sys/wait.h> -#include <termios.h> -#include <unistd.h> - -static bool s_disable_hyperlinks = false; -extern char** environ; - -//#define SH_DEBUG - -namespace Shell { - -// FIXME: This should eventually be removed once we've established that -// waitpid() is not passed the same job twice. -#ifdef __serenity__ -# define ENSURE_WAITID_ONCE -#endif - -void Shell::setup_signals() -{ - if (m_should_reinstall_signal_handlers) { - Core::EventLoop::register_signal(SIGCHLD, [this](int) { -#ifdef SH_DEBUG - dbgln("SIGCHLD!"); -#endif - notify_child_event(); - }); - - Core::EventLoop::register_signal(SIGTSTP, [this](auto) { - auto job = current_job(); - kill_job(job, SIGTSTP); - if (job) { - job->set_is_suspended(true); - job->unblock(); - } - }); - } -} - -void Shell::print_path(const String& path) -{ - if (s_disable_hyperlinks || !m_is_interactive) { - printf("%s", path.characters()); - return; - } - printf("\033]8;;file://%s%s\033\\%s\033]8;;\033\\", hostname, path.characters(), path.characters()); -} - -String Shell::prompt() const -{ - auto build_prompt = [&]() -> String { - auto* ps1 = getenv("PROMPT"); - if (!ps1) { - if (uid == 0) - return "# "; - - StringBuilder builder; - builder.appendf("\033]0;%s@%s:%s\007", username.characters(), hostname, cwd.characters()); - builder.appendf("\033[31;1m%s\033[0m@\033[37;1m%s\033[0m:\033[32;1m%s\033[0m$> ", username.characters(), hostname, cwd.characters()); - return builder.to_string(); - } - - StringBuilder builder; - for (char* ptr = ps1; *ptr; ++ptr) { - if (*ptr == '\\') { - ++ptr; - if (!*ptr) - break; - switch (*ptr) { - case 'X': - builder.append("\033]0;"); - break; - case 'a': - builder.append(0x07); - break; - case 'e': - builder.append(0x1b); - break; - case 'u': - builder.append(username); - break; - case 'h': - builder.append(hostname); - break; - case 'w': { - String home_path = getenv("HOME"); - if (cwd.starts_with(home_path)) { - builder.append('~'); - builder.append(cwd.substring_view(home_path.length(), cwd.length() - home_path.length())); - } else { - builder.append(cwd); - } - break; - } - case 'p': - builder.append(uid == 0 ? '#' : '$'); - break; - } - continue; - } - builder.append(*ptr); - } - return builder.to_string(); - }; - - return build_prompt(); -} - -String Shell::expand_tilde(const String& expression) -{ - ASSERT(expression.starts_with('~')); - - StringBuilder login_name; - size_t first_slash_index = expression.length(); - for (size_t i = 1; i < expression.length(); ++i) { - if (expression[i] == '/') { - first_slash_index = i; - break; - } - login_name.append(expression[i]); - } - - StringBuilder path; - for (size_t i = first_slash_index; i < expression.length(); ++i) - path.append(expression[i]); - - if (login_name.is_empty()) { - const char* home = getenv("HOME"); - if (!home) { - auto passwd = getpwuid(getuid()); - ASSERT(passwd && passwd->pw_dir); - return String::format("%s/%s", passwd->pw_dir, path.to_string().characters()); - } - return String::format("%s/%s", home, path.to_string().characters()); - } - - auto passwd = getpwnam(login_name.to_string().characters()); - if (!passwd) - return expression; - ASSERT(passwd->pw_dir); - - return String::format("%s/%s", passwd->pw_dir, path.to_string().characters()); -} - -bool Shell::is_glob(const StringView& s) -{ - for (size_t i = 0; i < s.length(); i++) { - char c = s.characters_without_null_termination()[i]; - if (c == '*' || c == '?') - return true; - } - return false; -} - -Vector<StringView> Shell::split_path(const StringView& path) -{ - Vector<StringView> parts; - - size_t substart = 0; - for (size_t i = 0; i < path.length(); i++) { - char ch = path[i]; - if (ch != '/') - continue; - size_t sublen = i - substart; - if (sublen != 0) - parts.append(path.substring_view(substart, sublen)); - substart = i + 1; - } - - size_t taillen = path.length() - substart; - if (taillen != 0) - parts.append(path.substring_view(substart, taillen)); - - return parts; -} - -Vector<String> Shell::expand_globs(const StringView& path, StringView base) -{ - auto explicitly_set_base = false; - if (path.starts_with('/')) { - base = "/"; - explicitly_set_base = true; - } - auto parts = split_path(path); - String base_string = base; - struct stat statbuf; - if (lstat(base_string.characters(), &statbuf) < 0) { - perror("lstat"); - return {}; - } - - StringBuilder resolved_base_path_builder; - resolved_base_path_builder.append(Core::File::real_path_for(base)); - if (S_ISDIR(statbuf.st_mode)) - resolved_base_path_builder.append('/'); - - auto resolved_base = resolved_base_path_builder.string_view(); - - auto results = expand_globs(move(parts), resolved_base); - - if (explicitly_set_base && base == "/") - resolved_base = resolved_base.substring_view(1, resolved_base.length() - 1); - for (auto& entry : results) { - entry = entry.substring(resolved_base.length(), entry.length() - resolved_base.length()); - if (entry.is_empty()) - entry = "."; - } - - // Make the output predictable and nice. - quick_sort(results); - - return results; -} - -Vector<String> Shell::expand_globs(Vector<StringView> path_segments, const StringView& base) -{ - if (path_segments.is_empty()) { - String base_str = base; - if (access(base_str.characters(), F_OK) == 0) - return { move(base_str) }; - return {}; - } - - auto first_segment = path_segments.take_first(); - if (is_glob(first_segment)) { - Vector<String> result; - - Core::DirIterator di(base, Core::DirIterator::SkipParentAndBaseDir); - if (di.has_error()) - return {}; - - while (di.has_next()) { - String path = di.next_path(); - - // Dotfiles have to be explicitly requested - if (path[0] == '.' && first_segment[0] != '.') - continue; - - if (path.matches(first_segment, CaseSensitivity::CaseSensitive)) { - StringBuilder builder; - builder.append(base); - if (!base.ends_with('/')) - builder.append('/'); - builder.append(path); - result.append(expand_globs(path_segments, builder.string_view())); - } - } - - return result; - } else { - StringBuilder builder; - builder.append(base); - if (!base.ends_with('/')) - builder.append('/'); - builder.append(first_segment); - - return expand_globs(move(path_segments), builder.string_view()); - } -} - -Vector<AST::Command> Shell::expand_aliases(Vector<AST::Command> initial_commands) -{ - Vector<AST::Command> commands; - - Function<void(AST::Command&)> resolve_aliases_and_append = [&](auto& command) { - if (!command.argv.is_empty()) { - auto alias = resolve_alias(command.argv[0]); - if (!alias.is_null()) { - auto argv0 = command.argv.take_first(); - auto subcommand_ast = Parser { alias }.parse(); - if (subcommand_ast) { - while (subcommand_ast->is_execute()) { - auto* ast = static_cast<AST::Execute*>(subcommand_ast.ptr()); - subcommand_ast = ast->command(); - } - auto subcommand_nonnull = subcommand_ast.release_nonnull(); - NonnullRefPtr<AST::Node> substitute = adopt(*new AST::Join(subcommand_nonnull->position(), - subcommand_nonnull, - adopt(*new AST::CommandLiteral(subcommand_nonnull->position(), command)))); - auto res = substitute->run(*this); - for (auto& subst_command : res->resolve_as_commands(*this)) { - if (!subst_command.argv.is_empty() && subst_command.argv.first() == argv0) // Disallow an alias resolving to itself. - commands.append(subst_command); - else - resolve_aliases_and_append(subst_command); - } - } else { - commands.append(command); - } - } else { - commands.append(command); - } - } else { - commands.append(command); - } - }; - - for (auto& command : initial_commands) - resolve_aliases_and_append(command); - - return commands; -} - -String Shell::resolve_path(String path) const -{ - if (!path.starts_with('/')) - path = String::format("%s/%s", cwd.characters(), path.characters()); - - return Core::File::real_path_for(path); -} - -Shell::LocalFrame* Shell::find_frame_containing_local_variable(const String& name) -{ - for (size_t i = m_local_frames.size(); i > 0; --i) { - auto& frame = m_local_frames[i - 1]; - if (frame.local_variables.contains(name)) - return &frame; - } - return nullptr; -} - -RefPtr<AST::Value> Shell::lookup_local_variable(const String& name) -{ - if (auto* frame = find_frame_containing_local_variable(name)) - return frame->local_variables.get(name).value(); - - if (auto index = name.to_uint(); index.has_value()) - return get_argument(index.value()); - - return nullptr; -} - -RefPtr<AST::Value> Shell::get_argument(size_t index) -{ - if (index == 0) - return adopt(*new AST::StringValue(current_script)); - - --index; - if (auto argv = lookup_local_variable("ARGV")) { - if (argv->is_list_without_resolution()) { - AST::ListValue* list = static_cast<AST::ListValue*>(argv.ptr()); - if (list->values().size() <= index) - return nullptr; - - return list->values().at(index); - } - - if (index != 0) - return nullptr; - - return argv; - } - - return nullptr; -} - -String Shell::local_variable_or(const String& name, const String& replacement) -{ - auto value = lookup_local_variable(name); - if (value) { - StringBuilder builder; - builder.join(" ", value->resolve_as_list(*this)); - return builder.to_string(); - } - return replacement; -} - -void Shell::set_local_variable(const String& name, RefPtr<AST::Value> value, bool only_in_current_frame) -{ - if (!only_in_current_frame) { - if (auto* frame = find_frame_containing_local_variable(name)) { - frame->local_variables.set(name, move(value)); - return; - } - } - - m_local_frames.last().local_variables.set(name, move(value)); -} - -void Shell::unset_local_variable(const String& name, bool only_in_current_frame) -{ - if (!only_in_current_frame) { - if (auto* frame = find_frame_containing_local_variable(name)) - frame->local_variables.remove(name); - return; - } - - m_local_frames.last().local_variables.remove(name); -} - -void Shell::define_function(String name, Vector<String> argnames, RefPtr<AST::Node> body) -{ - add_entry_to_cache(name); - m_functions.set(name, { name, move(argnames), move(body) }); -} - -bool Shell::has_function(const String& name) -{ - return m_functions.contains(name); -} - -bool Shell::invoke_function(const AST::Command& command, int& retval) -{ - if (command.argv.is_empty()) - return false; - - StringView name = command.argv.first(); - - TemporaryChange<String> script_change { current_script, name }; - - auto function_option = m_functions.get(name); - if (!function_option.has_value()) - return false; - - auto& function = function_option.value(); - - if (!function.body) { - retval = 0; - return true; - } - - if (command.argv.size() - 1 < function.arguments.size()) { - raise_error(ShellError::EvaluatedSyntaxError, String::formatted("Expected at least {} arguments to {}, but got {}", function.arguments.size(), function.name, command.argv.size() - 1), command.position); - retval = 1; - return true; - } - - auto frame = push_frame(String::formatted("function {}", function.name)); - size_t index = 0; - for (auto& arg : function.arguments) { - ++index; - set_local_variable(arg, adopt(*new AST::StringValue(command.argv[index])), true); - } - - auto argv = command.argv; - argv.take_first(); - set_local_variable("ARGV", adopt(*new AST::ListValue(move(argv))), true); - - Core::EventLoop loop; - setup_signals(); - - function.body->run(*this); - - retval = last_return_code; - return true; -} - -String Shell::format(const StringView& source, ssize_t& cursor) const -{ - Formatter formatter(source, cursor); - auto result = formatter.format(); - cursor = formatter.cursor(); - - return result; -} - -Shell::Frame Shell::push_frame(String name) -{ - m_local_frames.append(make<LocalFrame>(name, decltype(LocalFrame::local_variables) {})); -#ifdef SH_DEBUG - dbgln("New frame '{}' at {:p}", name, &m_local_frames.last()); -#endif - return { m_local_frames, m_local_frames.last() }; -} - -void Shell::pop_frame() -{ - ASSERT(m_local_frames.size() > 1); - m_local_frames.take_last(); -} - -Shell::Frame::~Frame() -{ - if (!should_destroy_frame) - return; - if (&frames.last() != &frame) { - dbgln("Frame destruction order violation near {:p} (container = {:p}) in '{}'", &frame, this, frame.name); - dbgln("Current frames:"); - for (auto& frame : frames) - dbgln("- {:p}: {}", &frame, frame.name); - ASSERT_NOT_REACHED(); - } - frames.take_last(); -} - -String Shell::resolve_alias(const String& name) const -{ - return m_aliases.get(name).value_or({}); -} - -bool Shell::is_runnable(const StringView& name) -{ - if (access(name.to_string().characters(), X_OK) == 0) - return true; - - return binary_search( - cached_path.span(), - name.to_string(), - nullptr, - [](auto& name, auto& program) { return strcmp(name.characters(), program.characters()); }); -} - -int Shell::run_command(const StringView& cmd, Optional<SourcePosition> source_position_override) -{ - // The default-constructed mode of the shell - // should not be used for execution! - ASSERT(!m_default_constructed); - - take_error(); - - ScopedValueRollback source_position_rollback { m_source_position }; - if (source_position_override.has_value()) - m_source_position = move(source_position_override); - - if (!m_source_position.has_value()) - m_source_position = SourcePosition { .source_file = {}, .literal_source_text = cmd, .position = {} }; - - if (cmd.is_empty()) - return 0; - - auto command = Parser(cmd).parse(); - - if (!command) - return 0; - -#ifdef SH_DEBUG - dbgln("Command follows"); - command->dump(0); -#endif - - if (command->is_syntax_error()) { - auto& error_node = command->syntax_error_node(); - auto& position = error_node.position(); - raise_error(ShellError::EvaluatedSyntaxError, error_node.error_text(), position); - } - - if (!has_error(ShellError::None)) { - possibly_print_error(); - take_error(); - return 1; - } - - tcgetattr(0, &termios); - - command->run(*this); - - if (!has_error(ShellError::None)) { - possibly_print_error(); - take_error(); - return 1; - } - - return last_return_code; -} - -RefPtr<Job> Shell::run_command(const AST::Command& command) -{ - FileDescriptionCollector fds; - - if (options.verbose) - warnln("+ {}", command); - - // If the command is empty, store the redirections and apply them to all later commands. - if (command.argv.is_empty() && !command.should_immediately_execute_next) { - m_global_redirections.append(command.redirections); - return nullptr; - } - - // Resolve redirections. - NonnullRefPtrVector<AST::Rewiring> rewirings; - auto resolve_redirection = [&](auto& redirection) -> IterationDecision { - auto rewiring_result = redirection.apply(); - if (rewiring_result.is_error()) { - if (!rewiring_result.error().is_empty()) - fprintf(stderr, "error: %s\n", rewiring_result.error().characters()); - return IterationDecision::Break; - } - auto& rewiring = rewiring_result.value(); - - if (rewiring->fd_action != AST::Rewiring::Close::ImmediatelyCloseNew) - rewirings.append(*rewiring); - - if (rewiring->fd_action == AST::Rewiring::Close::Old) { - fds.add(rewiring->old_fd); - } else if (rewiring->fd_action == AST::Rewiring::Close::New) { - if (rewiring->new_fd != -1) - fds.add(rewiring->new_fd); - } else if (rewiring->fd_action == AST::Rewiring::Close::ImmediatelyCloseNew) { - fds.add(rewiring->new_fd); - } else if (rewiring->fd_action == AST::Rewiring::Close::RefreshNew) { - ASSERT(rewiring->other_pipe_end); - - int pipe_fd[2]; - int rc = pipe(pipe_fd); - if (rc < 0) { - perror("pipe(RedirRefresh)"); - return IterationDecision::Break; - } - rewiring->new_fd = pipe_fd[1]; - rewiring->other_pipe_end->new_fd = pipe_fd[0]; // This fd will be added to the collection on one of the next iterations. - fds.add(pipe_fd[1]); - } else if (rewiring->fd_action == AST::Rewiring::Close::RefreshOld) { - ASSERT(rewiring->other_pipe_end); - - int pipe_fd[2]; - int rc = pipe(pipe_fd); - if (rc < 0) { - perror("pipe(RedirRefresh)"); - return IterationDecision::Break; - } - rewiring->old_fd = pipe_fd[1]; - rewiring->other_pipe_end->old_fd = pipe_fd[0]; // This fd will be added to the collection on one of the next iterations. - fds.add(pipe_fd[1]); - } - return IterationDecision::Continue; - }; - - auto apply_rewirings = [&] { - for (auto& rewiring : rewirings) { - -#ifdef SH_DEBUG - dbgln("in {}<{}>, dup2({}, {})", command.argv.is_empty() ? "(<Empty>)" : command.argv[0].characters(), getpid(), rewiring.old_fd, rewiring.new_fd); -#endif - int rc = dup2(rewiring.old_fd, rewiring.new_fd); - if (rc < 0) { - perror("dup2(run)"); - return IterationDecision::Break; - } - // {new,old}_fd is closed via the `fds` collector, but rewiring.other_pipe_end->{new,old}_fd - // isn't yet in that collector when the first child spawns. - if (rewiring.other_pipe_end) { - if (rewiring.fd_action == AST::Rewiring::Close::RefreshNew) { - if (rewiring.other_pipe_end && close(rewiring.other_pipe_end->new_fd) < 0) - perror("close other pipe end"); - } else if (rewiring.fd_action == AST::Rewiring::Close::RefreshOld) { - if (rewiring.other_pipe_end && close(rewiring.other_pipe_end->old_fd) < 0) - perror("close other pipe end"); - } - } - } - - return IterationDecision::Continue; - }; - - TemporaryChange signal_handler_install { m_should_reinstall_signal_handlers, false }; - - for (auto& redirection : m_global_redirections) { - if (resolve_redirection(redirection) == IterationDecision::Break) - return nullptr; - } - - for (auto& redirection : command.redirections) { - if (resolve_redirection(redirection) == IterationDecision::Break) - return nullptr; - } - - if (command.should_wait && run_builtin(command, rewirings, last_return_code)) { - for (auto& next_in_chain : command.next_chain) - run_tail(command, next_in_chain, last_return_code); - return nullptr; - } - - auto can_be_run_in_current_process = command.should_wait && !command.pipeline && !command.argv.is_empty(); - if (can_be_run_in_current_process && has_function(command.argv.first())) { - SavedFileDescriptors fds { rewirings }; - - for (auto& rewiring : rewirings) { - int rc = dup2(rewiring.old_fd, rewiring.new_fd); - if (rc < 0) { - perror("dup2(run)"); - return nullptr; - } - } - - if (invoke_function(command, last_return_code)) { - for (auto& next_in_chain : command.next_chain) - run_tail(command, next_in_chain, last_return_code); - return nullptr; - } - } - - if (command.argv.is_empty() && !command.next_chain.is_empty() && command.should_immediately_execute_next && command.next_chain.first().node->should_override_execution_in_current_process()) { - for (auto& next_in_chain : command.next_chain) - run_tail(command, next_in_chain, last_return_code); - return nullptr; - } - - Vector<const char*> argv; - Vector<String> copy_argv = command.argv; - argv.ensure_capacity(command.argv.size() + 1); - - for (auto& arg : copy_argv) - argv.append(arg.characters()); - - argv.append(nullptr); - - int sync_pipe[2]; - if (pipe(sync_pipe) < 0) { - perror("pipe"); - return nullptr; - } - - pid_t child = fork(); - if (child < 0) { - perror("fork"); - return nullptr; - } - - if (child == 0) { - close(sync_pipe[1]); - - m_is_subshell = true; - m_pid = getpid(); - Core::EventLoop::notify_forked(Core::EventLoop::ForkEvent::Child); - TemporaryChange signal_handler_install { m_should_reinstall_signal_handlers, true }; - - if (apply_rewirings() == IterationDecision::Break) - _exit(126); - - fds.collect(); - - u8 c; - while (read(sync_pipe[0], &c, 1) < 0) { - if (errno != EINTR) { - perror("read"); - // There's nothing interesting we can do here. - break; - } - } - -#ifdef SH_DEBUG - dbgln("Synced up with parent, we're good to exec()"); -#endif - - close(sync_pipe[0]); - - if (!m_is_subshell && command.should_wait) - tcsetattr(0, TCSANOW, &default_termios); - - if (command.should_immediately_execute_next) { - ASSERT(command.argv.is_empty()); - - Core::EventLoop mainloop; - setup_signals(); - - for (auto& next_in_chain : command.next_chain) - run_tail(command, next_in_chain, 0); - - _exit(last_return_code); - } - - if (run_builtin(command, {}, last_return_code)) - _exit(last_return_code); - - if (invoke_function(command, last_return_code)) - _exit(last_return_code); - - // We no longer need the jobs here. - jobs.clear(); - - execute_process(move(argv)); - ASSERT_NOT_REACHED(); - } - - close(sync_pipe[0]); - - bool is_first = !command.pipeline || (command.pipeline && command.pipeline->pgid == -1); - - if (command.pipeline) { - if (is_first) { - command.pipeline->pgid = child; - } - } - - pid_t pgid = is_first ? child : (command.pipeline ? command.pipeline->pgid : child); - if (!m_is_subshell || command.pipeline) { - if (setpgid(child, pgid) < 0 && m_is_interactive) - perror("setpgid"); - - if (!m_is_subshell) { - if (tcsetpgrp(STDOUT_FILENO, pgid) != 0 && m_is_interactive) - perror("tcsetpgrp(OUT)"); - if (tcsetpgrp(STDIN_FILENO, pgid) != 0 && m_is_interactive) - perror("tcsetpgrp(IN)"); - } - } - - while (write(sync_pipe[1], "x", 1) < 0) { - if (errno != EINTR) { - perror("write"); - // There's nothing interesting we can do here. - break; - } - } - - close(sync_pipe[1]); - - StringBuilder cmd; - cmd.join(" ", command.argv); - - auto command_copy = AST::Command(command); - // Clear the next chain if it's to be immediately executed - // as the child will run this chain. - if (command.should_immediately_execute_next) - command_copy.next_chain.clear(); - auto job = Job::create(child, pgid, cmd.build(), find_last_job_id() + 1, move(command_copy)); - jobs.set((u64)child, job); - - job->on_exit = [this](auto job) { - if (!job->exited()) - return; - - restore_ios(); - if (job->is_running_in_background() && job->should_announce_exit()) - warnln("Shell: Job {} ({}) exited\n", job->job_id(), job->cmd().characters()); - else if (job->signaled() && job->should_announce_signal()) - warnln("Shell: Job {} ({}) {}\n", job->job_id(), job->cmd().characters(), strsignal(job->termination_signal())); - - last_return_code = job->exit_code(); - job->disown(); - - run_tail(job); - }; - - fds.collect(); - - return *job; -} - -void Shell::execute_process(Vector<const char*>&& argv) -{ - int rc = execvp(argv[0], const_cast<char* const*>(argv.data())); - if (rc < 0) { - int saved_errno = errno; - struct stat st; - if (stat(argv[0], &st)) { - fprintf(stderr, "stat(%s): %s\n", argv[0], strerror(errno)); - _exit(126); - } - if (!(st.st_mode & S_IXUSR)) { - fprintf(stderr, "%s: Not executable\n", argv[0]); - _exit(126); - } - if (saved_errno == ENOENT) { - int shebang_fd = open(argv[0], O_RDONLY); - auto close_argv = ScopeGuard([shebang_fd]() { if (shebang_fd >= 0) close(shebang_fd); }); - char shebang[256] {}; - ssize_t num_read = -1; - if ((shebang_fd >= 0) && ((num_read = read(shebang_fd, shebang, sizeof(shebang))) >= 2) && (StringView(shebang).starts_with("#!"))) { - StringView shebang_path_view(&shebang[2], num_read - 2); - Optional<size_t> newline_pos = shebang_path_view.find_first_of("\n\r"); - shebang[newline_pos.has_value() ? (newline_pos.value() + 2) : num_read] = '\0'; - argv[0] = shebang; - int rc = execvp(argv[0], const_cast<char* const*>(argv.data())); - if (rc < 0) - fprintf(stderr, "%s: Invalid interpreter \"%s\": %s\n", argv[0], &shebang[2], strerror(errno)); - } else - fprintf(stderr, "%s: Command not found.\n", argv[0]); - } else { - if (S_ISDIR(st.st_mode)) { - fprintf(stderr, "Shell: %s: Is a directory\n", argv[0]); - _exit(126); - } - fprintf(stderr, "execvp(%s): %s\n", argv[0], strerror(saved_errno)); - } - _exit(126); - } - ASSERT_NOT_REACHED(); -} - -void Shell::run_tail(const AST::Command& invoking_command, const AST::NodeWithAction& next_in_chain, int head_exit_code) -{ - if (m_error != ShellError::None) { - possibly_print_error(); - if (!is_control_flow(m_error)) - take_error(); - return; - } - auto evaluate = [&] { - if (next_in_chain.node->would_execute()) { - next_in_chain.node->run(*this); - return; - } - auto node = next_in_chain.node; - if (!invoking_command.should_wait) - node = adopt(static_cast<AST::Node&>(*new AST::Background(next_in_chain.node->position(), move(node)))); - adopt(static_cast<AST::Node&>(*new AST::Execute(next_in_chain.node->position(), move(node))))->run(*this); - }; - switch (next_in_chain.action) { - case AST::NodeWithAction::And: - if (head_exit_code == 0) - evaluate(); - break; - case AST::NodeWithAction::Or: - if (head_exit_code != 0) - evaluate(); - break; - case AST::NodeWithAction::Sequence: - evaluate(); - break; - } -} - -void Shell::run_tail(RefPtr<Job> job) -{ - if (auto cmd = job->command_ptr()) { - deferred_invoke([=, this](auto&) { - for (auto& next_in_chain : cmd->next_chain) { - run_tail(*cmd, next_in_chain, job->exit_code()); - } - }); - } -} - -NonnullRefPtrVector<Job> Shell::run_commands(Vector<AST::Command>& commands) -{ - if (m_error != ShellError::None) { - possibly_print_error(); - if (!is_control_flow(m_error)) - take_error(); - return {}; - } - - NonnullRefPtrVector<Job> spawned_jobs; - - for (auto& command : commands) { -#ifdef SH_DEBUG - dbgln("Command"); - for (auto& arg : command.argv) - dbgln("argv: {}", arg); - for (auto& redir : command.redirections) { - if (redir.is_path_redirection()) { - auto path_redir = (const AST::PathRedirection*)&redir; - dbgln("redir path '{}' <-({})-> {}", path_redir->path, (int)path_redir->direction, path_redir->fd); - } else if (redir.is_fd_redirection()) { - auto* fdredir = (const AST::FdRedirection*)&redir; - dbgln("redir fd {} -> {}", fdredir->old_fd, fdredir->new_fd); - } else if (redir.is_close_redirection()) { - auto close_redir = (const AST::CloseRedirection*)&redir; - dbgln("close fd {}", close_redir->fd); - } else { - ASSERT_NOT_REACHED(); - } - } -#endif - auto job = run_command(command); - if (!job) - continue; - - spawned_jobs.append(*job); - if (command.should_wait) { - block_on_job(job); - } else { - job->set_running_in_background(true); - if (!command.is_pipe_source && command.should_notify_if_in_background) - job->set_should_announce_exit(true); - } - } - - if (m_error != ShellError::None) { - possibly_print_error(); - if (!is_control_flow(m_error)) - take_error(); - } - - return spawned_jobs; -} - -bool Shell::run_file(const String& filename, bool explicitly_invoked) -{ - TemporaryChange script_change { current_script, filename }; - TemporaryChange interactive_change { m_is_interactive, false }; - TemporaryChange<Optional<SourcePosition>> source_change { m_source_position, SourcePosition { .source_file = filename, .literal_source_text = {}, .position = {} } }; - - auto file_result = Core::File::open(filename, Core::File::ReadOnly); - if (file_result.is_error()) { - auto error = String::formatted("'{}': {}", escape_token_for_single_quotes(filename), file_result.error()); - if (explicitly_invoked) - raise_error(ShellError::OpenFailure, error); - else - dbgln("open() failed for {}", error); - return false; - } - auto file = file_result.value(); - auto data = file->read_all(); - run_command(data); - return true; -} -void Shell::restore_ios() -{ - if (m_is_subshell) - return; - tcsetattr(0, TCSANOW, &termios); - tcsetpgrp(STDOUT_FILENO, m_pid); - tcsetpgrp(STDIN_FILENO, m_pid); -} - -void Shell::block_on_pipeline(RefPtr<AST::Pipeline> pipeline) -{ - if (!pipeline) - return; - - for (auto& it : jobs) { - if (auto cmd = it.value->command_ptr(); cmd->pipeline == pipeline && cmd->is_pipe_source) - block_on_job(it.value); - } -} - -void Shell::block_on_job(RefPtr<Job> job) -{ - TemporaryChange<const Job*> current_job { m_current_job, job.ptr() }; - - if (!job) - return; - - if (job->is_suspended()) - return; // We cannot wait for a suspended job. - - ScopeGuard io_restorer { [&]() { - if (job->exited() && !job->is_running_in_background()) { - restore_ios(); - } - } }; - - Core::EventLoop loop; - job->on_exit = [&, old_exit = move(job->on_exit)](auto job) { - if (old_exit) - old_exit(job); - loop.quit(0); - }; - - if (job->exited()) - return; - - loop.exec(); - - // If the job is part of a pipeline, wait for the rest of the members too. - if (auto command = job->command_ptr()) - block_on_pipeline(command->pipeline); -} - -String Shell::get_history_path() -{ - if (auto histfile = getenv("HISTFILE")) - return { histfile }; - return String::formatted("{}/.history", home); -} - -String Shell::escape_token_for_single_quotes(const String& token) -{ - StringBuilder builder; - - for (auto c : token) { - switch (c) { - case '\'': - builder.append("'\\'"); - break; - default: - break; - } - builder.append(c); - } - - return builder.build(); -} - -bool Shell::is_special(char c) -{ - switch (c) { - case '\'': - case '"': - case '$': - case '|': - case '>': - case '<': - case '(': - case ')': - case '{': - case '}': - case '&': - case '\\': - case ' ': - return true; - default: - return false; - } -} - -String Shell::escape_token(const String& token) -{ - StringBuilder builder; - - for (auto c : token) { - if (is_special(c)) - builder.append('\\'); - builder.append(c); - } - - return builder.build(); -} - -String Shell::unescape_token(const String& token) -{ - StringBuilder builder; - - enum { - Free, - Escaped - } state { Free }; - - for (auto c : token) { - switch (state) { - case Escaped: - builder.append(c); - state = Free; - break; - case Free: - if (c == '\\') - state = Escaped; - else - builder.append(c); - break; - } - } - - if (state == Escaped) - builder.append('\\'); - - return builder.build(); -} - -void Shell::cache_path() -{ - if (!cached_path.is_empty()) - cached_path.clear_with_capacity(); - - // Add shell builtins to the cache. - for (const auto& builtin_name : builtin_names) - cached_path.append(escape_token(builtin_name)); - - // Add aliases to the cache. - for (const auto& alias : m_aliases) { - auto name = escape_token(alias.key); - if (cached_path.contains_slow(name)) - continue; - cached_path.append(name); - } - - String path = getenv("PATH"); - if (!path.is_empty()) { - auto directories = path.split(':'); - for (const auto& directory : directories) { - Core::DirIterator programs(directory.characters(), Core::DirIterator::SkipDots); - while (programs.has_next()) { - auto program = programs.next_path(); - String program_path = String::format("%s/%s", directory.characters(), program.characters()); - auto escaped_name = escape_token(program); - if (cached_path.contains_slow(escaped_name)) - continue; - if (access(program_path.characters(), X_OK) == 0) - cached_path.append(escaped_name); - } - } - } - - quick_sort(cached_path); -} - -void Shell::add_entry_to_cache(const String& entry) -{ - size_t index = 0; - auto match = binary_search( - cached_path.span(), - entry, - &index, - [](auto& name, auto& program) { return strcmp(name.characters(), program.characters()); }); - - if (match) - return; - - while (index < cached_path.size() && strcmp(cached_path[index].characters(), entry.characters()) < 0) { - index++; - } - cached_path.insert(index, entry); -} - -void Shell::highlight(Line::Editor& editor) const -{ - auto line = editor.line(); - Parser parser(line); - auto ast = parser.parse(); - if (!ast) - return; - ast->highlight_in_editor(editor, const_cast<Shell&>(*this)); -} - -Vector<Line::CompletionSuggestion> Shell::complete() -{ - auto line = m_editor->line(m_editor->cursor()); - - Parser parser(line); - - auto ast = parser.parse(); - - if (!ast) - return {}; - - return ast->complete_for_editor(*this, line.length()); -} - -Vector<Line::CompletionSuggestion> Shell::complete_path(const String& base, const String& part, size_t offset) -{ - auto token = offset ? part.substring_view(0, offset) : ""; - String path; - - ssize_t last_slash = token.length() - 1; - while (last_slash >= 0 && token[last_slash] != '/') - --last_slash; - - StringBuilder path_builder; - auto init_slash_part = token.substring_view(0, last_slash + 1); - auto last_slash_part = token.substring_view(last_slash + 1, token.length() - last_slash - 1); - - // Depending on the base, we will have to prepend cwd. - if (base.is_empty()) { - // '' /foo -> absolute - // '' foo -> relative - if (!token.starts_with('/')) - path_builder.append(cwd); - path_builder.append('/'); - path_builder.append(init_slash_part); - } else { - // /foo * -> absolute - // foo * -> relative - if (!base.starts_with('/')) - path_builder.append(cwd); - path_builder.append('/'); - path_builder.append(base); - path_builder.append('/'); - path_builder.append(init_slash_part); - } - path = path_builder.build(); - token = last_slash_part; - - // the invariant part of the token is actually just the last segment - // e. in `cd /foo/bar', 'bar' is the invariant - // since we are not suggesting anything starting with - // `/foo/', but rather just `bar...' - auto token_length = escape_token(token).length(); - if (m_editor) - m_editor->suggest(token_length, last_slash + 1); - - // only suggest dot-files if path starts with a dot - Core::DirIterator files(path, - token.starts_with('.') ? Core::DirIterator::SkipParentAndBaseDir : Core::DirIterator::SkipDots); - - Vector<Line::CompletionSuggestion> suggestions; - - while (files.has_next()) { - auto file = files.next_path(); - if (file.starts_with(token)) { - struct stat program_status; - String file_path = String::format("%s/%s", path.characters(), file.characters()); - int stat_error = stat(file_path.characters(), &program_status); - if (!stat_error) { - if (S_ISDIR(program_status.st_mode)) { - suggestions.append({ escape_token(file), "/" }); - } else { - suggestions.append({ escape_token(file), " " }); - } - suggestions.last().input_offset = token_length; - } - } - } - - return suggestions; -} - -Vector<Line::CompletionSuggestion> Shell::complete_program_name(const String& name, size_t offset) -{ - auto match = binary_search( - cached_path.span(), - name, - nullptr, - [](auto& name, auto& program) { return strncmp(name.characters(), program.characters(), name.length()); }); - - if (!match) - return complete_path("", name, offset); - - String completion = *match; - auto token_length = escape_token(name).length(); - if (m_editor) - m_editor->suggest(token_length, 0); - - // Now that we have a program name starting with our token, we look at - // other program names starting with our token and cut off any mismatching - // characters. - - Vector<Line::CompletionSuggestion> suggestions; - - int index = match - cached_path.data(); - for (int i = index - 1; i >= 0 && cached_path[i].starts_with(name); --i) { - suggestions.append({ cached_path[i], " " }); - suggestions.last().input_offset = token_length; - } - for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(name); ++i) { - suggestions.append({ cached_path[i], " " }); - suggestions.last().input_offset = token_length; - } - suggestions.append({ cached_path[index], " " }); - suggestions.last().input_offset = token_length; - - return suggestions; -} - -Vector<Line::CompletionSuggestion> Shell::complete_variable(const String& name, size_t offset) -{ - Vector<Line::CompletionSuggestion> suggestions; - auto pattern = offset ? name.substring_view(0, offset) : ""; - - if (m_editor) - m_editor->suggest(offset); - - // Look at local variables. - for (auto& frame : m_local_frames) { - for (auto& variable : frame.local_variables) { - if (variable.key.starts_with(pattern) && !suggestions.contains_slow(variable.key)) - suggestions.append(variable.key); - } - } - - // Look at the environment. - for (auto i = 0; environ[i]; ++i) { - auto entry = StringView { environ[i] }; - if (entry.starts_with(pattern)) { - auto parts = entry.split_view('='); - if (parts.is_empty() || parts.first().is_empty()) - continue; - String name = parts.first(); - if (suggestions.contains_slow(name)) - continue; - suggestions.append(move(name)); - suggestions.last().input_offset = offset; - } - } - - return suggestions; -} - -Vector<Line::CompletionSuggestion> Shell::complete_user(const String& name, size_t offset) -{ - Vector<Line::CompletionSuggestion> suggestions; - auto pattern = offset ? name.substring_view(0, offset) : ""; - - if (m_editor) - m_editor->suggest(offset); - - Core::DirIterator di("/home", Core::DirIterator::SkipParentAndBaseDir); - - if (di.has_error()) - return suggestions; - - while (di.has_next()) { - String name = di.next_path(); - if (name.starts_with(pattern)) { - suggestions.append(name); - suggestions.last().input_offset = offset; - } - } - - return suggestions; -} - -Vector<Line::CompletionSuggestion> Shell::complete_option(const String& program_name, const String& option, size_t offset) -{ - size_t start = 0; - while (start < option.length() && option[start] == '-' && start < 2) - ++start; - auto option_pattern = offset > start ? option.substring_view(start, offset - start) : ""; - if (m_editor) - m_editor->suggest(offset); - - Vector<Line::CompletionSuggestion> suggestions; - - dbgln("Shell::complete_option({}, {})", program_name, option_pattern); - - // FIXME: Figure out how to do this stuff. - if (has_builtin(program_name)) { - // Complete builtins. - if (program_name == "setopt") { - bool negate = false; - if (option_pattern.starts_with("no_")) { - negate = true; - option_pattern = option_pattern.substring_view(3, option_pattern.length() - 3); - } - auto maybe_negate = [&](const StringView& view) { - static StringBuilder builder; - builder.clear(); - builder.append("--"); - if (negate) - builder.append("no_"); - builder.append(view); - return builder.to_string(); - }; -#define __ENUMERATE_SHELL_OPTION(name, d_, descr_) \ - if (StringView { #name }.starts_with(option_pattern)) { \ - suggestions.append(maybe_negate(#name)); \ - suggestions.last().input_offset = offset; \ - } - - ENUMERATE_SHELL_OPTIONS(); -#undef __ENUMERATE_SHELL_OPTION - return suggestions; - } - } - return suggestions; -} - -void Shell::bring_cursor_to_beginning_of_a_line() const -{ - struct winsize ws; - if (m_editor) { - ws = m_editor->terminal_size(); - } else { - if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) < 0) { - // Very annoying assumptions. - ws.ws_col = 80; - ws.ws_row = 25; - } - } - - // Black with Cyan background. - constexpr auto default_mark = "\e[30;46m%\e[0m"; - String eol_mark = getenv("PROMPT_EOL_MARK"); - if (eol_mark.is_null()) - eol_mark = default_mark; - size_t eol_mark_length = Line::Editor::actual_rendered_string_metrics(eol_mark).line_metrics.last().total_length(); - if (eol_mark_length >= ws.ws_col) { - eol_mark = default_mark; - eol_mark_length = 1; - } - - fputs(eol_mark.characters(), stderr); - - for (auto i = eol_mark_length; i < ws.ws_col; ++i) - putc(' ', stderr); - - putc('\r', stderr); -} - -bool Shell::read_single_line() -{ - restore_ios(); - bring_cursor_to_beginning_of_a_line(); - auto line_result = m_editor->get_line(prompt()); - - if (line_result.is_error()) { - if (line_result.error() == Line::Editor::Error::Eof || line_result.error() == Line::Editor::Error::Empty) { - // Pretend the user tried to execute builtin_exit() - run_command("exit"); - return read_single_line(); - } else { - Core::EventLoop::current().quit(1); - return false; - } - } - - auto& line = line_result.value(); - - if (line.is_empty()) - return true; - - run_command(line); - - m_editor->add_to_history(line); - return true; -} - -void Shell::custom_event(Core::CustomEvent& event) -{ - if (event.custom_type() == ReadLine) { - if (read_single_line()) - Core::EventLoop::current().post_event(*this, make<Core::CustomEvent>(ShellEventType::ReadLine)); - return; - } -} - -void Shell::notify_child_event() -{ -#ifdef ENSURE_WAITID_ONCE - static HashTable<pid_t> s_waited_for_pids; -#endif - Vector<u64> disowned_jobs; - // Workaround the fact that we can't receive *who* exactly changed state. - // The child might still be alive (and even running) when this signal is dispatched to us - // so just...repeat until we find a suitable child. - // This, of course, will mean that someone can send us a SIGCHILD and we'd be spinning here - // until the next child event we can actually handle. - bool found_child = false; - do { - // Ignore stray SIGCHLD when there are no jobs. - if (jobs.is_empty()) - return; - - for (auto& it : jobs) { - auto job_id = it.key; - auto& job = *it.value; -#ifdef ENSURE_WAITID_ONCE - // Theoretically, this should never trip, as jobs are removed from - // the job table when waitpid() succeeds *and* the child is dead. - ASSERT(!s_waited_for_pids.contains(job.pid())); -#endif - - int wstatus = 0; -#ifdef SH_DEBUG - dbgln("waitpid({}) = ...", job.pid()); -#endif - auto child_pid = waitpid(job.pid(), &wstatus, WNOHANG | WUNTRACED); -#ifdef SH_DEBUG - dbgln("... = {} - {}", child_pid, wstatus); -#endif - - if (child_pid < 0) { - if (errno == ECHILD) { - // The child process went away before we could process its death, just assume it exited all ok. - // FIXME: This should never happen, the child should stay around until we do the waitpid above. - child_pid = job.pid(); - } else { - ASSERT_NOT_REACHED(); - } - } - if (child_pid == 0) { - // If the child existed, but wasn't dead. - continue; - } - if (child_pid == job.pid()) { - if (WIFSIGNALED(wstatus) && !WIFSTOPPED(wstatus)) { - job.set_signalled(WTERMSIG(wstatus)); - } else if (WIFEXITED(wstatus)) { - job.set_has_exit(WEXITSTATUS(wstatus)); - } else if (WIFSTOPPED(wstatus)) { - job.unblock(); - job.set_is_suspended(true); - } - found_child = true; -#ifdef ENSURE_WAITID_ONCE - // NOTE: This check is here to find bugs about our assumptions about waitpid(), - // it does not hold in general, and it definitely does not hold in the long run. - // Reasons that we would call waitpid() more than once: - // - PID reuse/wraparound: This will simply fail the assertion, ignored here. - // - Non-terminating unblocks: - // - Suspension: (e.g. via ^Z) - // - ? - // - ? - if (job.exited()) - s_waited_for_pids.set(child_pid); -#endif - } - if (job.should_be_disowned()) - disowned_jobs.append(job_id); - } - - for (auto job_id : disowned_jobs) { - jobs.remove(job_id); - } - } while (!found_child); -} - -Shell::Shell() - : m_default_constructed(true) -{ - push_frame("main").leak_frame(); - - int rc = gethostname(hostname, Shell::HostNameSize); - if (rc < 0) - perror("gethostname"); - - { - auto* pw = getpwuid(getuid()); - if (pw) { - username = pw->pw_name; - home = pw->pw_dir; - setenv("HOME", pw->pw_dir, 1); - } - endpwent(); - } - - // For simplicity, start at the user's home directory. - this->cwd = home; - setenv("PWD", home.characters(), 1); - - // Add the default PATH vars. - { - StringBuilder path; - path.append(getenv("PATH")); - if (path.length()) - path.append(":"); - path.append("/bin:/usr/bin:/usr/local/bin"); - setenv("PATH", path.to_string().characters(), true); - } - - cache_path(); -} - -Shell::Shell(Line::Editor& editor) - : m_editor(editor) -{ - uid = getuid(); - tcsetpgrp(0, getpgrp()); - m_pid = getpid(); - - push_frame("main").leak_frame(); - - int rc = gethostname(hostname, Shell::HostNameSize); - if (rc < 0) - perror("gethostname"); - - auto istty = isatty(STDIN_FILENO); - m_is_interactive = istty; - - if (istty) { - rc = ttyname_r(0, ttyname, Shell::TTYNameSize); - if (rc < 0) - perror("ttyname_r"); - } else { - ttyname[0] = 0; - } - - { - auto* cwd = getcwd(nullptr, 0); - this->cwd = cwd; - setenv("PWD", cwd, 1); - free(cwd); - } - - { - auto* pw = getpwuid(getuid()); - if (pw) { - username = pw->pw_name; - home = pw->pw_dir; - setenv("HOME", pw->pw_dir, 1); - } - endpwent(); - } - - directory_stack.append(cwd); - m_editor->load_history(get_history_path()); - cache_path(); - - m_editor->register_key_input_callback('\n', [](Line::Editor& editor) { - auto ast = Parser(editor.line()).parse(); - if (ast && ast->is_syntax_error() && ast->syntax_error_node().is_continuable()) - return true; - - return EDITOR_INTERNAL_FUNCTION(finish)(editor); - }); -} - -Shell::~Shell() -{ - if (m_default_constructed) - return; - - stop_all_jobs(); - m_editor->save_history(get_history_path()); -} - -void Shell::stop_all_jobs() -{ - if (!jobs.is_empty()) { - if (m_is_interactive && !m_is_subshell) - printf("Killing active jobs\n"); - for (auto& entry : jobs) { - if (entry.value->is_suspended()) { -#ifdef SH_DEBUG - dbgln("Job {} is suspended", entry.value->pid()); -#endif - kill_job(entry.value, SIGCONT); - } - - kill_job(entry.value, SIGHUP); - } - - usleep(10000); // Wait for a bit before killing the job - - for (auto& entry : jobs) { -#ifdef SH_DEBUG - dbgln("Actively killing {} ({})", entry.value->pid(), entry.value->cmd()); -#endif - kill_job(entry.value, SIGKILL); - } - - jobs.clear(); - } -} - -u64 Shell::find_last_job_id() const -{ - u64 job_id = 0; - for (auto& entry : jobs) { - if (entry.value->job_id() > job_id) - job_id = entry.value->job_id(); - } - return job_id; -} - -const Job* Shell::find_job(u64 id) -{ - for (auto& entry : jobs) { - if (entry.value->job_id() == id) - return entry.value; - } - return nullptr; -} - -void Shell::kill_job(const Job* job, int sig) -{ - if (!job) - return; - - if (killpg(job->pgid(), sig) < 0) { - if (kill(job->pid(), sig) < 0) { - perror("kill"); - } - } -} - -void Shell::save_to(JsonObject& object) -{ - Core::Object::save_to(object); - object.set("working_directory", cwd); - object.set("username", username); - object.set("user_home_path", home); - object.set("user_id", uid); - object.set("directory_stack_size", directory_stack.size()); - object.set("cd_history_size", cd_history.size()); - - // Jobs. - JsonArray job_objects; - for (auto& job_entry : jobs) { - JsonObject job_object; - job_object.set("pid", job_entry.value->pid()); - job_object.set("pgid", job_entry.value->pgid()); - job_object.set("running_time", job_entry.value->timer().elapsed()); - job_object.set("command", job_entry.value->cmd()); - job_object.set("is_running_in_background", job_entry.value->is_running_in_background()); - job_objects.append(move(job_object)); - } - object.set("jobs", move(job_objects)); -} - -void Shell::possibly_print_error() const -{ - switch (m_error) { - case ShellError::EvaluatedSyntaxError: - warnln("Shell Syntax Error: {}", m_error_description); - break; - case ShellError::InvalidGlobError: - case ShellError::NonExhaustiveMatchRules: - warnln("Shell: {}", m_error_description); - break; - case ShellError::OpenFailure: - warnln("Shell: Open failed for {}", m_error_description); - break; - case ShellError::InternalControlFlowBreak: - case ShellError::InternalControlFlowContinue: - return; - case ShellError::None: - return; - } - - if (m_source_position.has_value() && m_source_position->position.has_value()) { - auto& source_position = m_source_position.value(); - auto do_line = [&](auto line, auto& current_line) { - auto is_in_range = line >= (i64)source_position.position->start_line.line_number && line <= (i64)source_position.position->end_line.line_number; - warnln("{:>3}| {}", line, current_line); - if (is_in_range) { - warn("\x1b[31m"); - size_t length_written_so_far = 0; - if (line == (i64)source_position.position->start_line.line_number) { - warn("{:~>{}}", "", 5 + source_position.position->start_line.line_column); - length_written_so_far += source_position.position->start_line.line_column; - } else { - warn("{:~>{}}", "", 5); - } - if (line == (i64)source_position.position->end_line.line_number) { - warn("{:^>{}}", "", source_position.position->end_line.line_column - length_written_so_far); - length_written_so_far += source_position.position->start_line.line_column; - } else { - warn("{:^>{}}", "", current_line.length() - length_written_so_far); - } - warnln("\x1b[0m"); - } - }; - int line = -1; - String current_line; - i64 line_to_skip_to = max(source_position.position->start_line.line_number, 2ul) - 2; - - if (!source_position.source_file.is_null()) { - auto file = Core::File::open(source_position.source_file, Core::IODevice::OpenMode::ReadOnly); - if (file.is_error()) { - warnln("Shell: Internal error while trying to display source information: {} (while reading '{}')", file.error(), source_position.source_file); - return; - } - while (line < line_to_skip_to) { - if (file.value()->eof()) - return; - current_line = file.value()->read_line(); - ++line; - } - - for (; line < (i64)source_position.position->end_line.line_number + 2; ++line) { - do_line(line, current_line); - if (file.value()->eof()) - current_line = ""; - else - current_line = file.value()->read_line(); - } - } else if (!source_position.literal_source_text.is_empty()) { - GenericLexer lexer { source_position.literal_source_text }; - while (line < line_to_skip_to) { - if (lexer.is_eof()) - return; - current_line = lexer.consume_line(); - ++line; - } - - for (; line < (i64)source_position.position->end_line.line_number + 2; ++line) { - do_line(line, current_line); - if (lexer.is_eof()) - current_line = ""; - else - current_line = lexer.consume_line(); - } - } - } - warnln(); -} - -void FileDescriptionCollector::collect() -{ - for (auto fd : m_fds) - close(fd); - m_fds.clear(); -} - -FileDescriptionCollector::~FileDescriptionCollector() -{ - collect(); -} - -void FileDescriptionCollector::add(int fd) -{ - m_fds.append(fd); -} - -SavedFileDescriptors::SavedFileDescriptors(const NonnullRefPtrVector<AST::Rewiring>& intended_rewirings) -{ - for (auto& rewiring : intended_rewirings) { - int new_fd = dup(rewiring.new_fd); - if (new_fd < 0) { - if (errno != EBADF) - perror("dup"); - // The fd that will be overwritten isn't open right now, - // it will be cleaned up by the exec()-side collector - // and we have nothing to do here, so just ignore this error. - continue; - } - - auto flags = fcntl(new_fd, F_GETFL); - auto rc = fcntl(new_fd, F_SETFL, flags | FD_CLOEXEC); - ASSERT(rc == 0); - - m_saves.append({ rewiring.new_fd, new_fd }); - m_collector.add(new_fd); - } -} - -SavedFileDescriptors::~SavedFileDescriptors() -{ - for (auto& save : m_saves) { - if (dup2(save.saved, save.original) < 0) { - perror("dup2(~SavedFileDescriptors)"); - continue; - } - } -} - -} diff --git a/Shell/Shell.h b/Shell/Shell.h deleted file mode 100644 index 7a357ac870..0000000000 --- a/Shell/Shell.h +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#pragma once - -#include "Job.h" -#include "Parser.h" -#include <AK/CircularQueue.h> -#include <AK/HashMap.h> -#include <AK/NonnullOwnPtrVector.h> -#include <AK/String.h> -#include <AK/StringBuilder.h> -#include <AK/Types.h> -#include <AK/Vector.h> -#include <LibCore/Notifier.h> -#include <LibCore/Object.h> -#include <LibLine/Editor.h> -#include <termios.h> - -#define ENUMERATE_SHELL_BUILTINS() \ - __ENUMERATE_SHELL_BUILTIN(alias) \ - __ENUMERATE_SHELL_BUILTIN(cd) \ - __ENUMERATE_SHELL_BUILTIN(cdh) \ - __ENUMERATE_SHELL_BUILTIN(pwd) \ - __ENUMERATE_SHELL_BUILTIN(exec) \ - __ENUMERATE_SHELL_BUILTIN(exit) \ - __ENUMERATE_SHELL_BUILTIN(export) \ - __ENUMERATE_SHELL_BUILTIN(glob) \ - __ENUMERATE_SHELL_BUILTIN(unset) \ - __ENUMERATE_SHELL_BUILTIN(history) \ - __ENUMERATE_SHELL_BUILTIN(umask) \ - __ENUMERATE_SHELL_BUILTIN(dirs) \ - __ENUMERATE_SHELL_BUILTIN(pushd) \ - __ENUMERATE_SHELL_BUILTIN(popd) \ - __ENUMERATE_SHELL_BUILTIN(setopt) \ - __ENUMERATE_SHELL_BUILTIN(shift) \ - __ENUMERATE_SHELL_BUILTIN(source) \ - __ENUMERATE_SHELL_BUILTIN(time) \ - __ENUMERATE_SHELL_BUILTIN(jobs) \ - __ENUMERATE_SHELL_BUILTIN(disown) \ - __ENUMERATE_SHELL_BUILTIN(fg) \ - __ENUMERATE_SHELL_BUILTIN(bg) \ - __ENUMERATE_SHELL_BUILTIN(wait) - -#define ENUMERATE_SHELL_OPTIONS() \ - __ENUMERATE_SHELL_OPTION(inline_exec_keep_empty_segments, false, "Keep empty segments in inline execute $(...)") \ - __ENUMERATE_SHELL_OPTION(verbose, false, "Announce every command that is about to be executed") - -namespace Shell { - -class Shell; - -class Shell : public Core::Object { - C_OBJECT(Shell); - -public: - constexpr static auto local_init_file_path = "~/.shellrc"; - constexpr static auto global_init_file_path = "/etc/shellrc"; - - bool should_format_live() const { return m_should_format_live; } - void set_live_formatting(bool value) { m_should_format_live = value; } - - void setup_signals(); - - struct SourcePosition { - String source_file; - String literal_source_text; - Optional<AST::Position> position; - }; - - int run_command(const StringView&, Optional<SourcePosition> = {}); - bool is_runnable(const StringView&); - RefPtr<Job> run_command(const AST::Command&); - NonnullRefPtrVector<Job> run_commands(Vector<AST::Command>&); - bool run_file(const String&, bool explicitly_invoked = true); - bool run_builtin(const AST::Command&, const NonnullRefPtrVector<AST::Rewiring>&, int& retval); - bool has_builtin(const StringView&) const; - void block_on_job(RefPtr<Job>); - void block_on_pipeline(RefPtr<AST::Pipeline>); - String prompt() const; - - static String expand_tilde(const String&); - static Vector<String> expand_globs(const StringView& path, StringView base); - static Vector<String> expand_globs(Vector<StringView> path_segments, const StringView& base); - Vector<AST::Command> expand_aliases(Vector<AST::Command>); - String resolve_path(String) const; - String resolve_alias(const String&) const; - - RefPtr<AST::Value> get_argument(size_t); - RefPtr<AST::Value> lookup_local_variable(const String&); - String local_variable_or(const String&, const String&); - void set_local_variable(const String&, RefPtr<AST::Value>, bool only_in_current_frame = false); - void unset_local_variable(const String&, bool only_in_current_frame = false); - - void define_function(String name, Vector<String> argnames, RefPtr<AST::Node> body); - bool has_function(const String&); - bool invoke_function(const AST::Command&, int& retval); - - String format(const StringView&, ssize_t& cursor) const; - - RefPtr<Line::Editor> editor() const { return m_editor; } - - struct LocalFrame { - LocalFrame(const String& name, HashMap<String, RefPtr<AST::Value>> variables) - : name(name) - , local_variables(move(variables)) - { - } - - String name; - HashMap<String, RefPtr<AST::Value>> local_variables; - }; - - struct Frame { - Frame(NonnullOwnPtrVector<LocalFrame>& frames, const LocalFrame& frame) - : frames(frames) - , frame(frame) - { - } - ~Frame(); - - void leak_frame() { should_destroy_frame = false; } - - private: - NonnullOwnPtrVector<LocalFrame>& frames; - const LocalFrame& frame; - bool should_destroy_frame { true }; - }; - - [[nodiscard]] Frame push_frame(String name); - void pop_frame(); - - static String escape_token_for_single_quotes(const String& token); - static String escape_token(const String& token); - static String unescape_token(const String& token); - static bool is_special(char c); - - static bool is_glob(const StringView&); - static Vector<StringView> split_path(const StringView&); - - void highlight(Line::Editor&) const; - Vector<Line::CompletionSuggestion> complete(); - Vector<Line::CompletionSuggestion> complete_path(const String& base, const String&, size_t offset); - Vector<Line::CompletionSuggestion> complete_program_name(const String&, size_t offset); - Vector<Line::CompletionSuggestion> complete_variable(const String&, size_t offset); - Vector<Line::CompletionSuggestion> complete_user(const String&, size_t offset); - Vector<Line::CompletionSuggestion> complete_option(const String&, const String&, size_t offset); - - void restore_ios(); - - u64 find_last_job_id() const; - const Job* find_job(u64 id); - const Job* current_job() const { return m_current_job; } - void kill_job(const Job*, int sig); - - String get_history_path(); - void print_path(const String& path); - - bool read_single_line(); - - void notify_child_event(); - - struct termios termios; - struct termios default_termios; - bool was_interrupted { false }; - bool was_resized { false }; - - String cwd; - String username; - String home; - - constexpr static auto TTYNameSize = 32; - constexpr static auto HostNameSize = 64; - - char ttyname[TTYNameSize]; - char hostname[HostNameSize]; - - uid_t uid; - int last_return_code { 0 }; - Vector<String> directory_stack; - CircularQueue<String, 8> cd_history; // FIXME: have a configurable cd history length - HashMap<u64, NonnullRefPtr<Job>> jobs; - Vector<String, 256> cached_path; - - String current_script; - - enum ShellEventType { - ReadLine, - }; - - enum class ShellError { - None, - InternalControlFlowBreak, - InternalControlFlowContinue, - EvaluatedSyntaxError, - NonExhaustiveMatchRules, - InvalidGlobError, - OpenFailure, - }; - - void raise_error(ShellError kind, String description, Optional<AST::Position> position = {}) - { - m_error = kind; - m_error_description = move(description); - if (m_source_position.has_value() && position.has_value()) - m_source_position.value().position = position.release_value(); - } - bool has_error(ShellError err) const { return m_error == err; } - const String& error_description() const { return m_error_description; } - ShellError take_error() - { - auto err = m_error; - m_error = ShellError::None; - m_error_description = {}; - return err; - } - void possibly_print_error() const; - bool is_control_flow(ShellError error) - { - switch (error) { - case ShellError::InternalControlFlowBreak: - case ShellError::InternalControlFlowContinue: - return true; - default: - return false; - } - } - -#define __ENUMERATE_SHELL_OPTION(name, default_, description) \ - bool name { default_ }; - - struct Options { - ENUMERATE_SHELL_OPTIONS(); - } options; - -#undef __ENUMERATE_SHELL_OPTION - -private: - Shell(Line::Editor&); - Shell(); - virtual ~Shell() override; - - // FIXME: Port to Core::Property - void save_to(JsonObject&); - void bring_cursor_to_beginning_of_a_line() const; - - void cache_path(); - void add_entry_to_cache(const String&); - void stop_all_jobs(); - const Job* m_current_job { nullptr }; - LocalFrame* find_frame_containing_local_variable(const String& name); - - void run_tail(RefPtr<Job>); - void run_tail(const AST::Command&, const AST::NodeWithAction&, int head_exit_code); - - [[noreturn]] void execute_process(Vector<const char*>&& argv); - - virtual void custom_event(Core::CustomEvent&) override; - -#define __ENUMERATE_SHELL_BUILTIN(builtin) \ - int builtin_##builtin(int argc, const char** argv); - - ENUMERATE_SHELL_BUILTINS(); - -#undef __ENUMERATE_SHELL_BUILTIN - - constexpr static const char* builtin_names[] = { -#define __ENUMERATE_SHELL_BUILTIN(builtin) #builtin, - - ENUMERATE_SHELL_BUILTINS() - -#undef __ENUMERATE_SHELL_BUILTIN - }; - - bool m_should_ignore_jobs_on_next_exit { false }; - pid_t m_pid { 0 }; - - struct ShellFunction { - String name; - Vector<String> arguments; - RefPtr<AST::Node> body; - }; - - HashMap<String, ShellFunction> m_functions; - NonnullOwnPtrVector<LocalFrame> m_local_frames; - NonnullRefPtrVector<AST::Redirection> m_global_redirections; - - HashMap<String, String> m_aliases; - bool m_is_interactive { true }; - bool m_is_subshell { false }; - bool m_should_reinstall_signal_handlers { true }; - - ShellError m_error { ShellError::None }; - String m_error_description; - Optional<SourcePosition> m_source_position; - - bool m_should_format_live { false }; - - RefPtr<Line::Editor> m_editor; - - bool m_default_constructed { false }; - - mutable bool m_last_continuation_state { false }; // false == not needed. -}; - -static constexpr bool is_word_character(char c) -{ - return c == '_' || (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a'); -} - -inline size_t find_offset_into_node(const String& unescaped_text, size_t escaped_offset) -{ - size_t unescaped_offset = 0; - size_t offset = 0; - for (auto& c : unescaped_text) { - if (offset == escaped_offset) - return unescaped_offset; - - if (Shell::is_special(c)) - ++offset; - ++offset; - ++unescaped_offset; - } - - return unescaped_offset; -} - -} diff --git a/Shell/Tests/backgrounding.sh b/Shell/Tests/backgrounding.sh deleted file mode 100644 index ca300bdaef..0000000000 --- a/Shell/Tests/backgrounding.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/Shell - -echo "Not running Shell-backgrounding as it has a high failure rate" -exit 0 - -setopt --verbose - -fail(msg) { - echo FAIL: $msg - exit 1 -} - -last_idx='' -block_idx=0 -block() { - block_idx=$(expr 1 + $block_idx) - last_idx=$block_idx - mkfifo fifo$block_idx - cat fifo$block_idx& -} - -unblock(idx) { - echo unblock $idx > fifo$idx - rm -f fifo$idx -} - -assert_job_count(count) { - ecount=$(jobs | wc -l) - shift - if test $ecount -ne $count { - for $* { - unblock $it - } - fail "expected $ecount == $count" - } -} - -block -i=$last_idx - -assert_job_count 1 $i - -unblock $i -wait - -block -i=$last_idx -block -j=$last_idx - -assert_job_count 2 $i $j - -unblock $i -unblock $j -wait diff --git a/Shell/Tests/brace-exp.sh b/Shell/Tests/brace-exp.sh deleted file mode 100644 index e960c66d68..0000000000 --- a/Shell/Tests/brace-exp.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -setopt --verbose - -fail() { - echo $* - exit 1 -} - -test "$(echo {a,b,})" = "a b " || fail normal brace expansion with one empty slot -test "$(echo {a,,b})" = "a b" || fail normal brace expansion with one empty slot -test "$(echo {a,,,b})" = "a b" || fail normal brace expansion with two empty slots -test "$(echo {a,b,,})" = "a b " || fail normal brace expansion with two empty slots - -test "$(echo {a..c})" = "a b c" || fail range brace expansion, alpha -test "$(echo {0..3})" = "0 1 2 3" || fail range brace expansion, number -test "$(echo {😂..😄})" = "😂 😃 😄" || fail range brace expansion, unicode codepoint - -# Make sure that didn't mess with dots and commas in normal barewords -test .. = ".." || fail range brace expansion delimiter affects normal barewords -test , = "," || fail normal brace expansion delimiter affects normal barewords diff --git a/Shell/Tests/builtin-redir.sh b/Shell/Tests/builtin-redir.sh deleted file mode 100644 index ba9f1f15e2..0000000000 --- a/Shell/Tests/builtin-redir.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -rm -rf shell-test -mkdir -p shell-test -cd shell-test - - time sleep 1 2>timeerr >timeout - cat timeout - # We cannot be sure about the values, so just assert that they're not empty. - test -n "$(cat timeerr)" || echo "Failure: 'time' stderr output not redirected correctly" && exit 1 - test -e timeout || echo "Failure: 'time' stdout output not redirected correctly" && exit 1 - - time ls 2> /dev/null | head > timeout - test -n "$(cat timeout)" || echo "Failure: 'time' stdout not piped correctly" && exit 1 - -cd .. -rm -rf shell-test # TODO: Remove this file at the end once we have `trap' diff --git a/Shell/Tests/control-structure-as-command.sh b/Shell/Tests/control-structure-as-command.sh deleted file mode 100644 index 8bd31f01af..0000000000 --- a/Shell/Tests/control-structure-as-command.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh - -setopt --verbose - -rm -rf shell-test 2> /dev/null -mkdir shell-test -cd shell-test - - touch a b c - - # Can we do logical stuff with control structures? - ls && for $(seq 1) { echo yes > listing } - test "$(cat listing)" = "yes" || echo for cannot appear as second part of '&&' && exit 1 - rm listing - - # FIXME: This should work! - # for $(seq 1) { echo yes > listing } && echo HELLO! - # test "$(cat listing)" = "yes" || echo for cannot appear as first part of '&&' && exit 1 - # rm listing - - # Can we pipe things into and from control structures? - ls | if true { cat > listing } - test "$(cat listing)" = "a b c" || echo if cannot be correctly redirected to && exit 1 - rm listing - - ls | for $(seq 1) { cat > listing } - test "$(cat listing)" = "a b c" || echo for cannot be correctly redirected to && exit 1 - rm listing - - for $(seq 4) { echo $it } | cat > listing - test "$(cat listing)" = "1 2 3 4" || echo for cannot be correctly redirected from && exit 1 - rm listing - - if true { echo TRUE! } | cat > listing - test "$(cat listing)" = "TRUE!" || echo if cannot be correctly redirected from && exit 1 - rm listing - -cd .. -rm -rf shell-test diff --git a/Shell/Tests/function.sh b/Shell/Tests/function.sh deleted file mode 100644 index b9c23308ea..0000000000 --- a/Shell/Tests/function.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -# Syntax ok? -fn() { echo $* } - -# Can we invoke that? -test "$(fn 1)" = 1 || echo cannot invoke "'fn 1'" && exit 1 -test "$(fn 1 2)" = "1 2" || echo cannot invoke "'fn 1 2'" && exit 1 - -# With explicit argument names? -fn(a) { echo $a } - -# Can we invoke that? -test "$(fn 1)" = 1 || echo cannot invoke "'fn 1'" with explicit names && exit 1 -test "$(fn 1 2)" = 1 || echo cannot invoke "'fn 1 2'" with explicit names and extra arguments && exit 1 - -# Can it fail? -if fn 2>/dev/null { - echo "'fn'" with an explicit argument is not failing with not enough args - exit 1 -} - -# $0 in function should be its name -fn() { echo $0 } - -test "$(fn)" = fn || echo '$0' in function not equal to its name && exit 1 - -# Ensure ARGV does not leak from inner frames. -fn() { - fn2 1 2 3 - echo $* -} - -fn2() { } - -test "$(fn foobar)" = "foobar" || echo 'Frames are somehow messed up in nested functions' && exit 1 - -fn(xfoo) { } -xfoo=1 -fn 2 -test $xfoo -eq 1 || echo 'Functions overwrite parent scopes' && exit 1 diff --git a/Shell/Tests/if.sh b/Shell/Tests/if.sh deleted file mode 100644 index 598a9471bc..0000000000 --- a/Shell/Tests/if.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/sh - -setopt --verbose - -if test 1 -eq 1 { - # Are comments ok? - # Basic 'if' structure, empty block. - if true { - } else { - echo "if true runs false branch" - exit 2 - } - if false { - echo "if false runs true branch" - exit 2 - } else { - } - - # Basic 'if' structure, without 'else' - if false { - echo "Fail: 'if false' runs the branch" - exit 2 - } - - # Extended 'cond' form. - if false { - echo "Fail: 'if false' with 'else if' runs first branch" - exit 2 - } else if true { - } else { - echo "Fail: 'if false' with 'else if' runs last branch" - exit 2 - } - - # FIXME: Some form of 'not' would be nice - # &&/|| in condition - if true || false { - } else { - echo "Fail: 'if true || false' runs false branch" - exit 2 - } - - if true && false { - echo "Fail: 'if true && false' runs true branch" - exit 2 - } -} else { - echo "Fail: 'if test 1 -eq 1' runs false branch" - exit 1 -} diff --git a/Shell/Tests/loop.sh b/Shell/Tests/loop.sh deleted file mode 100644 index e4a985db87..0000000000 --- a/Shell/Tests/loop.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/sh - -singlecommand_ok=yes -multicommand_ok=yes -inlineexec_ok=yes -implicit_ok=yes -infinite_ok='' -break_ok=yes -continue_ok=yes -break_in_infinite_ok='' - -# Full form - # Empty -for x in () { } - - # Empty block but nonempty list -for x in (1 2 3) { } - - # Single command in block -for cmd in ((test 1 = 1) (test 2 = 2)) { - $cmd || unset singlecommand_ok -} - - # Multiple commands in block -for cmd in ((test 1 = 1) (test 2 = 2)) { - test -z "$cmd" - test -z "$cmd" && unset multicommand_ok - -} - - # $(...) as iterable expression -test_file=sh-test-1 -echo 1 > $test_file -echo 2 >> $test_file -echo 3 >> $test_file -echo 4 >> $test_file -lst=() -for line in $(cat $test_file) { - lst=($lst $line) -} -test "$lst" = "1 2 3 4" || unset inlineexec_ok -rm $test_file - -# Implicit var -for ((test 1 = 1) (test 2 = 2)) { - $it || unset implicit_ok -} - -# Infinite loop -loop { - infinite_ok=yes - break - unset break_ok -} - -# 'Continue' -for (1 2 3) { - continue - unset continue_ok -} - -# 'break' in infinite external loop -for $(yes) { - break_in_infinite_ok=yes - break -} - -test $singlecommand_ok || echo Fail: Single command inside for body -test $multicommand_ok || echo Fail: Multiple commands inside for body -test $inlineexec_ok || echo Fail: Inline Exec -test $implicit_ok || echo Fail: implicit iter variable -test $infinite_ok || echo Fail: infinite loop -test $break_ok || echo Fail: break -test $continue_ok || echo Fail: continue -test $break_in_infinite_ok || echo Fail: break from external infinite loop - -test "$singlecommand_ok $multicommand_ok $inlineexec_ok $implicit_ok $infinite_ok $break_ok $continue_ok $break_in_infinite_ok" = "yes yes yes yes yes yes yes yes" || exit 1 diff --git a/Shell/Tests/match.sh b/Shell/Tests/match.sh deleted file mode 100644 index 1f1809166c..0000000000 --- a/Shell/Tests/match.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/Shell - -result=no -match hello { - he* { result=yes } - * { result=fail } -}; - -test "$result" = yes || echo invalid result $result for normal string match, single option && exit 1 - -result=no -match hello { - he* | f* { result=yes } - * { result=fail } -}; - -test "$result" = yes || echo invalid result $result for normal string match, multiple options && exit 1 - -result=no -match (well hello friends) { - (* *) { result=fail } - (* * *) { result=yes } - * { result=fail } -}; - -test "$result" = yes || echo invalid result $result for list match && exit 1 - -result=no -match yes as v { - () { result=fail } - (*) { result=yes } - * { result=$v } -}; - -test "$result" = yes || echo invalid result $result for match with name && exit 1 - -result=no -# $(...) is a list, $(echo) should be an empty list, not an empty string -match $(echo) { - * { result=fail } - () { result=yes } -}; - -test "$result" = yes || echo invalid result $result for list subst match && exit 1 - -result=no -# "$(...)" is a string, "$(echo)" should be an empty string, not an empty list -match "$(echo)" { - * { result=yes } - () { result=fail } -}; - -test "$result" = yes || echo invalid result $result for string subst match && exit 1 - -match (foo bar) { - (f? *) as (x y) { - result=fail - } - (f* b*) as (x y) { - if [ "$x" = oo -a "$y" = ar ] { - result=yes - } else { - result=fail - } - } -} - -test "$result" = yes || echo invalid result $result for subst match with name && exit 1 - -match (foo bar baz) { - (f? * *z) as (x y z) { - result=fail - } - (f* b* *z) as (x y z) { - if [ "$x" = oo -a "$y" = ar -a "$z" = ba ] { - result=yes - } else { - result=fail - } - } -} - -test "$result" = yes || echo invalid result $result for subst match with name 2 && exit 1 diff --git a/Shell/Tests/sigpipe.sh b/Shell/Tests/sigpipe.sh deleted file mode 100644 index 1866b506ba..0000000000 --- a/Shell/Tests/sigpipe.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -# `head -n 1` should close stdout of the `Shell -c` command, which means the -# second echo should exit unsuccessfully and sigpipe.sh.out should not be -# created. -rm -f sigpipe.sh.out - -{ echo foo && echo bar && echo baz > sigpipe.sh.out } | head -n 1 > /dev/null - -# Failing commands don't make the test fail, just an explicit `exit 1` does. -# So the test only fails if sigpipe.sh.out exists (since then `exit 1` runs), -# not if the `test` statement returns false. -test -e sigpipe.sh.out && exit 1 diff --git a/Shell/Tests/special-vars.sh b/Shell/Tests/special-vars.sh deleted file mode 100644 index 597cc346c3..0000000000 --- a/Shell/Tests/special-vars.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -test "$*" = "" || echo "Fail: Argv list not empty" && exit 1 -test "$#" -eq 0 || echo "Fail: Argv list empty but count non-zero" && exit 1 -test "$ARGV" = "$*" || echo "Fail: \$ARGV not equal to \$*" && exit 1 - -ARGV=(1 2 3) -test "$#" -eq 3 || echo "Fail: Assignment to ARGV does not affect \$#" && exit 1 -test "$*" = "1 2 3" || echo "Fail: Assignment to ARGV does not affect \$*" && exit 1 - -shift -test "$*" = "2 3" || echo "Fail: 'shift' does not work correctly" && exit 1 - -shift 2 -test "$*" = "" || echo "Fail: 'shift 2' does not work correctly" && exit 1 diff --git a/Shell/Tests/subshell.sh b/Shell/Tests/subshell.sh deleted file mode 100644 index ac8331a7de..0000000000 --- a/Shell/Tests/subshell.sh +++ /dev/null @@ -1,28 +0,0 @@ -#/bin/sh - -setopt --verbose - -rm -rf shell-test -mkdir shell-test -cd shell-test - - # Simple sequence (grouping) - { echo test > testfile } - test "$(cat testfile)" = "test" || echo cannot write to file in subshell && exit 1 - - # Simple sequence - many commands - { echo test1 > testfile; echo test2 > testfile } - test "$(cat testfile)" = "test2" || echo cannot write to file in subshell 2 && exit 1 - - - # Does it exit with the last exit code? - { test -z "a" } - exitcode=$? - test "$exitcode" -eq 1 || echo exits with $exitcode when it should exit with 1 && exit 1 - - { test -z "a" || echo test } - exitcode=$? - test "$exitcode" -eq 0 || echo exits with $exitcode when it should exit with 0 && exit 1 - -cd .. -rm -rf shell-test diff --git a/Shell/Tests/valid.sh b/Shell/Tests/valid.sh deleted file mode 100644 index 872cc86cdf..0000000000 --- a/Shell/Tests/valid.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/sh - -# Are comments ignored? -# Sanity check: can we do && and || ? -true || exit 2 -false - -# Apply some useful aliases -fail() { - echo $* - exit 1 -} - -# Can we chain &&'s? -false && exit 2 && fail "can't chain &&'s" - -# Proper precedence between &&'s and ||'s -false && exit 2 || true && false && fail Invalid precedence between '&&' and '||' - - -# Sanity check: can we pass arguments to 'test'? -test yes = yes || exit 2 - -# Sanity check: can we use $(command)? -test "$(echo yes)" = yes || exit 2 -# Redirections. -test -z "$(echo foo > /dev/null)" || fail direct path redirection -test -z "$(echo foo 2> /dev/null 1>&2)" || fail indirect redirection -test -n "$(echo foo 2> /dev/null)" || fail fds interfere with each other - -# Argument unpack -test "$(echo (yes))" = yes || fail arguments inside bare lists -test "$(echo (no)() yes)" = yes || fail arguments inside juxtaposition: empty -test "$(echo (y)(es))" = yes || fail arguments inside juxtaposition: list -test "$(echo "y"es)" = yes || fail arguments inside juxtaposition: string - -# String substitution -foo=yes -test "$(echo $foo)" = yes || fail simple string var lookup -test "$(echo "$foo")" = yes || fail stringified string var lookup - -# List substitution -foo=(yes) -# Static lookup, as list -test "$(echo $foo)" = yes || fail simple list var lookup -# Static lookup, stringified -test "$(echo "$foo")" = yes || fail stringified list var lookup -# Dynamic lookup through static expression -test "$(echo $'foo')" = yes || fail dynamic lookup through static exp -# Dynamic lookup through dynamic expression -ref_to_foo=foo -test "$(echo $"$ref_to_foo")" = yes || fail dynamic lookup through dynamic exp - -# More redirections -echo test > /tmp/sh-test -test "$(cat /tmp/sh-test)" = test || fail simple path redirect -rm /tmp/sh-test - -# 'brace' expansions -test "$(echo x(yes no))" = "xyes xno" || fail simple juxtaposition expansion -test "$(echo (y n)(es o))" = "yes yo nes no" || fail list-list juxtaposition expansion -test "$(echo ()(foo bar baz))" = "" || fail empty expansion - -# Variables inside commands -to_devnull=(>/dev/null) -test "$(echo hewwo $to_devnull)" = "" || fail variable containing simple command - -word_count=(() | wc -w) -test "$(echo well hello friends $word_count)" -eq 3 || fail variable containing pipeline - -# Globs -mkdir sh-test -pushd sh-test - touch (a b c)(d e f) - test "$(echo a*)" = "ad ae af" || fail '*' glob expansion - test "$(echo a?)" = "ad ae af" || fail '?' glob expansion - - glob_in_var='*' - test "$(echo $glob_in_var)" = '*' || fail substituted string acts as glob - - test "$(echo (a*))" = "ad ae af" || fail globs in lists resolve wrong - test "$(echo x(a*))" = "xad xae xaf" || fail globs in lists do not resolve to lists - test "$(echo "foo"a*)" = "fooad fooae fooaf" || fail globs join to dquoted strings -popd -rm -fr sh-test - -# Setopt -setopt --inline_exec_keep_empty_segments -test "$(echo -n "a\n\nb")" = "a b" || fail inline_exec_keep_empty_segments has no effect - -setopt --no_inline_exec_keep_empty_segments -test "$(echo -n "a\n\nb")" = "a b" || fail cannot unset inline_exec_keep_empty_segments diff --git a/Shell/main.cpp b/Shell/main.cpp deleted file mode 100644 index 3616919844..0000000000 --- a/Shell/main.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "Execution.h" -#include "Shell.h" -#include <LibCore/ArgsParser.h> -#include <LibCore/Event.h> -#include <LibCore/EventLoop.h> -#include <LibCore/File.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -RefPtr<Line::Editor> editor; -Shell::Shell* s_shell; - -int main(int argc, char** argv) -{ - Core::EventLoop loop; - - Core::EventLoop::register_signal(SIGINT, [](int) { - s_shell->kill_job(s_shell->current_job(), SIGINT); - }); - - Core::EventLoop::register_signal(SIGWINCH, [](int) { - s_shell->kill_job(s_shell->current_job(), SIGWINCH); - }); - - Core::EventLoop::register_signal(SIGTTIN, [](int) {}); - Core::EventLoop::register_signal(SIGTTOU, [](int) {}); - - Core::EventLoop::register_signal(SIGHUP, [](int) { - for (auto& it : s_shell->jobs) - s_shell->kill_job(it.value.ptr(), SIGHUP); - - s_shell->editor()->save_history(s_shell->get_history_path()); - }); - - editor = Line::Editor::construct(); - editor->initialize(); - - auto shell = Shell::Shell::construct(*editor); - s_shell = shell.ptr(); - - s_shell->setup_signals(); - -#ifndef __serenity__ - sigset_t blocked; - sigemptyset(&blocked); - sigaddset(&blocked, SIGTTOU); - sigaddset(&blocked, SIGTTIN); - pthread_sigmask(SIG_BLOCK, &blocked, nullptr); -#endif -#ifdef __serenity__ - if (pledge("stdio rpath wpath cpath proc exec tty accept sigaction unix fattr", nullptr) < 0) { - perror("pledge"); - return 1; - } -#endif - - shell->termios = editor->termios(); - shell->default_termios = editor->default_termios(); - - editor->on_display_refresh = [&](auto& editor) { - editor.strip_styles(); - if (shell->should_format_live()) { - auto line = editor.line(); - ssize_t cursor = editor.cursor(); - editor.clear_line(); - editor.insert(shell->format(line, cursor)); - if (cursor >= 0) - editor.set_cursor(cursor); - } - shell->highlight(editor); - }; - editor->on_tab_complete = [&](const Line::Editor&) { - return shell->complete(); - }; - - const char* command_to_run = nullptr; - const char* file_to_read_from = nullptr; - Vector<const char*> script_args; - bool skip_rc_files = false; - const char* format = nullptr; - bool should_format_live = false; - - Core::ArgsParser parser; - parser.add_option(command_to_run, "String to read commands from", "command-string", 'c', "command-string"); - parser.add_option(skip_rc_files, "Skip running shellrc files", "skip-shellrc", 0); - parser.add_option(format, "Format the given file into stdout and exit", "format", 0, "file"); - parser.add_option(should_format_live, "Enable live formatting", "live-formatting", 'f'); - parser.add_positional_argument(file_to_read_from, "File to read commands from", "file", Core::ArgsParser::Required::No); - parser.add_positional_argument(script_args, "Extra argumets to pass to the script (via $* and co)", "argument", Core::ArgsParser::Required::No); - - parser.parse(argc, argv); - - shell->set_live_formatting(should_format_live); - - if (format) { - auto file = Core::File::open(format, Core::IODevice::ReadOnly); - if (file.is_error()) { - fprintf(stderr, "Error: %s", file.error().characters()); - return 1; - } - - ssize_t cursor = -1; - puts(shell->format(file.value()->read_all(), cursor).characters()); - return 0; - } - - auto pid = getpid(); - if (auto sid = getsid(pid); sid == 0) { - if (setsid() < 0) { - perror("setsid"); - // Let's just hope that it's ok. - } - } else if (sid != pid) { - if (getpgid(pid) != pid) { - dbgln("We were already in a session with sid={} (we are {}), let's do some gymnastics", sid, pid); - if (setpgid(pid, sid) < 0) { - auto strerr = strerror(errno); - dbgln("couldn't setpgid: {}", strerr); - } - if (setsid() < 0) { - auto strerr = strerror(errno); - dbgln("couldn't setsid: {}", strerr); - } - } - } - - shell->current_script = argv[0]; - - if (!skip_rc_files) { - auto run_rc_file = [&](auto& name) { - String file_path = name; - if (file_path.starts_with('~')) - file_path = shell->expand_tilde(file_path); - if (Core::File::exists(file_path)) { - shell->run_file(file_path, false); - } - }; - run_rc_file(Shell::Shell::global_init_file_path); - run_rc_file(Shell::Shell::local_init_file_path); - } - - { - Vector<String> args; - for (auto* arg : script_args) - args.empend(arg); - shell->set_local_variable("ARGV", adopt(*new Shell::AST::ListValue(move(args)))); - } - - if (command_to_run) { - dbgln("sh -c '{}'\n", command_to_run); - shell->run_command(command_to_run); - return 0; - } - - if (file_to_read_from && StringView { "-" } != file_to_read_from) { - if (shell->run_file(file_to_read_from)) - return 0; - return 1; - } - - shell->add_child(*editor); - - Core::EventLoop::current().post_event(*shell, make<Core::CustomEvent>(Shell::Shell::ShellEventType::ReadLine)); - - return loop.exec(); -} |