summaryrefslogtreecommitdiff
path: root/Shell
diff options
context:
space:
mode:
Diffstat (limited to 'Shell')
-rw-r--r--Shell/AST.cpp2996
-rw-r--r--Shell/AST.h1324
-rw-r--r--Shell/Builtin.cpp929
-rw-r--r--Shell/CMakeLists.txt19
-rw-r--r--Shell/Execution.h65
-rw-r--r--Shell/Formatter.cpp755
-rw-r--r--Shell/Formatter.h129
-rw-r--r--Shell/Forward.h85
-rw-r--r--Shell/Job.cpp124
-rw-r--r--Shell/Job.h136
-rw-r--r--Shell/NodeVisitor.cpp245
-rw-r--r--Shell/NodeVisitor.h76
-rw-r--r--Shell/Parser.cpp1555
-rw-r--r--Shell/Parser.h262
-rw-r--r--Shell/Shell.cpp1939
-rw-r--r--Shell/Shell.h351
-rw-r--r--Shell/Tests/backgrounding.sh55
-rw-r--r--Shell/Tests/brace-exp.sh21
-rw-r--r--Shell/Tests/builtin-redir.sh17
-rw-r--r--Shell/Tests/control-structure-as-command.sh39
-rw-r--r--Shell/Tests/function.sh41
-rw-r--r--Shell/Tests/if.sh50
-rw-r--r--Shell/Tests/loop.sh77
-rw-r--r--Shell/Tests/match.sh83
-rw-r--r--Shell/Tests/sigpipe.sh13
-rw-r--r--Shell/Tests/special-vars.sh15
-rw-r--r--Shell/Tests/subshell.sh28
-rw-r--r--Shell/Tests/valid.sh92
-rw-r--r--Shell/main.cpp194
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();
-}