From d1550ea64f0d2abf86b4bedd34ae7f7f71ccfc59 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sun, 13 Sep 2020 15:54:33 +0430 Subject: [PATCH] Shell: Add support for functions This implementation does not have support for 'return' yet. --- Shell/AST.cpp | 60 ++++++++++++++++++++++++++++++++ Shell/AST.h | 22 ++++++++++++ Shell/Parser.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++++++- Shell/Parser.h | 4 +++ Shell/Shell.cpp | 83 +++++++++++++++++++++++++++++++++++++++++--- Shell/Shell.h | 11 ++++++ 6 files changed, 264 insertions(+), 5 deletions(-) diff --git a/Shell/AST.cpp b/Shell/AST.cpp index e60472c2ab..5754eb3463 100644 --- a/Shell/AST.cpp +++ b/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 FunctionDeclaration::run(RefPtr shell) +{ + Vector args; + for (auto& arg : m_arguments) + args.append(arg.name); + + shell->define_function(m_name.name, move(args), m_block); + + return create({}); +} + +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 arguments, RefPtr 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 { Node::dump(level); diff --git a/Shell/AST.h b/Shell/AST.h index e180aa3594..0d76a9dbbf 100644 --- a/Shell/AST.h +++ b/Shell/AST.h @@ -636,6 +636,28 @@ private: int dest_fd { -1 }; }; +class FunctionDeclaration final : public Node { +public: + struct NameWithPosition { + String name; + Position position; + }; + FunctionDeclaration(Position, NameWithPosition name, Vector argument_names, RefPtr body); + virtual ~FunctionDeclaration(); + +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 "FunctionDeclaration"; } + virtual bool would_execute() const override { return true; } + + NameWithPosition m_name; + Vector m_arguments; + RefPtr m_block; +}; + class ForLoop final : public Node { public: ForLoop(Position, String variable_name, RefPtr iterated_expr, RefPtr block, Optional in_kw_position = {}); diff --git a/Shell/Parser.cpp b/Shell/Parser.cpp index 98ea06f5f7..3b101619e8 100644 --- a/Shell/Parser.cpp +++ b/Shell/Parser.cpp @@ -167,7 +167,10 @@ RefPtr Parser::parse_sequence() break; } - auto first = parse_or_logical_sequence(); + auto first = parse_function_decl(); + + if (!first) + first = parse_or_logical_sequence(); if (!first) return var_decls; @@ -259,6 +262,90 @@ RefPtr Parser::parse_variable_decls() return create(move(variables)); } +RefPtr 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 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 syntax_error; + { + auto obrace_error_start = push_start(); + syntax_error = create("Expected an open brace '{' to start a function body"); + } + if (!expect('{')) { + return create( + AST::FunctionDeclaration::NameWithPosition { + move(function_name), + { offset_before_name, offset_after_name } }, + move(arguments), + move(syntax_error)); + } + } + + auto body = parse_toplevel(); + + { + RefPtr syntax_error; + { + auto cbrace_error_start = push_start(); + syntax_error = create("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::NameWithPosition { + move(function_name), + { offset_before_name, offset_after_name } }, + move(arguments), + move(body)); + } + } + + return create( + AST::FunctionDeclaration::NameWithPosition { + move(function_name), + { offset_before_name, offset_after_name } }, + move(arguments), + move(body)); +} + RefPtr Parser::parse_or_logical_sequence() { consume_while(is_whitespace); diff --git a/Shell/Parser.h b/Shell/Parser.h index 375ea71c09..6d6b789e4b 100644 --- a/Shell/Parser.h +++ b/Shell/Parser.h @@ -45,6 +45,7 @@ public: private: RefPtr parse_toplevel(); RefPtr parse_sequence(); + RefPtr parse_function_decl(); RefPtr parse_and_logical_sequence(); RefPtr parse_or_logical_sequence(); RefPtr parse_variable_decls(); @@ -109,8 +110,11 @@ toplevel :: sequence? sequence :: variable_decls? or_logical_sequence terminator sequence | variable_decls? or_logical_sequence '&' sequence | variable_decls? or_logical_sequence + | variable_decls? function_decl (terminator sequence)? | variable_decls? terminator sequence +function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' toplevel '}' + or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence | and_logical_sequence diff --git a/Shell/Shell.cpp b/Shell/Shell.cpp index 057c1357c1..2c1ee0e212 100644 --- a/Shell/Shell.cpp +++ b/Shell/Shell.cpp @@ -388,6 +388,60 @@ void Shell::unset_local_variable(const String& name) frame->local_variables.remove(name); } +void Shell::define_function(String name, Vector argnames, RefPtr 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 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() { m_local_frames.empend(); @@ -544,6 +598,25 @@ RefPtr Shell::run_command(const AST::Command& command) 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 argv; Vector copy_argv = command.argv; argv.ensure_capacity(command.argv.size() + 1); @@ -597,19 +670,21 @@ RefPtr Shell::run_command(const AST::Command& command) if (!m_is_subshell && command.should_wait) tcsetattr(0, TCSANOW, &default_termios); + Core::EventLoop mainloop; + setup_signals(); + if (command.should_immediately_execute_next) { ASSERT(command.argv.is_empty()); - Core::EventLoop mainloop; - - setup_signals(); - for (auto& next_in_chain : command.next_chain) run_tail(next_in_chain, 0); _exit(last_return_code); } + if (invoke_function(command, last_return_code)) + _exit(last_return_code); + int rc = execvp(argv[0], const_cast(argv.data())); if (rc < 0) { if (errno == ENOENT) { diff --git a/Shell/Shell.h b/Shell/Shell.h index a95d73aafd..02f974df6a 100644 --- a/Shell/Shell.h +++ b/Shell/Shell.h @@ -98,6 +98,10 @@ public: void set_local_variable(const String&, RefPtr); void unset_local_variable(const String&); + void define_function(String name, Vector argnames, RefPtr body); + bool has_function(const String&); + bool invoke_function(const AST::Command&, int& retval); + struct LocalFrame { HashMap> local_variables; }; @@ -224,6 +228,13 @@ private: bool m_should_ignore_jobs_on_next_exit { false }; pid_t m_pid { 0 }; + struct ShellFunction { + String name; + Vector arguments; + RefPtr body; + }; + + HashMap m_functions; Vector m_local_frames; NonnullRefPtrVector m_global_redirections;