|
@@ -173,6 +173,101 @@ static inline Vector<Command> join_commands(Vector<Command> left, Vector<Command
|
|
|
return commands;
|
|
|
}
|
|
|
|
|
|
+static String resolve_slices(RefPtr<Shell> shell, String&& input_value, NonnullRefPtrVector<Slice> slices)
|
|
|
+{
|
|
|
+ if (slices.is_empty())
|
|
|
+ return move(input_value);
|
|
|
+
|
|
|
+ for (auto& slice : slices) {
|
|
|
+ auto value = slice.run(shell);
|
|
|
+ if (!value) {
|
|
|
+ shell->raise_error(Shell::ShellError::InvalidSliceContentsError, "Invalid slice contents", slice.position());
|
|
|
+ return move(input_value);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto index_values = value->resolve_as_list(shell);
|
|
|
+ Vector<size_t> indices;
|
|
|
+ indices.ensure_capacity(index_values.size());
|
|
|
+
|
|
|
+ size_t i = 0;
|
|
|
+ for (auto& value : index_values) {
|
|
|
+ auto maybe_index = value.to_int();
|
|
|
+ if (!maybe_index.has_value()) {
|
|
|
+ shell->raise_error(Shell::ShellError::InvalidSliceContentsError, String::formatted("Invalid value in slice index {}: {} (expected a number)", i, value), slice.position());
|
|
|
+ return move(input_value);
|
|
|
+ }
|
|
|
+ ++i;
|
|
|
+
|
|
|
+ auto index = maybe_index.value();
|
|
|
+ auto original_index = index;
|
|
|
+ if (index < 0)
|
|
|
+ index += input_value.length();
|
|
|
+
|
|
|
+ if (index < 0 || (size_t)index >= input_value.length()) {
|
|
|
+ shell->raise_error(Shell::ShellError::InvalidSliceContentsError, String::formatted("Slice index {} (evaluated as {}) out of value bounds [0-{})", index, original_index, input_value.length()), slice.position());
|
|
|
+ return move(input_value);
|
|
|
+ }
|
|
|
+ indices.unchecked_append(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ StringBuilder builder { indices.size() };
|
|
|
+ for (auto& index : indices)
|
|
|
+ builder.append(input_value[index]);
|
|
|
+
|
|
|
+ input_value = builder.build();
|
|
|
+ }
|
|
|
+
|
|
|
+ return move(input_value);
|
|
|
+}
|
|
|
+
|
|
|
+static Vector<String> resolve_slices(RefPtr<Shell> shell, Vector<String>&& values, NonnullRefPtrVector<Slice> slices)
|
|
|
+{
|
|
|
+ if (slices.is_empty())
|
|
|
+ return move(values);
|
|
|
+
|
|
|
+ for (auto& slice : slices) {
|
|
|
+ auto value = slice.run(shell);
|
|
|
+ if (!value) {
|
|
|
+ shell->raise_error(Shell::ShellError::InvalidSliceContentsError, "Invalid slice contents", slice.position());
|
|
|
+ return move(values);
|
|
|
+ }
|
|
|
+
|
|
|
+ auto index_values = value->resolve_as_list(shell);
|
|
|
+ Vector<size_t> indices;
|
|
|
+ indices.ensure_capacity(index_values.size());
|
|
|
+
|
|
|
+ size_t i = 0;
|
|
|
+ for (auto& value : index_values) {
|
|
|
+ auto maybe_index = value.to_int();
|
|
|
+ if (!maybe_index.has_value()) {
|
|
|
+ shell->raise_error(Shell::ShellError::InvalidSliceContentsError, String::formatted("Invalid value in slice index {}: {} (expected a number)", i, value), slice.position());
|
|
|
+ return move(values);
|
|
|
+ }
|
|
|
+ ++i;
|
|
|
+
|
|
|
+ auto index = maybe_index.value();
|
|
|
+ auto original_index = index;
|
|
|
+ if (index < 0)
|
|
|
+ index += values.size();
|
|
|
+
|
|
|
+ if (index < 0 || (size_t)index >= values.size()) {
|
|
|
+ shell->raise_error(Shell::ShellError::InvalidSliceContentsError, String::formatted("Slice index {} (evaluated as {}) out of value bounds [0-{})", index, original_index, values.size()), slice.position());
|
|
|
+ return move(values);
|
|
|
+ }
|
|
|
+ indices.unchecked_append(index);
|
|
|
+ }
|
|
|
+
|
|
|
+ Vector<String> result;
|
|
|
+ result.ensure_capacity(indices.size());
|
|
|
+ for (auto& index : indices)
|
|
|
+ result.unchecked_append(values[index]);
|
|
|
+
|
|
|
+ values = move(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ return move(values);
|
|
|
+}
|
|
|
+
|
|
|
void Node::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(NonnullRefPtr<Value>)> callback)
|
|
|
{
|
|
|
auto value = run(shell)->resolve_without_cast(shell);
|
|
@@ -2529,23 +2624,73 @@ Subshell::~Subshell()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
+void Slice::dump(int level) const
|
|
|
+{
|
|
|
+ Node::dump(level);
|
|
|
+ m_selector->dump(level + 1);
|
|
|
+}
|
|
|
+
|
|
|
+RefPtr<Value> Slice::run(RefPtr<Shell> shell)
|
|
|
+{
|
|
|
+ return m_selector->run(shell);
|
|
|
+}
|
|
|
+
|
|
|
+void Slice::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
|
|
+{
|
|
|
+ m_selector->highlight_in_editor(editor, shell, metadata);
|
|
|
+}
|
|
|
+
|
|
|
+HitTestResult Slice::hit_test_position(size_t offset) const
|
|
|
+{
|
|
|
+ return m_selector->hit_test_position(offset);
|
|
|
+}
|
|
|
+
|
|
|
+Vector<Line::CompletionSuggestion> Slice::complete_for_editor(Shell& shell, size_t offset, const HitTestResult& hit_test_result)
|
|
|
+{
|
|
|
+ // TODO: Maybe intercept this, and suggest values in range?
|
|
|
+ return m_selector->complete_for_editor(shell, offset, hit_test_result);
|
|
|
+}
|
|
|
+
|
|
|
+Slice::Slice(Position position, NonnullRefPtr<AST::Node> selector)
|
|
|
+ : Node(move(position))
|
|
|
+ , m_selector(move(selector))
|
|
|
+{
|
|
|
+ if (m_selector->is_syntax_error())
|
|
|
+ set_is_syntax_error(m_selector->syntax_error_node());
|
|
|
+}
|
|
|
+
|
|
|
+Slice::~Slice()
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
void SimpleVariable::dump(int level) const
|
|
|
{
|
|
|
Node::dump(level);
|
|
|
- print_indented(m_name, level + 1);
|
|
|
+ print_indented("(Name)", level + 1);
|
|
|
+ print_indented(m_name, level + 2);
|
|
|
+ print_indented("(Slice)", level + 1);
|
|
|
+ if (m_slice)
|
|
|
+ m_slice->dump(level + 2);
|
|
|
+ else
|
|
|
+ print_indented("(None)", level + 2);
|
|
|
}
|
|
|
|
|
|
RefPtr<Value> SimpleVariable::run(RefPtr<Shell>)
|
|
|
{
|
|
|
- return create<SimpleVariableValue>(m_name);
|
|
|
+ NonnullRefPtr<Value> value = create<SimpleVariableValue>(m_name);
|
|
|
+ if (m_slice)
|
|
|
+ value = value->with_slices(*m_slice);
|
|
|
+ return value;
|
|
|
}
|
|
|
|
|
|
-void SimpleVariable::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata metadata)
|
|
|
+void SimpleVariable::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
|
|
{
|
|
|
Line::Style style { Line::Style::Foreground(214, 112, 214) };
|
|
|
if (metadata.is_first_in_list)
|
|
|
style.unify_with({ Line::Style::Bold });
|
|
|
editor.stylize({ m_position.start_offset, m_position.end_offset }, move(style));
|
|
|
+ if (m_slice)
|
|
|
+ m_slice->highlight_in_editor(editor, shell, metadata);
|
|
|
}
|
|
|
|
|
|
HitTestResult SimpleVariable::hit_test_position(size_t offset) const
|
|
@@ -2553,6 +2698,9 @@ HitTestResult SimpleVariable::hit_test_position(size_t offset) const
|
|
|
if (!position().contains(offset))
|
|
|
return {};
|
|
|
|
|
|
+ if (m_slice && m_slice->position().contains(offset))
|
|
|
+ return m_slice->hit_test_position(offset);
|
|
|
+
|
|
|
return { this, this, nullptr };
|
|
|
}
|
|
|
|
|
@@ -2574,7 +2722,7 @@ Vector<Line::CompletionSuggestion> SimpleVariable::complete_for_editor(Shell& sh
|
|
|
}
|
|
|
|
|
|
SimpleVariable::SimpleVariable(Position position, String name)
|
|
|
- : Node(move(position))
|
|
|
+ : VariableNode(move(position))
|
|
|
, m_name(move(name))
|
|
|
{
|
|
|
}
|
|
@@ -2586,17 +2734,28 @@ SimpleVariable::~SimpleVariable()
|
|
|
void SpecialVariable::dump(int level) const
|
|
|
{
|
|
|
Node::dump(level);
|
|
|
+ print_indented("(Name)", level + 1);
|
|
|
print_indented(String { &m_name, 1 }, level + 1);
|
|
|
+ print_indented("(Slice)", level + 1);
|
|
|
+ if (m_slice)
|
|
|
+ m_slice->dump(level + 2);
|
|
|
+ else
|
|
|
+ print_indented("(None)", level + 2);
|
|
|
}
|
|
|
|
|
|
RefPtr<Value> SpecialVariable::run(RefPtr<Shell>)
|
|
|
{
|
|
|
- return create<SpecialVariableValue>(m_name);
|
|
|
+ NonnullRefPtr<Value> value = create<SpecialVariableValue>(m_name);
|
|
|
+ if (m_slice)
|
|
|
+ value = value->with_slices(*m_slice);
|
|
|
+ return value;
|
|
|
}
|
|
|
|
|
|
-void SpecialVariable::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
|
|
|
+void SpecialVariable::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
|
|
|
{
|
|
|
editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(214, 112, 214) });
|
|
|
+ if (m_slice)
|
|
|
+ m_slice->highlight_in_editor(editor, shell, metadata);
|
|
|
}
|
|
|
|
|
|
Vector<Line::CompletionSuggestion> SpecialVariable::complete_for_editor(Shell&, size_t, const HitTestResult&)
|
|
@@ -2609,11 +2768,14 @@ HitTestResult SpecialVariable::hit_test_position(size_t offset) const
|
|
|
if (!position().contains(offset))
|
|
|
return {};
|
|
|
|
|
|
+ if (m_slice && m_slice->position().contains(offset))
|
|
|
+ return m_slice->hit_test_position(offset);
|
|
|
+
|
|
|
return { this, this, nullptr };
|
|
|
}
|
|
|
|
|
|
SpecialVariable::SpecialVariable(Position position, char name)
|
|
|
- : Node(move(position))
|
|
|
+ : VariableNode(move(position))
|
|
|
, m_name(name)
|
|
|
{
|
|
|
}
|
|
@@ -3091,6 +3253,20 @@ ListValue::ListValue(Vector<String> values)
|
|
|
m_contained_values.append(adopt(*new StringValue(move(str))));
|
|
|
}
|
|
|
|
|
|
+NonnullRefPtr<Value> Value::with_slices(NonnullRefPtr<Slice> slice) const&
|
|
|
+{
|
|
|
+ auto value = clone();
|
|
|
+ value->m_slices.append(move(slice));
|
|
|
+ return value;
|
|
|
+}
|
|
|
+
|
|
|
+NonnullRefPtr<Value> Value::with_slices(NonnullRefPtrVector<Slice> slices) const&
|
|
|
+{
|
|
|
+ auto value = clone();
|
|
|
+ value->m_slices.append(move(slices));
|
|
|
+ return value;
|
|
|
+}
|
|
|
+
|
|
|
ListValue::~ListValue()
|
|
|
{
|
|
|
}
|
|
@@ -3101,7 +3277,7 @@ Vector<String> ListValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
for (auto& value : m_contained_values)
|
|
|
values.append(value.resolve_as_list(shell));
|
|
|
|
|
|
- return values;
|
|
|
+ return resolve_slices(shell, move(values), m_slices);
|
|
|
}
|
|
|
|
|
|
NonnullRefPtr<Value> ListValue::resolve_without_cast(RefPtr<Shell> shell)
|
|
@@ -3110,7 +3286,10 @@ NonnullRefPtr<Value> ListValue::resolve_without_cast(RefPtr<Shell> shell)
|
|
|
for (auto& value : m_contained_values)
|
|
|
values.append(value.resolve_without_cast(shell));
|
|
|
|
|
|
- return create<ListValue>(move(values));
|
|
|
+ NonnullRefPtr<Value> value = create<ListValue>(move(values));
|
|
|
+ if (!m_slices.is_empty())
|
|
|
+ value = value->with_slices(m_slices);
|
|
|
+ return value;
|
|
|
}
|
|
|
|
|
|
CommandValue::~CommandValue()
|
|
@@ -3121,9 +3300,9 @@ CommandSequenceValue::~CommandSequenceValue()
|
|
|
{
|
|
|
}
|
|
|
|
|
|
-Vector<String> CommandSequenceValue::resolve_as_list(RefPtr<Shell>)
|
|
|
+Vector<String> CommandSequenceValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
{
|
|
|
- // TODO: Somehow raise an "error".
|
|
|
+ shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Unexpected cast of a command sequence to a list");
|
|
|
return {};
|
|
|
}
|
|
|
|
|
@@ -3132,9 +3311,9 @@ Vector<Command> CommandSequenceValue::resolve_as_commands(RefPtr<Shell>)
|
|
|
return m_contained_values;
|
|
|
}
|
|
|
|
|
|
-Vector<String> CommandValue::resolve_as_list(RefPtr<Shell>)
|
|
|
+Vector<String> CommandValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
{
|
|
|
- // TODO: Somehow raise an "error".
|
|
|
+ shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Unexpected cast of a command to a list");
|
|
|
return {};
|
|
|
}
|
|
|
|
|
@@ -3150,7 +3329,7 @@ JobValue::~JobValue()
|
|
|
StringValue::~StringValue()
|
|
|
{
|
|
|
}
|
|
|
-Vector<String> StringValue::resolve_as_list(RefPtr<Shell>)
|
|
|
+Vector<String> StringValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
{
|
|
|
if (is_list()) {
|
|
|
auto parts = StringView(m_string).split_view(m_split, m_keep_empty);
|
|
@@ -3158,16 +3337,16 @@ Vector<String> StringValue::resolve_as_list(RefPtr<Shell>)
|
|
|
result.ensure_capacity(parts.size());
|
|
|
for (auto& part : parts)
|
|
|
result.append(part);
|
|
|
- return result;
|
|
|
+ return resolve_slices(shell, move(result), m_slices);
|
|
|
}
|
|
|
|
|
|
- return { m_string };
|
|
|
+ return { resolve_slices(shell, String { m_string }, m_slices) };
|
|
|
}
|
|
|
|
|
|
NonnullRefPtr<Value> StringValue::resolve_without_cast(RefPtr<Shell> shell)
|
|
|
{
|
|
|
if (is_list())
|
|
|
- return create<AST::ListValue>(resolve_as_list(shell));
|
|
|
+ return create<AST::ListValue>(resolve_as_list(shell)); // No need to reapply the slices.
|
|
|
|
|
|
return *this;
|
|
|
}
|
|
@@ -3178,12 +3357,12 @@ GlobValue::~GlobValue()
|
|
|
Vector<String> GlobValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
{
|
|
|
if (!shell)
|
|
|
- return { m_glob };
|
|
|
+ return { resolve_slices(shell, String { m_glob }, m_slices) };
|
|
|
|
|
|
auto results = shell->expand_globs(m_glob, shell->cwd);
|
|
|
if (results.is_empty())
|
|
|
shell->raise_error(Shell::ShellError::InvalidGlobError, "Glob did not match anything!", m_generation_position);
|
|
|
- return results;
|
|
|
+ return resolve_slices(shell, move(results), m_slices);
|
|
|
}
|
|
|
|
|
|
SimpleVariableValue::~SimpleVariableValue()
|
|
@@ -3192,29 +3371,31 @@ SimpleVariableValue::~SimpleVariableValue()
|
|
|
Vector<String> SimpleVariableValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
{
|
|
|
if (!shell)
|
|
|
- return {};
|
|
|
+ return resolve_slices(shell, Vector<String> {}, m_slices);
|
|
|
|
|
|
if (auto value = resolve_without_cast(shell); value != this)
|
|
|
return value->resolve_as_list(shell);
|
|
|
|
|
|
char* env_value = getenv(m_name.characters());
|
|
|
if (env_value == nullptr)
|
|
|
- return { "" };
|
|
|
+ return { resolve_slices(shell, "", m_slices) };
|
|
|
|
|
|
- Vector<String> res;
|
|
|
- String str_env_value = String(env_value);
|
|
|
- const auto& split_text = str_env_value.split_view(' ');
|
|
|
- for (auto& part : split_text)
|
|
|
- res.append(part);
|
|
|
- return res;
|
|
|
+ return { resolve_slices(shell, String { env_value }, m_slices) };
|
|
|
}
|
|
|
|
|
|
NonnullRefPtr<Value> SimpleVariableValue::resolve_without_cast(RefPtr<Shell> shell)
|
|
|
{
|
|
|
VERIFY(shell);
|
|
|
|
|
|
- if (auto value = shell->lookup_local_variable(m_name))
|
|
|
- return value.release_nonnull();
|
|
|
+ if (auto value = shell->lookup_local_variable(m_name)) {
|
|
|
+ auto result = value.release_nonnull();
|
|
|
+ // If a slice is applied, add it.
|
|
|
+ if (!m_slices.is_empty())
|
|
|
+ result = result->with_slices(m_slices);
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
@@ -3229,24 +3410,24 @@ Vector<String> SpecialVariableValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
|
|
|
switch (m_name) {
|
|
|
case '?':
|
|
|
- return { String::number(shell->last_return_code) };
|
|
|
+ return { resolve_slices(shell, String::number(shell->last_return_code), m_slices) };
|
|
|
case '$':
|
|
|
- return { String::number(getpid()) };
|
|
|
+ return { resolve_slices(shell, String::number(getpid()), m_slices) };
|
|
|
case '*':
|
|
|
if (auto argv = shell->lookup_local_variable("ARGV"))
|
|
|
- return argv->resolve_as_list(shell);
|
|
|
- return {};
|
|
|
+ return resolve_slices(shell, argv->resolve_as_list(shell), m_slices);
|
|
|
+ return resolve_slices(shell, Vector<String> {}, m_slices);
|
|
|
case '#':
|
|
|
if (auto argv = shell->lookup_local_variable("ARGV")) {
|
|
|
if (argv->is_list()) {
|
|
|
auto list_argv = static_cast<AST::ListValue*>(argv.ptr());
|
|
|
- return { String::number(list_argv->values().size()) };
|
|
|
+ return { resolve_slices(shell, String::number(list_argv->values().size()), m_slices) };
|
|
|
}
|
|
|
- return { "1" };
|
|
|
+ return { resolve_slices(shell, "1", m_slices) };
|
|
|
}
|
|
|
- return { "0" };
|
|
|
+ return { resolve_slices(shell, "0", m_slices) };
|
|
|
default:
|
|
|
- return { "" };
|
|
|
+ return { resolve_slices(shell, "", m_slices) };
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -3260,9 +3441,9 @@ Vector<String> TildeValue::resolve_as_list(RefPtr<Shell> shell)
|
|
|
builder.append(m_username);
|
|
|
|
|
|
if (!shell)
|
|
|
- return { builder.to_string() };
|
|
|
+ return { resolve_slices(shell, builder.to_string(), m_slices) };
|
|
|
|
|
|
- return { shell->expand_tilde(builder.to_string()) };
|
|
|
+ return { resolve_slices(shell, shell->expand_tilde(builder.to_string()), m_slices) };
|
|
|
}
|
|
|
|
|
|
Result<NonnullRefPtr<Rewiring>, String> CloseRedirection::apply() const
|