diff --git a/Shell/AST.h b/Shell/AST.h index d18f3aa9971..15fe59fa672 100644 --- a/Shell/AST.h +++ b/Shell/AST.h @@ -28,6 +28,7 @@ #include "Forward.h" #include "Job.h" +#include "NodeVisitor.h" #include #include #include @@ -414,6 +415,9 @@ public: Vector to_lazy_evaluated_commands(RefPtr shell); + virtual void visit(NodeVisitor&) { ASSERT_NOT_REACHED(); } + virtual void visit(NodeVisitor& visitor) const { const_cast(this)->visit(visitor); } + protected: Position m_position; bool m_is_syntax_error { false }; @@ -429,6 +433,10 @@ public: 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& path() const { return m_path; } + int fd() const { return m_fd; } protected: int m_fd { -1 }; @@ -439,6 +447,10 @@ class And final : public Node { public: And(Position, NonnullRefPtr, NonnullRefPtr); virtual ~And(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -455,6 +467,8 @@ class ListConcatenate final : public Node { public: ListConcatenate(Position, Vector>); virtual ~ListConcatenate(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + const Vector> list() const { return m_list; } private: virtual void dump(int level) const override; @@ -472,6 +486,9 @@ class Background final : public Node { public: Background(Position, NonnullRefPtr); virtual ~Background(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& command() const { return m_command; } private: virtual void dump(int level) const override; @@ -487,6 +504,8 @@ 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: @@ -504,6 +523,9 @@ class CastToCommand final : public Node { public: CastToCommand(Position, NonnullRefPtr); virtual ~CastToCommand(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& inner() const { return m_inner; } private: virtual void dump(int level) const override; @@ -523,6 +545,9 @@ class CastToList final : public Node { public: CastToList(Position, RefPtr); virtual ~CastToList(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const RefPtr& inner() const { return m_inner; } private: virtual void dump(int level) const override; @@ -540,6 +565,9 @@ 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: virtual void dump(int level) const override; @@ -555,6 +583,9 @@ 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: virtual void dump(int level) const override; @@ -571,6 +602,8 @@ 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: @@ -586,6 +619,9 @@ class DynamicEvaluate final : public Node { public: DynamicEvaluate(Position, NonnullRefPtr); virtual ~DynamicEvaluate(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& inner() const { return m_inner; } private: virtual void dump(int level) const override; @@ -610,6 +646,9 @@ class DoubleQuotedString final : public Node { public: DoubleQuotedString(Position, RefPtr); virtual ~DoubleQuotedString(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const RefPtr& inner() const { return m_inner; } private: virtual void dump(int level) const override; @@ -625,6 +664,10 @@ 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_source_fd; } + int dest_fd() const { return m_dest_fd; } private: virtual void dump(int level) const override; @@ -645,6 +688,11 @@ public: }; FunctionDeclaration(Position, NameWithPosition name, Vector argument_names, RefPtr body); virtual ~FunctionDeclaration(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NameWithPosition& name() const { return m_name; } + const Vector arguments() const { return m_arguments; } + const RefPtr& block() const { return m_block; } private: virtual void dump(int level) const override; @@ -664,6 +712,11 @@ class ForLoop final : public Node { public: ForLoop(Position, String variable_name, NonnullRefPtr iterated_expr, RefPtr block, Optional in_kw_position = {}); virtual ~ForLoop(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const String& variable_name() const { return m_variable_name; } + const NonnullRefPtr& iterated_expression() const { return m_iterated_expression; } + const RefPtr& block() const { return m_block; } private: virtual void dump(int level) const override; @@ -683,6 +736,8 @@ 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: @@ -701,8 +756,12 @@ public: Execute(Position, NonnullRefPtr, bool capture_stdout = false); virtual ~Execute(); void capture_stdout() { m_capture_stdout = true; } - NonnullRefPtr command() { return m_command; } + NonnullRefPtr& command() { return m_command; } virtual void for_each_entry(RefPtr shell, Function)> callback) override; + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& command() const { return m_command; } + bool does_capture_stdout() const { return m_capture_stdout; } private: virtual void dump(int level) const override; @@ -722,6 +781,12 @@ class IfCond final : public Node { public: IfCond(Position, Optional else_position, NonnullRefPtr cond_expr, RefPtr true_branch, RefPtr false_branch); virtual ~IfCond(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& condition() const { return m_condition; } + const RefPtr& true_branch() const { return m_true_branch; } + const RefPtr& false_branch() const { return m_false_branch; } + const Optional else_position() const { return m_else_position; } private: virtual void dump(int level) const override; @@ -742,6 +807,10 @@ class Join final : public Node { public: Join(Position, NonnullRefPtr, NonnullRefPtr); virtual ~Join(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -767,6 +836,11 @@ class MatchExpr final : public Node { public: MatchExpr(Position, NonnullRefPtr expr, String name, Optional as_position, Vector entries); virtual ~MatchExpr(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& matched_expr() const { return m_matched_expr; } + const String& expr_name() const { return m_expr_name; } + const Vector& entries() const { return m_entries; } private: virtual void dump(int level) const override; @@ -786,6 +860,10 @@ class Or final : public Node { public: Or(Position, NonnullRefPtr, NonnullRefPtr); virtual ~Or(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -802,6 +880,10 @@ class Pipe final : public Node { public: Pipe(Position, NonnullRefPtr, NonnullRefPtr); virtual ~Pipe(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -809,7 +891,7 @@ private: virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; virtual HitTestResult hit_test_position(size_t) override; virtual String class_name() const override { return "Pipe"; } - virtual bool is_list() const override { return true; } + virtual bool is_command() const override { return true; } NonnullRefPtr m_left; NonnullRefPtr m_right; @@ -819,6 +901,7 @@ class ReadRedirection final : public PathRedirectionNode { public: ReadRedirection(Position, int, NonnullRefPtr); virtual ~ReadRedirection(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } private: virtual void dump(int level) const override; @@ -830,6 +913,7 @@ class ReadWriteRedirection final : public PathRedirectionNode { public: ReadWriteRedirection(Position, int, NonnullRefPtr); virtual ~ReadWriteRedirection(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } private: virtual void dump(int level) const override; @@ -841,6 +925,10 @@ class Sequence final : public Node { public: Sequence(Position, NonnullRefPtr, NonnullRefPtr); virtual ~Sequence(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -859,6 +947,9 @@ class Subshell final : public Node { public: Subshell(Position, RefPtr block); virtual ~Subshell(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const RefPtr& block() const { return m_block; } private: virtual void dump(int level) const override; @@ -876,6 +967,7 @@ public: SimpleVariable(Position, String); virtual ~SimpleVariable(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } const String& name() const { return m_name; } private: @@ -894,6 +986,9 @@ 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: virtual void dump(int level) const override; @@ -910,6 +1005,10 @@ class Juxtaposition final : public Node { public: Juxtaposition(Position, NonnullRefPtr, NonnullRefPtr); virtual ~Juxtaposition(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -927,6 +1026,8 @@ 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: @@ -943,6 +1044,10 @@ class StringPartCompose final : public Node { public: StringPartCompose(Position, NonnullRefPtr, NonnullRefPtr); virtual ~StringPartCompose(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + const NonnullRefPtr& left() const { return m_left; } + const NonnullRefPtr& right() const { return m_right; } private: virtual void dump(int level) const override; @@ -959,6 +1064,7 @@ class SyntaxError final : public Node { public: SyntaxError(Position, String); virtual ~SyntaxError(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } const String& error_text() const { return m_syntax_error_text; } @@ -978,6 +1084,8 @@ class Tilde final : public Node { public: Tilde(Position, String); virtual ~Tilde(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + String text() const; private: @@ -1000,6 +1108,7 @@ public: }; VariableDeclarations(Position, Vector variables); virtual ~VariableDeclarations(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } const Vector& variables() const { return m_variables; } @@ -1018,6 +1127,7 @@ class WriteAppendRedirection final : public PathRedirectionNode { public: WriteAppendRedirection(Position, int, NonnullRefPtr); virtual ~WriteAppendRedirection(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } private: virtual void dump(int level) const override; @@ -1029,6 +1139,7 @@ class WriteRedirection final : public PathRedirectionNode { public: WriteRedirection(Position, int, NonnullRefPtr); virtual ~WriteRedirection(); + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } private: virtual void dump(int level) const override; diff --git a/Shell/CMakeLists.txt b/Shell/CMakeLists.txt index 10bec5f2828..db4bbe128b9 100644 --- a/Shell/CMakeLists.txt +++ b/Shell/CMakeLists.txt @@ -1,7 +1,9 @@ set(SOURCES AST.cpp Builtin.cpp + Formatter.cpp Job.cpp + NodeVisitor.cpp Parser.cpp Shell.cpp main.cpp diff --git a/Shell/Formatter.cpp b/Shell/Formatter.cpp new file mode 100644 index 00000000000..e1cb05c5d09 --- /dev/null +++ b/Shell/Formatter.cpp @@ -0,0 +1,597 @@ +/* + * 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 + +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 callback) +{ + TemporaryChange indent_change { m_current_indent, m_current_indent + indent }; + callback(); +} + +void Formatter::in_new_block(Function 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::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) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::And* node) +{ + test_and_update_output_cursor(node); + auto should_indent = m_parent_node && m_parent_node->class_name() != "And"; + TemporaryChange 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); + }); +} + +void Formatter::visit(const AST::ListConcatenate* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + auto first = true; + for (auto& subnode : node->list()) { + if (!first) + current_builder().append(' '); + first = false; + subnode->visit(*this); + } +} + +void Formatter::visit(const AST::Background* node) +{ + test_and_update_output_cursor(node); + + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); + current_builder().append(" &"); +} + +void Formatter::visit(const AST::BarewordLiteral* node) +{ + test_and_update_output_cursor(node); + current_builder().append(node->text()); +} + +void Formatter::visit(const AST::CastToCommand* node) +{ + test_and_update_output_cursor(node); + if (m_options.explicit_parentheses) + current_builder().append('('); + + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); + + if (m_options.explicit_parentheses) + current_builder().append(')'); +} + +void Formatter::visit(const AST::CastToList* node) +{ + test_and_update_output_cursor(node); + current_builder().append('('); + + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); + + current_builder().append(')'); +} + +void Formatter::visit(const AST::CloseFdRedirection* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + current_builder().appendf(" %d>&-", node->fd()); +} + +void Formatter::visit(const AST::CommandLiteral*) +{ + ASSERT_NOT_REACHED(); +} + +void Formatter::visit(const AST::Comment* node) +{ + test_and_update_output_cursor(node); + current_builder().append("#"); + current_builder().append(node->text()); +} + +void Formatter::visit(const AST::DynamicEvaluate* node) +{ + test_and_update_output_cursor(node); + current_builder().append('$'); + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::DoubleQuotedString* node) +{ + test_and_update_output_cursor(node); + current_builder().append("\""); + + TemporaryChange quotes { m_options.in_double_quotes, true }; + TemporaryChange parent { m_parent_node, node }; + + NodeVisitor::visit(node); + + current_builder().append("\""); +} + +void Formatter::visit(const AST::Fd2FdRedirection* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + current_builder().appendf(" %d>&%d", node->source_fd(), node->dest_fd()); + if (m_hit_node == node) + ++m_output_cursor; +} + +void Formatter::visit(const AST::FunctionDeclaration* node) +{ + test_and_update_output_cursor(node); + current_builder().append(node->name().name); + current_builder().append('('); + TemporaryChange 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); + }); +} + +void Formatter::visit(const AST::ForLoop* node) +{ + test_and_update_output_cursor(node); + current_builder().append("for "); + TemporaryChange parent { m_parent_node, node }; + + 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); + }); +} + +void Formatter::visit(const AST::Glob* node) +{ + test_and_update_output_cursor(node); + current_builder().append(node->text()); +} + +void Formatter::visit(const AST::Execute* node) +{ + test_and_update_output_cursor(node); + auto& builder = current_builder(); + TemporaryChange 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); +} + +void Formatter::visit(const AST::IfCond* node) +{ + test_and_update_output_cursor(node); + + current_builder().append("if "); + TemporaryChange 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()->class_name() != "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 "); + } +} + +void Formatter::visit(const AST::Join* node) +{ + test_and_update_output_cursor(node); + auto should_parenthesise = m_options.explicit_parentheses; + + TemporaryChange 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(')'); +} + +void Formatter::visit(const AST::MatchExpr* node) +{ + test_and_update_output_cursor(node); + current_builder().append("match "); + + TemporaryChange 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); + } + + in_new_block([&] { + if (entry.body) + entry.body->visit(*this); + }); + } + }); +} + +void Formatter::visit(const AST::Or* node) +{ + test_and_update_output_cursor(node); + auto should_indent = m_parent_node && m_parent_node->class_name() != "Or"; + TemporaryChange 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); + }); +} + +void Formatter::visit(const AST::Pipe* node) +{ + test_and_update_output_cursor(node); + auto should_indent = m_parent_node && m_parent_node->class_name() != "Pipe"; + TemporaryChange 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); + }); +} + +void Formatter::visit(const AST::ReadRedirection* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + if (node->fd() != 0) + current_builder().appendf(" %d<", node->fd()); + else + current_builder().append(" <"); + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::ReadWriteRedirection* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + if (node->fd() != 0) + current_builder().appendf(" %d<>", node->fd()); + else + current_builder().append(" <>"); + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::Sequence* node) +{ + test_and_update_output_cursor(node); + + TemporaryChange parent { m_parent_node, node }; + node->left()->visit(*this); + insert_separator(); + + node->right()->visit(*this); +} + +void Formatter::visit(const AST::Subshell* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + in_new_block([&] { + insert_separator(); + NodeVisitor::visit(node); + insert_separator(); + }); +} + +void Formatter::visit(const AST::SimpleVariable* node) +{ + test_and_update_output_cursor(node); + current_builder().append('$'); + current_builder().append(node->name()); +} + +void Formatter::visit(const AST::SpecialVariable* node) +{ + test_and_update_output_cursor(node); + current_builder().append('$'); + current_builder().append(node->name()); +} + +void Formatter::visit(const AST::Juxtaposition* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::StringLiteral* 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("'"); +} + +void Formatter::visit(const AST::StringPartCompose* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::SyntaxError* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::Tilde* node) +{ + test_and_update_output_cursor(node); + current_builder().append(node->text()); +} + +void Formatter::visit(const AST::VariableDeclarations* node) +{ + test_and_update_output_cursor(node); + TemporaryChange 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(')'); + } +} + +void Formatter::visit(const AST::WriteAppendRedirection* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + if (node->fd() != 1) + current_builder().appendf(" %d>>", node->fd()); + else + current_builder().append(" >>"); + NodeVisitor::visit(node); +} + +void Formatter::visit(const AST::WriteRedirection* node) +{ + test_and_update_output_cursor(node); + TemporaryChange parent { m_parent_node, node }; + + if (node->fd() != 1) + current_builder().appendf(" %d>", node->fd()); + else + current_builder().append(" >"); + NodeVisitor::visit(node); +} diff --git a/Shell/Formatter.h b/Shell/Formatter.h new file mode 100644 index 00000000000..900246ae7fd --- /dev/null +++ b/Shell/Formatter.h @@ -0,0 +1,121 @@ +/* + * 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 +#include +#include +#include +#include +#include + +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::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::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::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 insert_separator(); + void insert_indent(); + + ALWAYS_INLINE void with_added_indent(int indent, Function); + ALWAYS_INLINE void in_new_block(Function); + + 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_line_length { 0 }; + 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 }; + + StringView m_trivia; +}; diff --git a/Shell/Forward.h b/Shell/Forward.h index bbe92ea8070..597a1f76d1b 100644 --- a/Shell/Forward.h +++ b/Shell/Forward.h @@ -35,5 +35,43 @@ class Value; class SyntaxError; class Pipeline; struct Rewiring; +class NodeVisitor; + +class PathRedirectionNode; +class And; +class ListConcatenate; +class Background; +class BarewordLiteral; +class CastToCommand; +class CastToList; +class CloseFdRedirection; +class CommandLiteral; +class Comment; +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 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; } diff --git a/Shell/NodeVisitor.cpp b/Shell/NodeVisitor.cpp new file mode 100644 index 00000000000..2b58711cfb1 --- /dev/null +++ b/Shell/NodeVisitor.cpp @@ -0,0 +1,228 @@ +/* + * 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 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::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::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) +{ + 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::ReadRedirection* node) +{ + visit(static_cast(node)); +} + +void NodeVisitor::visit(const AST::ReadWriteRedirection* node) +{ + visit(static_cast(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(node)); +} + +void NodeVisitor::visit(const AST::WriteRedirection* node) +{ + visit(static_cast(node)); +} + +} diff --git a/Shell/NodeVisitor.h b/Shell/NodeVisitor.h new file mode 100644 index 00000000000..3b493329777 --- /dev/null +++ b/Shell/NodeVisitor.h @@ -0,0 +1,73 @@ +/* + * 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 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::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::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::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/Shell.cpp b/Shell/Shell.cpp index c6f60e72708..450293de700 100644 --- a/Shell/Shell.cpp +++ b/Shell/Shell.cpp @@ -26,6 +26,7 @@ #include "Shell.h" #include "Execution.h" +#include "Formatter.h" #include #include #include @@ -449,6 +450,15 @@ bool Shell::invoke_function(const AST::Command& command, int& retval) 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() { m_local_frames.empend(); diff --git a/Shell/Shell.h b/Shell/Shell.h index e4dbb195bc3..7ca6f580aad 100644 --- a/Shell/Shell.h +++ b/Shell/Shell.h @@ -102,6 +102,8 @@ public: bool has_function(const String&); bool invoke_function(const AST::Command&, int& retval); + String format(const StringView&, ssize_t& cursor) const; + struct LocalFrame { HashMap> local_variables; }; diff --git a/Shell/main.cpp b/Shell/main.cpp index ff266349139..f605730531a 100644 --- a/Shell/main.cpp +++ b/Shell/main.cpp @@ -200,21 +200,34 @@ int main(int argc, char** argv) const char* file_to_read_from = nullptr; Vector script_args; bool skip_rc_files = false; + const char* format = nullptr; 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, "File to format", "format", 0, "file"); 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); + 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; + } + if (getsid(getpid()) == 0) { if (setsid() < 0) { perror("setsid"); // Let's just hope that it's ok. } - } shell->current_script = argv[0];