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

Shell: Allow parts of globs to be named in match expressions

This patchset allows a match expression to have a list of names for its
glob parts, which are assigned to the matched values in the body of the
match.
For example,
```sh
stuff=foobarblahblah/target_{1..30}
for $stuff {
    match $it {
        */* as (dir sub) {
            echo "doing things with $sub in $dir"
            make -C $dir $sub # or whatever...
        }
    }
}
```

With this, match expressions are now significantly more powerful!
This commit is contained in:
AnotherTest 2020-10-25 10:42:03 +03:30 committed by Andreas Kling
parent 0801b1fada
commit 1a4ac3531f
6 changed files with 115 additions and 10 deletions

View file

@ -1520,7 +1520,23 @@ void MatchExpr::dump(int level) const
print_indented(String::format("(named: %s)", m_expr_name.characters()), level + 1);
print_indented("(entries)", level + 1);
for (auto& entry : m_entries) {
print_indented("(match)", level + 2);
StringBuilder builder;
builder.append("(match");
if (entry.match_names.has_value()) {
builder.append(" to names (");
bool first = true;
for (auto& name : entry.match_names.value()) {
if (!first)
builder.append(' ');
first = false;
builder.append(name);
}
builder.append("))");
} else {
builder.append(')');
}
print_indented(builder.string_view(), level + 2);
for (auto& node : entry.options)
node.dump(level + 3);
print_indented("(execute)", level + 2);
@ -1536,13 +1552,16 @@ RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell)
auto value = m_matched_expr->run(shell)->resolve_without_cast(shell);
auto list = value->resolve_as_list(shell);
auto list_matches = [&](auto&& pattern) {
auto list_matches = [&](auto&& pattern, auto& spans) {
if (pattern.size() != list.size())
return false;
for (size_t i = 0; i < pattern.size(); ++i) {
if (!list[i].matches(pattern[i]))
Vector<AK::MaskSpan> mask_spans;
if (!list[i].matches(pattern[i], mask_spans))
return false;
for (auto& span : mask_spans)
spans.append(list[i].substring(span.start, span.length));
}
return true;
@ -1554,7 +1573,7 @@ RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell)
pattern.append(static_cast<const Glob*>(&option)->text());
} else if (option.is_bareword()) {
pattern.append(static_cast<const BarewordLiteral*>(&option)->text());
} else if (option.is_list()) {
} else {
auto list = option.run(shell);
option.for_each_entry(shell, [&](auto&& value) {
pattern.append(value->resolve_as_list(nullptr)); // Note: 'nullptr' incurs special behaviour,
@ -1572,11 +1591,21 @@ RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell)
for (auto& entry : m_entries) {
for (auto& option : entry.options) {
if (list_matches(resolve_pattern(option))) {
if (entry.body)
Vector<String> spans;
if (list_matches(resolve_pattern(option), spans)) {
if (entry.body) {
if (entry.match_names.has_value()) {
size_t i = 0;
for (auto& name : entry.match_names.value()) {
if (spans.size() > i)
shell->set_local_variable(name, create<AST::StringValue>(spans[i]));
++i;
}
}
return entry.body->run(shell);
else
} else {
return create<AST::ListValue>({});
}
}
}
}
@ -1606,6 +1635,9 @@ void MatchExpr::highlight_in_editor(Line::Editor& editor, Shell& shell, Highligh
for (auto& position : entry.pipe_positions)
editor.stylize({ position.start_offset, position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
if (entry.match_as_position.has_value())
editor.stylize({ entry.match_as_position.value().start_offset, entry.match_as_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
}
}