mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 10:28:10 +00:00
Shell: Add support for the bashy list literals in POSIX mode
This is currently only allowed in assignments, where x=(...) is parsed as the assignment of the list (...) to the variable x.
This commit is contained in:
parent
ebe254a6d3
commit
dc495e299e
5 changed files with 98 additions and 4 deletions
|
@ -451,6 +451,7 @@ public:
|
||||||
virtual bool should_override_execution_in_current_process() const { return false; }
|
virtual bool should_override_execution_in_current_process() const { return false; }
|
||||||
|
|
||||||
Position const& position() const { return m_position; }
|
Position const& position() const { return m_position; }
|
||||||
|
Position& position() { return m_position; }
|
||||||
virtual void clear_syntax_error();
|
virtual void clear_syntax_error();
|
||||||
virtual void set_is_syntax_error(SyntaxError& error_node);
|
virtual void set_is_syntax_error(SyntaxError& error_node);
|
||||||
virtual SyntaxError& syntax_error_node()
|
virtual SyntaxError& syntax_error_node()
|
||||||
|
|
|
@ -961,6 +961,8 @@ StringView Token::type_name() const
|
||||||
return "HeredocContents"sv;
|
return "HeredocContents"sv;
|
||||||
case Type::AssignmentWord:
|
case Type::AssignmentWord:
|
||||||
return "AssignmentWord"sv;
|
return "AssignmentWord"sv;
|
||||||
|
case Type::ListAssignmentWord:
|
||||||
|
return "ListAssignmentWord"sv;
|
||||||
case Type::Bang:
|
case Type::Bang:
|
||||||
return "Bang"sv;
|
return "Bang"sv;
|
||||||
case Type::Case:
|
case Type::Case:
|
||||||
|
|
|
@ -242,6 +242,7 @@ struct Token {
|
||||||
|
|
||||||
// Not produced by this lexer, but generated in later stages.
|
// Not produced by this lexer, but generated in later stages.
|
||||||
AssignmentWord,
|
AssignmentWord,
|
||||||
|
ListAssignmentWord,
|
||||||
Bang,
|
Bang,
|
||||||
Case,
|
Case,
|
||||||
CloseBrace,
|
CloseBrace,
|
||||||
|
|
|
@ -141,6 +141,27 @@ ErrorOr<void> Parser::fill_token_buffer(Optional<Reduction> starting_reduction)
|
||||||
}
|
}
|
||||||
m_token_index = 0;
|
m_token_index = 0;
|
||||||
|
|
||||||
|
// Detect Assignment words, bash-like lists extension
|
||||||
|
for (size_t i = 1; i < m_token_buffer.size(); ++i) {
|
||||||
|
// Treat 'ASSIGNMENT_WORD OPEN_PAREN' where ASSIGNMENT_WORD is `word=' and OPEN_PAREN has no preceding trivia as a bash-like list assignment.
|
||||||
|
auto& token = m_token_buffer[i - 1];
|
||||||
|
auto& next_token = m_token_buffer[i];
|
||||||
|
|
||||||
|
if (token.type != Token::Type::AssignmentWord)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!token.value.ends_with('='))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (next_token.type != Token::Type::OpenParen)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (token.position.map([](auto& x) { return x.end_offset + 1; }) != next_token.position.map([](auto& x) { return x.start_offset; }))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
token.type = Token::Type::ListAssignmentWord;
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1437,15 +1458,24 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_for_clause()
|
||||||
Optional<AST::Position> {});
|
Optional<AST::Position> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
RefPtr<AST::Node> Parser::parse_word_list()
|
RefPtr<AST::Node> Parser::parse_word_list(AllowNewlines allow_newlines)
|
||||||
{
|
{
|
||||||
Vector<NonnullRefPtr<AST::Node>> nodes;
|
Vector<NonnullRefPtr<AST::Node>> nodes;
|
||||||
|
|
||||||
auto start_position = peek().position.value_or(empty_position());
|
auto start_position = peek().position.value_or(empty_position());
|
||||||
|
|
||||||
|
if (allow_newlines == AllowNewlines::Yes) {
|
||||||
|
while (peek().type == Token::Type::Newline)
|
||||||
|
skip();
|
||||||
|
}
|
||||||
|
|
||||||
for (; peek().type == Token::Type::Word;) {
|
for (; peek().type == Token::Type::Word;) {
|
||||||
auto word = TRY_OR_THROW_PARSE_ERROR_AT(parse_word(), start_position);
|
auto word = TRY_OR_THROW_PARSE_ERROR_AT(parse_word(), start_position);
|
||||||
nodes.append(word.release_nonnull());
|
nodes.append(word.release_nonnull());
|
||||||
|
if (allow_newlines == AllowNewlines::Yes) {
|
||||||
|
while (peek().type == Token::Type::Newline)
|
||||||
|
skip();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return make_ref_counted<AST::ListConcatenate>(
|
return make_ref_counted<AST::ListConcatenate>(
|
||||||
|
@ -1857,6 +1887,32 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_word()
|
||||||
return word;
|
return word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ErrorOr<RefPtr<AST::Node>> Parser::parse_bash_like_list()
|
||||||
|
{
|
||||||
|
if (peek().type != Token::Type::OpenParen)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto start_position = peek().position.value_or(empty_position());
|
||||||
|
consume();
|
||||||
|
|
||||||
|
auto list = parse_word_list(AllowNewlines::Yes);
|
||||||
|
|
||||||
|
if (peek().type != Token::Type::CloseParen) {
|
||||||
|
return make_ref_counted<AST::SyntaxError>(
|
||||||
|
peek().position.value_or(empty_position()),
|
||||||
|
TRY(String::formatted("Expected ')', not {}", peek().type_name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
consume();
|
||||||
|
|
||||||
|
if (list)
|
||||||
|
list->position() = start_position.with_end(peek().position.value_or(empty_position()));
|
||||||
|
else
|
||||||
|
list = make_ref_counted<AST::ListConcatenate>(start_position.with_end(peek().position.value_or(empty_position())), Vector<NonnullRefPtr<AST::Node>> {});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
ErrorOr<RefPtr<AST::Node>> Parser::parse_do_group()
|
ErrorOr<RefPtr<AST::Node>> Parser::parse_do_group()
|
||||||
{
|
{
|
||||||
if (peek().type != Token::Type::Do) {
|
if (peek().type != Token::Type::Do) {
|
||||||
|
@ -1893,6 +1949,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_simple_command()
|
||||||
auto start_position = peek().position.value_or(empty_position());
|
auto start_position = peek().position.value_or(empty_position());
|
||||||
|
|
||||||
Vector<String> definitions;
|
Vector<String> definitions;
|
||||||
|
HashMap<String, NonnullRefPtr<AST::Node>> list_assignments;
|
||||||
Vector<NonnullRefPtr<AST::Node>> nodes;
|
Vector<NonnullRefPtr<AST::Node>> nodes;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -1902,7 +1959,19 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_simple_command()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (peek().type == Token::Type::AssignmentWord) {
|
while (is_one_of(peek().type, Token::Type::ListAssignmentWord, Token::Type::AssignmentWord)) {
|
||||||
|
if (peek().type == Token::Type::ListAssignmentWord) {
|
||||||
|
auto token = consume();
|
||||||
|
auto value = TRY(parse_bash_like_list());
|
||||||
|
if (!value)
|
||||||
|
return make_ref_counted<AST::SyntaxError>(
|
||||||
|
token.position.value_or(empty_position()),
|
||||||
|
TRY(String::formatted("Expected a list literal after '{}', not {}", token.value, peek().type_name())));
|
||||||
|
|
||||||
|
list_assignments.set(token.value, value.release_nonnull());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
definitions.append(peek().value);
|
definitions.append(peek().value);
|
||||||
|
|
||||||
if (nodes.is_empty()) {
|
if (nodes.is_empty()) {
|
||||||
|
@ -1939,7 +2008,7 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_simple_command()
|
||||||
Token::Type::Word, Token::Type::IoNumber,
|
Token::Type::Word, Token::Type::IoNumber,
|
||||||
Token::Type::Less, Token::Type::LessAnd, Token::Type::Great, Token::Type::GreatAnd,
|
Token::Type::Less, Token::Type::LessAnd, Token::Type::Great, Token::Type::GreatAnd,
|
||||||
Token::Type::DoubleGreat, Token::Type::LessGreat, Token::Type::Clobber)) {
|
Token::Type::DoubleGreat, Token::Type::LessGreat, Token::Type::Clobber)) {
|
||||||
if (!nodes.is_empty()) {
|
if (!definitions.is_empty() || !list_assignments.is_empty()) {
|
||||||
Vector<AST::VariableDeclarations::Variable> variables;
|
Vector<AST::VariableDeclarations::Variable> variables;
|
||||||
for (auto& definition : definitions) {
|
for (auto& definition : definitions) {
|
||||||
auto equal_offset = definition.find_byte_offset('=');
|
auto equal_offset = definition.find_byte_offset('=');
|
||||||
|
@ -1965,12 +2034,27 @@ ErrorOr<RefPtr<AST::Node>> Parser::parse_simple_command()
|
||||||
|
|
||||||
variables.append({ move(name), move(expanded_value) });
|
variables.append({ move(name), move(expanded_value) });
|
||||||
}
|
}
|
||||||
|
for (auto& [key, value] : list_assignments) {
|
||||||
|
auto equal_offset = key.find_byte_offset('=');
|
||||||
|
auto split_offset = equal_offset.value_or(key.bytes().size());
|
||||||
|
auto name = make_ref_counted<AST::BarewordLiteral>(
|
||||||
|
empty_position(),
|
||||||
|
TRY(key.substring_from_byte_offset_with_shared_superstring(0, split_offset)));
|
||||||
|
|
||||||
|
variables.append({ move(name), move(value) });
|
||||||
|
}
|
||||||
|
|
||||||
return make_ref_counted<AST::VariableDeclarations>(empty_position(), move(variables));
|
return make_ref_counted<AST::VariableDeclarations>(empty_position(), move(variables));
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!list_assignments.is_empty()) {
|
||||||
|
return make_ref_counted<AST::SyntaxError>(
|
||||||
|
peek().position.value_or(empty_position()),
|
||||||
|
"List assignments are not allowed as a command prefix"_string);
|
||||||
|
}
|
||||||
|
|
||||||
// auto first = true;
|
// auto first = true;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (peek().type == Token::Type::Word) {
|
if (peek().type == Token::Type::Word) {
|
||||||
|
|
|
@ -21,8 +21,13 @@ public:
|
||||||
(void)fill_token_buffer(starting_reduction);
|
(void)fill_token_buffer(starting_reduction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class AllowNewlines {
|
||||||
|
No,
|
||||||
|
Yes,
|
||||||
|
};
|
||||||
|
|
||||||
RefPtr<AST::Node> parse();
|
RefPtr<AST::Node> parse();
|
||||||
RefPtr<AST::Node> parse_word_list();
|
RefPtr<AST::Node> parse_word_list(AllowNewlines = AllowNewlines::No);
|
||||||
|
|
||||||
struct Error {
|
struct Error {
|
||||||
DeprecatedString message;
|
DeprecatedString message;
|
||||||
|
@ -94,6 +99,7 @@ private:
|
||||||
ErrorOr<RefPtr<AST::Node>> parse_io_file(AST::Position, Optional<int> fd);
|
ErrorOr<RefPtr<AST::Node>> parse_io_file(AST::Position, Optional<int> fd);
|
||||||
ErrorOr<RefPtr<AST::Node>> parse_io_here(AST::Position, Optional<int> fd);
|
ErrorOr<RefPtr<AST::Node>> parse_io_here(AST::Position, Optional<int> fd);
|
||||||
ErrorOr<RefPtr<AST::Node>> parse_word();
|
ErrorOr<RefPtr<AST::Node>> parse_word();
|
||||||
|
ErrorOr<RefPtr<AST::Node>> parse_bash_like_list();
|
||||||
ErrorOr<CaseItemsResult> parse_case_list();
|
ErrorOr<CaseItemsResult> parse_case_list();
|
||||||
|
|
||||||
template<typename... Ts>
|
template<typename... Ts>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue