ladybird/Userland/Shell/Formatter.cpp

846 lines
23 KiB
C++
Raw Normal View History

2020-09-16 03:43:04 +03:00
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Formatter.h"
#include "AST.h"
#include "Parser.h"
#include <AK/ScopedValueRollback.h>
2020-09-16 03:43:04 +03:00
#include <AK/TemporaryChange.h>
namespace Shell {
2020-09-16 03:43:04 +03:00
String Formatter::format()
{
auto node = m_root_node ? m_root_node : Parser(m_source).parse();
2020-09-16 03:43:04 +03:00
if (m_cursor >= 0)
m_output_cursor = m_cursor;
if (!node)
return String();
if (node->is_syntax_error())
return m_source;
if (m_cursor >= 0) {
auto hit_test = node->hit_test_position(m_cursor);
if (hit_test.matching_node)
m_hit_node = hit_test.matching_node.ptr();
else
m_hit_node = nullptr;
}
m_parent_node = nullptr;
node->visit(*this);
auto string = m_builder.string_view();
if (!string.ends_with(" "))
m_builder.append(m_trivia);
return m_builder.to_string();
}
void Formatter::with_added_indent(int indent, Function<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('}');
}
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();
}
}
2020-09-16 03:43:04 +03:00
void Formatter::insert_separator()
{
current_builder().append('\n');
insert_indent();
}
void Formatter::insert_indent()
{
for (size_t i = 0; i < m_current_indent; ++i)
current_builder().append(" ");
}
void Formatter::visit(const AST::PathRedirectionNode* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::And* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::And;
2020-09-16 03:43:04 +03:00
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();
current_builder().append("&& ");
node->right()->visit(*this);
});
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::ListConcatenate* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Background* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
current_builder().append(" &");
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::BarewordLiteral* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append(node->text());
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::BraceExpansion* node)
{
will_visit(node);
test_and_update_output_cursor(node);
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);
}
current_builder().append('}');
visited(node);
}
2020-09-16 03:43:04 +03:00
void Formatter::visit(const AST::CastToCommand* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::CastToList* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::CloseFdRedirection* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
current_builder().appendf("%d>&-", node->fd());
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::CommandLiteral*)
{
VERIFY_NOT_REACHED();
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Comment* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append("#");
current_builder().append(node->text());
visited(node);
2020-09-16 03:43:04 +03:00
}
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);
}
2020-09-16 03:43:04 +03:00
void Formatter::visit(const AST::DynamicEvaluate* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append('$');
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::DoubleQuotedString* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append("\"");
TemporaryChange quotes { m_options.in_double_quotes, true };
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
current_builder().append("\"");
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Fd2FdRedirection* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
current_builder().appendf("%d>&%d", node->source_fd(), node->dest_fd());
2020-09-16 03:43:04 +03:00
if (m_hit_node == node)
++m_output_cursor;
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::FunctionDeclaration* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::ForLoop* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
auto is_loop = node->iterated_expression().is_null();
current_builder().append(is_loop ? "loop" : "for ");
2020-09-16 03:43:04 +03:00
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 ");
}
2020-09-16 03:43:04 +03:00
node->iterated_expression()->visit(*this);
}
2020-09-16 03:43:04 +03:00
current_builder().append(' ');
in_new_block([&] {
if (node->block())
node->block()->visit(*this);
});
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Glob* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append(node->text());
visited(node);
2020-09-16 03:43:04 +03:00
}
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);
}
2020-09-16 03:43:04 +03:00
void Formatter::visit(const AST::Execute* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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("$(");
2020-09-16 03:43:04 +03:00
NodeVisitor::visit(node);
if (node->does_capture_stdout())
builder.append(")");
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::IfCond* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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) {
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
}
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);
}
2020-09-16 03:43:04 +03:00
void Formatter::visit(const AST::Join* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
if (should_parenthesise)
current_builder().append(')');
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::MatchExpr* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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(") ");
}
2020-09-16 03:43:04 +03:00
in_new_block([&] {
if (entry.body)
entry.body->visit(*this);
});
}
});
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Or* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::Or;
2020-09-16 03:43:04 +03:00
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();
current_builder().append("|| ");
node->right()->visit(*this);
});
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Pipe* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
auto should_indent = m_parent_node && m_parent_node->kind() != AST::Node::Kind::Pipe;
2020-09-16 03:43:04 +03:00
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();
current_builder().append("| ");
node->right()->visit(*this);
});
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Range* node)
{
will_visit(node);
test_and_update_output_cursor(node);
current_builder().append('{');
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
node->start()->visit(*this);
current_builder().append("..");
node->end()->visit(*this);
current_builder().append('}');
visited(node);
}
2020-09-16 03:43:04 +03:00
void Formatter::visit(const AST::ReadRedirection* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
if (node->fd() != 0)
current_builder().appendf(" %d<", node->fd());
else
current_builder().append(" <");
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::ReadWriteRedirection* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
if (node->fd() != 0)
current_builder().appendf(" %d<>", node->fd());
else
current_builder().append(" <>");
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Sequence* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Subshell* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
in_new_block([&] {
NodeVisitor::visit(node);
});
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::SimpleVariable* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append('$');
current_builder().append(node->name());
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::SpecialVariable* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append('$');
current_builder().append(node->name());
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Juxtaposition* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::StringLiteral* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
if (!m_options.in_double_quotes)
current_builder().append("'");
if (m_options.in_double_quotes) {
for (auto ch : node->text()) {
switch (ch) {
case '"':
case '\\':
case '$':
current_builder().append('\\');
break;
case '\n':
current_builder().append("\\n");
continue;
case '\r':
current_builder().append("\\r");
continue;
case '\t':
current_builder().append("\\t");
continue;
case '\v':
current_builder().append("\\v");
continue;
case '\f':
current_builder().append("\\f");
continue;
case '\a':
current_builder().append("\\a");
continue;
case '\e':
current_builder().append("\\e");
continue;
default:
break;
}
current_builder().append(ch);
}
} else {
current_builder().append(node->text());
}
if (!m_options.in_double_quotes)
current_builder().append("'");
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::StringPartCompose* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::SyntaxError* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::Tilde* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
current_builder().append(node->text());
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::VariableDeclarations* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
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);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::WriteAppendRedirection* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
if (node->fd() != 1)
current_builder().appendf(" %d>>", node->fd());
else
current_builder().append(" >>");
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
void Formatter::visit(const AST::WriteRedirection* node)
{
will_visit(node);
2020-09-16 03:43:04 +03:00
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
if (node->fd() != 1)
current_builder().appendf(" %d>", node->fd());
else
current_builder().append(" >");
NodeVisitor::visit(node);
visited(node);
2020-09-16 03:43:04 +03:00
}
}