mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 00:48:11 +00:00
Shell: Add 'if' expressions
```sh if foo bar baz { quux } else if foobar || whatever { echo I ran out of example words } else { exit 2 } ```
This commit is contained in:
parent
c2be38e50f
commit
b90eb5c9ba
4 changed files with 210 additions and 2 deletions
102
Shell/AST.cpp
102
Shell/AST.cpp
|
@ -1119,6 +1119,108 @@ Execute::~Execute()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IfCond::dump(int level) const
|
||||||
|
{
|
||||||
|
Node::dump(level);
|
||||||
|
print_indented("Condition", ++level);
|
||||||
|
m_condition->dump(level + 1);
|
||||||
|
print_indented("True Branch", level);
|
||||||
|
if (m_true_branch)
|
||||||
|
m_true_branch->dump(level + 1);
|
||||||
|
else
|
||||||
|
print_indented("(empty)", level + 1);
|
||||||
|
print_indented("False Branch", level);
|
||||||
|
if (m_false_branch)
|
||||||
|
m_false_branch->dump(level + 1);
|
||||||
|
else
|
||||||
|
print_indented("(empty)", level + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<Value> IfCond::run(RefPtr<Shell> shell)
|
||||||
|
{
|
||||||
|
auto cond = m_condition->run(shell)->resolve_without_cast(shell);
|
||||||
|
ASSERT(cond->is_job());
|
||||||
|
|
||||||
|
auto cond_job_value = static_cast<const JobValue*>(cond.ptr());
|
||||||
|
auto cond_job = cond_job_value->job();
|
||||||
|
|
||||||
|
shell->block_on_job(cond_job);
|
||||||
|
|
||||||
|
if (cond_job->signaled())
|
||||||
|
return create<ListValue>({}); // Exit early.
|
||||||
|
|
||||||
|
if (cond_job->exit_code() == 0) {
|
||||||
|
if (m_true_branch)
|
||||||
|
return m_true_branch->run(shell);
|
||||||
|
} else {
|
||||||
|
if (m_false_branch)
|
||||||
|
return m_false_branch->run(shell);
|
||||||
|
}
|
||||||
|
|
||||||
|
return create<ListValue>({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IfCond::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
||||||
|
{
|
||||||
|
metadata.is_first_in_list = true;
|
||||||
|
|
||||||
|
editor.stylize({ m_position.start_offset, m_position.start_offset + 2 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
|
||||||
|
if (m_else_position.has_value())
|
||||||
|
editor.stylize({ m_else_position.value().start_offset, m_else_position.value().start_offset + 4 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
|
||||||
|
|
||||||
|
m_condition->highlight_in_editor(editor, shell, metadata);
|
||||||
|
if (m_true_branch)
|
||||||
|
m_true_branch->highlight_in_editor(editor, shell, metadata);
|
||||||
|
if (m_false_branch)
|
||||||
|
m_false_branch->highlight_in_editor(editor, shell, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
HitTestResult IfCond::hit_test_position(size_t offset)
|
||||||
|
{
|
||||||
|
if (!position().contains(offset))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (auto result = m_condition->hit_test_position(offset); result.matching_node)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (m_true_branch) {
|
||||||
|
if (auto result = m_true_branch->hit_test_position(offset); result.matching_node)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_false_branch) {
|
||||||
|
if (auto result = m_false_branch->hit_test_position(offset); result.matching_node)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
IfCond::IfCond(Position position, Optional<Position> else_position, RefPtr<Node> condition, RefPtr<Node> true_branch, RefPtr<Node> false_branch)
|
||||||
|
: Node(move(position))
|
||||||
|
, m_condition(move(condition))
|
||||||
|
, m_true_branch(move(true_branch))
|
||||||
|
, m_false_branch(move(false_branch))
|
||||||
|
, m_else_position(move(else_position))
|
||||||
|
{
|
||||||
|
if (m_condition->is_syntax_error())
|
||||||
|
set_is_syntax_error(m_condition->syntax_error_node());
|
||||||
|
else if (m_true_branch && m_true_branch->is_syntax_error())
|
||||||
|
set_is_syntax_error(m_true_branch->syntax_error_node());
|
||||||
|
else if (m_false_branch && m_false_branch->is_syntax_error())
|
||||||
|
set_is_syntax_error(m_false_branch->syntax_error_node());
|
||||||
|
|
||||||
|
m_condition = create<AST::Execute>(m_condition->position(), m_condition);
|
||||||
|
if (m_true_branch)
|
||||||
|
m_true_branch = create<AST::Execute>(m_true_branch->position(), m_true_branch);
|
||||||
|
if (m_false_branch)
|
||||||
|
m_false_branch = create<AST::Execute>(m_false_branch->position(), m_false_branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
IfCond::~IfCond()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void Join::dump(int level) const
|
void Join::dump(int level) const
|
||||||
{
|
{
|
||||||
Node::dump(level);
|
Node::dump(level);
|
||||||
|
|
20
Shell/AST.h
20
Shell/AST.h
|
@ -675,6 +675,26 @@ private:
|
||||||
bool m_capture_stdout { false };
|
bool m_capture_stdout { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IfCond final : public Node {
|
||||||
|
public:
|
||||||
|
IfCond(Position, Optional<Position> else_position, RefPtr<AST::Node> cond_expr, RefPtr<AST::Node> true_branch, RefPtr<AST::Node> false_branch);
|
||||||
|
virtual ~IfCond();
|
||||||
|
|
||||||
|
private:
|
||||||
|
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) override;
|
||||||
|
virtual String class_name() const override { return "IfCond"; }
|
||||||
|
virtual bool would_execute() const override { return true; }
|
||||||
|
|
||||||
|
RefPtr<AST::Node> m_condition;
|
||||||
|
RefPtr<AST::Node> m_true_branch;
|
||||||
|
RefPtr<AST::Node> m_false_branch;
|
||||||
|
|
||||||
|
Optional<Position> m_else_position;
|
||||||
|
};
|
||||||
|
|
||||||
class Join final : public Node {
|
class Join final : public Node {
|
||||||
public:
|
public:
|
||||||
Join(Position, RefPtr<Node>, RefPtr<Node>);
|
Join(Position, RefPtr<Node>, RefPtr<Node>);
|
||||||
|
|
|
@ -364,6 +364,9 @@ RefPtr<AST::Node> Parser::parse_control_structure()
|
||||||
if (auto for_loop = parse_for_loop())
|
if (auto for_loop = parse_for_loop())
|
||||||
return for_loop;
|
return for_loop;
|
||||||
|
|
||||||
|
if (auto if_expr = parse_if_expr())
|
||||||
|
return if_expr;
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,6 +434,83 @@ RefPtr<AST::Node> Parser::parse_for_loop()
|
||||||
return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block
|
return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefPtr<AST::Node> Parser::parse_if_expr()
|
||||||
|
{
|
||||||
|
auto rule_start = push_start();
|
||||||
|
if (!expect("if")) {
|
||||||
|
m_offset = rule_start->offset;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consume_while(is_any_of(" \t\n")).is_empty()) {
|
||||||
|
m_offset = rule_start->offset;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<AST::Node> condition;
|
||||||
|
{
|
||||||
|
auto cond_error_start = push_start();
|
||||||
|
condition = parse_or_logical_sequence();
|
||||||
|
if (!condition) {
|
||||||
|
auto syntax_error = create<AST::SyntaxError>("Expected a logical sequence after 'if'");
|
||||||
|
return create<AST::IfCond>(Optional<AST::Position> {}, move(syntax_error), nullptr, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parse_braced_toplevel = [&]() -> RefPtr<AST::Node> {
|
||||||
|
{
|
||||||
|
auto obrace_error_start = push_start();
|
||||||
|
if (!expect('{')) {
|
||||||
|
auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start an 'if' true branch");
|
||||||
|
return syntax_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto body = parse_toplevel();
|
||||||
|
|
||||||
|
{
|
||||||
|
auto cbrace_error_start = push_start();
|
||||||
|
if (!expect('}')) {
|
||||||
|
auto error_start = push_start();
|
||||||
|
RefPtr<AST::SyntaxError> syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end an 'if' true branch");
|
||||||
|
if (body)
|
||||||
|
body->set_is_syntax_error(*syntax_error);
|
||||||
|
else
|
||||||
|
body = syntax_error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
|
};
|
||||||
|
|
||||||
|
consume_while(is_whitespace);
|
||||||
|
auto true_branch = parse_braced_toplevel();
|
||||||
|
|
||||||
|
if (true_branch && true_branch->is_syntax_error())
|
||||||
|
return create<AST::IfCond>(Optional<AST::Position> {}, move(condition), move(true_branch), nullptr); // If expr syntax_error
|
||||||
|
|
||||||
|
consume_while(is_whitespace);
|
||||||
|
Optional<AST::Position> else_position;
|
||||||
|
{
|
||||||
|
auto else_start = push_start();
|
||||||
|
if (expect("else"))
|
||||||
|
else_position = AST::Position { else_start->offset, m_offset };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (else_position.has_value()) {
|
||||||
|
consume_while(is_whitespace);
|
||||||
|
if (peek() == '{') {
|
||||||
|
auto false_branch = parse_braced_toplevel();
|
||||||
|
return create<AST::IfCond>(else_position, move(condition), move(true_branch), move(false_branch)); // If expr true_branch Else false_branch
|
||||||
|
}
|
||||||
|
|
||||||
|
auto else_if_branch = parse_if_expr();
|
||||||
|
return create<AST::IfCond>(else_position, move(condition), move(true_branch), move(else_if_branch)); // If expr true_branch Else If ...
|
||||||
|
}
|
||||||
|
|
||||||
|
return create<AST::IfCond>(else_position, move(condition), move(true_branch), nullptr); // If expr true_branch
|
||||||
|
}
|
||||||
|
|
||||||
RefPtr<AST::Node> Parser::parse_redirection()
|
RefPtr<AST::Node> Parser::parse_redirection()
|
||||||
{
|
{
|
||||||
auto rule_start = push_start();
|
auto rule_start = push_start();
|
||||||
|
|
|
@ -52,6 +52,7 @@ private:
|
||||||
RefPtr<AST::Node> parse_command();
|
RefPtr<AST::Node> parse_command();
|
||||||
RefPtr<AST::Node> parse_control_structure();
|
RefPtr<AST::Node> parse_control_structure();
|
||||||
RefPtr<AST::Node> parse_for_loop();
|
RefPtr<AST::Node> parse_for_loop();
|
||||||
|
RefPtr<AST::Node> parse_if_expr();
|
||||||
RefPtr<AST::Node> parse_redirection();
|
RefPtr<AST::Node> parse_redirection();
|
||||||
RefPtr<AST::Node> parse_list_expression();
|
RefPtr<AST::Node> parse_list_expression();
|
||||||
RefPtr<AST::Node> parse_expression();
|
RefPtr<AST::Node> parse_expression();
|
||||||
|
@ -125,9 +126,14 @@ variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '*
|
||||||
pipe_sequence :: command '|' pipe_sequence
|
pipe_sequence :: command '|' pipe_sequence
|
||||||
| command
|
| command
|
||||||
|
|
||||||
control_structure :: for_loop
|
control_structure :: for_expr
|
||||||
|
| if_expr
|
||||||
|
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' toplevel '}'
|
||||||
|
|
||||||
for_loop :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' toplevel '}'
|
if_expr :: 'if' ws+ or_logical_sequence ws+ '{' toplevel '}' else_clause?
|
||||||
|
|
||||||
|
else_clause :: else '{' toplevel '}'
|
||||||
|
| else if_expr
|
||||||
|
|
||||||
command :: redirection command
|
command :: redirection command
|
||||||
| list_expression command?
|
| list_expression command?
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue