1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 08:18:11 +00:00

Shell: Add support for brace expansions

This adds support for (basic) brace expansions with the following
syntaxes:
- `{expr?,expr?,expr?,...}` which is directly equivalent to `(expr expr
  expr ...)`, with the missing expressions replaced with an empty string
  literal.
- `{expr..expr}` which is a new range expansion, with two modes:
    - if both expressions are one unicode code point long, the range is
      equivalent to the two code points and all code points between the
      two (numerically).
    - if both expressions are numeric, the range is equivalent to both
      numbers, and all numbers between the two.
    - otherwise, it is equivalent to `(expr expr)`.

Closes #3832.
This commit is contained in:
AnotherTest 2020-10-24 18:13:02 +03:30 committed by Andreas Kling
parent 567f2f3548
commit 5640e1bc3a
9 changed files with 374 additions and 3 deletions

View file

@ -25,6 +25,7 @@
*/
#include "Parser.h"
#include <AK/TemporaryChange.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
@ -940,7 +941,13 @@ RefPtr<AST::Node> Parser::parse_expression()
return move(expr);
};
if (strchr("&|){} ;<>\n", starting_char) != nullptr)
if (strchr("&|)} ;<>\n", starting_char) != nullptr)
return nullptr;
if (m_is_in_brace_expansion_spec && starting_char == ',')
return nullptr;
if (m_is_in_brace_expansion_spec && next_is(".."))
return nullptr;
if (isdigit(starting_char)) {
@ -1002,6 +1009,13 @@ RefPtr<AST::Node> Parser::parse_string_composite()
return glob;
}
if (auto expansion = parse_brace_expansion()) {
if (auto next_part = parse_string_composite())
return create<AST::Juxtaposition>(expansion.release_nonnull(), next_part.release_nonnull()); // Concatenate BraceExpansion StringComposite
return expansion;
}
if (auto bareword = parse_bareword()) {
if (auto next_part = parse_string_composite())
return create<AST::Juxtaposition>(bareword.release_nonnull(), next_part.release_nonnull()); // Concatenate Bareword StringComposite
@ -1223,8 +1237,9 @@ RefPtr<AST::Node> Parser::parse_bareword()
{
auto rule_start = push_start();
StringBuilder builder;
auto is_acceptable_bareword_character = [](char c) {
return strchr("\\\"'*$&#|(){} ?;<>\n", c) == nullptr;
auto is_acceptable_bareword_character = [&](char c) {
return strchr("\\\"'*$&#|(){} ?;<>\n", c) == nullptr
&& ((m_is_in_brace_expansion_spec && c != ',') || !m_is_in_brace_expansion_spec);
};
while (!at_end()) {
char ch = peek();
@ -1239,6 +1254,11 @@ RefPtr<AST::Node> Parser::parse_bareword()
continue;
}
if (m_is_in_brace_expansion_spec && next_is("..")) {
// Don't eat '..' in a brace expansion spec.
break;
}
if (is_acceptable_bareword_character(ch)) {
builder.append(consume());
continue;
@ -1343,6 +1363,61 @@ RefPtr<AST::Node> Parser::parse_glob()
return bareword_part;
}
RefPtr<AST::Node> Parser::parse_brace_expansion()
{
auto rule_start = push_start();
if (!expect('{'))
return nullptr;
if (auto spec = parse_brace_expansion_spec()) {
if (!expect('}'))
spec->set_is_syntax_error(create<AST::SyntaxError>("Expected a close brace '}' to end a brace expansion"));
return spec;
}
restore_to(*rule_start);
return nullptr;
}
RefPtr<AST::Node> Parser::parse_brace_expansion_spec()
{
TemporaryChange is_in_brace_expansion { m_is_in_brace_expansion_spec, true };
auto rule_start = push_start();
auto start_expr = parse_expression();
if (start_expr) {
if (expect("..")) {
if (auto end_expr = parse_expression()) {
if (end_expr->position().start_offset != start_expr->position().end_offset + 2)
end_expr->set_is_syntax_error(create<AST::SyntaxError>("Expected no whitespace between '..' and the following expression in brace expansion"));
return create<AST::Range>(start_expr.release_nonnull(), end_expr.release_nonnull());
}
return create<AST::Range>(start_expr.release_nonnull(), create<AST::SyntaxError>("Expected an expression to end range brace expansion with"));
}
}
NonnullRefPtrVector<AST::Node> subexpressions;
if (start_expr)
subexpressions.append(start_expr.release_nonnull());
while (expect(',')) {
auto expr = parse_expression();
if (expr) {
subexpressions.append(expr.release_nonnull());
} else {
subexpressions.append(create<AST::StringLiteral>(""));
}
}
if (subexpressions.is_empty())
return nullptr;
return create<AST::BraceExpansion>(move(subexpressions));
}
StringView Parser::consume_while(Function<bool(char)> condition)
{
auto start_offset = m_offset;
@ -1353,4 +1428,12 @@ StringView Parser::consume_while(Function<bool(char)> condition)
return m_input.substring_view(start_offset, m_offset - start_offset);
}
bool Parser::next_is(const StringView& next)
{
auto start = push_start();
auto res = expect(next);
restore_to(*start);
return res;
}
}