mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 17:10:23 +00:00
902 lines
24 KiB
C++
902 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(" ");
|
|
}
|
|
|
|
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("&& ");
|
|
|
|
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);
|
|
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("#");
|
|
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
|
|
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("\"");
|
|
|
|
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("\"");
|
|
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(") ");
|
|
|
|
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->index_variable().has_value()) {
|
|
current_builder().append("index ");
|
|
current_builder().append(node->index_variable()->name);
|
|
current_builder().append(" ");
|
|
}
|
|
if (node->variable().has_value() && 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::Heredoc* node)
|
|
{
|
|
will_visit(node);
|
|
test_and_update_output_cursor(node);
|
|
|
|
current_builder().append("<<");
|
|
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("$(");
|
|
|
|
NodeVisitor::visit(node);
|
|
|
|
if (node->does_capture_stdout())
|
|
builder.append(")");
|
|
|
|
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::ImmediateExpression* node)
|
|
{
|
|
will_visit(node);
|
|
test_and_update_output_cursor(node);
|
|
|
|
current_builder().append("${");
|
|
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 ");
|
|
|
|
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(true);
|
|
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(true);
|
|
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);
|
|
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("..");
|
|
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(" <");
|
|
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(" <>");
|
|
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("'");
|
|
|
|
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");
|
|
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 && !m_options.in_heredoc)
|
|
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().appendff(" {}>>", 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().appendff(" {}>", node->fd());
|
|
else
|
|
current_builder().append(" >");
|
|
NodeVisitor::visit(node);
|
|
visited(node);
|
|
}
|
|
|
|
}
|