Shell: Add support for inline environment variables

e.g. `foo=$(echo lol) Shell -c 'echo $foo'` will now print lol!
This commit is contained in:
Bananymous 2024-10-15 23:45:08 +03:00
parent 8317bb13ca
commit 6e981d1222
4 changed files with 85 additions and 4 deletions

View File

@ -60,9 +60,15 @@ struct CommandArgument
struct SingleCommand struct SingleCommand
{ {
BAN::ErrorOr<BAN::Vector<BAN::String>> evaluate_arguments(Execute& execute) const; struct EnvironmentVariable
{
COMMAND_RULE5(EnvironmentVariable, name, value);
BAN::String name;
CommandArgument value;
};
COMMAND_RULE5(SingleCommand, arguments); COMMAND_RULE5(SingleCommand, environment, arguments);
BAN::Vector<EnvironmentVariable> environment;
BAN::Vector<CommandArgument> arguments; BAN::Vector<CommandArgument> arguments;
}; };

View File

@ -86,6 +86,9 @@ BAN::ErrorOr<Execute::ExecuteResult> Execute::execute_command_no_wait(const Inte
exit(builtin_ret.value()); exit(builtin_ret.value());
} }
for (const auto& environment : command.environments)
setenv(environment.name.data(), environment.value.data(), true);
BAN::Vector<const char*> exec_args; BAN::Vector<const char*> exec_args;
TRY_OR_EXIT(exec_args.reserve(command.arguments.size() + 1)); TRY_OR_EXIT(exec_args.reserve(command.arguments.size() + 1));
for (const auto& argument : command.arguments) for (const auto& argument : command.arguments)
@ -119,6 +122,7 @@ BAN::ErrorOr<int> Execute::execute_command_sync(BAN::Span<const BAN::String> arg
InternalCommand command { InternalCommand command {
.command = {}, .command = {},
.arguments = arguments, .arguments = arguments,
.environments = {},
.fd_in = fd_in, .fd_in = fd_in,
.fd_out = fd_out, .fd_out = fd_out,
.background = false, .background = false,
@ -168,6 +172,26 @@ BAN::ErrorOr<void> Execute::execute_command(const PipedCommand& piped_command)
BAN::Vector<int> child_codes; BAN::Vector<int> child_codes;
TRY(child_codes.resize(piped_command.commands.size(), 126)); TRY(child_codes.resize(piped_command.commands.size(), 126));
const auto evaluate_arguments =
[this](BAN::Span<const CommandArgument> arguments) -> BAN::ErrorOr<BAN::Vector<BAN::String>>
{
BAN::Vector<BAN::String> result;
TRY(result.reserve(arguments.size()));
for (const auto& argument : arguments)
TRY(result.push_back(TRY(argument.evaluate(*this))));
return result;
};
const auto evaluate_environment =
[this](BAN::Span<const SingleCommand::EnvironmentVariable> environments) -> BAN::ErrorOr<BAN::Vector<InternalCommand::Environment>>
{
BAN::Vector<InternalCommand::Environment> result;
TRY(result.reserve(environments.size()));
for (const auto& environment : environments)
TRY(result.emplace_back(environment.name, TRY(environment.value.evaluate(*this))));
return result;
};
for (size_t i = 0; i < piped_command.commands.size(); i++) for (size_t i = 0; i < piped_command.commands.size(); i++)
{ {
int new_pipe[2] { STDIN_FILENO, STDOUT_FILENO }; int new_pipe[2] { STDIN_FILENO, STDOUT_FILENO };
@ -189,11 +213,13 @@ BAN::ErrorOr<void> Execute::execute_command(const PipedCommand& piped_command)
const int fd_in = last_pipe_rd; const int fd_in = last_pipe_rd;
const int fd_out = new_pipe[1]; const int fd_out = new_pipe[1];
auto arguments = TRY_OR_PERROR_AND_BREAK(piped_command.commands[i].evaluate_arguments(*this)); const auto arguments = TRY_OR_PERROR_AND_BREAK(evaluate_arguments(piped_command.commands[i].arguments.span()));
const auto environments = TRY_OR_PERROR_AND_BREAK(evaluate_environment(piped_command.commands[i].environment.span()));
InternalCommand command { InternalCommand command {
.command = {}, .command = {},
.arguments = arguments.span(), .arguments = arguments.span(),
.environments = environments.span(),
.fd_in = fd_in, .fd_in = fd_in,
.fd_out = fd_out, .fd_out = fd_out,
.background = piped_command.background, .background = piped_command.background,

View File

@ -25,14 +25,23 @@ public:
private: private:
struct InternalCommand struct InternalCommand
{ {
using Command = BAN::Variant<Builtin::BuiltinCommand, BAN::String>;
enum class Type enum class Type
{ {
Builtin, Builtin,
External, External,
}; };
BAN::Variant<Builtin::BuiltinCommand, BAN::String> command; struct Environment
{
BAN::String name;
BAN::String value;
};
Command command;
BAN::Span<const BAN::String> arguments; BAN::Span<const BAN::String> arguments;
BAN::Span<const Environment> environments;
int fd_in; int fd_in;
int fd_out; int fd_out;
bool background; bool background;

View File

@ -469,6 +469,43 @@ BAN::ErrorOr<SingleCommand> TokenParser::parse_single_command()
{ {
SingleCommand result; SingleCommand result;
while (peek_token().type() == Token::Type::Whitespace)
consume_token();
while (peek_token().type() == Token::Type::String)
{
BAN::String env_name;
const auto& string = peek_token().string();
if (!isalpha(string.front()))
break;
const auto env_len = string.sv().find([](char ch) { return !(isalnum(ch) || ch == '_'); });
if (!env_len.has_value() || string[*env_len] != '=')
break;
TRY(env_name.append(string.sv().substring(0, *env_len)));
auto full_value = TRY(parse_argument());
ASSERT(!full_value.parts.empty());
ASSERT(full_value.parts.front().has<CommandArgument::FixedString>());
auto& first_arg = full_value.parts.front().get<CommandArgument::FixedString>();
ASSERT(first_arg.value.sv().starts_with(env_name));
ASSERT(first_arg.value[*env_len] == '=');
for (size_t i = 0; i < *env_len + 1; i++)
first_arg.value.remove(0);
if (first_arg.value.empty())
full_value.parts.remove(0);
SingleCommand::EnvironmentVariable environment_variable;
environment_variable.name = BAN::move(env_name);
environment_variable.value = BAN::move(full_value);
TRY(result.environment.emplace_back(BAN::move(environment_variable)));
while (peek_token().type() == Token::Type::Whitespace)
consume_token();
}
BAN::HashSet<BAN::String> used_aliases; BAN::HashSet<BAN::String> used_aliases;
while (peek_token().type() == Token::Type::String) while (peek_token().type() == Token::Type::String)
{ {
@ -519,6 +556,9 @@ BAN::ErrorOr<SingleCommand> TokenParser::parse_single_command()
for (size_t i = tokenized_alias.size(); i > 0; i--) for (size_t i = tokenized_alias.size(); i > 0; i--)
TRY(unget_token(BAN::move(tokenized_alias[i - 1]))); TRY(unget_token(BAN::move(tokenized_alias[i - 1])));
TRY(used_aliases.insert(TRY(BAN::String::formatted("{}", token.string())))); TRY(used_aliases.insert(TRY(BAN::String::formatted("{}", token.string()))));
while (peek_token().type() == Token::Type::Whitespace)
consume_token();
} }
while (peek_token().type() != Token::Type::EOF_) while (peek_token().type() != Token::Type::EOF_)