mirror of
https://github.com/RGBCube/serenity
synced 2025-05-30 22:38:12 +00:00
Shell: Add support for functions
This implementation does not have support for 'return' yet.
This commit is contained in:
parent
519aa2048a
commit
d1550ea64f
6 changed files with 264 additions and 5 deletions
|
@ -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
Shell/AST.h
22
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 = {});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue