mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 16:52:43 +00:00 
			
		
		
		
	 c8585b77d2
			
		
	
	
		c8585b77d2
		
	
	
	
	
		
			
			This prevents us from needing a sv suffix, and potentially reduces the need to run generic code for a single character (as contains, starts_with, ends_with etc. for a char will be just a length and equality check). No functional changes.
		
			
				
	
	
		
			914 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			914 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2020, the SerenityOS developers.
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include "Formatter.h"
 | |
| #include "AST.h"
 | |
| #include "Parser.h"
 | |
| #include <AK/Hex.h>
 | |
| #include <AK/ScopedValueRollback.h>
 | |
| #include <AK/TemporaryChange.h>
 | |
| 
 | |
| namespace Shell {
 | |
| 
 | |
| String Formatter::format()
 | |
| {
 | |
|     auto node = m_root_node ? m_root_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);
 | |
| 
 | |
|     VERIFY(m_builders.size() == 1);
 | |
| 
 | |
|     auto string = current_builder().string_view();
 | |
| 
 | |
|     if (!string.ends_with(' '))
 | |
|         current_builder().append(m_trivia);
 | |
| 
 | |
|     return current_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('}');
 | |
| }
 | |
| 
 | |
| String Formatter::in_new_builder(Function<void()> callback, StringBuilder new_builder)
 | |
| {
 | |
|     m_builders.append(move(new_builder));
 | |
|     callback();
 | |
|     return m_builders.take_last().to_string();
 | |
| }
 | |
| 
 | |
| 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 && node->kind() != AST::Node::Kind::Execute) {
 | |
|         // 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)
 | |
|             insert_separator();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void Formatter::insert_separator(bool escaped)
 | |
| {
 | |
|     if (escaped)
 | |
|         current_builder().append('\\');
 | |
|     current_builder().append('\n');
 | |
|     if (!escaped && !m_heredocs_to_append_after_sequence.is_empty()) {
 | |
|         for (auto& entry : m_heredocs_to_append_after_sequence) {
 | |
|             current_builder().append(entry);
 | |
|         }
 | |
|         m_heredocs_to_append_after_sequence.clear();
 | |
|     }
 | |
|     insert_indent();
 | |
| }
 | |
| 
 | |
| void Formatter::insert_indent()
 | |
| {
 | |
|     for (size_t i = 0; i < m_current_indent; ++i)
 | |
|         current_builder().append("  "sv);
 | |
| }
 | |
| 
 | |
| 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(true);
 | |
|         current_builder().append("&& "sv);
 | |
| 
 | |
|         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(" &"sv);
 | |
|     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);
 | |
|     if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
 | |
|         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);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
 | |
|         current_builder().append('}');
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::CastToCommand* 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::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().appendff("{}>&-", node->fd());
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::CommandLiteral*)
 | |
| {
 | |
|     VERIFY_NOT_REACHED();
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::Comment* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
|     current_builder().append("#"sv);
 | |
|     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"sv);
 | |
|     else if (node->continuation_kind() == AST::ContinuationControl::Continue)
 | |
|         current_builder().append("continue"sv);
 | |
|     else
 | |
|         VERIFY_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);
 | |
|     auto not_in_heredoc = m_parent_node->kind() != AST::Node::Kind::Heredoc;
 | |
|     if (not_in_heredoc)
 | |
|         current_builder().append("\""sv);
 | |
| 
 | |
|     TemporaryChange quotes { m_options.in_double_quotes, true };
 | |
|     TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
| 
 | |
|     NodeVisitor::visit(node);
 | |
| 
 | |
|     if (not_in_heredoc)
 | |
|         current_builder().append("\""sv);
 | |
|     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().appendff("{}>&{}", 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(") "sv);
 | |
| 
 | |
|     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"sv : "for "sv);
 | |
|     TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
| 
 | |
|     if (!is_loop) {
 | |
|         if (node->index_variable().has_value()) {
 | |
|             current_builder().append("index "sv);
 | |
|             current_builder().append(node->index_variable()->name);
 | |
|             current_builder().append(" "sv);
 | |
|         }
 | |
|         if (node->variable().has_value() && node->variable()->name != "it") {
 | |
|             current_builder().append(node->variable()->name);
 | |
|             current_builder().append(" in "sv);
 | |
|         }
 | |
| 
 | |
|         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::Heredoc* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
| 
 | |
|     current_builder().append("<<"sv);
 | |
|     if (node->deindent())
 | |
|         current_builder().append('~');
 | |
|     else
 | |
|         current_builder().append('-');
 | |
| 
 | |
|     if (node->allow_interpolation())
 | |
|         current_builder().appendff("{}", node->end());
 | |
|     else
 | |
|         current_builder().appendff("'{}'", node->end());
 | |
| 
 | |
|     auto content = in_new_builder([&] {
 | |
|         if (!node->contents())
 | |
|             return;
 | |
| 
 | |
|         TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
|         TemporaryChange heredoc { m_options.in_heredoc, true };
 | |
| 
 | |
|         auto& contents = *node->contents();
 | |
|         contents.visit(*this);
 | |
|         current_builder().appendff("\n{}\n", node->end());
 | |
|     });
 | |
| 
 | |
|     m_heredocs_to_append_after_sequence.append(move(content));
 | |
| 
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::HistoryEvent* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
| 
 | |
|     current_builder().append('!');
 | |
|     switch (node->selector().event.kind) {
 | |
|     case AST::HistorySelector::EventKind::ContainingStringLookup:
 | |
|         current_builder().append('?');
 | |
|         current_builder().append(node->selector().event.text);
 | |
|         break;
 | |
|     case AST::HistorySelector::EventKind::StartingStringLookup:
 | |
|         current_builder().append(node->selector().event.text);
 | |
|         break;
 | |
|     case AST::HistorySelector::EventKind::IndexFromStart:
 | |
|         current_builder().append(node->selector().event.text);
 | |
|         break;
 | |
|     case AST::HistorySelector::EventKind::IndexFromEnd:
 | |
|         if (node->selector().event.index == 0)
 | |
|             current_builder().append('!');
 | |
|         else
 | |
|             current_builder().append(node->selector().event.text);
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     auto& range = node->selector().word_selector_range;
 | |
|     if (!range.end.has_value()
 | |
|         || range.end.value().kind != AST::HistorySelector::WordSelectorKind::Last
 | |
|         || range.start.kind != AST::HistorySelector::WordSelectorKind::Index || range.start.selector != 0) {
 | |
| 
 | |
|         auto append_word = [this](auto& selector) {
 | |
|             switch (selector.kind) {
 | |
|             case AST::HistorySelector::WordSelectorKind::Index:
 | |
|                 if (selector.selector == 0)
 | |
|                     current_builder().append('^');
 | |
|                 else
 | |
|                     current_builder().appendff("{}", selector.selector);
 | |
|                 break;
 | |
|             case AST::HistorySelector::WordSelectorKind::Last:
 | |
|                 current_builder().append('$');
 | |
|                 break;
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         current_builder().append(':');
 | |
|         append_word(range.start);
 | |
| 
 | |
|         if (range.end.has_value()) {
 | |
|             current_builder().append('-');
 | |
|             append_word(range.end.value());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     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("$("sv);
 | |
| 
 | |
|     NodeVisitor::visit(node);
 | |
| 
 | |
|     if (node->does_capture_stdout())
 | |
|         builder.append(")"sv);
 | |
| 
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::IfCond* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
| 
 | |
|     current_builder().append("if "sv);
 | |
|     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 "sv);
 | |
|         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 "sv);
 | |
|     }
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::ImmediateExpression* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
| 
 | |
|     current_builder().append("${"sv);
 | |
|     TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
| 
 | |
|     current_builder().append(node->function_name());
 | |
| 
 | |
|     for (auto& node : node->arguments()) {
 | |
|         current_builder().append(' ');
 | |
|         node.visit(*this);
 | |
|     }
 | |
| 
 | |
|     if (node->has_closing_brace())
 | |
|         current_builder().append('}');
 | |
| 
 | |
|     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('(');
 | |
| 
 | |
|     node->left()->visit(*this);
 | |
| 
 | |
|     current_builder().append(' ');
 | |
| 
 | |
|     node->right()->visit(*this);
 | |
| 
 | |
|     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 "sv);
 | |
| 
 | |
|     TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
| 
 | |
|     node->matched_expr()->visit(*this);
 | |
| 
 | |
|     if (!node->expr_name().is_empty()) {
 | |
|         current_builder().append(" as "sv);
 | |
|         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;
 | |
|             entry.options.visit(
 | |
|                 [&](NonnullRefPtrVector<AST::Node> const& patterns) {
 | |
|                     for (auto& option : patterns) {
 | |
|                         if (!first)
 | |
|                             current_builder().append(" | "sv);
 | |
|                         first = false;
 | |
|                         option.visit(*this);
 | |
|                     }
 | |
|                 },
 | |
|                 [&](Vector<Regex<ECMA262>> const& patterns) {
 | |
|                     for (auto& option : patterns) {
 | |
|                         if (!first)
 | |
|                             current_builder().append(" | "sv);
 | |
|                         first = false;
 | |
|                         auto node = make_ref_counted<AST::BarewordLiteral>(AST::Position {}, option.pattern_value);
 | |
|                         node->visit(*this);
 | |
|                     }
 | |
|                 });
 | |
| 
 | |
|             current_builder().append(' ');
 | |
|             if (entry.match_names.has_value() && !entry.match_names.value().is_empty()) {
 | |
|                 current_builder().append("as ("sv);
 | |
|                 auto first = true;
 | |
|                 for (auto& name : entry.match_names.value()) {
 | |
|                     if (!first)
 | |
|                         current_builder().append(' ');
 | |
|                     first = false;
 | |
|                     current_builder().append(name);
 | |
|                 }
 | |
|                 current_builder().append(") "sv);
 | |
|             }
 | |
|             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(" "sv);
 | |
|         insert_separator(true);
 | |
|         current_builder().append("|| "sv);
 | |
| 
 | |
|         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(" "sv);
 | |
| 
 | |
|     with_added_indent(should_indent ? 1 : 0, [&] {
 | |
|         insert_separator(true);
 | |
|         current_builder().append("| "sv);
 | |
| 
 | |
|         node->right()->visit(*this);
 | |
|     });
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::Range* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
|     if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
 | |
|         current_builder().append('{');
 | |
| 
 | |
|     TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
|     node->start()->visit(*this);
 | |
|     current_builder().append(".."sv);
 | |
|     node->end()->visit(*this);
 | |
| 
 | |
|     if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
 | |
|         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().appendff(" {}<", node->fd());
 | |
|     else
 | |
|         current_builder().append(" <"sv);
 | |
|     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().appendff(" {}<>", node->fd());
 | |
|     else
 | |
|         current_builder().append(" <>"sv);
 | |
|     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 };
 | |
| 
 | |
|     bool first = true;
 | |
|     for (auto& entry : node->entries()) {
 | |
|         if (first)
 | |
|             first = false;
 | |
|         else
 | |
|             insert_separator();
 | |
| 
 | |
|         entry.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([&] {
 | |
|         NodeVisitor::visit(node);
 | |
|     });
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| void Formatter::visit(const AST::Slice* node)
 | |
| {
 | |
|     will_visit(node);
 | |
|     test_and_update_output_cursor(node);
 | |
|     TemporaryChange<const AST::Node*> parent { m_parent_node, node };
 | |
| 
 | |
|     current_builder().append('[');
 | |
|     node->selector()->visit(*this);
 | |
|     current_builder().append(']');
 | |
| 
 | |
|     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());
 | |
|     if (const AST::Node* slice = node->slice())
 | |
|         slice->visit(*this);
 | |
|     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());
 | |
|     if (const AST::Node* slice = node->slice())
 | |
|         slice->visit(*this);
 | |
|     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 && !m_options.in_heredoc)
 | |
|         current_builder().append("'"sv);
 | |
| 
 | |
|     if (m_options.in_double_quotes && !m_options.in_heredoc) {
 | |
|         for (auto ch : node->text()) {
 | |
|             switch (ch) {
 | |
|             case '"':
 | |
|             case '\\':
 | |
|             case '$':
 | |
|                 current_builder().append('\\');
 | |
|                 break;
 | |
|             case '\n':
 | |
|                 current_builder().append("\\n"sv);
 | |
|                 continue;
 | |
|             case '\r':
 | |
|                 current_builder().append("\\r"sv);
 | |
|                 continue;
 | |
|             case '\t':
 | |
|                 current_builder().append("\\t"sv);
 | |
|                 continue;
 | |
|             case '\v':
 | |
|                 current_builder().append("\\v"sv);
 | |
|                 continue;
 | |
|             case '\f':
 | |
|                 current_builder().append("\\f"sv);
 | |
|                 continue;
 | |
|             case '\a':
 | |
|                 current_builder().append("\\a"sv);
 | |
|                 continue;
 | |
|             case '\e':
 | |
|                 current_builder().append("\\e"sv);
 | |
|                 continue;
 | |
|             default:
 | |
|                 break;
 | |
|             }
 | |
|             current_builder().append(ch);
 | |
|         }
 | |
|     } else {
 | |
|         current_builder().append(node->text());
 | |
|     }
 | |
| 
 | |
|     if (!m_options.in_double_quotes && !m_options.in_heredoc)
 | |
|         current_builder().append("'"sv);
 | |
|     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().appendff(" {}>>", node->fd());
 | |
|     else
 | |
|         current_builder().append(" >>"sv);
 | |
|     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().appendff(" {}>", node->fd());
 | |
|     else
 | |
|         current_builder().append(" >"sv);
 | |
|     NodeVisitor::visit(node);
 | |
|     visited(node);
 | |
| }
 | |
| 
 | |
| }
 |