Przeglądaj źródła

Shell: Add 'if' expressions

```sh
if foo bar baz {
    quux
} else if foobar || whatever {
    echo I ran out of example words
} else {
    exit 2
}
```
AnotherTest 5 lat temu
rodzic
commit
b90eb5c9ba
4 zmienionych plików z 210 dodań i 2 usunięć
  1. 102 0
      Shell/AST.cpp
  2. 20 0
      Shell/AST.h
  3. 80 0
      Shell/Parser.cpp
  4. 8 2
      Shell/Parser.h

+ 102 - 0
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
 {
     Node::dump(level);

+ 20 - 0
Shell/AST.h

@@ -675,6 +675,26 @@ private:
     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 {
 public:
     Join(Position, RefPtr<Node>, RefPtr<Node>);

+ 80 - 0
Shell/Parser.cpp

@@ -364,6 +364,9 @@ RefPtr<AST::Node> 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<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
 }
 
+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()
 {
     auto rule_start = push_start();

+ 8 - 2
Shell/Parser.h

@@ -52,6 +52,7 @@ private:
     RefPtr<AST::Node> parse_command();
     RefPtr<AST::Node> parse_control_structure();
     RefPtr<AST::Node> parse_for_loop();
+    RefPtr<AST::Node> parse_if_expr();
     RefPtr<AST::Node> parse_redirection();
     RefPtr<AST::Node> parse_list_expression();
     RefPtr<AST::Node> 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?