Selaa lähdekoodia

Shell: Add support for functions

This implementation does not have support for 'return' yet.
AnotherTest 4 vuotta sitten
vanhempi
commit
d1550ea64f
6 muutettua tiedostoa jossa 264 lisäystä ja 5 poistoa
  1. 60 0
      Shell/AST.cpp
  2. 22 0
      Shell/AST.h
  3. 88 1
      Shell/Parser.cpp
  4. 4 0
      Shell/Parser.h
  5. 79 4
      Shell/Shell.cpp
  6. 11 0
      Shell/Shell.h

+ 60 - 0
Shell/AST.cpp

@@ -764,6 +764,66 @@ Fd2FdRedirection::~Fd2FdRedirection()
 {
 {
 }
 }
 
 
+void FunctionDeclaration::dump(int level) const
+{
+    Node::dump(level);
+    print_indented(String::format("(name: %s)\n", m_name.name.characters()), level + 1);
+    print_indented("(argument namess)", level + 1);
+    for (auto& arg : m_arguments)
+        print_indented(String::format("(name: %s)\n", arg.name.characters()), level + 2);
+
+    print_indented("(body)", level + 1);
+    if (m_block)
+        m_block->dump(level + 2);
+    else
+        print_indented("(null)", level + 2);
+}
+
+RefPtr<Value> FunctionDeclaration::run(RefPtr<Shell> shell)
+{
+    Vector<String> args;
+    for (auto& arg : m_arguments)
+        args.append(arg.name);
+
+    shell->define_function(m_name.name, move(args), m_block);
+
+    return create<ListValue>({});
+}
+
+void FunctionDeclaration::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
+{
+    editor.stylize({ m_name.position.start_offset, m_name.position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue) });
+
+    for (auto& arg : m_arguments)
+        editor.stylize({ arg.position.start_offset, arg.position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic });
+
+    metadata.is_first_in_list = true;
+    if (m_block)
+        m_block->highlight_in_editor(editor, shell, metadata);
+}
+
+HitTestResult FunctionDeclaration::hit_test_position(size_t offset)
+{
+    if (!position().contains(offset))
+        return {};
+
+    return m_block->hit_test_position(offset);
+}
+
+FunctionDeclaration::FunctionDeclaration(Position position, NameWithPosition name, Vector<NameWithPosition> arguments, RefPtr<AST::Node> body)
+    : Node(move(position))
+    , m_name(move(name))
+    , m_arguments(arguments)
+    , m_block(move(body))
+{
+    if (m_block && m_block->is_syntax_error())
+        set_is_syntax_error(m_block->syntax_error_node());
+}
+
+FunctionDeclaration::~FunctionDeclaration()
+{
+}
+
 void ForLoop::dump(int level) const
 void ForLoop::dump(int level) const
 {
 {
     Node::dump(level);
     Node::dump(level);

+ 22 - 0
Shell/AST.h

@@ -636,6 +636,28 @@ private:
     int dest_fd { -1 };
     int dest_fd { -1 };
 };
 };
 
 
+class FunctionDeclaration final : public Node {
+public:
+    struct NameWithPosition {
+        String name;
+        Position position;
+    };
+    FunctionDeclaration(Position, NameWithPosition name, Vector<NameWithPosition> argument_names, RefPtr<AST::Node> body);
+    virtual ~FunctionDeclaration();
+
+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 "FunctionDeclaration"; }
+    virtual bool would_execute() const override { return true; }
+
+    NameWithPosition m_name;
+    Vector<NameWithPosition> m_arguments;
+    RefPtr<AST::Node> m_block;
+};
+
 class ForLoop final : public Node {
 class ForLoop final : public Node {
 public:
 public:
     ForLoop(Position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<size_t> in_kw_position = {});
     ForLoop(Position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<size_t> in_kw_position = {});

+ 88 - 1
Shell/Parser.cpp

@@ -167,7 +167,10 @@ RefPtr<AST::Node> Parser::parse_sequence()
         break;
         break;
     }
     }
 
 
-    auto first = parse_or_logical_sequence();
+    auto first = parse_function_decl();
+
+    if (!first)
+        first = parse_or_logical_sequence();
 
 
     if (!first)
     if (!first)
         return var_decls;
         return var_decls;
@@ -259,6 +262,90 @@ RefPtr<AST::Node> Parser::parse_variable_decls()
     return create<AST::VariableDeclarations>(move(variables));
     return create<AST::VariableDeclarations>(move(variables));
 }
 }
 
 
+RefPtr<AST::Node> Parser::parse_function_decl()
+{
+    auto rule_start = push_start();
+
+    auto restore = [&] {
+        m_offset = rule_start->offset;
+        return nullptr;
+    };
+
+    consume_while(is_whitespace);
+    auto offset_before_name = m_offset;
+    auto function_name = consume_while(is_word_character);
+    auto offset_after_name = m_offset;
+    if (function_name.is_empty())
+        return restore();
+
+    if (!expect('('))
+        return restore();
+
+    Vector<AST::FunctionDeclaration::NameWithPosition> arguments;
+    for (;;) {
+        consume_while(is_whitespace);
+
+        if (expect(')'))
+            break;
+
+        auto name_offset = m_offset;
+        auto arg_name = consume_while(is_word_character);
+        if (arg_name.is_empty()) {
+            // FIXME: Should this be a syntax error, or just return?
+            return restore();
+        }
+        arguments.append({ arg_name, { name_offset, m_offset } });
+    }
+
+    consume_while(is_whitespace);
+
+    {
+        RefPtr<AST::Node> syntax_error;
+        {
+            auto obrace_error_start = push_start();
+            syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a function body");
+        }
+        if (!expect('{')) {
+            return create<AST::FunctionDeclaration>(
+                AST::FunctionDeclaration::NameWithPosition {
+                    move(function_name),
+                    { offset_before_name, offset_after_name } },
+                move(arguments),
+                move(syntax_error));
+        }
+    }
+
+    auto body = parse_toplevel();
+
+    {
+        RefPtr<AST::SyntaxError> syntax_error;
+        {
+            auto cbrace_error_start = push_start();
+            syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a function body");
+        }
+        if (!expect('}')) {
+            if (body)
+                body->set_is_syntax_error(*syntax_error);
+            else
+                body = move(syntax_error);
+
+            return create<AST::FunctionDeclaration>(
+                AST::FunctionDeclaration::NameWithPosition {
+                    move(function_name),
+                    { offset_before_name, offset_after_name } },
+                move(arguments),
+                move(body));
+        }
+    }
+
+    return create<AST::FunctionDeclaration>(
+        AST::FunctionDeclaration::NameWithPosition {
+            move(function_name),
+            { offset_before_name, offset_after_name } },
+        move(arguments),
+        move(body));
+}
+
 RefPtr<AST::Node> Parser::parse_or_logical_sequence()
 RefPtr<AST::Node> Parser::parse_or_logical_sequence()
 {
 {
     consume_while(is_whitespace);
     consume_while(is_whitespace);

+ 4 - 0
Shell/Parser.h

@@ -45,6 +45,7 @@ public:
 private:
 private:
     RefPtr<AST::Node> parse_toplevel();
     RefPtr<AST::Node> parse_toplevel();
     RefPtr<AST::Node> parse_sequence();
     RefPtr<AST::Node> parse_sequence();
+    RefPtr<AST::Node> parse_function_decl();
     RefPtr<AST::Node> parse_and_logical_sequence();
     RefPtr<AST::Node> parse_and_logical_sequence();
     RefPtr<AST::Node> parse_or_logical_sequence();
     RefPtr<AST::Node> parse_or_logical_sequence();
     RefPtr<AST::Node> parse_variable_decls();
     RefPtr<AST::Node> parse_variable_decls();
@@ -109,8 +110,11 @@ toplevel :: sequence?
 sequence :: variable_decls? or_logical_sequence terminator sequence
 sequence :: variable_decls? or_logical_sequence terminator sequence
           | variable_decls? or_logical_sequence '&' sequence
           | variable_decls? or_logical_sequence '&' sequence
           | variable_decls? or_logical_sequence
           | variable_decls? or_logical_sequence
+          | variable_decls? function_decl (terminator sequence)?
           | variable_decls? terminator sequence
           | variable_decls? terminator sequence
 
 
+function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' toplevel '}'
+
 or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence
 or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence
                      | and_logical_sequence
                      | and_logical_sequence
 
 

+ 79 - 4
Shell/Shell.cpp

@@ -388,6 +388,60 @@ void Shell::unset_local_variable(const String& name)
         frame->local_variables.remove(name);
         frame->local_variables.remove(name);
 }
 }
 
 
+void Shell::define_function(String name, Vector<String> argnames, RefPtr<AST::Node> body)
+{
+    add_entry_to_cache(name);
+    m_functions.set(name, { name, move(argnames), move(body) });
+}
+
+bool Shell::has_function(const String& name)
+{
+    return m_functions.contains(name);
+}
+
+bool Shell::invoke_function(const AST::Command& command, int& retval)
+{
+    if (command.argv.is_empty())
+        return false;
+
+    StringView name = command.argv.first();
+
+    TemporaryChange<String> script_change { current_script, name };
+
+    auto function_option = m_functions.get(name);
+    if (!function_option.has_value())
+        return false;
+
+    auto& function = function_option.value();
+
+    if (!function.body) {
+        retval = 0;
+        return true;
+    }
+
+    if (command.argv.size() - 1 < function.arguments.size()) {
+        fprintf(stderr, "Shell: expected at least %zu arguments to %s, but got %zu\n", function.arguments.size(), function.name.characters(), command.argv.size() - 1);
+        retval = 1;
+        return true;
+    }
+
+    auto frame = push_frame();
+    size_t index = 0;
+    for (auto& arg : function.arguments) {
+        ++index;
+        set_local_variable(arg, adopt(*new AST::StringValue(command.argv[index])));
+    }
+
+    auto argv = command.argv;
+    argv.take_first();
+    set_local_variable("ARGV", adopt(*new AST::ListValue(move(argv))));
+
+    function.body->run(*this);
+
+    retval = last_return_code;
+    return true;
+}
+
 Shell::Frame Shell::push_frame()
 Shell::Frame Shell::push_frame()
 {
 {
     m_local_frames.empend();
     m_local_frames.empend();
@@ -544,6 +598,25 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
         return nullptr;
         return nullptr;
     }
     }
 
 
+    auto can_be_run_in_current_process = command.should_wait && !command.pipeline;
+    if (can_be_run_in_current_process && has_function(command.argv.first())) {
+        SavedFileDescriptors fds { rewirings };
+
+        for (auto& rewiring : rewirings) {
+            int rc = dup2(rewiring.dest_fd, rewiring.source_fd);
+            if (rc < 0) {
+                perror("dup2(run)");
+                return nullptr;
+            }
+        }
+
+        if (invoke_function(command, retval)) {
+            for (auto& next_in_chain : command.next_chain)
+                run_tail(next_in_chain, retval);
+            return nullptr;
+        }
+    }
+
     Vector<const char*> argv;
     Vector<const char*> argv;
     Vector<String> copy_argv = command.argv;
     Vector<String> copy_argv = command.argv;
     argv.ensure_capacity(command.argv.size() + 1);
     argv.ensure_capacity(command.argv.size() + 1);
@@ -597,19 +670,21 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
         if (!m_is_subshell && command.should_wait)
         if (!m_is_subshell && command.should_wait)
             tcsetattr(0, TCSANOW, &default_termios);
             tcsetattr(0, TCSANOW, &default_termios);
 
 
+        Core::EventLoop mainloop;
+        setup_signals();
+
         if (command.should_immediately_execute_next) {
         if (command.should_immediately_execute_next) {
             ASSERT(command.argv.is_empty());
             ASSERT(command.argv.is_empty());
 
 
-            Core::EventLoop mainloop;
-
-            setup_signals();
-
             for (auto& next_in_chain : command.next_chain)
             for (auto& next_in_chain : command.next_chain)
                 run_tail(next_in_chain, 0);
                 run_tail(next_in_chain, 0);
 
 
             _exit(last_return_code);
             _exit(last_return_code);
         }
         }
 
 
+        if (invoke_function(command, last_return_code))
+            _exit(last_return_code);
+
         int rc = execvp(argv[0], const_cast<char* const*>(argv.data()));
         int rc = execvp(argv[0], const_cast<char* const*>(argv.data()));
         if (rc < 0) {
         if (rc < 0) {
             if (errno == ENOENT) {
             if (errno == ENOENT) {

+ 11 - 0
Shell/Shell.h

@@ -98,6 +98,10 @@ public:
     void set_local_variable(const String&, RefPtr<AST::Value>);
     void set_local_variable(const String&, RefPtr<AST::Value>);
     void unset_local_variable(const String&);
     void unset_local_variable(const String&);
 
 
+    void define_function(String name, Vector<String> argnames, RefPtr<AST::Node> body);
+    bool has_function(const String&);
+    bool invoke_function(const AST::Command&, int& retval);
+
     struct LocalFrame {
     struct LocalFrame {
         HashMap<String, RefPtr<AST::Value>> local_variables;
         HashMap<String, RefPtr<AST::Value>> local_variables;
     };
     };
@@ -224,6 +228,13 @@ private:
     bool m_should_ignore_jobs_on_next_exit { false };
     bool m_should_ignore_jobs_on_next_exit { false };
     pid_t m_pid { 0 };
     pid_t m_pid { 0 };
 
 
+    struct ShellFunction {
+        String name;
+        Vector<String> arguments;
+        RefPtr<AST::Node> body;
+    };
+
+    HashMap<String, ShellFunction> m_functions;
     Vector<LocalFrame> m_local_frames;
     Vector<LocalFrame> m_local_frames;
     NonnullRefPtrVector<AST::Redirection> m_global_redirections;
     NonnullRefPtrVector<AST::Redirection> m_global_redirections;