mirror of
https://github.com/RGBCube/serenity
synced 2025-05-15 09:54:57 +00:00

We previously allowed globs as match pattern, but for more complex matching needs, it's nice to have regular expressions. And as the existing "name a part of the match" concept maps nicely to named capture groups, we can simply reuse the same code and make groups with names available in the match body.
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(" ");
|
|
}
|
|
|
|
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;
|
|
entry.options.visit(
|
|
[&](NonnullRefPtrVector<AST::Node> const& patterns) {
|
|
for (auto& option : patterns) {
|
|
if (!first)
|
|
current_builder().append(" | ");
|
|
first = false;
|
|
option.visit(*this);
|
|
}
|
|
},
|
|
[&](Vector<Regex<ECMA262>> const& patterns) {
|
|
for (auto& option : patterns) {
|
|
if (!first)
|
|
current_builder().append(" | ");
|
|
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 (");
|
|
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);
|
|
}
|
|
|
|
}
|