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 join_commands(Vector left, Vector shell, String&& input_value, NonnullRefPtrVector 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 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 resolve_slices(RefPtr shell, Vector&& values, NonnullRefPtrVector 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 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 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, Function)> 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 Slice::run(RefPtr 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 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 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 SimpleVariable::run(RefPtr) { - return create(m_name); + NonnullRefPtr value = create(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 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 SpecialVariable::run(RefPtr) { - return create(m_name); + NonnullRefPtr value = create(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 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 values) m_contained_values.append(adopt(*new StringValue(move(str)))); } +NonnullRefPtr Value::with_slices(NonnullRefPtr slice) const& +{ + auto value = clone(); + value->m_slices.append(move(slice)); + return value; +} + +NonnullRefPtr Value::with_slices(NonnullRefPtrVector slices) const& +{ + auto value = clone(); + value->m_slices.append(move(slices)); + return value; +} + ListValue::~ListValue() { } @@ -3101,7 +3277,7 @@ Vector ListValue::resolve_as_list(RefPtr 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 ListValue::resolve_without_cast(RefPtr shell) @@ -3110,7 +3286,10 @@ NonnullRefPtr ListValue::resolve_without_cast(RefPtr shell) for (auto& value : m_contained_values) values.append(value.resolve_without_cast(shell)); - return create(move(values)); + NonnullRefPtr value = create(move(values)); + if (!m_slices.is_empty()) + value = value->with_slices(m_slices); + return value; } CommandValue::~CommandValue() @@ -3121,9 +3300,9 @@ CommandSequenceValue::~CommandSequenceValue() { } -Vector CommandSequenceValue::resolve_as_list(RefPtr) +Vector CommandSequenceValue::resolve_as_list(RefPtr 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 CommandSequenceValue::resolve_as_commands(RefPtr) return m_contained_values; } -Vector CommandValue::resolve_as_list(RefPtr) +Vector CommandValue::resolve_as_list(RefPtr 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 StringValue::resolve_as_list(RefPtr) +Vector StringValue::resolve_as_list(RefPtr shell) { if (is_list()) { auto parts = StringView(m_string).split_view(m_split, m_keep_empty); @@ -3158,16 +3337,16 @@ Vector StringValue::resolve_as_list(RefPtr) 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 StringValue::resolve_without_cast(RefPtr shell) { if (is_list()) - return create(resolve_as_list(shell)); + return create(resolve_as_list(shell)); // No need to reapply the slices. return *this; } @@ -3178,12 +3357,12 @@ GlobValue::~GlobValue() Vector GlobValue::resolve_as_list(RefPtr 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 SimpleVariableValue::resolve_as_list(RefPtr shell) { if (!shell) - return {}; + return resolve_slices(shell, Vector {}, 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 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 SimpleVariableValue::resolve_without_cast(RefPtr 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 SpecialVariableValue::resolve_as_list(RefPtr 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 {}, m_slices); case '#': if (auto argv = shell->lookup_local_variable("ARGV")) { if (argv->is_list()) { auto list_argv = static_cast(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 TildeValue::resolve_as_list(RefPtr 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, 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 resolve_as_list(RefPtr) = 0; virtual Vector resolve_as_commands(RefPtr); virtual NonnullRefPtr resolve_without_cast(RefPtr) { return *this; } + virtual NonnullRefPtr clone() const = 0; + virtual NonnullRefPtr with_slices(NonnullRefPtr slice) const&; + virtual NonnullRefPtr with_slices(NonnullRefPtrVector 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 slices) + { + m_slices = move(slices); + return *this; + } + NonnullRefPtrVector m_slices; }; class CommandValue final : public Value { public: virtual Vector resolve_as_list(RefPtr) override; virtual Vector resolve_as_commands(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 resolve_as_list(RefPtr) override; virtual Vector resolve_as_commands(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(m_contained_values)->set_slices(m_slices); } virtual ~CommandSequenceValue(); virtual bool is_command() const override { return true; } CommandSequenceValue(Vector commands) @@ -292,6 +305,7 @@ class JobValue final : public Value { public: virtual Vector resolve_as_list(RefPtr) override { VERIFY_NOT_REACHED(); } virtual Vector resolve_as_commands(RefPtr) override { VERIFY_NOT_REACHED(); } + virtual NonnullRefPtr clone() const override { return create(m_job)->set_slices(m_slices); } virtual ~JobValue(); virtual bool is_job() const override { return true; } JobValue(RefPtr job) @@ -309,6 +323,7 @@ class ListValue final : public Value { public: virtual Vector resolve_as_list(RefPtr) override; virtual NonnullRefPtr resolve_without_cast(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 resolve_as_list(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 resolve_as_list(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 resolve_as_list(RefPtr) override; virtual NonnullRefPtr resolve_without_cast(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 resolve_as_list(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 resolve_as_list(RefPtr) override; + virtual NonnullRefPtr clone() const override { return create(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 m_block; }; -class SimpleVariable final : public Node { +class Slice final : public Node { +public: + Slice(Position, NonnullRefPtr); + virtual ~Slice() override; + + virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); } + + NonnullRefPtr selector() const { return m_selector; } + + virtual void dump(int level) const override; + virtual RefPtr run(RefPtr) override; + virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override; + virtual Vector complete_for_editor(Shell&, size_t, const HitTestResult&) override; + virtual HitTestResult hit_test_position(size_t) const override; + +protected: + NODE(Slice); + NonnullRefPtr m_selector; +}; + +class VariableNode : public Node { +public: + VariableNode(Position position) + : Node(move(position)) + { + } + + void set_slice(NonnullRefPtr&& 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 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 parent { m_parent_node, node }; - bool first = true; - for (auto& entry : node->entries()) { - if (!first) - current_builder().append(','); - first = false; - entry.visit(*this); + { + TemporaryChange 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 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 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 @@ -1329,6 +1329,21 @@ RefPtr Parser::parse_doublequoted_string_inner() } RefPtr Parser::parse_variable() +{ + auto rule_start = push_start(); + auto ref = parse_variable_ref(); + + if (!ref) + return nullptr; + + auto variable = static_ptr_cast(ref); + if (auto slice = parse_slice()) + variable->set_slice(slice.release_nonnull()); + + return variable; +} + +RefPtr Parser::parse_variable_ref() { auto rule_start = push_start(); if (at_end()) @@ -1358,6 +1373,38 @@ RefPtr Parser::parse_variable() return create(move(name)); // Variable Simple } +RefPtr 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 error; + + if (peek() != ']') + error = create("Expected a close bracket ']' to end a variable slice"); + else + consume(); + + if (!spec) { + if (error) + spec = move(error); + else + spec = create("Expected either a range, or a comma-seprated list of selectors"); + } + + auto node = create(spec.release_nonnull()); + if (error) + node->set_is_syntax_error(*error); + return node; +} + RefPtr Parser::parse_evaluate() { auto rule_start = push_start(); @@ -1787,7 +1834,9 @@ RefPtr Parser::parse_brace_expansion() RefPtr 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 parse_string(); RefPtr parse_doublequoted_string_inner(); RefPtr parse_variable(); + RefPtr parse_variable_ref(); + RefPtr parse_slice(); RefPtr parse_evaluate(); RefPtr parse_history_designator(); RefPtr 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, };