1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-23 09:07:41 +00:00

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
```
This commit is contained in:
AnotherTest 2021-03-13 03:10:18 +03:30 committed by Andreas Kling
parent ddcef0452a
commit 3b8fa5a753
11 changed files with 391 additions and 56 deletions

View file

@ -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

View file

@ -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();

View file

@ -195,8 +195,10 @@ void Formatter::visit(const AST::BraceExpansion* node)
{
will_visit(node);
test_and_update_output_cursor(node);
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()) {
@ -205,7 +207,9 @@ void Formatter::visit(const AST::BraceExpansion* node)
first = false;
entry.visit(*this);
}
}
if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
current_builder().append('}');
visited(node);
}
@ -615,6 +619,7 @@ void Formatter::visit(const AST::Range* node)
{
will_visit(node);
test_and_update_output_cursor(node);
if (!m_parent_node || m_parent_node->kind() != AST::Node::Kind::Slice)
current_builder().append('{');
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
@ -622,6 +627,7 @@ void Formatter::visit(const AST::Range* node)
current_builder().append("..");
node->end()->visit(*this);
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);
}

View file

@ -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;

View file

@ -67,6 +67,7 @@ class ReadRedirection;
class ReadWriteRedirection;
class Sequence;
class Subshell;
class Slice;
class SimpleVariable;
class SpecialVariable;
class Juxtaposition;

View file

@ -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)

View file

@ -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*);

View file

@ -1329,6 +1329,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())
@ -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();

View file

@ -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* '}'

View file

@ -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);

View file

@ -235,6 +235,7 @@ public:
EvaluatedSyntaxError,
NonExhaustiveMatchRules,
InvalidGlobError,
InvalidSliceContentsError,
OpenFailure,
};