summaryrefslogtreecommitdiff
path: root/Userland/Shell
diff options
context:
space:
mode:
authorAnotherTest <ali.mpfard@gmail.com>2021-03-13 03:10:18 +0330
committerAndreas Kling <kling@serenityos.org>2021-03-22 13:15:08 +0100
commit3b8fa5a75387d1b9a609dca1d911bebfacdf7060 (patch)
tree557fe261c7c4a0af3a0da026d87fa63e89a55697 /Userland/Shell
parentddcef0452aad489a51f4cf3d67e7517f3e573e7c (diff)
downloadserenity-3b8fa5a75387d1b9a609dca1d911bebfacdf7060.zip
Shell: Add support for indexing into variables
Now a variable may have an optional slice (only _one_ slice), which can also use negative indices to index from the end. This works on both lists and strings. The contents of the slice have the same semantics as brace expansions. For example: ```sh $ x=(1 2 3 4 5 6) $ echo $x[1..3] # select indices 1, 2, 3 2 3 4 $ echo $x[3,4,1,0] # select indices 3, 4, 1, 0 (in that order) 4 5 2 1 $ x="Well Hello Friends!" $ echo $x[5..9] Hello ```
Diffstat (limited to 'Userland/Shell')
-rw-r--r--Userland/Shell/AST.cpp259
-rw-r--r--Userland/Shell/AST.h66
-rw-r--r--Userland/Shell/Formatter.cpp45
-rw-r--r--Userland/Shell/Formatter.h1
-rw-r--r--Userland/Shell/Forward.h1
-rw-r--r--Userland/Shell/NodeVisitor.cpp13
-rw-r--r--Userland/Shell/NodeVisitor.h1
-rw-r--r--Userland/Shell/Parser.cpp51
-rw-r--r--Userland/Shell/Parser.h8
-rw-r--r--Userland/Shell/Shell.cpp1
-rw-r--r--Userland/Shell/Shell.h1
11 files changed, 391 insertions, 56 deletions
diff --git a/Userland/Shell/AST.cpp b/Userland/Shell/AST.cpp
index 02c873e241..3c0cab49ca 100644
--- a/Userland/Shell/AST.cpp
+++ b/Userland/Shell/AST.cpp
@@ -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
diff --git a/Userland/Shell/AST.h b/Userland/Shell/AST.h
index f013a8e07b..54aad9c4b7 100644
--- a/Userland/Shell/AST.h
+++ b/Userland/Shell/AST.h
@@ -244,6 +244,9 @@ public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) = 0;
virtual Vector<Command> resolve_as_commands(RefPtr<Shell>);
virtual NonnullRefPtr<Value> resolve_without_cast(RefPtr<Shell>) { return *this; }
+ virtual NonnullRefPtr<Value> clone() const = 0;
+ virtual NonnullRefPtr<Value> with_slices(NonnullRefPtr<Slice> slice) const&;
+ virtual NonnullRefPtr<Value> with_slices(NonnullRefPtrVector<Slice> slices) const&;
virtual ~Value();
virtual bool is_command() const { return false; }
virtual bool is_glob() const { return false; }
@@ -251,12 +254,21 @@ public:
virtual bool is_list() const { return false; }
virtual bool is_string() const { return false; }
virtual bool is_list_without_resolution() const { return false; }
+
+protected:
+ Value& set_slices(NonnullRefPtrVector<Slice> slices)
+ {
+ m_slices = move(slices);
+ return *this;
+ }
+ NonnullRefPtrVector<Slice> m_slices;
};
class CommandValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
virtual Vector<Command> resolve_as_commands(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<CommandValue>(m_command)->set_slices(m_slices); }
virtual ~CommandValue();
virtual bool is_command() const override { return true; }
CommandValue(Command command)
@@ -277,6 +289,7 @@ class CommandSequenceValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
virtual Vector<Command> resolve_as_commands(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<CommandSequenceValue>(m_contained_values)->set_slices(m_slices); }
virtual ~CommandSequenceValue();
virtual bool is_command() const override { return true; }
CommandSequenceValue(Vector<Command> commands)
@@ -292,6 +305,7 @@ class JobValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override { VERIFY_NOT_REACHED(); }
virtual Vector<Command> resolve_as_commands(RefPtr<Shell>) override { VERIFY_NOT_REACHED(); }
+ virtual NonnullRefPtr<Value> clone() const override { return create<JobValue>(m_job)->set_slices(m_slices); }
virtual ~JobValue();
virtual bool is_job() const override { return true; }
JobValue(RefPtr<Job> job)
@@ -309,6 +323,7 @@ class ListValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
virtual NonnullRefPtr<Value> resolve_without_cast(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<ListValue>(m_contained_values)->set_slices(m_slices); }
virtual ~ListValue();
virtual bool is_list() const override { return true; }
virtual bool is_list_without_resolution() const override { return true; }
@@ -332,6 +347,7 @@ private:
class StringValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<StringValue>(m_string, m_split, m_keep_empty)->set_slices(m_slices); }
virtual ~StringValue();
virtual bool is_string() const override { return m_split.is_null(); }
virtual bool is_list() const override { return !m_split.is_null(); }
@@ -352,6 +368,7 @@ private:
class GlobValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<GlobValue>(m_glob, m_generation_position)->set_slices(m_slices); }
virtual ~GlobValue();
virtual bool is_glob() const override { return true; }
GlobValue(String glob, Position position)
@@ -369,6 +386,7 @@ class SimpleVariableValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
virtual NonnullRefPtr<Value> resolve_without_cast(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<SimpleVariableValue>(m_name)->set_slices(m_slices); }
virtual ~SimpleVariableValue();
SimpleVariableValue(String name)
: m_name(move(name))
@@ -382,6 +400,7 @@ private:
class SpecialVariableValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<SpecialVariableValue>(m_name)->set_slices(m_slices); }
virtual ~SpecialVariableValue();
SpecialVariableValue(char name)
: m_name(name)
@@ -395,6 +414,7 @@ private:
class TildeValue final : public Value {
public:
virtual Vector<String> resolve_as_list(RefPtr<Shell>) override;
+ virtual NonnullRefPtr<Value> clone() const override { return create<TildeValue>(m_username)->set_slices(m_slices); }
virtual ~TildeValue();
virtual bool is_string() const override { return true; }
TildeValue(String name)
@@ -489,6 +509,7 @@ public:
ReadRedirection,
ReadWriteRedirection,
Sequence,
+ Slice,
SimpleVariable,
SpecialVariable,
StringLiteral,
@@ -1214,7 +1235,48 @@ private:
RefPtr<AST::Node> m_block;
};
-class SimpleVariable final : public Node {
+class Slice final : public Node {
+public:
+ Slice(Position, NonnullRefPtr<AST::Node>);
+ virtual ~Slice() override;
+
+ virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
+
+ NonnullRefPtr<AST::Node> selector() const { return m_selector; }
+
+ 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 Vector<Line::CompletionSuggestion> complete_for_editor(Shell&, size_t, const HitTestResult&) override;
+ virtual HitTestResult hit_test_position(size_t) const override;
+
+protected:
+ NODE(Slice);
+ NonnullRefPtr<AST::Node> m_selector;
+};
+
+class VariableNode : public Node {
+public:
+ VariableNode(Position position)
+ : Node(move(position))
+ {
+ }
+
+ void set_slice(NonnullRefPtr<Slice>&& slice)
+ {
+ VERIFY(!m_slice);
+ m_slice = move(slice);
+ if (m_slice->is_syntax_error())
+ set_is_syntax_error(m_slice->syntax_error_node());
+ }
+
+ const Slice* slice() const { return m_slice.ptr(); }
+
+protected:
+ RefPtr<Slice> m_slice;
+};
+
+class SimpleVariable final : public VariableNode {
public:
SimpleVariable(Position, String);
virtual ~SimpleVariable();
@@ -1234,7 +1296,7 @@ private:
String m_name;
};
-class SpecialVariable final : public Node {
+class SpecialVariable final : public VariableNode {
public:
SpecialVariable(Position, char);
virtual ~SpecialVariable();
diff --git a/Userland/Shell/Formatter.cpp b/Userland/Shell/Formatter.cpp
index 2d7e0908d5..22d85d2e8a 100644
--- a/Userland/Shell/Formatter.cpp
+++ b/Userland/Shell/Formatter.cpp
@@ -195,18 +195,22 @@ void Formatter::visit(const AST::BraceExpansion* node)
{
will_visit(node);
test_and_update_output_cursor(node);
- current_builder().append('{');
+ if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
+ 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);
+ {
+ 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('}');
+ if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
+ current_builder().append('}');
visited(node);
}
@@ -615,14 +619,16 @@ void Formatter::visit(const AST::Range* node)
{
will_visit(node);
test_and_update_output_cursor(node);
- current_builder().append('{');
+ if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
+ 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('}');
+ if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
+ current_builder().append('}');
visited(node);
}
@@ -686,12 +692,27 @@ void Formatter::visit(const AST::Subshell* node)
visited(node);
}
+void Formatter::visit(const AST::Slice* node)
+{
+ will_visit(node);
+ test_and_update_output_cursor(node);
+ TemporaryChange<const AST::Node*> parent { m_parent_node, node };
+
+ current_builder().append('[');
+ node->selector()->visit(*this);
+ current_builder().append(']');
+
+ visited(node);
+}
+
void Formatter::visit(const AST::SimpleVariable* node)
{
will_visit(node);
test_and_update_output_cursor(node);
current_builder().append('$');
current_builder().append(node->name());
+ if (const AST::Node* slice = node->slice())
+ slice->visit(*this);
visited(node);
}
@@ -701,6 +722,8 @@ void Formatter::visit(const AST::SpecialVariable* node)
test_and_update_output_cursor(node);
current_builder().append('$');
current_builder().append(node->name());
+ if (const AST::Node* slice = node->slice())
+ slice->visit(*this);
visited(node);
}
diff --git a/Userland/Shell/Formatter.h b/Userland/Shell/Formatter.h
index d7bff364e0..76aab3d912 100644
--- a/Userland/Shell/Formatter.h
+++ b/Userland/Shell/Formatter.h
@@ -90,6 +90,7 @@ private:
virtual void visit(const AST::ReadWriteRedirection*) override;
virtual void visit(const AST::Sequence*) override;
virtual void visit(const AST::Subshell*) override;
+ virtual void visit(const AST::Slice*) override;
virtual void visit(const AST::SimpleVariable*) override;
virtual void visit(const AST::SpecialVariable*) override;
virtual void visit(const AST::Juxtaposition*) override;
diff --git a/Userland/Shell/Forward.h b/Userland/Shell/Forward.h
index c504588405..8a977e13bd 100644
--- a/Userland/Shell/Forward.h
+++ b/Userland/Shell/Forward.h
@@ -67,6 +67,7 @@ class ReadRedirection;
class ReadWriteRedirection;
class Sequence;
class Subshell;
+class Slice;
class SimpleVariable;
class SpecialVariable;
class Juxtaposition;
diff --git a/Userland/Shell/NodeVisitor.cpp b/Userland/Shell/NodeVisitor.cpp
index c84094c860..9fa64c19d5 100644
--- a/Userland/Shell/NodeVisitor.cpp
+++ b/Userland/Shell/NodeVisitor.cpp
@@ -202,12 +202,21 @@ void NodeVisitor::visit(const AST::Subshell* node)
node->block()->visit(*this);
}
-void NodeVisitor::visit(const AST::SimpleVariable*)
+void NodeVisitor::visit(const AST::Slice* node)
{
+ node->selector()->visit(*this);
}
-void NodeVisitor::visit(const AST::SpecialVariable*)
+void NodeVisitor::visit(const AST::SimpleVariable* node)
{
+ if (const AST::Node* slice = node->slice())
+ slice->visit(*this);
+}
+
+void NodeVisitor::visit(const AST::SpecialVariable* node)
+{
+ if (const AST::Node* slice = node->slice())
+ slice->visit(*this);
}
void NodeVisitor::visit(const AST::Juxtaposition* node)
diff --git a/Userland/Shell/NodeVisitor.h b/Userland/Shell/NodeVisitor.h
index 6d45bc3ab1..8452d6d968 100644
--- a/Userland/Shell/NodeVisitor.h
+++ b/Userland/Shell/NodeVisitor.h
@@ -63,6 +63,7 @@ public:
virtual void visit(const AST::ReadWriteRedirection*);
virtual void visit(const AST::Sequence*);
virtual void visit(const AST::Subshell*);
+ virtual void visit(const AST::Slice*);
virtual void visit(const AST::SimpleVariable*);
virtual void visit(const AST::SpecialVariable*);
virtual void visit(const AST::Juxtaposition*);
diff --git a/Userland/Shell/Parser.cpp b/Userland/Shell/Parser.cpp
index 6208b0a8fb..690f353f9a 100644
--- a/Userland/Shell/Parser.cpp
+++ b/Userland/Shell/Parser.cpp
@@ -1331,6 +1331,21 @@ RefPtr<AST::Node> Parser::parse_doublequoted_string_inner()
RefPtr<AST::Node> Parser::parse_variable()
{
auto rule_start = push_start();
+ auto ref = parse_variable_ref();
+
+ if (!ref)
+ return nullptr;
+
+ auto variable = static_ptr_cast<AST::VariableNode>(ref);
+ if (auto slice = parse_slice())
+ variable->set_slice(slice.release_nonnull());
+
+ return variable;
+}
+
+RefPtr<AST::Node> Parser::parse_variable_ref()
+{
+ auto rule_start = push_start();
if (at_end())
return nullptr;
@@ -1358,6 +1373,38 @@ RefPtr<AST::Node> Parser::parse_variable()
return create<AST::SimpleVariable>(move(name)); // Variable Simple
}
+RefPtr<AST::Node> Parser::parse_slice()
+{
+ auto rule_start = push_start();
+ if (!next_is("["))
+ return nullptr;
+
+ consume(); // [
+
+ ScopedValueRollback chars_change { m_extra_chars_not_allowed_in_barewords };
+ m_extra_chars_not_allowed_in_barewords.append(']');
+ auto spec = parse_brace_expansion_spec();
+
+ RefPtr<AST::SyntaxError> error;
+
+ if (peek() != ']')
+ error = create<AST::SyntaxError>("Expected a close bracket ']' to end a variable slice");
+ else
+ consume();
+
+ if (!spec) {
+ if (error)
+ spec = move(error);
+ else
+ spec = create<AST::SyntaxError>("Expected either a range, or a comma-seprated list of selectors");
+ }
+
+ auto node = create<AST::Slice>(spec.release_nonnull());
+ if (error)
+ node->set_is_syntax_error(*error);
+ return node;
+}
+
RefPtr<AST::Node> Parser::parse_evaluate()
{
auto rule_start = push_start();
@@ -1787,7 +1834,9 @@ RefPtr<AST::Node> Parser::parse_brace_expansion()
RefPtr<AST::Node> Parser::parse_brace_expansion_spec()
{
TemporaryChange is_in_brace_expansion { m_is_in_brace_expansion_spec, true };
- TemporaryChange chars_change { m_extra_chars_not_allowed_in_barewords, { ',' } };
+ ScopedValueRollback chars_change { m_extra_chars_not_allowed_in_barewords };
+
+ m_extra_chars_not_allowed_in_barewords.append(',');
auto rule_start = push_start();
auto start_expr = parse_expression();
diff --git a/Userland/Shell/Parser.h b/Userland/Shell/Parser.h
index 4d70ec83ae..fa9511a8d2 100644
--- a/Userland/Shell/Parser.h
+++ b/Userland/Shell/Parser.h
@@ -91,6 +91,8 @@ private:
RefPtr<AST::Node> parse_string();
RefPtr<AST::Node> parse_doublequoted_string_inner();
RefPtr<AST::Node> parse_variable();
+ RefPtr<AST::Node> parse_variable_ref();
+ RefPtr<AST::Node> parse_slice();
RefPtr<AST::Node> parse_evaluate();
RefPtr<AST::Node> parse_history_designator();
RefPtr<AST::Node> parse_comment();
@@ -261,13 +263,17 @@ dquoted_string_inner :: '\' . dquoted_string_inner? {concat}
| '\' 'x' digit digit dquoted_string_inner?
| '\' [abefrn] dquoted_string_inner?
-variable :: '$' identifier
+variable :: variable_ref slice?
+
+variable_ref :: '$' identifier
| '$' '$'
| '$' '?'
| '$' '*'
| '$' '#'
| ...
+slice :: '[' brace_expansion_spec ']'
+
comment :: '#' [^\n]*
immediate_expression :: '$' '{' immediate_function expression* '}'
diff --git a/Userland/Shell/Shell.cpp b/Userland/Shell/Shell.cpp
index bd78a9d411..cac2d10c1e 100644
--- a/Userland/Shell/Shell.cpp
+++ b/Userland/Shell/Shell.cpp
@@ -1885,6 +1885,7 @@ void Shell::possibly_print_error() const
case ShellError::EvaluatedSyntaxError:
warnln("Shell Syntax Error: {}", m_error_description);
break;
+ case ShellError::InvalidSliceContentsError:
case ShellError::InvalidGlobError:
case ShellError::NonExhaustiveMatchRules:
warnln("Shell: {}", m_error_description);
diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h
index 556ee613fd..832bc8d723 100644
--- a/Userland/Shell/Shell.h
+++ b/Userland/Shell/Shell.h
@@ -235,6 +235,7 @@ public:
EvaluatedSyntaxError,
NonExhaustiveMatchRules,
InvalidGlobError,
+ InvalidSliceContentsError,
OpenFailure,
};