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:
parent
567f2f3548
commit
5640e1bc3a
Notes:
sideshowbarker
2024-07-19 01:46:16 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/5640e1bc3a2 Pull-request: https://github.com/SerenityOS/serenity/pull/3835 Issue: https://github.com/SerenityOS/serenity/issues/3832 Reviewed-by: https://github.com/bugaevc Reviewed-by: https://github.com/linusg
9 changed files with 374 additions and 3 deletions
181
Shell/AST.cpp
181
Shell/AST.cpp
|
@ -446,6 +446,66 @@ BarewordLiteral::~BarewordLiteral()
|
|||
{
|
||||
}
|
||||
|
||||
void BraceExpansion::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
for (auto& entry : m_entries)
|
||||
entry.dump(level + 1);
|
||||
}
|
||||
|
||||
RefPtr<Value> BraceExpansion::run(RefPtr<Shell> shell)
|
||||
{
|
||||
NonnullRefPtrVector<Value> values;
|
||||
for (auto& entry : m_entries) {
|
||||
auto value = entry.run(shell);
|
||||
if (value)
|
||||
values.append(value.release_nonnull());
|
||||
}
|
||||
|
||||
return create<ListValue>(move(values));
|
||||
}
|
||||
|
||||
HitTestResult BraceExpansion::hit_test_position(size_t offset)
|
||||
{
|
||||
if (!position().contains(offset))
|
||||
return {};
|
||||
|
||||
for (auto& entry : m_entries) {
|
||||
auto result = entry.hit_test_position(offset);
|
||||
if (result.matching_node) {
|
||||
if (!result.closest_command_node)
|
||||
result.closest_command_node = &entry;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void BraceExpansion::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
||||
{
|
||||
for (auto& entry : m_entries) {
|
||||
entry.highlight_in_editor(editor, shell, metadata);
|
||||
metadata.is_first_in_list = false;
|
||||
}
|
||||
}
|
||||
|
||||
BraceExpansion::BraceExpansion(Position position, NonnullRefPtrVector<Node> entries)
|
||||
: Node(move(position))
|
||||
, m_entries(move(entries))
|
||||
{
|
||||
for (auto& entry : m_entries) {
|
||||
if (entry.is_syntax_error()) {
|
||||
set_is_syntax_error(entry.syntax_error_node());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BraceExpansion::~BraceExpansion()
|
||||
{
|
||||
}
|
||||
|
||||
void CastToCommand::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
|
@ -1700,6 +1760,124 @@ PathRedirectionNode::~PathRedirectionNode()
|
|||
{
|
||||
}
|
||||
|
||||
void Range::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
print_indented("(From)", level + 1);
|
||||
m_start->dump(level + 2);
|
||||
print_indented("(To)", level + 1);
|
||||
m_end->dump(level + 2);
|
||||
}
|
||||
|
||||
RefPtr<Value> Range::run(RefPtr<Shell> shell)
|
||||
{
|
||||
constexpr static auto interpolate = [](RefPtr<Value> start, RefPtr<Value> end, RefPtr<Shell> shell) -> NonnullRefPtrVector<Value> {
|
||||
NonnullRefPtrVector<Value> values;
|
||||
|
||||
if (start->is_string() && end->is_string()) {
|
||||
auto start_str = start->resolve_as_list(shell)[0];
|
||||
auto end_str = end->resolve_as_list(shell)[0];
|
||||
|
||||
Utf8View start_view { start_str }, end_view { end_str };
|
||||
if (start_view.validate() && end_view.validate()) {
|
||||
if (start_view.length() == 1 && end_view.length() == 1) {
|
||||
// Interpolate between two code points.
|
||||
auto start_code_point = *start_view.begin();
|
||||
auto end_code_point = *end_view.begin();
|
||||
auto step = start_code_point > end_code_point ? -1 : 1;
|
||||
StringBuilder builder;
|
||||
for (u32 code_point = start_code_point; code_point != end_code_point; code_point += step) {
|
||||
builder.clear();
|
||||
builder.append_code_point(code_point);
|
||||
values.append(create<StringValue>(builder.to_string()));
|
||||
}
|
||||
// Append the ending code point too, most shells treat this as inclusive.
|
||||
builder.clear();
|
||||
builder.append_code_point(end_code_point);
|
||||
values.append(create<StringValue>(builder.to_string()));
|
||||
} else {
|
||||
// Could be two numbers?
|
||||
auto start_int = start_str.to_int();
|
||||
auto end_int = end_str.to_int();
|
||||
if (start_int.has_value() && end_int.has_value()) {
|
||||
auto start = start_int.value();
|
||||
auto end = end_int.value();
|
||||
auto step = start > end ? 1 : -1;
|
||||
for (int value = start; value != end; value += step)
|
||||
values.append(create<StringValue>(String::number(value)));
|
||||
// Append the range end too, most shells treat this as inclusive.
|
||||
values.append(create<StringValue>(String::number(end)));
|
||||
} else {
|
||||
goto yield_start_end;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
yield_start_end:;
|
||||
warnln("Shell: Cannot interpolate between '{}' and '{}'!", start_str, end_str);
|
||||
// We can't really interpolate between the two, so just yield both.
|
||||
values.append(create<StringValue>(move(start_str)));
|
||||
values.append(create<StringValue>(move(end_str)));
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
warnln("Shell: Cannot apply the requested interpolation");
|
||||
return values;
|
||||
};
|
||||
|
||||
auto start_value = m_start->run(shell);
|
||||
auto end_value = m_end->run(shell);
|
||||
if (!start_value || !end_value)
|
||||
return create<ListValue>({});
|
||||
|
||||
return create<ListValue>(interpolate(*start_value, *end_value, shell));
|
||||
}
|
||||
|
||||
void Range::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
||||
{
|
||||
m_start->highlight_in_editor(editor, shell, metadata);
|
||||
|
||||
// Highlight the '..'
|
||||
editor.stylize({ m_start->position().end_offset, m_end->position().start_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
|
||||
|
||||
metadata.is_first_in_list = false;
|
||||
m_end->highlight_in_editor(editor, shell, metadata);
|
||||
}
|
||||
|
||||
HitTestResult Range::hit_test_position(size_t offset)
|
||||
{
|
||||
if (!position().contains(offset))
|
||||
return {};
|
||||
|
||||
auto result = m_start->hit_test_position(offset);
|
||||
if (result.matching_node) {
|
||||
if (!result.closest_command_node)
|
||||
result.closest_command_node = m_start;
|
||||
return result;
|
||||
}
|
||||
|
||||
result = m_end->hit_test_position(offset);
|
||||
if (!result.closest_command_node)
|
||||
result.closest_command_node = m_end;
|
||||
return result;
|
||||
}
|
||||
|
||||
Range::Range(Position position, NonnullRefPtr<Node> start, NonnullRefPtr<Node> end)
|
||||
: Node(move(position))
|
||||
, m_start(move(start))
|
||||
, m_end(move(end))
|
||||
{
|
||||
if (m_start->is_syntax_error())
|
||||
set_is_syntax_error(m_start->syntax_error_node());
|
||||
else if (m_end->is_syntax_error())
|
||||
set_is_syntax_error(m_end->syntax_error_node());
|
||||
}
|
||||
|
||||
Range::~Range()
|
||||
{
|
||||
}
|
||||
|
||||
void ReadRedirection::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
|
@ -2116,6 +2294,9 @@ RefPtr<Value> StringLiteral::run(RefPtr<Shell>)
|
|||
|
||||
void StringLiteral::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata)
|
||||
{
|
||||
if (m_text.is_empty())
|
||||
return;
|
||||
|
||||
Line::Style style { Line::Style::Foreground(Line::Style::XtermColor::Yellow) };
|
||||
if (metadata.is_first_in_list)
|
||||
style.unify_with({ Line::Style::Bold });
|
||||
|
|
44
Shell/AST.h
44
Shell/AST.h
|
@ -297,6 +297,10 @@ public:
|
|||
: m_contained_values(move(static_cast<NonnullRefPtrVector<Value>&>(values)))
|
||||
{
|
||||
}
|
||||
ListValue(NonnullRefPtrVector<Value> values)
|
||||
: m_contained_values(move(values))
|
||||
{
|
||||
}
|
||||
|
||||
const NonnullRefPtrVector<Value>& values() const { return m_contained_values; }
|
||||
NonnullRefPtrVector<Value>& values() { return m_contained_values; }
|
||||
|
@ -433,6 +437,7 @@ public:
|
|||
ListConcatenate,
|
||||
Background,
|
||||
BarewordLiteral,
|
||||
BraceExpansion,
|
||||
CastToCommand,
|
||||
CastToList,
|
||||
CloseFdRedirection,
|
||||
|
@ -450,6 +455,7 @@ public:
|
|||
MatchExpr,
|
||||
Or,
|
||||
Pipe,
|
||||
Range,
|
||||
ReadRedirection,
|
||||
ReadWriteRedirection,
|
||||
Sequence,
|
||||
|
@ -576,6 +582,24 @@ private:
|
|||
String m_text;
|
||||
};
|
||||
|
||||
class BraceExpansion final : public Node {
|
||||
public:
|
||||
BraceExpansion(Position, NonnullRefPtrVector<Node>);
|
||||
virtual ~BraceExpansion();
|
||||
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
||||
|
||||
const NonnullRefPtrVector<Node>& entries() const { return m_entries; }
|
||||
|
||||
private:
|
||||
NODE(BraceExpansion);
|
||||
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;
|
||||
|
||||
NonnullRefPtrVector<Node> m_entries;
|
||||
};
|
||||
|
||||
class CastToCommand final : public Node {
|
||||
public:
|
||||
CastToCommand(Position, NonnullRefPtr<Node>);
|
||||
|
@ -958,6 +982,26 @@ private:
|
|||
NonnullRefPtr<Node> m_right;
|
||||
};
|
||||
|
||||
class Range final : public Node {
|
||||
public:
|
||||
Range(Position, NonnullRefPtr<Node>, NonnullRefPtr<Node>);
|
||||
virtual ~Range();
|
||||
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
||||
|
||||
const NonnullRefPtr<Node>& start() const { return m_start; }
|
||||
const NonnullRefPtr<Node>& end() const { return m_end; }
|
||||
|
||||
private:
|
||||
NODE(Range);
|
||||
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;
|
||||
|
||||
NonnullRefPtr<Node> m_start;
|
||||
NonnullRefPtr<Node> m_end;
|
||||
};
|
||||
|
||||
class ReadRedirection final : public PathRedirectionNode {
|
||||
public:
|
||||
ReadRedirection(Position, int, NonnullRefPtr<Node>);
|
||||
|
|
|
@ -190,6 +190,25 @@ void Formatter::visit(const AST::BarewordLiteral* node)
|
|||
visited(node);
|
||||
}
|
||||
|
||||
void Formatter::visit(const AST::BraceExpansion* node)
|
||||
{
|
||||
will_visit(node);
|
||||
test_and_update_output_cursor(node);
|
||||
current_builder().append('{');
|
||||
|
||||
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
|
||||
bool first = true;
|
||||
for (auto& entry : node->entries()) {
|
||||
if (!first)
|
||||
current_builder().append(',');
|
||||
first = false;
|
||||
entry.visit(*this);
|
||||
}
|
||||
|
||||
current_builder().append('}');
|
||||
visited(node);
|
||||
}
|
||||
|
||||
void Formatter::visit(const AST::CastToCommand* node)
|
||||
{
|
||||
will_visit(node);
|
||||
|
@ -480,6 +499,21 @@ void Formatter::visit(const AST::Pipe* node)
|
|||
visited(node);
|
||||
}
|
||||
|
||||
void Formatter::visit(const AST::Range* node)
|
||||
{
|
||||
will_visit(node);
|
||||
test_and_update_output_cursor(node);
|
||||
current_builder().append('{');
|
||||
|
||||
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
|
||||
node->start()->visit(*this);
|
||||
current_builder().append("..");
|
||||
node->end()->visit(*this);
|
||||
|
||||
current_builder().append('}');
|
||||
visited(node);
|
||||
}
|
||||
|
||||
void Formatter::visit(const AST::ReadRedirection* node)
|
||||
{
|
||||
will_visit(node);
|
||||
|
|
|
@ -59,6 +59,7 @@ private:
|
|||
virtual void visit(const AST::ListConcatenate*) override;
|
||||
virtual void visit(const AST::Background*) override;
|
||||
virtual void visit(const AST::BarewordLiteral*) override;
|
||||
virtual void visit(const AST::BraceExpansion*) override;
|
||||
virtual void visit(const AST::CastToCommand*) override;
|
||||
virtual void visit(const AST::CastToList*) override;
|
||||
virtual void visit(const AST::CloseFdRedirection*) override;
|
||||
|
@ -76,6 +77,7 @@ private:
|
|||
virtual void visit(const AST::MatchExpr*) override;
|
||||
virtual void visit(const AST::Or*) override;
|
||||
virtual void visit(const AST::Pipe*) override;
|
||||
virtual void visit(const AST::Range*) override;
|
||||
virtual void visit(const AST::ReadRedirection*) override;
|
||||
virtual void visit(const AST::ReadWriteRedirection*) override;
|
||||
virtual void visit(const AST::Sequence*) override;
|
||||
|
|
|
@ -41,6 +41,7 @@ class And;
|
|||
class ListConcatenate;
|
||||
class Background;
|
||||
class BarewordLiteral;
|
||||
class BraceExpansion;
|
||||
class CastToCommand;
|
||||
class CastToList;
|
||||
class CloseFdRedirection;
|
||||
|
@ -58,6 +59,7 @@ class Join;
|
|||
class MatchExpr;
|
||||
class Or;
|
||||
class Pipe;
|
||||
class Range;
|
||||
class ReadRedirection;
|
||||
class ReadWriteRedirection;
|
||||
class Sequence;
|
||||
|
|
|
@ -55,6 +55,12 @@ void NodeVisitor::visit(const AST::BarewordLiteral*)
|
|||
{
|
||||
}
|
||||
|
||||
void NodeVisitor::visit(const AST::BraceExpansion* node)
|
||||
{
|
||||
for (auto& entry : node->entries())
|
||||
entry.visit(*this);
|
||||
}
|
||||
|
||||
void NodeVisitor::visit(const AST::CastToCommand* node)
|
||||
{
|
||||
node->inner()->visit(*this);
|
||||
|
@ -153,6 +159,12 @@ void NodeVisitor::visit(const AST::Pipe* node)
|
|||
node->right()->visit(*this);
|
||||
}
|
||||
|
||||
void NodeVisitor::visit(const AST::Range* node)
|
||||
{
|
||||
node->start()->visit(*this);
|
||||
node->end()->visit(*this);
|
||||
}
|
||||
|
||||
void NodeVisitor::visit(const AST::ReadRedirection* node)
|
||||
{
|
||||
visit(static_cast<const AST::PathRedirectionNode*>(node));
|
||||
|
|
|
@ -37,6 +37,7 @@ public:
|
|||
virtual void visit(const AST::ListConcatenate*);
|
||||
virtual void visit(const AST::Background*);
|
||||
virtual void visit(const AST::BarewordLiteral*);
|
||||
virtual void visit(const AST::BraceExpansion*);
|
||||
virtual void visit(const AST::CastToCommand*);
|
||||
virtual void visit(const AST::CastToList*);
|
||||
virtual void visit(const AST::CloseFdRedirection*);
|
||||
|
@ -54,6 +55,7 @@ public:
|
|||
virtual void visit(const AST::MatchExpr*);
|
||||
virtual void visit(const AST::Or*);
|
||||
virtual void visit(const AST::Pipe*);
|
||||
virtual void visit(const AST::Range*);
|
||||
virtual void visit(const AST::ReadRedirection*);
|
||||
virtual void visit(const AST::ReadWriteRedirection*);
|
||||
virtual void visit(const AST::Sequence*);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -77,6 +77,8 @@ private:
|
|||
RefPtr<AST::Node> parse_comment();
|
||||
RefPtr<AST::Node> parse_bareword();
|
||||
RefPtr<AST::Node> parse_glob();
|
||||
RefPtr<AST::Node> parse_brace_expansion();
|
||||
RefPtr<AST::Node> parse_brace_expansion_spec();
|
||||
|
||||
template<typename A, typename... Args>
|
||||
NonnullRefPtr<A> create(Args... args);
|
||||
|
@ -86,6 +88,7 @@ private:
|
|||
char consume();
|
||||
bool expect(char);
|
||||
bool expect(const StringView&);
|
||||
bool next_is(const StringView&);
|
||||
|
||||
void restore_to(size_t offset, AST::Position::Line line)
|
||||
{
|
||||
|
@ -133,6 +136,8 @@ private:
|
|||
|
||||
Vector<size_t> m_rule_start_offsets;
|
||||
Vector<AST::Position::Line> m_rule_start_lines;
|
||||
|
||||
bool m_is_in_brace_expansion_spec { false };
|
||||
};
|
||||
|
||||
#if 0
|
||||
|
@ -206,6 +211,7 @@ string_composite :: string string_composite?
|
|||
| variable string_composite?
|
||||
| bareword string_composite?
|
||||
| glob string_composite?
|
||||
| brace_expansion string_composite?
|
||||
|
||||
string :: '"' dquoted_string_inner '"'
|
||||
| "'" [^']* "'"
|
||||
|
@ -232,6 +238,11 @@ bareword_with_tilde_expansion :: '~' bareword?
|
|||
|
||||
glob :: [*?] bareword?
|
||||
| bareword [*?]
|
||||
|
||||
brace_expansion :: '{' brace_expansion_spec '}'
|
||||
|
||||
brace_expansion_spec :: expression? (',' expression?)*
|
||||
| expression '..' expression
|
||||
)";
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue