266 changes: 266 additions & 0 deletions regression-tests/test-results/pure2-break-continue.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
// ----- Cpp2 support -----
#define CPP2_USE_MODULES Yes
#include "cpp2util.h"


#line 2 "pure2-break-continue.cpp2"
[[nodiscard]] auto main() -> int;
#line 20 "pure2-break-continue.cpp2"
auto while_continue_inner() -> void;
#line 36 "pure2-break-continue.cpp2"
auto while_continue_outer() -> void;
#line 52 "pure2-break-continue.cpp2"
auto while_break_inner() -> void;
#line 68 "pure2-break-continue.cpp2"
auto while_break_outer() -> void;
#line 84 "pure2-break-continue.cpp2"
auto do_continue_inner() -> void;
#line 103 "pure2-break-continue.cpp2"
auto do_continue_outer() -> void;
#line 122 "pure2-break-continue.cpp2"
auto do_break_inner() -> void;
#line 141 "pure2-break-continue.cpp2"
auto do_break_outer() -> void;
#line 160 "pure2-break-continue.cpp2"
auto for_continue_inner() -> void;
#line 178 "pure2-break-continue.cpp2"
auto for_continue_outer() -> void;
#line 196 "pure2-break-continue.cpp2"
auto for_break_inner() -> void;
#line 214 "pure2-break-continue.cpp2"
auto for_break_outer() -> void;

//=== Cpp2 definitions ==========================================================

#line 1 "pure2-break-continue.cpp2"

[[nodiscard]] auto main() -> int
{
std::cout << "while_continue_inner:\n "; while_continue_inner();
std::cout << "\nwhile_continue_outer:\n "; while_continue_outer();
std::cout << "\nwhile_break_inner:\n "; while_break_inner();
std::cout << "\nwhile_break_outer:\n "; while_break_outer();

std::cout << "\n\ndo_continue_inner:\n "; do_continue_inner();
std::cout << "\ndo_continue_outer:\n "; do_continue_outer();
std::cout << "\ndo_break_inner:\n "; do_break_inner();
std::cout << "\ndo_break_outer:\n "; do_break_outer();

std::cout << "\n\nfor_continue_inner:\n "; for_continue_inner();
std::cout << "\nfor_continue_outer:\n "; for_continue_outer();
std::cout << "\nfor_break_inner:\n "; for_break_inner();
std::cout << "\nfor_break_outer:\n "; for_break_outer();
}

auto while_continue_inner() -> void
{
auto i {0};
for( ; cpp2::cmp_less(i,3); ++i ) {{
auto j {0};
for( ; cpp2::cmp_less(j,3); ++j ) {{
std::cout << i << j << " ";
if (j == 1) {
goto CONTINUE_25_9;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(25_9) }
std::cout << "outer ";
} CPP2_BREAK_CONTINUE(23_5) }
}

auto while_continue_outer() -> void
{
auto i {0};
for( ; cpp2::cmp_less(i,3); ++i ) {{
auto j {0};
for( ; cpp2::cmp_less(j,3); ++j ) {{
std::cout << i << j << " ";
if (j == 1) {
goto CONTINUE_39_5;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(41_9) }
std::cout << "outer ";
} CPP2_BREAK_CONTINUE(39_5) }
}

auto while_break_inner() -> void
{
auto i {0};
for( ; cpp2::cmp_less(i,3); ++i ) {{
auto j {0};
for( ; cpp2::cmp_less(j,3); ++j ) {{
std::cout << i << j << " ";
if (j == 1) {
goto BREAK_57_9;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(57_9) }
std::cout << "outer ";
} CPP2_BREAK_CONTINUE(55_5) }
}

auto while_break_outer() -> void
{
auto i {0};
for( ; cpp2::cmp_less(i,3); ++i ) {{
auto j {0};
for( ; cpp2::cmp_less(j,3); ++j ) {{
std::cout << i << j << " ";
if (j == 1) {
goto BREAK_71_5;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(73_9) }
std::cout << "outer ";
} CPP2_BREAK_CONTINUE(71_5) }
}

auto do_continue_inner() -> void
{
auto i {0};
do { {
auto j {0};
do { {
std::cout << i << j << " ";
if (j == 1) {
goto CONTINUE_89_9;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(89_9) } while (
cpp2::cmp_less(j,3) && [&]{ ++j ; return true; }() );

std::cout << "outer ";
} CPP2_BREAK_CONTINUE(87_5) } while (
cpp2::cmp_less(i,3) && [&]{ ++i ; return true; }() );
}

auto do_continue_outer() -> void
{
auto i {0};
do { {
auto j {0};
do { {
std::cout << i << j << " ";
if (j == 1) {
goto CONTINUE_106_5;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(108_9) } while (
cpp2::cmp_less(j,3) && [&]{ ++j ; return true; }() );

std::cout << "outer ";
} CPP2_BREAK_CONTINUE(106_5) } while (
cpp2::cmp_less(i,3) && [&]{ ++i ; return true; }() );
}

auto do_break_inner() -> void
{
auto i {0};
do { {
auto j {0};
do { {
std::cout << i << j << " ";
if (j == 1) {
goto BREAK_127_9;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(127_9) } while (
cpp2::cmp_less(j,3) && [&]{ ++j ; return true; }() );

std::cout << "outer ";
} CPP2_BREAK_CONTINUE(125_5) } while (
cpp2::cmp_less(i,3) && [&]{ ++i ; return true; }() );
}

auto do_break_outer() -> void
{
auto i {0};
do { {
auto j {0};
do { {
std::cout << i << j << " ";
if (j == 1) {
goto BREAK_144_5;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(146_9) } while (
cpp2::cmp_less(j,3) && [&]{ ++j ; return true; }() );

std::cout << "outer ";
} CPP2_BREAK_CONTINUE(144_5) } while (
cpp2::cmp_less(i,3) && [&]{ ++i ; return true; }() );
}

auto for_continue_inner() -> void
{
std::vector vi {0, 1, 2};
auto counter {0};
for ( auto&& cpp2_range = vi; auto const& i : cpp2_range ) { { do {
std::vector vj {0, 1, 2};
for ( auto&& cpp2_range = vj; auto const& j : cpp2_range ) {{
std::cout << i << j << " ";
if (j == 1) {
goto CONTINUE_166_9;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(166_9) }

std::cout << "outer ";
} while (false); ++counter; } CPP2_BREAK_CONTINUE(164_5) }
}

auto for_continue_outer() -> void
{
std::vector vi {0, 1, 2};
auto counter {0};
for ( auto&& cpp2_range = vi; auto const& i : cpp2_range ) { { do {
std::vector vj {0, 1, 2};
for ( auto&& cpp2_range = vj; auto const& j : cpp2_range ) {{
std::cout << i << j << " ";
if (j == 1) {
goto CONTINUE_182_5;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(184_9) }

std::cout << "outer ";
} while (false); ++counter; } CPP2_BREAK_CONTINUE(182_5) }
}

auto for_break_inner() -> void
{
std::vector vi {0, 1, 2};
auto counter {0};
for ( auto&& cpp2_range = vi; auto const& i : cpp2_range ) { { do {
std::vector vj {0, 1, 2};
for ( auto&& cpp2_range = vj; auto const& j : cpp2_range ) {{
std::cout << i << j << " ";
if (j == 1) {
goto BREAK_202_9;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(202_9) }

std::cout << "outer ";
} while (false); ++counter; } CPP2_BREAK_CONTINUE(200_5) }
}

auto for_break_outer() -> void
{
std::vector vi {0, 1, 2};
auto counter {0};
for ( auto&& cpp2_range = vi; auto const& i : cpp2_range ) { { do {
std::vector vj {0, 1, 2};
for ( auto&& cpp2_range = vj; auto const& j : cpp2_range ) {{
std::cout << i << j << " ";
if (j == 1) {
goto BREAK_218_5;
}
std::cout << "inner ";
} CPP2_BREAK_CONTINUE(220_9) }

std::cout << "outer ";
} while (false); ++counter; } CPP2_BREAK_CONTINUE(218_5) }
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pure2-break-continue.cpp2... ok (all Cpp2, passes safety checks)

18 changes: 18 additions & 0 deletions source/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,24 @@ auto replace_all(std::string& s, std::string_view what, std::string_view with)
return s;
}

auto to_upper(char c) -> char {
// C toupper is only not-UB in [0,127] and returns the wrong type,
// so wrap the range check and the type cast here in one place...
// note the 126 (not 127) is intentional to avoid a GCC warning
if (0 <= c && c <= 126) { return (char)std::toupper(c); }
// else
return c;
}

auto to_upper_and_underbar(std::string_view s) -> std::string {
auto ret = std::string{s};
for (char& c : ret) {
if (std::isalnum(c)) { c = to_upper(c); }
else { c = '_'; }
}
return ret;
}


//-----------------------------------------------------------------------
//
Expand Down
83 changes: 67 additions & 16 deletions source/cppfront.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ class cppfront
parameter_declaration_list_node single_anon;
// special value - hack for now to note single-anon-return type kind in this function_returns working list
std::vector<std::string> function_requires_conditions;
std::vector<iteration_statement_node const*> iteration_statements;

std::vector<bool> in_non_rvalue_context = { false };
std::vector<bool> need_expression_list_parens = { true };
Expand Down Expand Up @@ -879,11 +880,7 @@ class cppfront
}

// Generate a reasonable macroized name
auto cpp1_FILENAME = cpp1_filename;
for (char& c : cpp1_FILENAME) {
if (0 <= c && c <= 127) { c = std::toupper(c); }
if ( !std::isalnum(c) ) { c = '_'; }
}
auto cpp1_FILENAME = to_upper_and_underbar(cpp1_filename);

// Only emit extra lines if we actually have Cpp2, because
// we want pure-Cpp1 files to pass through with zero changes
Expand Down Expand Up @@ -1478,6 +1475,9 @@ class cppfront
assert(n.identifier);
in_non_rvalue_context.push_back(true);

iteration_statements.push_back( &n );
auto labelname = labelized_position(n.label);

// Handle while
//
if (*n.identifier == "while") {
Expand All @@ -1496,19 +1496,30 @@ class cppfront
emit(*n.next_expression);
}
printer.print_cpp2(" ) ", n.position());
if (!labelname.empty()) {
printer.print_extra("{");
}
emit(*n.statement);
if (!labelname.empty()) {
printer.print_extra(" CPP2_BREAK_CONTINUE("+labelname+") }");
}

in_non_rvalue_context.pop_back();
return;
}

// Handle do
//
if (*n.identifier == "do") {
else if (*n.identifier == "do") {
assert(n.condition && n.statement && !n.range && !n.body);

printer.print_cpp2("do ", n.position());
if (!labelname.empty()) {
printer.print_extra("{");
}
emit(*n.statement);
if (!labelname.empty()) {
printer.print_extra(" CPP2_BREAK_CONTINUE("+labelname+") }");
}
printer.print_cpp2(" while ( ", n.position());
emit(*n.condition);
if (n.next_expression) {
Expand All @@ -1521,24 +1532,21 @@ class cppfront
printer.print_cpp2(");", n.position());

in_non_rvalue_context.pop_back();
return;
}

// Handle for
//
if (*n.identifier == "for") {
else if (*n.identifier == "for") {
assert(!n.condition && !n.statement && n.range && n.body);

// TODO: break n.range into subexpressions and lifetime-extend
// each subexpression, because that's the right thing to do
// For now, just use a for-scope auto&&
// Also: using the name 'cpp2_range' for now because '__range' is reserved
// and common enough that it might clash with existing impls
printer.print_cpp2("for ( auto&& cpp2_range = ", n.position());
emit(*n.range);
printer.print_cpp2("; ", n.position());
emit(*n.get_for_parameter());
printer.print_cpp2(" : cpp2_range ) ", n.position());
if (!labelname.empty()) {
printer.print_extra("{");
}

// If there's a next-expression, smuggle it in via a nested do/while(false) loop
// (nested "continue" will work, but "break" won't until we do extra work to implement
Expand All @@ -1556,11 +1564,19 @@ class cppfront
printer.print_cpp2("; }", n.position());
}

printer.print_cpp2("", n.position());
if (!labelname.empty()) {
printer.print_extra(" CPP2_BREAK_CONTINUE("+labelname+") }");
}

in_non_rvalue_context.pop_back();
return;
}

assert(!"compiler bug: unexpected case");
else {
assert(!"ICE: unexpected case");
}

iteration_statements.pop_back();
}


Expand Down Expand Up @@ -1617,6 +1633,40 @@ class cppfront
}


//-----------------------------------------------------------------------
//
auto emit(jump_statement_node const& n) -> void
{
assert(n.keyword);

if (n.label) {
auto iter_stmt =
std::find_if(
iteration_statements.begin(),
iteration_statements.end(),
[&](auto& s){ return s->label && std::string_view{*s->label} == std::string_view{*n.label}; }
);
if (iter_stmt == iteration_statements.end())
{
errors.emplace_back(
n.position(),
"a named " + n.keyword->to_string(true) + " must use the name of an enclosing local loop label"
);
return;
}
assert((*iter_stmt)->label);
printer.print_cpp2(
"goto " + to_upper_and_underbar(*n.keyword) + "_" + labelized_position((*iter_stmt)->label) + ";",
n.position()
);
}
else {
emit(*n.keyword);
printer.print_cpp2(";", n.position());
}
}


//-----------------------------------------------------------------------
//
auto build_capture_lambda_intro_for( capture_group& captures, source_position pos ) -> std::string
Expand Down Expand Up @@ -2607,6 +2657,7 @@ class cppfront
try_emit<statement_node::iteration >(n.statement);
try_emit<statement_node::contract >(n.statement);
try_emit<statement_node::inspect >(n.statement, false);
try_emit<statement_node::jump >(n.statement);

printer.preempt_position_pop();
}
Expand Down
12 changes: 12 additions & 0 deletions source/lex.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,18 @@ class token
static_assert (CHAR_BIT == 8);


auto labelized_position(token const* t) -> std::string {
auto ret = std::string{};
if (t) {
ret +=
std::to_string(t->position().lineno) +
"_" +
std::to_string(t->position().colno);
}
return ret;
}


//-----------------------------------------------------------------------
//
// A StringLiteral could include captures
Expand Down
111 changes: 103 additions & 8 deletions source/parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@ struct selection_statement_node
struct parameter_declaration_node;
struct iteration_statement_node
{
token const* label;
token const* identifier;
std::unique_ptr<assignment_expression_node> next_expression; // if used, else null
std::unique_ptr<logical_or_expression_node> condition; // used for "do" and "while", else null
Expand All @@ -769,6 +770,9 @@ struct iteration_statement_node

auto position() const -> source_position
{
if (label) {
return label->position();
}
assert(identifier);
return identifier->position();
}
Expand Down Expand Up @@ -894,10 +898,35 @@ struct contract_node
};


struct jump_statement_node
{
token const* keyword;
token const* label;

auto position() const -> source_position
{
assert(keyword);
return keyword->position();
}

auto visit(auto& v, int depth) -> void
{
v.start(*this, depth);
if (keyword) {
keyword->visit(v, depth+1);
}
if (label) {
label->visit(v, depth+1);
}
v.end(*this, depth);
}
};


struct parameter_declaration_list_node;
struct statement_node
{
enum active { expression=0, compound, selection, declaration, return_, iteration, contract, inspect };
enum active { expression=0, compound, selection, declaration, return_, iteration, contract, inspect, jump };
std::variant<
std::unique_ptr<expression_statement_node>,
std::unique_ptr<compound_statement_node>,
Expand All @@ -906,7 +935,8 @@ struct statement_node
std::unique_ptr<return_statement_node>,
std::unique_ptr<iteration_statement_node>,
std::unique_ptr<contract_node>,
std::unique_ptr<inspect_expression_node>
std::unique_ptr<inspect_expression_node>,
std::unique_ptr<jump_statement_node>
> statement;

auto position() const -> source_position;
Expand Down Expand Up @@ -996,6 +1026,7 @@ auto statement_node::visit(auto& v, int depth) -> void
try_visit<iteration >(statement, v, depth);
try_visit<contract >(statement, v, depth);
try_visit<inspect >(statement, v, depth);
try_visit<jump >(statement, v, depth);
v.end(*this, depth);
}

Expand Down Expand Up @@ -1213,6 +1244,12 @@ auto iteration_statement_node::get_for_parameter() const -> parameter_declaratio
auto iteration_statement_node::visit(auto& v, int depth) -> void
{
v.start(*this, depth);
if (label) {
label->visit(v, depth+1);
}
if (identifier) {
identifier->visit(v, depth+1);
}
if (statement) {
statement->visit(v, depth+1);
}
Expand Down Expand Up @@ -2538,23 +2575,40 @@ class parser


//G iteration-statement:
//G 'while' logical-or-expression next-clause? compound-statement
//G 'do' compound-statement 'while' logical-or-expression next-clause? ';'
//G 'for' expression next-clause? 'do' unnamed-declaration
//G label? 'while' logical-or-expression next-clause? compound-statement
//G label? 'do' compound-statement 'while' logical-or-expression next-clause? ';'
//G label? 'for' expression next-clause? 'do' unnamed-declaration
//G
//G label:
//G identifier ':'
//G
//G next-clause:
//G 'next' assignment-expression
//G
auto iteration_statement() -> std::unique_ptr<iteration_statement_node>
{
auto n = std::make_unique<iteration_statement_node>();

// If the next three tokens are: identifier ':' 'for/while/do', it's a labeled iteration statement
if (curr().type() == lexeme::Identifier &&
peek(1) && peek(2) &&
peek(1)->type() == lexeme::Colon &&
peek(2)->type() == lexeme::Keyword &&
(*peek(2) == "while" || *peek(2) == "do" || *peek(2) == "for")
)
{
n->label = &curr();
next();
next();
}

if (curr().type() != lexeme::Keyword ||
(curr() != "while" && curr() != "do" && curr() != "for")
)
{
return {};
}

auto n = std::make_unique<iteration_statement_node>();
n->identifier = &curr();
next();

Expand Down Expand Up @@ -2814,17 +2868,47 @@ class parser
}


//G jump-statement:
//G 'break' identifier? ';'
//G 'continue' identifier? ';'
//G
auto jump_statement() -> std::unique_ptr<jump_statement_node>
{
auto n = std::make_unique<jump_statement_node>();

if (curr() != "break" && curr() != "continue") {
return {};
}

n->keyword = &curr();
next();

if (curr().type() == lexeme::Identifier) {
n->label = &curr();
next();
}

if (curr().type() != lexeme::Semicolon) {
error("expected ; at end of jump-statement");
return {};
}
next();

return n;
}


//G statement:
//G selection-statement
//G inspect-expression
//G return-statement
//G jump-statement
//G iteration-statement
//G compound-statement
//G declaration-statement
//G declaration
//G expression-statement
//G contract
//
//GTODO jump-statement
//GTODO try-block
//G
auto statement(bool semicolon_required, source_position equal_sign = source_position{})
Expand Down Expand Up @@ -2852,6 +2936,12 @@ class parser
return n;
}

else if (auto s = jump_statement()) {
n->statement = std::move(s);
assert (n->statement.index() == statement_node::jump);
return n;
}

else if (auto s = iteration_statement()) {
n->statement = std::move(s);
assert (n->statement.index() == statement_node::iteration);
Expand Down Expand Up @@ -3615,6 +3705,11 @@ class parse_tree_printer : printing_visitor
o << pre(indent) << "alternative\n";
}

auto start(jump_statement_node const&, int indent) -> void
{
o << pre(indent) << "jump\n";
}

auto start(inspect_expression_node const& n, int indent) -> void
{
o << pre(indent) << "inspect-expression\n";
Expand Down