Skip to content

Commit

Permalink
Shell: Add support for heredocs
Browse files Browse the repository at this point in the history
Closes SerenityOS#4283.
Heredocs are implemented in a way that makes them feel more like a
string (and not a weird redirection, a la bash).
There are two tunables, whether the string is dedented (`<<-` vs `<<~`)
and whether it allows interpolation (quoted key vs not).
To the familiar people, this is how Ruby handles them, and I feel is the
most elegant heredoc syntax.
Unlike the oddjob that is bash, heredocs are treated exactly as normal
strings, and can be used _anywhere_ where a string can be used.
They are *required* to appear in the same order as used after a newline
is seen when parsing the sequence that the heredoc is used in.
For instance:
```sh
echo <<-doc1 <<-doc2 | blah blah
contents for doc1
doc1
contents for doc2
doc2
```
The typical nice errors are also implemented :^)
  • Loading branch information
alimpfard authored and awesomekling committed Apr 29, 2021
1 parent 7c8d39e commit 3048274
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 10 deletions.
72 changes: 72 additions & 0 deletions Userland/Shell/AST.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,78 @@ Glob::~Glob()
{
}

void Heredoc::dump(int level) const
{
Node::dump(level);
print_indented("(End Key)", level + 1);
print_indented(m_end, level + 2);
print_indented("(Allows Interpolation)", level + 1);
print_indented(String::formatted("{}", m_allows_interpolation), level + 2);
print_indented("(Contents)", level + 1);
if (m_contents)
m_contents->dump(level + 2);
else
print_indented("(null)", level + 2);
}

RefPtr<Value> Heredoc::run(RefPtr<Shell> shell)
{
if (!m_deindent)
return m_contents->run(shell);

// To deindent, first split to lines...
auto value = m_contents->run(shell);
if (!value)
return value;
auto list = value->resolve_as_list(shell);
// The list better have one entry, otherwise we've put the wrong kind of node inside this heredoc
VERIFY(list.size() == 1);
auto lines = list.first().split_view('\n');

// Now just trim each line and put them back in a string
StringBuilder builder { list.first().length() };
for (auto& line : lines) {
builder.append(line.trim_whitespace(TrimMode::Left));
builder.append('\n');
}

return create<StringValue>(builder.to_string());
}

void Heredoc::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
{
Line::Style content_style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) };
if (metadata.is_first_in_list)
content_style.unify_with({ Line::Style::Bold });

if (!m_contents)
content_style.unify_with({ Line::Style::Foreground(Line::Style::XtermColor::Red) }, true);

editor.stylize({ m_position.start_offset, m_position.end_offset }, content_style);
if (m_contents)
m_contents->highlight_in_editor(editor, shell, metadata);
}

HitTestResult Heredoc::hit_test_position(size_t offset) const
{
if (!m_contents)
return {};

return m_contents->hit_test_position(offset);
}

Heredoc::Heredoc(Position position, String end, bool allow_interpolation, bool deindent)
: Node(move(position))
, m_end(move(end))
, m_allows_interpolation(allow_interpolation)
, m_deindent(deindent)
{
}

Heredoc::~Heredoc()
{
}

void HistoryEvent::dump(int level) const
{
Node::dump(level);
Expand Down
34 changes: 34 additions & 0 deletions Userland/Shell/AST.h
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ class Node : public RefCounted<Node> {
ForLoop,
FunctionDeclaration,
Glob,
Heredoc,
HistoryEvent,
IfCond,
ImmediateExpression,
Expand Down Expand Up @@ -1313,6 +1314,39 @@ class Juxtaposition final : public Node {
NonnullRefPtr<Node> m_right;
};

class Heredoc final : public Node {
public:
Heredoc(Position, String end, bool allow_interpolation, bool deindent);
virtual ~Heredoc();
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }

const String& end() const { return m_end; }
bool allow_interpolation() const { return m_allows_interpolation; }
bool deindent() const { return m_deindent; }
const RefPtr<AST::Node>& contents() const { return m_contents; }
void set_contents(RefPtr<AST::Node> contents)
{
m_contents = move(contents);
if (m_contents->is_syntax_error())
set_is_syntax_error(m_contents->syntax_error_node());
else
clear_syntax_error();
}

private:
NODE(Heredoc);
virtual void dump(int level) const override;
virtual RefPtr<Value> run(RefPtr<Shell>) override;
virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
virtual HitTestResult hit_test_position(size_t) const override;
virtual RefPtr<Node> leftmost_trivial_literal() const override { return this; };

String m_end;
bool m_allows_interpolation { false };
bool m_deindent { false };
RefPtr<AST::Node> m_contents;
};

class StringLiteral final : public Node {
public:
StringLiteral(Position, String);
Expand Down
1 change: 1 addition & 0 deletions Userland/Shell/Forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class Fd2FdRedirection;
class FunctionDeclaration;
class ForLoop;
class Glob;
class Heredoc;
class HistoryEvent;
class Execute;
class IfCond;
Expand Down
6 changes: 6 additions & 0 deletions Userland/Shell/NodeVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ void NodeVisitor::visit(const AST::Glob*)
{
}

void NodeVisitor::visit(const AST::Heredoc* node)
{
if (node->contents())
node->contents()->visit(*this);
}

void NodeVisitor::visit(const AST::HistoryEvent*)
{
}
Expand Down
1 change: 1 addition & 0 deletions Userland/Shell/NodeVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class NodeVisitor {
virtual void visit(const AST::FunctionDeclaration*);
virtual void visit(const AST::ForLoop*);
virtual void visit(const AST::Glob*);
virtual void visit(const AST::Heredoc*);
virtual void visit(const AST::HistoryEvent*);
virtual void visit(const AST::Execute*);
virtual void visit(const AST::IfCond*);
Expand Down
Loading

0 comments on commit 3048274

Please sign in to comment.