Shell: Add a (very basic) formatter

This commit is contained in:
AnotherTest 2020-09-16 05:13:04 +04:30 committed by Andreas Kling
parent 6e6be8e56e
commit b3dd97a694
Notes: sideshowbarker 2024-07-19 02:12:06 +09:00
10 changed files with 1198 additions and 3 deletions

View file

@ -28,6 +28,7 @@
#include "Forward.h"
#include "Job.h"
#include "NodeVisitor.h"
#include <AK/InlineLinkedList.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefCounted.h>
@ -414,6 +415,9 @@ public:
Vector<Command> to_lazy_evaluated_commands(RefPtr<Shell> shell);
virtual void visit(NodeVisitor&) { ASSERT_NOT_REACHED(); }
virtual void visit(NodeVisitor& visitor) const { const_cast<Node*>(this)->visit(visitor); }
protected:
Position m_position;
bool m_is_syntax_error { false };
@ -429,6 +433,10 @@ public:
virtual HitTestResult hit_test_position(size_t offset) override;
virtual bool is_command() const override { return true; }
virtual bool is_list() const override { return true; }
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& path() const { return m_path; }
int fd() const { return m_fd; }
protected:
int m_fd { -1 };
@ -439,6 +447,10 @@ class And final : public Node {
public:
And(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~And();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -455,6 +467,8 @@ class ListConcatenate final : public Node {
public:
ListConcatenate(Position, Vector<NonnullRefPtr<Node>>);
virtual ~ListConcatenate();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const Vector<NonnullRefPtr<Node>> list() const { return m_list; }
private:
virtual void dump(int level) const override;
@ -472,6 +486,9 @@ class Background final : public Node {
public:
Background(Position, NonnullRefPtr<Node>);
virtual ~Background();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& command() const { return m_command; }
private:
virtual void dump(int level) const override;
@ -487,6 +504,8 @@ class BarewordLiteral final : public Node {
public:
BarewordLiteral(Position, String);
virtual ~BarewordLiteral();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& text() const { return m_text; }
private:
@ -504,6 +523,9 @@ class CastToCommand final : public Node {
public:
CastToCommand(Position, NonnullRefPtr<Node>);
virtual ~CastToCommand();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& inner() const { return m_inner; }
private:
virtual void dump(int level) const override;
@ -523,6 +545,9 @@ class CastToList final : public Node {
public:
CastToList(Position, RefPtr<Node>);
virtual ~CastToList();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const RefPtr<Node>& inner() const { return m_inner; }
private:
virtual void dump(int level) const override;
@ -540,6 +565,9 @@ class CloseFdRedirection final : public Node {
public:
CloseFdRedirection(Position, int);
virtual ~CloseFdRedirection();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
int fd() const { return m_fd; }
private:
virtual void dump(int level) const override;
@ -555,6 +583,9 @@ class CommandLiteral final : public Node {
public:
CommandLiteral(Position, Command);
virtual ~CommandLiteral();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const Command& command() const { return m_command; }
private:
virtual void dump(int level) const override;
@ -571,6 +602,8 @@ class Comment : public Node {
public:
Comment(Position, String);
virtual ~Comment();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& text() const { return m_text; }
private:
@ -586,6 +619,9 @@ class DynamicEvaluate final : public Node {
public:
DynamicEvaluate(Position, NonnullRefPtr<Node>);
virtual ~DynamicEvaluate();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& inner() const { return m_inner; }
private:
virtual void dump(int level) const override;
@ -610,6 +646,9 @@ class DoubleQuotedString final : public Node {
public:
DoubleQuotedString(Position, RefPtr<Node>);
virtual ~DoubleQuotedString();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const RefPtr<Node>& inner() const { return m_inner; }
private:
virtual void dump(int level) const override;
@ -625,6 +664,10 @@ class Fd2FdRedirection final : public Node {
public:
Fd2FdRedirection(Position, int, int);
virtual ~Fd2FdRedirection();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
int source_fd() const { return m_source_fd; }
int dest_fd() const { return m_dest_fd; }
private:
virtual void dump(int level) const override;
@ -645,6 +688,11 @@ public:
};
FunctionDeclaration(Position, NameWithPosition name, Vector<NameWithPosition> argument_names, RefPtr<AST::Node> body);
virtual ~FunctionDeclaration();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NameWithPosition& name() const { return m_name; }
const Vector<NameWithPosition> arguments() const { return m_arguments; }
const RefPtr<Node>& block() const { return m_block; }
private:
virtual void dump(int level) const override;
@ -664,6 +712,11 @@ class ForLoop final : public Node {
public:
ForLoop(Position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<size_t> in_kw_position = {});
virtual ~ForLoop();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& variable_name() const { return m_variable_name; }
const NonnullRefPtr<Node>& iterated_expression() const { return m_iterated_expression; }
const RefPtr<Node>& block() const { return m_block; }
private:
virtual void dump(int level) const override;
@ -683,6 +736,8 @@ class Glob final : public Node {
public:
Glob(Position, String);
virtual ~Glob();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& text() const { return m_text; }
private:
@ -701,8 +756,12 @@ public:
Execute(Position, NonnullRefPtr<Node>, bool capture_stdout = false);
virtual ~Execute();
void capture_stdout() { m_capture_stdout = true; }
NonnullRefPtr<Node> command() { return m_command; }
NonnullRefPtr<Node>& command() { return m_command; }
virtual void for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(NonnullRefPtr<Value>)> callback) override;
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& command() const { return m_command; }
bool does_capture_stdout() const { return m_capture_stdout; }
private:
virtual void dump(int level) const override;
@ -722,6 +781,12 @@ class IfCond final : public Node {
public:
IfCond(Position, Optional<Position> else_position, NonnullRefPtr<AST::Node> cond_expr, RefPtr<AST::Node> true_branch, RefPtr<AST::Node> false_branch);
virtual ~IfCond();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& condition() const { return m_condition; }
const RefPtr<Node>& true_branch() const { return m_true_branch; }
const RefPtr<Node>& false_branch() const { return m_false_branch; }
const Optional<Position> else_position() const { return m_else_position; }
private:
virtual void dump(int level) const override;
@ -742,6 +807,10 @@ class Join final : public Node {
public:
Join(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~Join();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -767,6 +836,11 @@ class MatchExpr final : public Node {
public:
MatchExpr(Position, NonnullRefPtr<Node> expr, String name, Optional<Position> as_position, Vector<MatchEntry> entries);
virtual ~MatchExpr();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& matched_expr() const { return m_matched_expr; }
const String& expr_name() const { return m_expr_name; }
const Vector<MatchEntry>& entries() const { return m_entries; }
private:
virtual void dump(int level) const override;
@ -786,6 +860,10 @@ class Or final : public Node {
public:
Or(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~Or();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -802,6 +880,10 @@ class Pipe final : public Node {
public:
Pipe(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~Pipe();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -809,7 +891,7 @@ private:
virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
virtual HitTestResult hit_test_position(size_t) override;
virtual String class_name() const override { return "Pipe"; }
virtual bool is_list() const override { return true; }
virtual bool is_command() const override { return true; }
NonnullRefPtr<Node> m_left;
NonnullRefPtr<Node> m_right;
@ -819,6 +901,7 @@ class ReadRedirection final : public PathRedirectionNode {
public:
ReadRedirection(Position, int, NonnullRefPtr<Node>);
virtual ~ReadRedirection();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
private:
virtual void dump(int level) const override;
@ -830,6 +913,7 @@ class ReadWriteRedirection final : public PathRedirectionNode {
public:
ReadWriteRedirection(Position, int, NonnullRefPtr<Node>);
virtual ~ReadWriteRedirection();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
private:
virtual void dump(int level) const override;
@ -841,6 +925,10 @@ class Sequence final : public Node {
public:
Sequence(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~Sequence();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -859,6 +947,9 @@ class Subshell final : public Node {
public:
Subshell(Position, RefPtr<Node> block);
virtual ~Subshell();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const RefPtr<Node>& block() const { return m_block; }
private:
virtual void dump(int level) const override;
@ -876,6 +967,7 @@ public:
SimpleVariable(Position, String);
virtual ~SimpleVariable();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& name() const { return m_name; }
private:
@ -894,6 +986,9 @@ class SpecialVariable final : public Node {
public:
SpecialVariable(Position, char);
virtual ~SpecialVariable();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
char name() const { return m_name; }
private:
virtual void dump(int level) const override;
@ -910,6 +1005,10 @@ class Juxtaposition final : public Node {
public:
Juxtaposition(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~Juxtaposition();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -927,6 +1026,8 @@ class StringLiteral final : public Node {
public:
StringLiteral(Position, String);
virtual ~StringLiteral();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& text() const { return m_text; }
private:
@ -943,6 +1044,10 @@ class StringPartCompose final : public Node {
public:
StringPartCompose(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
virtual ~StringPartCompose();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const NonnullRefPtr<Node>& left() const { return m_left; }
const NonnullRefPtr<Node>& right() const { return m_right; }
private:
virtual void dump(int level) const override;
@ -959,6 +1064,7 @@ class SyntaxError final : public Node {
public:
SyntaxError(Position, String);
virtual ~SyntaxError();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const String& error_text() const { return m_syntax_error_text; }
@ -978,6 +1084,8 @@ class Tilde final : public Node {
public:
Tilde(Position, String);
virtual ~Tilde();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
String text() const;
private:
@ -1000,6 +1108,7 @@ public:
};
VariableDeclarations(Position, Vector<Variable> variables);
virtual ~VariableDeclarations();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
const Vector<Variable>& variables() const { return m_variables; }
@ -1018,6 +1127,7 @@ class WriteAppendRedirection final : public PathRedirectionNode {
public:
WriteAppendRedirection(Position, int, NonnullRefPtr<Node>);
virtual ~WriteAppendRedirection();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
private:
virtual void dump(int level) const override;
@ -1029,6 +1139,7 @@ class WriteRedirection final : public PathRedirectionNode {
public:
WriteRedirection(Position, int, NonnullRefPtr<Node>);
virtual ~WriteRedirection();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
private:
virtual void dump(int level) const override;

View file

@ -1,7 +1,9 @@
set(SOURCES
AST.cpp
Builtin.cpp
Formatter.cpp
Job.cpp
NodeVisitor.cpp
Parser.cpp
Shell.cpp
main.cpp

597
Shell/Formatter.cpp Normal file
View file

@ -0,0 +1,597 @@
/*
* 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/TemporaryChange.h>
String Formatter::format()
{
auto 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);
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::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)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
}
void Formatter::visit(const AST::And* node)
{
test_and_update_output_cursor(node);
auto should_indent = m_parent_node && m_parent_node->class_name() != "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();
current_builder().append("&& ");
node->right()->visit(*this);
});
}
void Formatter::visit(const AST::ListConcatenate* 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);
}
}
void Formatter::visit(const AST::Background* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
current_builder().append(" &");
}
void Formatter::visit(const AST::BarewordLiteral* node)
{
test_and_update_output_cursor(node);
current_builder().append(node->text());
}
void Formatter::visit(const AST::CastToCommand* node)
{
test_and_update_output_cursor(node);
if (m_options.explicit_parentheses)
current_builder().append('(');
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
if (m_options.explicit_parentheses)
current_builder().append(')');
}
void Formatter::visit(const AST::CastToList* 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(')');
}
void Formatter::visit(const AST::CloseFdRedirection* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
current_builder().appendf(" %d>&-", node->fd());
}
void Formatter::visit(const AST::CommandLiteral*)
{
ASSERT_NOT_REACHED();
}
void Formatter::visit(const AST::Comment* node)
{
test_and_update_output_cursor(node);
current_builder().append("#");
current_builder().append(node->text());
}
void Formatter::visit(const AST::DynamicEvaluate* node)
{
test_and_update_output_cursor(node);
current_builder().append('$');
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
}
void Formatter::visit(const AST::DoubleQuotedString* node)
{
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("\"");
}
void Formatter::visit(const AST::Fd2FdRedirection* node)
{
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());
if (m_hit_node == node)
++m_output_cursor;
}
void Formatter::visit(const AST::FunctionDeclaration* 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);
});
}
void Formatter::visit(const AST::ForLoop* node)
{
test_and_update_output_cursor(node);
current_builder().append("for ");
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
if (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);
});
}
void Formatter::visit(const AST::Glob* node)
{
test_and_update_output_cursor(node);
current_builder().append(node->text());
}
void Formatter::visit(const AST::Execute* 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("$");
m_options.explicit_parentheses = true;
}
NodeVisitor::visit(node);
}
void Formatter::visit(const AST::IfCond* 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()->class_name() != "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 ");
}
}
void Formatter::visit(const AST::Join* 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('(');
NodeVisitor::visit(node);
if (should_parenthesise)
current_builder().append(')');
}
void Formatter::visit(const AST::MatchExpr* 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);
}
in_new_block([&] {
if (entry.body)
entry.body->visit(*this);
});
}
});
}
void Formatter::visit(const AST::Or* node)
{
test_and_update_output_cursor(node);
auto should_indent = m_parent_node && m_parent_node->class_name() != "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();
current_builder().append("|| ");
node->right()->visit(*this);
});
}
void Formatter::visit(const AST::Pipe* node)
{
test_and_update_output_cursor(node);
auto should_indent = m_parent_node && m_parent_node->class_name() != "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();
current_builder().append("| ");
node->right()->visit(*this);
});
}
void Formatter::visit(const AST::ReadRedirection* node)
{
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);
}
void Formatter::visit(const AST::ReadWriteRedirection* node)
{
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);
}
void Formatter::visit(const AST::Sequence* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
node->left()->visit(*this);
insert_separator();
node->right()->visit(*this);
}
void Formatter::visit(const AST::Subshell* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
in_new_block([&] {
insert_separator();
NodeVisitor::visit(node);
insert_separator();
});
}
void Formatter::visit(const AST::SimpleVariable* node)
{
test_and_update_output_cursor(node);
current_builder().append('$');
current_builder().append(node->name());
}
void Formatter::visit(const AST::SpecialVariable* node)
{
test_and_update_output_cursor(node);
current_builder().append('$');
current_builder().append(node->name());
}
void Formatter::visit(const AST::Juxtaposition* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
}
void Formatter::visit(const AST::StringLiteral* node)
{
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("'");
}
void Formatter::visit(const AST::StringPartCompose* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
}
void Formatter::visit(const AST::SyntaxError* node)
{
test_and_update_output_cursor(node);
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
NodeVisitor::visit(node);
}
void Formatter::visit(const AST::Tilde* node)
{
test_and_update_output_cursor(node);
current_builder().append(node->text());
}
void Formatter::visit(const AST::VariableDeclarations* 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(')');
}
}
void Formatter::visit(const AST::WriteAppendRedirection* node)
{
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);
}
void Formatter::visit(const AST::WriteRedirection* node)
{
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);
}

121
Shell/Formatter.h Normal file
View file

@ -0,0 +1,121 @@
/*
* 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.
*/
#pragma once
#include "NodeVisitor.h"
#include <AK/Forward.h>
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <AK/Vector.h>
#include <ctype.h>
class Formatter final : public AST::NodeVisitor {
public:
Formatter(const StringView& source, ssize_t cursor = -1)
: m_builder(round_up_to_power_of_two(source.length(), 16))
, m_source(source)
, m_cursor(cursor)
{
size_t offset = 0;
for (auto ptr = m_source.end() - 1; ptr >= m_source.begin() && isspace(*ptr); --ptr)
++offset;
m_trivia = m_source.substring_view(m_source.length() - offset, offset);
}
String format();
size_t cursor() const { return m_output_cursor; }
private:
virtual void visit(const AST::PathRedirectionNode*) override;
virtual void visit(const AST::And*) override;
virtual void visit(const AST::ListConcatenate*) override;
virtual void visit(const AST::Background*) override;
virtual void visit(const AST::BarewordLiteral*) override;
virtual void visit(const AST::CastToCommand*) override;
virtual void visit(const AST::CastToList*) override;
virtual void visit(const AST::CloseFdRedirection*) override;
virtual void visit(const AST::CommandLiteral*) override;
virtual void visit(const AST::Comment*) override;
virtual void visit(const AST::DynamicEvaluate*) override;
virtual void visit(const AST::DoubleQuotedString*) override;
virtual void visit(const AST::Fd2FdRedirection*) override;
virtual void visit(const AST::FunctionDeclaration*) override;
virtual void visit(const AST::ForLoop*) override;
virtual void visit(const AST::Glob*) override;
virtual void visit(const AST::Execute*) override;
virtual void visit(const AST::IfCond*) override;
virtual void visit(const AST::Join*) override;
virtual void visit(const AST::MatchExpr*) override;
virtual void visit(const AST::Or*) override;
virtual void visit(const AST::Pipe*) override;
virtual void visit(const AST::ReadRedirection*) override;
virtual void visit(const AST::ReadWriteRedirection*) override;
virtual void visit(const AST::Sequence*) override;
virtual void visit(const AST::Subshell*) override;
virtual void visit(const AST::SimpleVariable*) override;
virtual void visit(const AST::SpecialVariable*) override;
virtual void visit(const AST::Juxtaposition*) override;
virtual void visit(const AST::StringLiteral*) override;
virtual void visit(const AST::StringPartCompose*) override;
virtual void visit(const AST::SyntaxError*) override;
virtual void visit(const AST::Tilde*) override;
virtual void visit(const AST::VariableDeclarations*) override;
virtual void visit(const AST::WriteAppendRedirection*) override;
virtual void visit(const AST::WriteRedirection*) override;
void test_and_update_output_cursor(const AST::Node*);
void insert_separator();
void insert_indent();
ALWAYS_INLINE void with_added_indent(int indent, Function<void()>);
ALWAYS_INLINE void in_new_block(Function<void()>);
StringBuilder& current_builder() { return m_builder; }
struct Options {
size_t max_line_length_hint { 80 };
bool explicit_parentheses { false };
bool explicit_braces { false };
bool in_double_quotes { false };
} m_options;
size_t m_current_line_length { 0 };
size_t m_current_indent { 0 };
StringBuilder m_builder;
StringView m_source;
size_t m_output_cursor { 0 };
ssize_t m_cursor { -1 };
AST::Node* m_hit_node { nullptr };
const AST::Node* m_parent_node { nullptr };
StringView m_trivia;
};

View file

@ -35,5 +35,43 @@ class Value;
class SyntaxError;
class Pipeline;
struct Rewiring;
class NodeVisitor;
class PathRedirectionNode;
class And;
class ListConcatenate;
class Background;
class BarewordLiteral;
class CastToCommand;
class CastToList;
class CloseFdRedirection;
class CommandLiteral;
class Comment;
class DynamicEvaluate;
class DoubleQuotedString;
class Fd2FdRedirection;
class FunctionDeclaration;
class ForLoop;
class Glob;
class Execute;
class IfCond;
class Join;
class MatchExpr;
class Or;
class Pipe;
class ReadRedirection;
class ReadWriteRedirection;
class Sequence;
class Subshell;
class SimpleVariable;
class SpecialVariable;
class Juxtaposition;
class StringLiteral;
class StringPartCompose;
class SyntaxError;
class Tilde;
class VariableDeclarations;
class WriteAppendRedirection;
class WriteRedirection;
}

228
Shell/NodeVisitor.cpp Normal file
View file

@ -0,0 +1,228 @@
/*
* 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 "NodeVisitor.h"
#include "AST.h"
namespace AST {
void NodeVisitor::visit(const AST::PathRedirectionNode* node)
{
node->path()->visit(*this);
}
void NodeVisitor::visit(const AST::And* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::ListConcatenate* node)
{
for (auto& subnode : node->list())
subnode->visit(*this);
}
void NodeVisitor::visit(const AST::Background* node)
{
node->command()->visit(*this);
}
void NodeVisitor::visit(const AST::BarewordLiteral*)
{
}
void NodeVisitor::visit(const AST::CastToCommand* node)
{
node->inner()->visit(*this);
}
void NodeVisitor::visit(const AST::CastToList* node)
{
if (node->inner())
node->inner()->visit(*this);
}
void NodeVisitor::visit(const AST::CloseFdRedirection*)
{
}
void NodeVisitor::visit(const AST::CommandLiteral*)
{
}
void NodeVisitor::visit(const AST::Comment*)
{
}
void NodeVisitor::visit(const AST::DynamicEvaluate* node)
{
node->inner()->visit(*this);
}
void NodeVisitor::visit(const AST::DoubleQuotedString* node)
{
if (node->inner())
node->inner()->visit(*this);
}
void NodeVisitor::visit(const AST::Fd2FdRedirection*)
{
}
void NodeVisitor::visit(const AST::FunctionDeclaration* node)
{
if (node->block())
node->block()->visit(*this);
}
void NodeVisitor::visit(const AST::ForLoop* node)
{
node->iterated_expression()->visit(*this);
if (node->block())
node->block()->visit(*this);
}
void NodeVisitor::visit(const AST::Glob*)
{
}
void NodeVisitor::visit(const AST::Execute* node)
{
node->command()->visit(*this);
}
void NodeVisitor::visit(const AST::IfCond* node)
{
node->condition()->visit(*this);
if (node->true_branch())
node->true_branch()->visit(*this);
if (node->false_branch())
node->false_branch()->visit(*this);
}
void NodeVisitor::visit(const AST::Join* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::MatchExpr* node)
{
node->matched_expr()->visit(*this);
for (auto& entry : node->entries()) {
for (auto& option : entry.options)
option.visit(*this);
if (entry.body)
entry.body->visit(*this);
}
}
void NodeVisitor::visit(const AST::Or* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::Pipe* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::ReadRedirection* node)
{
visit(static_cast<const AST::PathRedirectionNode*>(node));
}
void NodeVisitor::visit(const AST::ReadWriteRedirection* node)
{
visit(static_cast<const AST::PathRedirectionNode*>(node));
}
void NodeVisitor::visit(const AST::Sequence* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::Subshell* node)
{
if (node->block())
node->block()->visit(*this);
}
void NodeVisitor::visit(const AST::SimpleVariable*)
{
}
void NodeVisitor::visit(const AST::SpecialVariable*)
{
}
void NodeVisitor::visit(const AST::Juxtaposition* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::StringLiteral*)
{
}
void NodeVisitor::visit(const AST::StringPartCompose* node)
{
node->left()->visit(*this);
node->right()->visit(*this);
}
void NodeVisitor::visit(const AST::SyntaxError*)
{
}
void NodeVisitor::visit(const AST::Tilde*)
{
}
void NodeVisitor::visit(const AST::VariableDeclarations* node)
{
for (auto& entry : node->variables()) {
entry.name->visit(*this);
entry.value->visit(*this);
}
}
void NodeVisitor::visit(const AST::WriteAppendRedirection* node)
{
visit(static_cast<const AST::PathRedirectionNode*>(node));
}
void NodeVisitor::visit(const AST::WriteRedirection* node)
{
visit(static_cast<const AST::PathRedirectionNode*>(node));
}
}

73
Shell/NodeVisitor.h Normal file
View file

@ -0,0 +1,73 @@
/*
* 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.
*/
#pragma once
#include "Forward.h"
namespace AST {
class NodeVisitor {
public:
virtual void visit(const AST::PathRedirectionNode*);
virtual void visit(const AST::And*);
virtual void visit(const AST::ListConcatenate*);
virtual void visit(const AST::Background*);
virtual void visit(const AST::BarewordLiteral*);
virtual void visit(const AST::CastToCommand*);
virtual void visit(const AST::CastToList*);
virtual void visit(const AST::CloseFdRedirection*);
virtual void visit(const AST::CommandLiteral*);
virtual void visit(const AST::Comment*);
virtual void visit(const AST::DynamicEvaluate*);
virtual void visit(const AST::DoubleQuotedString*);
virtual void visit(const AST::Fd2FdRedirection*);
virtual void visit(const AST::FunctionDeclaration*);
virtual void visit(const AST::ForLoop*);
virtual void visit(const AST::Glob*);
virtual void visit(const AST::Execute*);
virtual void visit(const AST::IfCond*);
virtual void visit(const AST::Join*);
virtual void visit(const AST::MatchExpr*);
virtual void visit(const AST::Or*);
virtual void visit(const AST::Pipe*);
virtual void visit(const AST::ReadRedirection*);
virtual void visit(const AST::ReadWriteRedirection*);
virtual void visit(const AST::Sequence*);
virtual void visit(const AST::Subshell*);
virtual void visit(const AST::SimpleVariable*);
virtual void visit(const AST::SpecialVariable*);
virtual void visit(const AST::Juxtaposition*);
virtual void visit(const AST::StringLiteral*);
virtual void visit(const AST::StringPartCompose*);
virtual void visit(const AST::SyntaxError*);
virtual void visit(const AST::Tilde*);
virtual void visit(const AST::VariableDeclarations*);
virtual void visit(const AST::WriteAppendRedirection*);
virtual void visit(const AST::WriteRedirection*);
};
}

View file

@ -26,6 +26,7 @@
#include "Shell.h"
#include "Execution.h"
#include "Formatter.h"
#include <AK/Function.h>
#include <AK/LexicalPath.h>
#include <AK/ScopeGuard.h>
@ -449,6 +450,15 @@ bool Shell::invoke_function(const AST::Command& command, int& retval)
return true;
}
String Shell::format(const StringView& source, ssize_t& cursor) const
{
Formatter formatter(source, cursor);
auto result = formatter.format();
cursor = formatter.cursor();
return result;
}
Shell::Frame Shell::push_frame()
{
m_local_frames.empend();

View file

@ -102,6 +102,8 @@ public:
bool has_function(const String&);
bool invoke_function(const AST::Command&, int& retval);
String format(const StringView&, ssize_t& cursor) const;
struct LocalFrame {
HashMap<String, RefPtr<AST::Value>> local_variables;
};

View file

@ -200,21 +200,34 @@ int main(int argc, char** argv)
const char* file_to_read_from = nullptr;
Vector<const char*> script_args;
bool skip_rc_files = false;
const char* format = nullptr;
Core::ArgsParser parser;
parser.add_option(command_to_run, "String to read commands from", "command-string", 'c', "command-string");
parser.add_option(skip_rc_files, "Skip running shellrc files", "skip-shellrc", 0);
parser.add_option(format, "File to format", "format", 0, "file");
parser.add_positional_argument(file_to_read_from, "File to read commands from", "file", Core::ArgsParser::Required::No);
parser.add_positional_argument(script_args, "Extra argumets to pass to the script (via $* and co)", "argument", Core::ArgsParser::Required::No);
parser.parse(argc, argv);
if (format) {
auto file = Core::File::open(format, Core::IODevice::ReadOnly);
if (file.is_error()) {
fprintf(stderr, "Error: %s", file.error().characters());
return 1;
}
ssize_t cursor = -1;
puts(shell->format(file.value()->read_all(), cursor).characters());
return 0;
}
if (getsid(getpid()) == 0) {
if (setsid() < 0) {
perror("setsid");
// Let's just hope that it's ok.
}
}
shell->current_script = argv[0];