diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 15c4f6deae..4374d044db 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -284,8 +284,9 @@ pub mut: // break, continue pub struct BranchStmt { pub: - kind token.Kind - pos token.Position + kind token.Kind + label string + pos token.Position } pub struct CallExpr { @@ -626,6 +627,8 @@ pub: stmts []Stmt is_inf bool // `for {}` pos token.Position +pub mut: + label string // `label: for {` } pub struct ForInStmt { @@ -644,6 +647,7 @@ pub mut: val_type table.Type cond_type table.Type kind table.Kind // array/map/string + label string // `label: for {` } pub struct ForCStmt { @@ -656,6 +660,8 @@ pub: has_inc bool stmts []Stmt pos token.Position +pub mut: + label string // `label: for {` } // #include etc diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index d7addc0369..1df3a0b4a5 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2410,6 +2410,7 @@ fn (mut c Checker) stmt(node ast.Stmt) { if c.in_for_count == 0 { c.error('$node.kind.str() statement not within a loop', node.pos) } + // TODO: check any node.label is in scope for goto } ast.CompFor { // node.typ = c.expr(node.expr) @@ -2577,7 +2578,9 @@ fn (mut c Checker) stmt(node ast.Stmt) { } } ast.GotoLabel {} - ast.GotoStmt {} + ast.GotoStmt { + // TODO: check label doesn't bypass variable declarations + } ast.HashStmt { c.hash_stmt(mut node) } diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index f00a70aa23..6aabe14843 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -821,9 +821,17 @@ fn (mut g Gen) stmt(node ast.Stmt) { } ast.BranchStmt { g.write_v_source_line_info(node.pos) - // continue or break - g.write(node.kind.str()) - g.writeln(';') + if node.label.len > 0 { + if node.kind == .key_break { + g.writeln('goto $node.label\__break;') + } else { + assert node.kind == .key_continue + g.writeln('goto $node.label\__continue;') + } + } else { + // continue or break + g.writeln('$node.kind;') + } } ast.ConstDecl { g.write_v_source_line_info(node.pos) @@ -930,6 +938,9 @@ fn (mut g Gen) stmt(node ast.Stmt) { ast.ForCStmt { g.write_v_source_line_info(node.pos) g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } g.write('for (') if !node.has_init { g.write('; ') @@ -952,7 +963,13 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.writeln(') {') g.is_vlines_enabled = true g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('$node.label\__continue: {}') + } g.writeln('}') + if node.label.len > 0 { + g.writeln('$node.label\__break: {}') + } } ast.ForInStmt { g.write_v_source_line_info(node.pos) @@ -961,6 +978,9 @@ fn (mut g Gen) stmt(node ast.Stmt) { ast.ForStmt { g.write_v_source_line_info(node.pos) g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } g.writeln('for (;;) {') if !node.is_inf { g.indent++ @@ -972,7 +992,13 @@ fn (mut g Gen) stmt(node ast.Stmt) { } g.is_vlines_enabled = true g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('\t$node.label\__continue: {}') + } g.writeln('}') + if node.label.len > 0 { + g.writeln('$node.label\__break: {}') + } } ast.GlobalDecl { g.global_decl(node) @@ -1097,6 +1123,9 @@ fn (mut g Gen) write_defer_stmts() { } fn (mut g Gen) for_in(it ast.ForInStmt) { + if it.label.len > 0 { + g.writeln('\t$it.label: {}') + } if it.is_range { // `for x in 1..10 {` i := if it.val_var == '_' { g.new_tmp_var() } else { c_name(it.val_var) } @@ -1105,8 +1134,6 @@ fn (mut g Gen) for_in(it ast.ForInStmt) { g.write('; $i < ') g.expr(it.high) g.writeln('; ++$i) {') - g.stmts(it.stmts) - g.writeln('}') } else if it.kind == .array { // `for num in nums {` g.writeln('// FOR IN array') @@ -1136,8 +1163,6 @@ fn (mut g Gen) for_in(it ast.ForInStmt) { g.writeln('\t$styp ${c_name(it.val_var)} = $right;') } } - g.stmts(it.stmts) - g.writeln('}') } else if it.kind == .array_fixed { atmp := g.new_tmp_var() atmp_type := g.typ(it.cond_type) @@ -1163,8 +1188,6 @@ fn (mut g Gen) for_in(it ast.ForInStmt) { } g.writeln(' = (*$atmp)[$i];') } - g.stmts(it.stmts) - g.writeln('}') } else if it.kind == .map { // `for key, val in map {` g.writeln('// FOR IN map') @@ -1201,10 +1224,17 @@ fn (mut g Gen) for_in(it ast.ForInStmt) { if it.key_type == table.string_type && !g.is_builtin_mod { // g.writeln('string_free(&$key);') } + if it.label.len > 0 { + g.writeln('\t$it.label\__continue: {}') + } g.writeln('}') + if it.label.len > 0 { + g.writeln('\t$it.label\__break: {}') + } g.writeln('/*for in map cleanup*/') g.writeln('for (int $idx = 0; $idx < ${keys_tmp}.len; ++$idx) { string_free(&(($key_styp*)${keys_tmp}.data)[$idx]); }') g.writeln('array_free(&$keys_tmp);') + return } else if it.cond_type.has_flag(.variadic) { g.writeln('// FOR IN cond_type/variadic') i := if it.key_var in ['', '_'] { g.new_tmp_var() } else { it.key_var } @@ -1215,8 +1245,6 @@ fn (mut g Gen) for_in(it ast.ForInStmt) { g.write('\t$styp ${c_name(it.val_var)} = ') g.expr(it.cond) g.writeln('.args[$i];') - g.stmts(it.stmts) - g.writeln('}') } else if it.kind == .string { i := if it.key_var in ['', '_'] { g.new_tmp_var() } else { it.key_var } g.write('for (int $i = 0; $i < ') @@ -1227,12 +1255,18 @@ fn (mut g Gen) for_in(it ast.ForInStmt) { g.expr(it.cond) g.writeln('.str[$i];') } - g.stmts(it.stmts) - g.writeln('}') } else { s := g.table.type_to_str(it.cond_type) g.error('for in: unhandled symbol `$it.cond` of type `$s`', it.pos) } + g.stmts(it.stmts) + if it.label.len > 0 { + g.writeln('\t$it.label\__continue: {}') + } + g.writeln('}') + if it.label.len > 0 { + g.writeln('\t$it.label\__break: {}') + } } // use instead of expr() when you need to cast to union sum type (can add other casts also) diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 5a9105182c..f009833341 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -594,6 +594,26 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { spos := p.tok.position() name := p.check_name() p.next() + if p.tok.kind == .key_for { + mut stmt := p.stmt(is_top_level) + match mut stmt { + ast.ForStmt { + stmt.label = name + return *stmt + } + ast.ForInStmt { + stmt.label = name + return *stmt + } + ast.ForCStmt { + stmt.label = name + return *stmt + } + else { + assert false + } + } + } return ast.GotoLabel{ name: name pos: spos.extend(p.tok.position()) @@ -630,9 +650,15 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } .key_continue, .key_break { tok := p.tok + line := p.tok.line_nr p.next() + mut label := '' + if p.tok.line_nr == line && p.tok.kind == .name { + label = p.check_name() + } return ast.BranchStmt{ kind: tok.kind + label: label pos: tok.position() } } diff --git a/vlib/v/tests/named_break_continue_test.v b/vlib/v/tests/named_break_continue_test.v new file mode 100644 index 0000000000..c537528fe1 --- /dev/null +++ b/vlib/v/tests/named_break_continue_test.v @@ -0,0 +1,31 @@ +fn test_labelled_for() { + mut i := 4 + goto L1 + L1: for { + i++ + for { + if i < 7 {continue L1} + else {break L1} + } + } + assert i == 7 + + goto L2 + L2: for ;; i++ { + for { + if i < 17 {continue L2} + else {break L2} + } + } + assert i == 17 + + goto L3 + L3: for e in [1,2,3,4] { + i = e + for { + if i < 3 {continue L3} + else {break L3} + } + } + assert i == 3 +}