mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 13:07:46 +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
8 changed files with 133 additions and 23 deletions
|
@ -211,9 +211,12 @@ if A {
|
||||||
##### For Loops
|
##### For Loops
|
||||||
For Loops evaluate a sequence of commands once per element in a given list.
|
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 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.
|
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
|
# Iterate over a sequence and write each element to a file
|
||||||
$ for i in $(seq 1 100) { echo $i >> foo }
|
$ 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
|
##### Infinite Loops
|
||||||
|
@ -365,7 +371,7 @@ control_structure[c] :: for_expr
|
||||||
continuation_control :: 'break'
|
continuation_control :: 'break'
|
||||||
| 'continue'
|
| '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 '}'
|
loop_expr :: 'loop' ws* '{' [c] toplevel '}'
|
||||||
|
|
||||||
|
|
|
@ -1059,7 +1059,10 @@ FunctionDeclaration::~FunctionDeclaration()
|
||||||
void ForLoop::dump(int level) const
|
void ForLoop::dump(int level) const
|
||||||
{
|
{
|
||||||
Node::dump(level);
|
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)
|
if (m_iterated_expression)
|
||||||
m_iterated_expression->dump(level + 2);
|
m_iterated_expression->dump(level + 2);
|
||||||
else
|
else
|
||||||
|
@ -1110,6 +1113,9 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (m_iterated_expression) {
|
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) {
|
m_iterated_expression->for_each_entry(shell, [&](auto value) {
|
||||||
if (consecutive_interruptions == 2)
|
if (consecutive_interruptions == 2)
|
||||||
return IterationDecision::Break;
|
return IterationDecision::Break;
|
||||||
|
@ -1118,10 +1124,16 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
|
||||||
|
|
||||||
{
|
{
|
||||||
auto frame = shell->push_frame(String::formatted("for ({})", this));
|
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);
|
block_value = m_block->run(shell);
|
||||||
}
|
}
|
||||||
|
|
||||||
return run(block_value);
|
return run(block_value);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -1146,10 +1158,19 @@ void ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightM
|
||||||
if (m_in_kw_position.has_value())
|
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) });
|
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;
|
metadata.is_first_in_list = false;
|
||||||
m_iterated_expression->highlight_in_editor(editor, shell, metadata);
|
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;
|
metadata.is_first_in_list = true;
|
||||||
if (m_block)
|
if (m_block)
|
||||||
m_block->highlight_in_editor(editor, shell, metadata);
|
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);
|
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))
|
: 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_iterated_expression(move(iterated_expr))
|
||||||
, m_block(move(block))
|
, m_block(move(block))
|
||||||
, m_in_kw_position(move(in_kw_position))
|
, 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())
|
if (m_iterated_expression && m_iterated_expression->is_syntax_error())
|
||||||
set_is_syntax_error(m_iterated_expression->syntax_error_node());
|
set_is_syntax_error(m_iterated_expression->syntax_error_node());
|
||||||
|
|
|
@ -846,13 +846,15 @@ private:
|
||||||
|
|
||||||
class ForLoop final : public Node {
|
class ForLoop final : public Node {
|
||||||
public:
|
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 ~ForLoop();
|
||||||
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
|
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>& iterated_expression() const { return m_iterated_expression; }
|
||||||
const RefPtr<Node>& block() const { return m_block; }
|
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; }
|
const Optional<Position> in_keyword_position() const { return m_in_kw_position; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -864,10 +866,12 @@ private:
|
||||||
virtual bool would_execute() const override { return true; }
|
virtual bool would_execute() const override { return true; }
|
||||||
virtual bool should_override_execution_in_current_process() 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_iterated_expression;
|
||||||
RefPtr<AST::Node> m_block;
|
RefPtr<AST::Node> m_block;
|
||||||
Optional<Position> m_in_kw_position;
|
Optional<Position> m_in_kw_position;
|
||||||
|
Optional<Position> m_index_kw_position;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Glob final : public Node {
|
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 };
|
TemporaryChange<const AST::Node*> parent { m_parent_node, node };
|
||||||
|
|
||||||
if (!is_loop) {
|
if (!is_loop) {
|
||||||
if (node->variable_name() != "it") {
|
if (node->index_variable().has_value()) {
|
||||||
current_builder().append(node->variable_name());
|
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 ");
|
current_builder().append(" in ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -592,16 +592,46 @@ RefPtr<AST::Node> Parser::parse_for_loop()
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto variable_name = consume_while(is_word_character);
|
Optional<AST::NameWithPosition> index_variable_name, variable_name;
|
||||||
Optional<AST::Position> in_start_position;
|
Optional<AST::Position> in_start_position, index_start_position;
|
||||||
if (variable_name.is_empty()) {
|
|
||||||
variable_name = "it";
|
auto offset_before_index = current_position();
|
||||||
} else {
|
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);
|
consume_while(is_whitespace);
|
||||||
auto in_error_start = push_start();
|
auto in_error_start = push_start();
|
||||||
if (!expect("in")) {
|
if (!expect("in")) {
|
||||||
auto syntax_error = create<AST::SyntaxError>("Expected 'in' after a variable name in a 'for' loop", true);
|
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() };
|
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();
|
auto obrace_error_start = push_start();
|
||||||
if (!expect('{')) {
|
if (!expect('{')) {
|
||||||
auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'for' loop body", true);
|
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()
|
RefPtr<AST::Node> Parser::parse_loop_loop()
|
||||||
|
@ -657,7 +687,7 @@ RefPtr<AST::Node> Parser::parse_loop_loop()
|
||||||
auto obrace_error_start = push_start();
|
auto obrace_error_start = push_start();
|
||||||
if (!expect('{')) {
|
if (!expect('{')) {
|
||||||
auto syntax_error = create<AST::SyntaxError>("Expected an open brace '{' to start a 'loop' loop body", true);
|
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()
|
RefPtr<AST::Node> Parser::parse_if_expr()
|
||||||
|
|
|
@ -207,7 +207,7 @@ control_structure[c] :: for_expr
|
||||||
continuation_control :: 'break'
|
continuation_control :: 'break'
|
||||||
| 'continue'
|
| '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 '}'
|
loop_expr :: 'loop' ws* '{' [c] toplevel '}'
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,35 @@ private:
|
||||||
set_offset_range_end(in_span.range, position.end_line);
|
set_offset_range_end(in_span.range, position.end_line);
|
||||||
in_span.attributes.color = m_palette.syntax_keyword();
|
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
|
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
|
$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
|
# Multiple commands in block
|
||||||
for cmd in ((test 1 = 1) (test 2 = 2)) {
|
for cmd in ((test 1 = 1) (test 2 = 2)) {
|
||||||
test -z "$cmd"
|
test -z "$cmd"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue