From b90eb5c9ba2fbe518aebe76a27c940cba8fcfa90 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Tue, 11 Aug 2020 12:05:46 +0430 Subject: [PATCH] Shell: Add 'if' expressions ```sh if foo bar baz { quux } else if foobar || whatever { echo I ran out of example words } else { exit 2 } ``` --- Shell/AST.cpp | 102 +++++++++++++++++++++++++++++++++++++++++++++++ Shell/AST.h | 20 ++++++++++ Shell/Parser.cpp | 80 +++++++++++++++++++++++++++++++++++++ Shell/Parser.h | 10 ++++- 4 files changed, 210 insertions(+), 2 deletions(-) diff --git a/Shell/AST.cpp b/Shell/AST.cpp index 27c38824737..fa7cae54841 100644 --- a/Shell/AST.cpp +++ b/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 IfCond::run(RefPtr shell) +{ + auto cond = m_condition->run(shell)->resolve_without_cast(shell); + ASSERT(cond->is_job()); + + auto cond_job_value = static_cast(cond.ptr()); + auto cond_job = cond_job_value->job(); + + shell->block_on_job(cond_job); + + if (cond_job->signaled()) + return create({}); // 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({}); +} + +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 else_position, RefPtr condition, RefPtr true_branch, RefPtr 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(m_condition->position(), m_condition); + if (m_true_branch) + m_true_branch = create(m_true_branch->position(), m_true_branch); + if (m_false_branch) + m_false_branch = create(m_false_branch->position(), m_false_branch); +} + +IfCond::~IfCond() +{ +} + void Join::dump(int level) const { Node::dump(level); diff --git a/Shell/AST.h b/Shell/AST.h index d7c793e7bd1..b4154a19903 100644 --- a/Shell/AST.h +++ b/Shell/AST.h @@ -675,6 +675,26 @@ private: bool m_capture_stdout { false }; }; +class IfCond final : public Node { +public: + IfCond(Position, Optional else_position, RefPtr cond_expr, RefPtr true_branch, RefPtr false_branch); + virtual ~IfCond(); + +private: + virtual void dump(int level) const override; + virtual RefPtr run(RefPtr) 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 m_condition; + RefPtr m_true_branch; + RefPtr m_false_branch; + + Optional m_else_position; +}; + class Join final : public Node { public: Join(Position, RefPtr, RefPtr); diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp index 16e33790ad8..4d4b3276aeb 100644 --- a/Shell/Parser.cpp +++ b/Shell/Parser.cpp @@ -364,6 +364,9 @@ RefPtr Parser::parse_control_structure() if (auto for_loop = parse_for_loop()) return for_loop; + if (auto if_expr = parse_if_expr()) + return if_expr; + return nullptr; } @@ -431,6 +434,83 @@ RefPtr Parser::parse_for_loop() return create(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block } +RefPtr 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 condition; + { + auto cond_error_start = push_start(); + condition = parse_or_logical_sequence(); + if (!condition) { + auto syntax_error = create("Expected a logical sequence after 'if'"); + return create(Optional {}, move(syntax_error), nullptr, nullptr); + } + } + + auto parse_braced_toplevel = [&]() -> RefPtr { + { + auto obrace_error_start = push_start(); + if (!expect('{')) { + auto syntax_error = create("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 syntax_error = create("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(Optional {}, move(condition), move(true_branch), nullptr); // If expr syntax_error + + consume_while(is_whitespace); + Optional 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(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(else_position, move(condition), move(true_branch), move(else_if_branch)); // If expr true_branch Else If ... + } + + return create(else_position, move(condition), move(true_branch), nullptr); // If expr true_branch +} + RefPtr Parser::parse_redirection() { auto rule_start = push_start(); diff --git a/Shell/Parser.h b/Shell/Parser.h index 7de24b82aa3..24f9e63ec4b 100644 --- a/Shell/Parser.h +++ b/Shell/Parser.h @@ -52,6 +52,7 @@ private: RefPtr parse_command(); RefPtr parse_control_structure(); RefPtr parse_for_loop(); + RefPtr parse_if_expr(); RefPtr parse_redirection(); RefPtr parse_list_expression(); RefPtr parse_expression(); @@ -125,9 +126,14 @@ variable_decls :: identifier '=' expression (' '+ variable_decls)? ' '* pipe_sequence :: command '|' pipe_sequence | 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 | list_expression command?