Shell: Add runtime errors and implement break/continue

Such errors are raised when SyntaxError nodes are executed, and are also
used for internal control flow.
The 'break' and 'continue' commands are currently only allowed inside
for loops, and outside function bodies.

This also adds a 'loop' keyword for infinite loops.
This commit is contained in:
AnotherTest 2020-12-10 18:25:13 +03:30 committed by Andreas Kling
parent 9bd81f34a5
commit 5e5eb615ec
Notes: sideshowbarker 2024-07-19 00:26:20 +09:00
14 changed files with 384 additions and 62 deletions

View file

@ -221,6 +221,28 @@ $ for * { mv $it 1-$it }
$ for i in $(seq 1 100) { echo $i >> foo } $ for i in $(seq 1 100) { echo $i >> foo }
``` ```
##### Infinite Loops
Infinite loops (as denoted by the keyword `loop`) can be used to repeat a block until the block runs `break`, or the loop terminates by external sources (interrupts, program exit, and terminating signals).
The behaviour regarding SIGINT and other signals is the same as for loops (mentioned above).
###### Examples
```sh
# Keep deleting a file
loop {
rm -f foo
}
```
###### Examples
```sh
# Iterate over every non-hidden file in the current directory, and prepend '1-' to its name.
$ 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 }
```
##### Subshells ##### Subshells
Subshells evaluate a given block in a new instance (fork) of the current shell process. to create a subshell, any valid shell code can be enclosed in braces. Subshells evaluate a given block in a new instance (fork) of the current shell process. to create a subshell, any valid shell code can be enclosed in braces.
@ -299,7 +321,7 @@ sequence :: variable_decls? or_logical_sequence terminator sequence
| variable_decls? function_decl (terminator sequence)? | variable_decls? function_decl (terminator sequence)?
| variable_decls? terminator sequence | variable_decls? terminator sequence
function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' toplevel '}' function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' [!c] toplevel '}'
or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence
| and_logical_sequence | and_logical_sequence
@ -318,12 +340,19 @@ pipe_sequence :: command '|' pipe_sequence
| control_structure '|' pipe_sequence | control_structure '|' pipe_sequence
| control_structure | control_structure
control_structure :: for_expr control_structure[c] :: for_expr
| if_expr | loop_expr
| subshell | if_expr
| match_expr | subshell
| match_expr
| ?c: continuation_control
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' toplevel '}' continuation_control :: 'break'
| 'continue'
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}'
loop_expr :: 'loop' ws* '{' [c] toplevel '}'
if_expr :: 'if' ws+ or_logical_sequence ws+ '{' toplevel '}' else_clause? if_expr :: 'if' ws+ or_logical_sequence ws+ '{' toplevel '}' else_clause?

View file

@ -190,6 +190,13 @@ private:
auto& span = span_for_node(node); auto& span = span_for_node(node);
span.color = m_palette.syntax_comment(); span.color = m_palette.syntax_comment();
} }
virtual void visit(const AST::ContinuationControl* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.color = m_palette.syntax_control_keyword();
}
virtual void visit(const AST::DynamicEvaluate* node) override virtual void visit(const AST::DynamicEvaluate* node) override
{ {
NodeVisitor::visit(node); NodeVisitor::visit(node);

View file

@ -792,6 +792,28 @@ Comment::~Comment()
{ {
} }
void ContinuationControl::dump(int level) const
{
Node::dump(level);
print_indented(m_kind == Continue ? "(Continue)" : "(Break)", level + 1);
}
RefPtr<Value> ContinuationControl::run(RefPtr<Shell> shell)
{
if (m_kind == Break)
shell->raise_error(Shell::ShellError::InternalControlFlowBreak, {});
else if (m_kind == Continue)
shell->raise_error(Shell::ShellError::InternalControlFlowContinue, {});
else
ASSERT_NOT_REACHED();
return create<ListValue>({});
}
void ContinuationControl::highlight_in_editor(Line::Editor& editor, Shell&, HighlightMetadata)
{
editor.stylize({ m_position.start_offset, m_position.end_offset }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
}
void DoubleQuotedString::dump(int level) const void DoubleQuotedString::dump(int level) const
{ {
Node::dump(level); Node::dump(level);
@ -1011,7 +1033,10 @@ void ForLoop::dump(int level) const
{ {
Node::dump(level); Node::dump(level);
print_indented(String::format("%s in\n", m_variable_name.characters()), level + 1); print_indented(String::format("%s in\n", m_variable_name.characters()), level + 1);
m_iterated_expression->dump(level + 2); if (m_iterated_expression)
m_iterated_expression->dump(level + 2);
else
print_indented("(ever)", level + 2);
print_indented("Running", level + 1); print_indented("Running", level + 1);
if (m_block) if (m_block)
m_block->dump(level + 2); m_block->dump(level + 2);
@ -1025,18 +1050,15 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
return create<ListValue>({}); return create<ListValue>({});
size_t consecutive_interruptions = 0; size_t consecutive_interruptions = 0;
auto run = [&](auto& block_value) {
m_iterated_expression->for_each_entry(shell, [&](auto value) { if (shell->has_error(Shell::ShellError::InternalControlFlowBreak)) {
if (consecutive_interruptions == 2) shell->take_error();
return IterationDecision::Break; return IterationDecision::Break;
}
RefPtr<Value> block_value; if (shell->has_error(Shell::ShellError::InternalControlFlowContinue)) {
shell->take_error();
{ return IterationDecision::Continue;
auto frame = shell->push_frame(String::formatted("for ({})", this));
shell->set_local_variable(m_variable_name, value, true);
block_value = m_block->run(shell);
} }
if (block_value->is_job()) { if (block_value->is_job()) {
@ -1055,19 +1077,48 @@ RefPtr<Value> ForLoop::run(RefPtr<Shell> shell)
} }
} }
return IterationDecision::Continue; return IterationDecision::Continue;
}); };
if (m_iterated_expression) {
m_iterated_expression->for_each_entry(shell, [&](auto value) {
if (consecutive_interruptions == 2)
return IterationDecision::Break;
RefPtr<Value> block_value;
{
auto frame = shell->push_frame(String::formatted("for ({})", this));
shell->set_local_variable(m_variable_name, value, true);
block_value = m_block->run(shell);
}
return run(block_value);
});
} else {
for (;;) {
if (consecutive_interruptions == 2)
break;
RefPtr<Value> block_value = m_block->run(shell);
if (run(block_value) == IterationDecision::Break)
break;
}
}
return create<ListValue>({}); return create<ListValue>({});
} }
void ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata) void ForLoop::highlight_in_editor(Line::Editor& editor, Shell& shell, HighlightMetadata metadata)
{ {
editor.stylize({ m_position.start_offset, m_position.start_offset + 3 }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) }); auto is_loop = m_iterated_expression.is_null();
if (m_in_kw_position.has_value()) editor.stylize({ m_position.start_offset, m_position.start_offset + (is_loop ? 4 : 3) }, { 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 (!is_loop) {
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) });
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);
}
metadata.is_first_in_list = true; metadata.is_first_in_list = true;
if (m_block) if (m_block)
@ -1079,8 +1130,10 @@ HitTestResult ForLoop::hit_test_position(size_t offset)
if (!position().contains(offset)) if (!position().contains(offset))
return {}; return {};
if (auto result = m_iterated_expression->hit_test_position(offset); result.matching_node) if (m_iterated_expression) {
return result; if (auto result = m_iterated_expression->hit_test_position(offset); result.matching_node)
return result;
}
if (!m_block) if (!m_block)
return {}; return {};
@ -1088,14 +1141,14 @@ HitTestResult ForLoop::hit_test_position(size_t offset)
return m_block->hit_test_position(offset); return m_block->hit_test_position(offset);
} }
ForLoop::ForLoop(Position position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position) ForLoop::ForLoop(Position position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position)
: Node(move(position)) : Node(move(position))
, m_variable_name(move(variable_name)) , m_variable_name(move(variable_name))
, 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))
{ {
if (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());
else if (m_block && m_block->is_syntax_error()) else if (m_block && m_block->is_syntax_error())
set_is_syntax_error(m_block->syntax_error_node()); set_is_syntax_error(m_block->syntax_error_node());
@ -1221,8 +1274,10 @@ void Execute::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(Non
notifier->set_event_mask(Core::Notifier::Read); notifier->set_event_mask(Core::Notifier::Read);
} }; } };
if (check_and_call() == Break) if (check_and_call() == Break) {
loop.quit(Break);
return; return;
}
auto read_size = read(pipefd[0], buffer, remaining_size); auto read_size = read(pipefd[0], buffer, remaining_size);
if (read_size < 0) { if (read_size < 0) {
@ -1243,12 +1298,20 @@ void Execute::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(Non
stream.write({ buffer, (size_t)read_size }); stream.write({ buffer, (size_t)read_size });
} }
loop.quit(Break); loop.quit(NothingLeft);
}; };
shell->run_commands(commands); auto jobs = shell->run_commands(commands);
ScopeGuard kill_jobs_if_around { [&] {
for (auto& job : jobs) {
if (job.is_running_in_background() && !job.exited() && !job.signaled()) {
job.set_should_announce_signal(false); // We're explicitly killing it here.
shell->kill_job(&job, SIGTERM);
}
}
} };
loop.exec(); auto exit_reason = loop.exec();
notifier->on_ready_to_read = nullptr; notifier->on_ready_to_read = nullptr;
@ -1256,7 +1319,7 @@ void Execute::for_each_entry(RefPtr<Shell> shell, Function<IterationDecision(Non
dbgln("close() failed: {}", strerror(errno)); dbgln("close() failed: {}", strerror(errno));
} }
if (!stream.eof()) { if (exit_reason != Break && !stream.eof()) {
auto action = Continue; auto action = Continue;
do { do {
action = check_and_call(); action = check_and_call();
@ -1610,8 +1673,7 @@ RefPtr<Value> MatchExpr::run(RefPtr<Shell> shell)
} }
} }
// FIXME: Somehow raise an error in the shell. shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, "Non-exhaustive match rules!");
dbgln("Non-exhaustive match rules!");
return create<AST::ListValue>({}); return create<AST::ListValue>({});
} }
@ -1927,7 +1989,7 @@ RefPtr<Value> Range::run(RefPtr<Shell> shell)
} }
} else { } else {
yield_start_end:; yield_start_end:;
warnln("Shell: Cannot interpolate between '{}' and '{}'!", start_str, end_str); shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, String::formatted("Cannot interpolate between '{}' and '{}'!", start_str, end_str));
// We can't really interpolate between the two, so just yield both. // We can't really interpolate between the two, so just yield both.
values.append(create<StringValue>(move(start_str))); values.append(create<StringValue>(move(start_str)));
values.append(create<StringValue>(move(end_str))); values.append(create<StringValue>(move(end_str)));
@ -2471,10 +2533,9 @@ void SyntaxError::dump(int level) const
print_indented(String::formatted("{}", m_is_continuable), level + 2); print_indented(String::formatted("{}", m_is_continuable), level + 2);
} }
RefPtr<Value> SyntaxError::run(RefPtr<Shell>) RefPtr<Value> SyntaxError::run(RefPtr<Shell> shell)
{ {
dbgln("SYNTAX ERROR executed from"); shell->raise_error(Shell::ShellError::EvaluatedSyntaxError, m_syntax_error_text);
dump(1);
return create<StringValue>(""); return create<StringValue>("");
} }
@ -2786,7 +2847,10 @@ Vector<String> GlobValue::resolve_as_list(RefPtr<Shell> shell)
if (!shell) if (!shell)
return { m_glob }; return { m_glob };
return shell->expand_globs(m_glob, shell->cwd); auto results = shell->expand_globs(m_glob, shell->cwd);
if (results.is_empty())
shell->raise_error(Shell::ShellError::InvalidGlobError, "Glob did not match anything!");
return results;
} }
SimpleVariableValue::~SimpleVariableValue() SimpleVariableValue::~SimpleVariableValue()

View file

@ -447,6 +447,7 @@ public:
CloseFdRedirection, CloseFdRedirection,
CommandLiteral, CommandLiteral,
Comment, Comment,
ContinuationControl,
DynamicEvaluate, DynamicEvaluate,
DoubleQuotedString, DoubleQuotedString,
Fd2FdRedirection, Fd2FdRedirection,
@ -700,6 +701,32 @@ private:
String m_text; String m_text;
}; };
class ContinuationControl final : public Node {
public:
enum ContinuationKind {
Break,
Continue,
};
ContinuationControl(Position position, ContinuationKind kind)
: Node(move(position))
, m_kind(kind)
{
}
virtual ~ContinuationControl() { }
virtual void visit(NodeVisitor& visitor) override { visitor.visit(this); }
ContinuationKind continuation_kind() const { return m_kind; }
private:
NODE(ContinuationControl);
virtual void dump(int level) const override;
virtual RefPtr<Value> run(RefPtr<Shell>) override;
virtual void highlight_in_editor(Line::Editor&, Shell&, HighlightMetadata = {}) override;
ContinuationKind m_kind { ContinuationKind::Break };
};
class DynamicEvaluate final : public Node { class DynamicEvaluate final : public Node {
public: public:
DynamicEvaluate(Position, NonnullRefPtr<Node>); DynamicEvaluate(Position, NonnullRefPtr<Node>);
@ -795,12 +822,12 @@ private:
class ForLoop final : public Node { class ForLoop final : public Node {
public: public:
ForLoop(Position, String variable_name, NonnullRefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_kw_position = {}); ForLoop(Position, String variable_name, RefPtr<AST::Node> iterated_expr, RefPtr<AST::Node> block, Optional<Position> in_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 String& variable_name() const { return m_variable_name; }
const NonnullRefPtr<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> in_keyword_position() const { return m_in_kw_position; } const Optional<Position> in_keyword_position() const { return m_in_kw_position; }
@ -813,7 +840,7 @@ private:
virtual bool would_execute() const override { return true; } virtual bool would_execute() const override { return true; }
String m_variable_name; String m_variable_name;
NonnullRefPtr<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;
}; };

View file

@ -261,6 +261,19 @@ void Formatter::visit(const AST::Comment* node)
visited(node); visited(node);
} }
void Formatter::visit(const AST::ContinuationControl* node)
{
will_visit(node);
test_and_update_output_cursor(node);
if (node->continuation_kind() == AST::ContinuationControl::Break)
current_builder().append("break");
else if (node->continuation_kind() == AST::ContinuationControl::Continue)
current_builder().append("continue");
else
ASSERT_NOT_REACHED();
visited(node);
}
void Formatter::visit(const AST::DynamicEvaluate* node) void Formatter::visit(const AST::DynamicEvaluate* node)
{ {
will_visit(node); will_visit(node);
@ -327,15 +340,18 @@ void Formatter::visit(const AST::ForLoop* node)
{ {
will_visit(node); will_visit(node);
test_and_update_output_cursor(node); test_and_update_output_cursor(node);
current_builder().append("for "); auto is_loop = node->iterated_expression().is_null();
current_builder().append(is_loop ? "loop" : "for ");
TemporaryChange<const AST::Node*> parent { m_parent_node, node }; TemporaryChange<const AST::Node*> parent { m_parent_node, node };
if (node->variable_name() != "it") { if (!is_loop) {
current_builder().append(node->variable_name()); if (node->variable_name() != "it") {
current_builder().append(" in "); current_builder().append(node->variable_name());
} current_builder().append(" in ");
}
node->iterated_expression()->visit(*this); node->iterated_expression()->visit(*this);
}
current_builder().append(' '); current_builder().append(' ');
in_new_block([&] { in_new_block([&] {

View file

@ -64,6 +64,7 @@ private:
virtual void visit(const AST::CloseFdRedirection*) override; virtual void visit(const AST::CloseFdRedirection*) override;
virtual void visit(const AST::CommandLiteral*) override; virtual void visit(const AST::CommandLiteral*) override;
virtual void visit(const AST::Comment*) override; virtual void visit(const AST::Comment*) override;
virtual void visit(const AST::ContinuationControl*) override;
virtual void visit(const AST::DynamicEvaluate*) override; virtual void visit(const AST::DynamicEvaluate*) override;
virtual void visit(const AST::DoubleQuotedString*) override; virtual void visit(const AST::DoubleQuotedString*) override;
virtual void visit(const AST::Fd2FdRedirection*) override; virtual void visit(const AST::Fd2FdRedirection*) override;

View file

@ -47,6 +47,7 @@ class CastToList;
class CloseFdRedirection; class CloseFdRedirection;
class CommandLiteral; class CommandLiteral;
class Comment; class Comment;
class ContinuationControl;
class DynamicEvaluate; class DynamicEvaluate;
class DoubleQuotedString; class DoubleQuotedString;
class Fd2FdRedirection; class Fd2FdRedirection;

View file

@ -84,6 +84,10 @@ void NodeVisitor::visit(const AST::Comment*)
{ {
} }
void NodeVisitor::visit(const AST::ContinuationControl*)
{
}
void NodeVisitor::visit(const AST::DynamicEvaluate* node) void NodeVisitor::visit(const AST::DynamicEvaluate* node)
{ {
node->inner()->visit(*this); node->inner()->visit(*this);
@ -107,7 +111,8 @@ void NodeVisitor::visit(const AST::FunctionDeclaration* node)
void NodeVisitor::visit(const AST::ForLoop* node) void NodeVisitor::visit(const AST::ForLoop* node)
{ {
node->iterated_expression()->visit(*this); if (node->iterated_expression())
node->iterated_expression()->visit(*this);
if (node->block()) if (node->block())
node->block()->visit(*this); node->block()->visit(*this);
} }

View file

@ -43,6 +43,7 @@ public:
virtual void visit(const AST::CloseFdRedirection*); virtual void visit(const AST::CloseFdRedirection*);
virtual void visit(const AST::CommandLiteral*); virtual void visit(const AST::CommandLiteral*);
virtual void visit(const AST::Comment*); virtual void visit(const AST::Comment*);
virtual void visit(const AST::ContinuationControl*);
virtual void visit(const AST::DynamicEvaluate*); virtual void visit(const AST::DynamicEvaluate*);
virtual void visit(const AST::DoubleQuotedString*); virtual void visit(const AST::DoubleQuotedString*);
virtual void visit(const AST::Fd2FdRedirection*); virtual void visit(const AST::Fd2FdRedirection*);

View file

@ -362,6 +362,7 @@ RefPtr<AST::Node> Parser::parse_function_decl()
} }
} }
TemporaryChange controls { m_continuation_controls_allowed, false };
auto body = parse_toplevel(); auto body = parse_toplevel();
{ {
@ -499,9 +500,15 @@ RefPtr<AST::Node> Parser::parse_control_structure()
{ {
auto rule_start = push_start(); auto rule_start = push_start();
consume_while(is_whitespace); consume_while(is_whitespace);
if (auto control = parse_continuation_control())
return control;
if (auto for_loop = parse_for_loop()) if (auto for_loop = parse_for_loop())
return for_loop; return for_loop;
if (auto loop = parse_loop_loop())
return loop;
if (auto if_expr = parse_if_expr()) if (auto if_expr = parse_if_expr())
return if_expr; return if_expr;
@ -514,6 +521,40 @@ RefPtr<AST::Node> Parser::parse_control_structure()
return nullptr; return nullptr;
} }
RefPtr<AST::Node> Parser::parse_continuation_control()
{
if (!m_continuation_controls_allowed)
return nullptr;
auto rule_start = push_start();
if (expect("break")) {
{
auto break_end = push_start();
if (consume_while(is_any_of(" \t\n;")).is_empty()) {
restore_to(*rule_start);
return nullptr;
}
restore_to(*break_end);
}
return create<AST::ContinuationControl>(AST::ContinuationControl::Break);
}
if (expect("continue")) {
{
auto continue_end = push_start();
if (consume_while(is_any_of(" \t\n;")).is_empty()) {
restore_to(*rule_start);
return nullptr;
}
restore_to(*continue_end);
}
return create<AST::ContinuationControl>(AST::ContinuationControl::Continue);
}
return nullptr;
}
RefPtr<AST::Node> Parser::parse_for_loop() RefPtr<AST::Node> Parser::parse_for_loop()
{ {
auto rule_start = push_start(); auto rule_start = push_start();
@ -544,10 +585,8 @@ RefPtr<AST::Node> Parser::parse_for_loop()
{ {
auto iter_error_start = push_start(); auto iter_error_start = push_start();
iterated_expression = parse_expression(); iterated_expression = parse_expression();
if (!iterated_expression) { if (!iterated_expression)
auto syntax_error = create<AST::SyntaxError>("Expected an expression in 'for' loop", true); iterated_expression = create<AST::SyntaxError>("Expected an expression in 'for' loop", true);
return create<AST::ForLoop>(move(variable_name), move(syntax_error), nullptr, move(in_start_position)); // ForLoop Var Iterated Block
}
} }
consume_while(is_any_of(" \t\n")); consume_while(is_any_of(" \t\n"));
@ -555,10 +594,11 @@ 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), iterated_expression.release_nonnull(), move(syntax_error), move(in_start_position)); // ForLoop Var Iterated Block return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(syntax_error), move(in_start_position)); // ForLoop Var Iterated Block
} }
} }
TemporaryChange controls { m_continuation_controls_allowed, true };
auto body = parse_toplevel(); auto body = parse_toplevel();
{ {
@ -573,7 +613,44 @@ RefPtr<AST::Node> Parser::parse_for_loop()
} }
} }
return create<AST::ForLoop>(move(variable_name), iterated_expression.release_nonnull(), move(body), move(in_start_position)); // ForLoop Var Iterated Block return create<AST::ForLoop>(move(variable_name), move(iterated_expression), move(body), move(in_start_position)); // ForLoop Var Iterated Block
}
RefPtr<AST::Node> Parser::parse_loop_loop()
{
auto rule_start = push_start();
if (!expect("loop"))
return nullptr;
if (consume_while(is_any_of(" \t\n")).is_empty()) {
restore_to(*rule_start);
return nullptr;
}
{
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
}
}
TemporaryChange controls { m_continuation_controls_allowed, true };
auto body = parse_toplevel();
{
auto cbrace_error_start = push_start();
if (!expect('}')) {
auto error_start = push_start();
auto syntax_error = create<AST::SyntaxError>("Expected a close brace '}' to end a 'loop' loop body", true);
if (body)
body->set_is_syntax_error(*syntax_error);
else
body = syntax_error;
}
}
return create<AST::ForLoop>(String::empty(), nullptr, move(body), Optional<AST::Position> {}); // ForLoop null null Block
} }
RefPtr<AST::Node> Parser::parse_if_expr() RefPtr<AST::Node> Parser::parse_if_expr()

View file

@ -61,7 +61,9 @@ private:
RefPtr<AST::Node> parse_pipe_sequence(); RefPtr<AST::Node> parse_pipe_sequence();
RefPtr<AST::Node> parse_command(); RefPtr<AST::Node> parse_command();
RefPtr<AST::Node> parse_control_structure(); RefPtr<AST::Node> parse_control_structure();
RefPtr<AST::Node> parse_continuation_control();
RefPtr<AST::Node> parse_for_loop(); RefPtr<AST::Node> parse_for_loop();
RefPtr<AST::Node> parse_loop_loop();
RefPtr<AST::Node> parse_if_expr(); RefPtr<AST::Node> parse_if_expr();
RefPtr<AST::Node> parse_subshell(); RefPtr<AST::Node> parse_subshell();
RefPtr<AST::Node> parse_match_expr(); RefPtr<AST::Node> parse_match_expr();
@ -139,6 +141,7 @@ private:
Vector<AST::Position::Line> m_rule_start_lines; Vector<AST::Position::Line> m_rule_start_lines;
bool m_is_in_brace_expansion_spec { false }; bool m_is_in_brace_expansion_spec { false };
bool m_continuation_controls_allowed { false };
}; };
#if 0 #if 0
@ -151,7 +154,7 @@ sequence :: variable_decls? or_logical_sequence terminator sequence
| variable_decls? function_decl (terminator sequence)? | variable_decls? function_decl (terminator sequence)?
| variable_decls? terminator sequence | variable_decls? terminator sequence
function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' toplevel '}' function_decl :: identifier '(' (ws* identifier)* ')' ws* '{' [!c] toplevel '}'
or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence or_logical_sequence :: and_logical_sequence '|' '|' and_logical_sequence
| and_logical_sequence | and_logical_sequence
@ -170,12 +173,19 @@ pipe_sequence :: command '|' pipe_sequence
| control_structure '|' pipe_sequence | control_structure '|' pipe_sequence
| control_structure | control_structure
control_structure :: for_expr control_structure[c] :: for_expr
| if_expr | loop_expr
| subshell | if_expr
| match_expr | subshell
| match_expr
| ?c: continuation_control
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' toplevel '}' continuation_control :: 'break'
| 'continue'
for_expr :: 'for' ws+ (identifier ' '+ 'in' ws*)? expression ws+ '{' [c] toplevel '}'
loop_expr :: 'loop' ws* '{' [c] toplevel '}'
if_expr :: 'if' ws+ or_logical_sequence ws+ '{' toplevel '}' else_clause? if_expr :: 'if' ws+ or_logical_sequence ws+ '{' toplevel '}' else_clause?

View file

@ -554,6 +554,8 @@ int Shell::run_command(const StringView& cmd)
// should not be used for execution! // should not be used for execution!
ASSERT(!m_default_constructed); ASSERT(!m_default_constructed);
take_error();
if (cmd.is_empty()) if (cmd.is_empty())
return 0; return 0;
@ -891,6 +893,10 @@ RefPtr<Job> Shell::run_command(const AST::Command& command)
void Shell::run_tail(const AST::Command& invoking_command, const AST::NodeWithAction& next_in_chain, int head_exit_code) void Shell::run_tail(const AST::Command& invoking_command, const AST::NodeWithAction& next_in_chain, int head_exit_code)
{ {
if (m_error != ShellError::None) {
possibly_print_error();
return;
}
auto evaluate = [&] { auto evaluate = [&] {
if (next_in_chain.node->would_execute()) { if (next_in_chain.node->would_execute()) {
next_in_chain.node->run(*this); next_in_chain.node->run(*this);
@ -929,6 +935,11 @@ void Shell::run_tail(RefPtr<Job> job)
NonnullRefPtrVector<Job> Shell::run_commands(Vector<AST::Command>& commands) NonnullRefPtrVector<Job> Shell::run_commands(Vector<AST::Command>& commands)
{ {
if (m_error != ShellError::None) {
possibly_print_error();
return {};
}
NonnullRefPtrVector<Job> spawned_jobs; NonnullRefPtrVector<Job> spawned_jobs;
for (auto& command : commands) { for (auto& command : commands) {
@ -1722,6 +1733,24 @@ void Shell::save_to(JsonObject& object)
object.set("jobs", move(job_objects)); object.set("jobs", move(job_objects));
} }
void Shell::possibly_print_error() const
{
switch (m_error) {
case ShellError::EvaluatedSyntaxError:
warnln("Shell Syntax Error: {}", m_error_description);
return;
case ShellError::InvalidGlobError:
case ShellError::NonExhaustiveMatchRules:
warnln("Shell: {}", m_error_description);
return;
case ShellError::InternalControlFlowBreak:
case ShellError::InternalControlFlowContinue:
return;
case ShellError::None:
return;
}
}
void FileDescriptionCollector::collect() void FileDescriptionCollector::collect()
{ {
for (auto fd : m_fds) for (auto fd : m_fds)

View file

@ -201,6 +201,31 @@ public:
ReadLine, ReadLine,
}; };
enum class ShellError {
None,
InternalControlFlowBreak,
InternalControlFlowContinue,
EvaluatedSyntaxError,
NonExhaustiveMatchRules,
InvalidGlobError,
};
void raise_error(ShellError kind, String description)
{
m_error = kind;
m_error_description = move(description);
}
bool has_error(ShellError err) const { return m_error == err; }
const String& error_description() const { return m_error_description; }
ShellError take_error()
{
auto err = m_error;
m_error = ShellError::None;
m_error_description = {};
return err;
}
void possibly_print_error() const;
#define __ENUMERATE_SHELL_OPTION(name, default_, description) \ #define __ENUMERATE_SHELL_OPTION(name, default_, description) \
bool name { default_ }; bool name { default_ };
@ -263,6 +288,9 @@ private:
bool m_is_subshell { false }; bool m_is_subshell { false };
bool m_should_reinstall_signal_handlers { true }; bool m_should_reinstall_signal_handlers { true };
ShellError m_error { ShellError::None };
String m_error_description;
bool m_should_format_live { false }; bool m_should_format_live { false };
RefPtr<Line::Editor> m_editor; RefPtr<Line::Editor> m_editor;

View file

@ -4,6 +4,10 @@ singlecommand_ok=yes
multicommand_ok=yes multicommand_ok=yes
inlineexec_ok=yes inlineexec_ok=yes
implicit_ok=yes implicit_ok=yes
infinite_ok=''
break_ok=yes
continue_ok=yes
break_in_infinite_ok=''
# Full form # Full form
# Empty # Empty
@ -42,9 +46,32 @@ for ((test 1 = 1) (test 2 = 2)) {
$it || unset implicit_ok $it || unset implicit_ok
} }
# Infinite loop
loop {
infinite_ok=yes
break
unset break_ok
}
# 'Continue'
for (1 2 3) {
continue
unset continue_ok
}
# 'break' in infinite external loop
for $(yes) {
break_in_infinite_ok=yes
break
}
test $singlecommand_ok || echo Fail: Single command inside for body test $singlecommand_ok || echo Fail: Single command inside for body
test $multicommand_ok || echo Fail: Multiple commands inside for body test $multicommand_ok || echo Fail: Multiple commands inside for body
test $inlineexec_ok || echo Fail: Inline Exec test $inlineexec_ok || echo Fail: Inline Exec
test $implicit_ok || echo Fail: implicit iter variable test $implicit_ok || echo Fail: implicit iter variable
test $infinite_ok || echo Fail: infinite loop
test $break_ok || echo Fail: break
test $continue_ok || echo Fail: continue
test $break_in_infinite_ok || echo Fail: break from external infinite loop
test "$singlecommand_ok $multicommand_ok $inlineexec_ok $implicit_ok" = "yes yes yes yes" || exit 1 test "$singlecommand_ok $multicommand_ok $inlineexec_ok $implicit_ok $infinite_ok $break_ok $continue_ok $break_in_infinite_ok" = "yes yes yes yes yes yes yes yes" || exit 1