mirror of
https://github.com/SerenityOS/serenity
synced 2026-05-12 01:47:00 +02:00
This change ensures that users can use LibShell easily understand this library now, because we have an actual library directory. In addition to that, we move the test scripts to Tests/LibShell, to match the usual pattern of putting test-related files in the Tests/ directory.
916 lines
25 KiB
C++
916 lines
25 KiB
C++
/*
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "Formatter.h"
|
|
#include "AST.h"
|
|
#include "Parser.h"
|
|
#include "PosixParser.h"
|
|
#include <AK/Hex.h>
|
|
#include <AK/ScopedValueRollback.h>
|
|
#include <AK/TemporaryChange.h>
|
|
|
|
namespace Shell {
|
|
|
|
ByteString Formatter::format()
|
|
{
|
|
auto node = m_root_node ?: (m_parse_as_posix ? Posix::Parser(m_source).parse() : Parser(m_source).parse());
|
|
if (m_cursor >= 0)
|
|
m_output_cursor = m_cursor;
|
|
|
|
if (!node)
|
|
return ByteString();
|
|
|
|
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_byte_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('}');
|
|
}
|
|
|
|
ByteString Formatter::in_new_builder(Function<void()> callback, StringBuilder new_builder)
|
|
{
|
|
m_builders.append(move(new_builder));
|
|
callback();
|
|
return m_builders.take_last().to_byte_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(
|
|
[&](Vector<NonnullRefPtr<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 {}, String::from_byte_string(option.pattern_value).release_value_but_fixme_should_propagate_errors());
|
|
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().bytes_as_string_view()) {
|
|
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);
|
|
}
|
|
|
|
}
|