mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
Shell: Add support for enumerating lists in for loops
With some odd syntax to boot: ```sh $ for index i x in $whatever {} ```
This commit is contained in:
parent
a45b2ea6fb
commit
13b65b632a
Notes:
sideshowbarker
2024-07-18 21:39:42 +09:00
Author: https://github.com/alimpfard Commit: https://github.com/SerenityOS/serenity/commit/13b65b632af Pull-request: https://github.com/SerenityOS/serenity/pull/5652 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/BenWiederhake
8 changed files with 133 additions and 23 deletions
|
@ -211,9 +211,12 @@ if A {
|
|||
##### For Loops
|
||||
For Loops evaluate a sequence of commands once per element in a given list.
|
||||
The shell has two forms of _for loops_, one with an explicitly named iteration variable, and one with an implicitly named one.
|
||||
The general syntax follows the form `for name in expr { sequence }`, and allows omitting the `name in` part to implicitly name the variable `it`.
|
||||
The general syntax follows the form `for index index_name name in expr { sequence }`, and allows omitting the `index index_name name in` part to implicitly name the variable `it`.
|
||||
|
||||
A for-loop evaluates the _sequence_ once per every element in the _expr_, seetting the local variable _name_ to the element being processed.
|
||||
It should be noted that the `index index_name` section is optional, but if supplied, will require an explicit iteration variable as well.
|
||||
In other words, `for index i in foo` is not valid syntax.
|
||||
|
||||
A for-loop evaluates the _sequence_ once per every element in the _expr_, seetting the local variable _name_ to the element being processed, and the local variable _enum name_ to the enumeration index (if set).
|
||||
|
||||
The Shell shall cancel the for loop if two consecutive commands are interrupted via SIGINT (\^C), and any other terminating signal aborts the loop entirely.
|
||||
|
||||
|
@ -224,6 +227,9 @@ $ for * { mv $it 1-$it }
|
|||
|
||||
# Iterate over a sequence and write each element to a file
|
||||
$ for i in $(seq 1 100) { echo $i >> foo }
|
||||
|
||||
# Iterate over some files and get their index
|
||||
$ for index i x in * { echo file at index $i is named $x }
|
||||
```
|
||||
|
||||
##### Infinite Loops
|
||||
|
@ -365,7 +371,7 @@ control_structure[c] :: for_expr
|
|||
continuation_control :: 'break'
|
||||
| 'continue'
|
||||
|
||||
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}'
|
||||
for_expr :: 'for' ws+ (('enum' ' '+ identifier)? identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}'
|
||||
|
||||
loop_expr :: 'loop' ws* '{' [c] toplevel '}'
|
||||
|
||||
|
|
|
@ -1059,7 +1059,10 @@ FunctionDeclaration::~FunctionDeclaration()
|
|||
void ForLoop::dump(int level) const
|
||||
{
|
||||
Node::dump(level);
|
||||
print_indented(String::format("%s in", m_variable_name.characters()), level + 1);
|
||||
if (m_variable.has_value())
|
||||
print_indented(String::formatted("iterating with {} in", m_variable->name), level + 1);
|
||||
if (m_index_variable.has_value())
|
||||
print_indented(String::formatted("with index name {} in", m_index_variable->name), level + 1);
|
||||
if (m_iterated_expression)
|
||||
m_iterated_expression->dump(level + 2);
|
||||
else
|
||||
|
@ -1110,6 +1113,9 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
|
|||
};
|
||||
|
||||
if (m_iterated_expression) {
|
||||
auto variable_name = m_variable.has_value() ? m_variable->name : "it";
|
||||
Optional<StringView> index_name = m_index_variable.has_value() ? Optional<StringView>(m_index_variable->name) : Optional<StringView>();
|
||||
size_t i = 0;
|
||||
m_iterated_expression->for_each_entry(shell, [&](auto value) {
|
||||
if (consecutive_interruptions == 2)
|
||||
return IterationDecision::Break;
|
||||
|
@ -1118,10 +1124,16 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
|
|||
|
||||
{
|
||||
auto frame = shell->push_frame(String::formatted("for ({})", this));
|
||||
shell->set_local_variable(m_variable_name, value, true);
|
||||
shell->set_local_variable(variable_name, value, true);
|
||||
|
||||
if (index_name.has_value())
|
||||
shell->set_local_variable(index_name.value(), create<AST::StringValue>(String::number(i)), true);
|
||||
|
||||
++i;
|
||||
|
||||
block_value = m_block->run(shell);
|
||||
}
|
||||
|
||||
return run(block_value);
|
||||
});
|
||||
} else {
|
||||
|
@ -1146,10 +1158,19 @@ void ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightM
|
|||
if (m_in_kw_position.has_value())
|
||||
editor.stylize({ m_in_kw_position.value().start_offset, m_in_kw_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
|
||||
|
||||
if (m_index_kw_position.has_value())
|
||||
editor.stylize({ m_index_kw_position.value().start_offset, m_index_kw_position.value().end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
|
||||
|
||||
metadata.is_first_in_list = false;
|
||||
m_iterated_expression->highlight_in_editor(editor, shell, metadata);
|
||||
}
|
||||
|
||||
if (m_index_variable.has_value())
|
||||
editor.stylize({ m_index_variable->position.start_offset, m_index_variable->position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic });
|
||||
|
||||
if (m_variable.has_value())
|
||||
editor.stylize({ m_variable->position.start_offset, m_variable->position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Italic });
|
||||
|
||||
metadata.is_first_in_list = true;
|
||||
if (m_block)
|
||||
m_block->highlight_in_editor(editor, shell, metadata);
|
||||
|
@ -1171,12 +1192,14 @@ HitTestResult ForLoop::hit_test_position(size_t offset) const
|
|||
return m_block->hit_test_position(offset);
|
||||
}
|
||||
|
||||
ForLoop::ForLoop(Position position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position)
|
||||
ForLoop::ForLoop(Position position, Optional<NameWithPosition> variable, Optional<NameWithPosition> index_variable, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position, Optional<Position> index_kw_position)
|
||||
: Node(move(position))
|
||||
, m_variable_name(move(variable_name))
|
||||
, m_variable(move(variable))
|
||||
, m_index_variable(move(index_variable))
|
||||
, m_iterated_expression(move(iterated_expr))
|
||||
, m_block(move(block))
|
||||
, m_in_kw_position(move(in_kw_position))
|
||||
, m_index_kw_position(move(index_kw_position))
|
||||
{
|
||||
if (m_iterated_expression && m_iterated_expression->is_syntax_error())
|
||||
set_is_syntax_error(m_iterated_expression->syntax_error_node());
|
||||
|
|
|
@ -846,13 +846,15 @@ private:
|
|||
|
||||
class ForLoop final : public Node {
|
||||
public:
|
||||
ForLoop(Position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position = {});
|
||||
ForLoop(Position, Optional<NameWithPosition> variable, Optional<NameWithPosition> index_variable, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position = {}, Optional<Position> index_kw_position = {});
|
||||
virtual ~ForLoop();
|
||||
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
||||
|
||||
const String& variable_name() const { return m_variable_name; }
|
||||
const Optional<NameWithPosition>& variable() const { return m_variable; }
|
||||
const Optional<NameWithPosition>& index_variable() const { return m_index_variable; }
|
||||
const RefPtr<Node>& iterated_expression() const { return m_iterated_expression; }
|
||||
const RefPtr<Node>& block() const { return m_block; }
|
||||
const Optional<Position> index_keyword_position() const { return m_index_kw_position; }
|
||||
const Optional<Position> in_keyword_position() const { return m_in_kw_position; }
|
||||
|
||||
private:
|
||||
|
@ -864,10 +866,12 @@ private:
|
|||
virtual bool would_execute() const override { return true; }
|
||||
virtual bool should_override_execution_in_current_process() const override { return true; }
|
||||
|
||||
String m_variable_name;
|
||||
Optional<NameWithPosition> m_variable;
|
||||
Optional<NameWithPosition> m_index_variable;
|
||||
RefPtr<AST::Node> m_iterated_expression;
|
||||
RefPtr<AST::Node> m_block;
|
||||
Optional<Position> m_in_kw_position;
|
||||
Optional<Position> m_index_kw_position;
|
||||
};
|
||||
|
||||
class Glob final : public Node {
|
||||
|
|
|
@ -341,8 +341,13 @@ void Formatter::visit(const AST::ForLoop* node)
|
|||
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
|
||||
|
||||
if (!is_loop) {
|
||||
if (node->variable_name() != "it") {
|
||||
current_builder().append(node->variable_name());
|
||||
if (node->index_variable().has_value()) {
|
||||
current_builder().append("index ");
|
||||
current_builder().append(node->index_variable()->name);
|
||||
current_builder().append(" ");
|
||||
}
|
||||
if (node->variable().has_value() && node->variable()->name != "it") {
|
||||
current_builder().append(node->variable()->name);
|
||||
current_builder().append(" in ");
|
||||
}
|
||||
|
||||
|
|
|
@ -592,16 +592,46 @@ RefPtr<AST::Node> Parser::parse_for_loop()
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
auto variable_name = consume_while(is_word_character);
|
||||
Optional<AST::Position> in_start_position;
|
||||
if (variable_name.is_empty()) {
|
||||
variable_name = "it";
|
||||
} else {
|
||||
Optional<AST::NameWithPosition> index_variable_name, variable_name;
|
||||
Optional<AST::Position> in_start_position, index_start_position;
|
||||
|
||||
auto offset_before_index = current_position();
|
||||
if (expect("index")) {
|
||||
auto offset = current_position();
|
||||
if (!consume_while(is_whitespace).is_empty()) {
|
||||
auto offset_before_variable = current_position();
|
||||
auto variable = consume_while(is_word_character);
|
||||
if (!variable.is_empty()) {
|
||||
index_start_position = AST::Position { offset_before_index.offset, offset.offset, offset_before_index.line, offset.line };
|
||||
|
||||
auto offset_after_variable = current_position();
|
||||
index_variable_name = AST::NameWithPosition {
|
||||
variable,
|
||||
{ offset_before_variable.offset, offset_after_variable.offset, offset_before_variable.line, offset_after_variable.line },
|
||||
};
|
||||
|
||||
consume_while(is_whitespace);
|
||||
} else {
|
||||
restore_to(offset_before_index.offset, offset_before_index.line);
|
||||
}
|
||||
} else {
|
||||
restore_to(offset_before_index.offset, offset_before_index.line);
|
||||
}
|
||||
}
|
||||
|
||||
auto variable_name_start_offset = current_position();
|
||||
auto name = consume_while(is_word_character);
|
||||
auto variable_name_end_offset = current_position();
|
||||
if (!name.is_empty()) {
|
||||
variable_name = AST::NameWithPosition {
|
||||
name,
|
||||
{ variable_name_start_offset.offset, variable_name_end_offset.offset, variable_name_start_offset.line, variable_name_end_offset.line }
|
||||
};
|
||||
consume_while(is_whitespace);
|
||||
auto in_error_start = push_start();
|
||||
if (!expect("in")) {
|
||||
auto syntax_error = create<AST::SyntaxError>("Expected 'in' after a variable name in a 'for' loop", true);
|
||||
return create<AST::ForLoop>(move(variable_name), move(syntax_error), nullptr); // ForLoop Var Iterated Block
|
||||
return create<AST::ForLoop>(move(variable_name), move(index_variable_name), move(syntax_error), nullptr); // ForLoop Var Iterated Block
|
||||
}
|
||||
in_start_position = AST::Position { in_error_start->offset, m_offset, in_error_start->line, line() };
|
||||
}
|
||||
|
@ -620,7 +650,7 @@ RefPtr<AST::Node> Parser::parse_for_loop()
|
|||
auto obrace_error_start = push_start();
|
||||
if (!expect('{')) {
|
||||
auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'for' loop body", true);
|
||||
return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(syntax_error), move(in_start_position)); // ForLoop Var Iterated Block
|
||||
return create<AST::ForLoop>(move(variable_name), move(index_variable_name), move(iterated_expression), move(syntax_error), move(in_start_position), move(index_start_position)); // ForLoop Var Iterated Block
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,7 +669,7 @@ RefPtr<AST::Node> Parser::parse_for_loop()
|
|||
}
|
||||
}
|
||||
|
||||
return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block
|
||||
return create<AST::ForLoop>(move(variable_name), move(index_variable_name), move(iterated_expression), move(body), move(in_start_position), move(index_start_position)); // ForLoop Var Iterated Block
|
||||
}
|
||||
|
||||
RefPtr<AST::Node> Parser::parse_loop_loop()
|
||||
|
@ -657,7 +687,7 @@ RefPtr<AST::Node> Parser::parse_loop_loop()
|
|||
auto obrace_error_start = push_start();
|
||||
if (!expect('{')) {
|
||||
auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'loop' loop body", true);
|
||||
return create<AST::ForLoop>(String::empty(), nullptr, move(syntax_error), Optional<AST::Position> {}); // ForLoop null null Block
|
||||
return create<AST::ForLoop>(AST::NameWithPosition {}, AST::NameWithPosition {}, nullptr, move(syntax_error)); // ForLoop null null Block
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,7 +706,7 @@ RefPtr<AST::Node> Parser::parse_loop_loop()
|
|||
}
|
||||
}
|
||||
|
||||
return create<AST::ForLoop>(String::empty(), nullptr, move(body), Optional<AST::Position> {}); // ForLoop null null Block
|
||||
return create<AST::ForLoop>(AST::NameWithPosition {}, AST::NameWithPosition {}, nullptr, move(body)); // ForLoop null null Block
|
||||
}
|
||||
|
||||
RefPtr<AST::Node> Parser::parse_if_expr()
|
||||
|
|
|
@ -207,7 +207,7 @@ control_structure[c] :: for_expr
|
|||
continuation_control :: 'break'
|
||||
| 'continue'
|
||||
|
||||
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}'
|
||||
for_expr :: 'for' ws+ (('index' ' '+ identifier ' '+)? identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}'
|
||||
|
||||
loop_expr :: 'loop' ws* '{' [c] toplevel '}'
|
||||
|
||||
|
|
|
@ -261,6 +261,35 @@ private:
|
|||
set_offset_range_end(in_span.range, position.end_line);
|
||||
in_span.attributes.color = m_palette.syntax_keyword();
|
||||
}
|
||||
|
||||
// "index"
|
||||
if (auto maybe_position = node->index_keyword_position(); maybe_position.has_value()) {
|
||||
auto& position = maybe_position.value();
|
||||
|
||||
auto& index_span = span_for_node(node);
|
||||
set_offset_range_start(index_span.range, position.start_line);
|
||||
set_offset_range_end(index_span.range, position.end_line);
|
||||
index_span.attributes.color = m_palette.syntax_keyword();
|
||||
}
|
||||
|
||||
// variables
|
||||
if (auto maybe_variable = node->variable(); maybe_variable.has_value()) {
|
||||
auto& position = maybe_variable->position;
|
||||
|
||||
auto& variable_span = span_for_node(node);
|
||||
set_offset_range_start(variable_span.range, position.start_line);
|
||||
set_offset_range_end(variable_span.range, position.end_line);
|
||||
variable_span.attributes.color = m_palette.syntax_identifier();
|
||||
}
|
||||
|
||||
if (auto maybe_variable = node->index_variable(); maybe_variable.has_value()) {
|
||||
auto& position = maybe_variable->position;
|
||||
|
||||
auto& variable_span = span_for_node(node);
|
||||
set_offset_range_start(variable_span.range, position.start_line);
|
||||
set_offset_range_end(variable_span.range, position.end_line);
|
||||
variable_span.attributes.color = m_palette.syntax_identifier();
|
||||
}
|
||||
}
|
||||
virtual void visit(const AST::Glob* node) override
|
||||
{
|
||||
|
|
|
@ -23,6 +23,19 @@ for cmd in ((test 1 = 1) (test 2 = 2)) {
|
|||
$cmd || unset singlecommand_ok
|
||||
}
|
||||
|
||||
# with index
|
||||
for index i val in (0 1 2) {
|
||||
if not test "$i" -eq "$val" {
|
||||
unset singlecommand_ok
|
||||
}
|
||||
}
|
||||
|
||||
for index i val in (1 2 3) {
|
||||
if not test "$i" -ne "$val" {
|
||||
unset singlecommand_ok
|
||||
}
|
||||
}
|
||||
|
||||
# Multiple commands in block
|
||||
for cmd in ((test 1 = 1) (test 2 = 2)) {
|
||||
test -z "$cmd"
|
||||
|
|
Loading…
Reference in a new issue