From 1bc906357319d2fd72399868b18bcc353639ab59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kr=C3=BCger?= <45282134+UweKrueger@users.noreply.github.com> Date: Wed, 16 Sep 2020 15:34:57 +0200 Subject: [PATCH] parser: implement parsing of `select` block (#6379) --- vlib/v/ast/ast.v | 26 ++++- vlib/v/checker/checker.v | 4 + vlib/v/fmt/fmt.v | 3 + vlib/v/gen/cgen.v | 3 + vlib/v/gen/js/js.v | 3 + vlib/v/parser/if_match.v | 136 +++++++++++++++++++++++ vlib/v/parser/parser.v | 5 +- vlib/v/parser/pratt.v | 3 + vlib/v/parser/tests/select_bad_key_1.out | 7 ++ vlib/v/parser/tests/select_bad_key_1.vv | 47 ++++++++ vlib/v/parser/tests/select_bad_key_2.out | 7 ++ vlib/v/parser/tests/select_bad_key_2.vv | 13 +++ vlib/v/parser/tests/select_bad_key_3.out | 7 ++ vlib/v/parser/tests/select_bad_key_3.vv | 13 +++ vlib/v/parser/tests/select_bad_key_4.out | 7 ++ vlib/v/parser/tests/select_bad_key_4.vv | 13 +++ vlib/v/parser/tests/select_else_1.out | 7 ++ vlib/v/parser/tests/select_else_1.vv | 18 +++ vlib/v/parser/tests/select_else_2.out | 7 ++ vlib/v/parser/tests/select_else_2.vv | 18 +++ 20 files changed, 344 insertions(+), 3 deletions(-) create mode 100644 vlib/v/parser/tests/select_bad_key_1.out create mode 100644 vlib/v/parser/tests/select_bad_key_1.vv create mode 100644 vlib/v/parser/tests/select_bad_key_2.out create mode 100644 vlib/v/parser/tests/select_bad_key_2.vv create mode 100644 vlib/v/parser/tests/select_bad_key_3.out create mode 100644 vlib/v/parser/tests/select_bad_key_3.vv create mode 100644 vlib/v/parser/tests/select_bad_key_4.out create mode 100644 vlib/v/parser/tests/select_bad_key_4.vv create mode 100644 vlib/v/parser/tests/select_else_1.out create mode 100644 vlib/v/parser/tests/select_else_1.vv create mode 100644 vlib/v/parser/tests/select_else_2.out create mode 100644 vlib/v/parser/tests/select_else_2.vv diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 6e7445b59..12015c477 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -12,7 +12,7 @@ pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ConcatExpr | EnumVal | FloatLiteral | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | - MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | + MapInit | MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type | TypeOf | UnsafeExpr @@ -536,6 +536,27 @@ pub: post_comments []Comment } +pub struct SelectExpr { +pub: + branches []SelectBranch + pos token.Position +pub mut: + is_expr bool // returns a value + return_type table.Type + expected_type table.Type // for debugging only +} + +pub struct SelectBranch { +pub: + stmt Stmt // `a := <-ch` or `ch <- a` + stmts []Stmt // right side + pos token.Position + comment Comment // comment above `select {` + is_else bool + is_timeout bool + post_comments []Comment +} + /* CompIf.is_opt: `$if xyz? {}` => this compile time `if` is optional, @@ -1062,6 +1083,9 @@ pub fn (expr Expr) position() token.Position { return expr.pos } // ast.ParExpr { } + SelectExpr { + return expr.pos + } SelectorExpr { return expr.pos } diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 440298044..39edec6f0 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2641,6 +2641,10 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { // never happens return table.void_type } + ast.SelectExpr { + // TODO: to be implemented + return table.void_type + } ast.SelectorExpr { return c.selector_expr(mut node) } diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index ebc337311..53b76be00 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -953,6 +953,9 @@ pub fn (mut f Fmt) expr(node ast.Expr) { f.write('..') f.expr(node.high) } + ast.SelectExpr { + // TODO: implement this + } ast.SelectorExpr { f.expr(node.expr) f.write('.') diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 801d66ba7..fe059dc25 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2137,6 +2137,9 @@ fn (mut g Gen) expr(node ast.Expr) { ast.RangeExpr { // Only used in IndexExpr } + ast.SelectExpr { + // TODO: to be implemented + } ast.SizeOf { if node.is_type { node_typ := g.unwrap_generic(node.typ) diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 5fd8b97e1..d1461c7ed 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -619,6 +619,9 @@ fn (mut g JsGen) expr(node ast.Expr) { ast.RangeExpr { // Only used in IndexExpr, requires index type info } + ast.SelectExpr { + // TODO: to be implemented + } ast.SelectorExpr { g.gen_selector_expr(node) } diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 044f82c69..03260f4d7 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -288,3 +288,139 @@ fn (mut p Parser) match_expr() ast.MatchExpr { var_name: var_name } } + +fn (mut p Parser) select_expr() ast.SelectExpr { + match_first_pos := p.tok.position() + p.check(.key_select) + no_lcbr := p.tok.kind != .lcbr + if !no_lcbr { + p.check(.lcbr) + } + mut branches := []ast.SelectBranch{} + mut has_else := false + mut has_timeout := false + for { + branch_first_pos := p.tok.position() + comment := p.check_comment() // comment before {} + p.open_scope() + // final else + mut is_else := false + mut is_timeout := false + mut stmt := ast.Stmt{} + if p.tok.kind == .key_else { + if has_timeout { + p.error_with_pos('timeout `> t` and `else` are mutually exclusive `select` keys', p.tok.position()) + } + if has_else { + p.error_with_pos('at most one `else` branch allowed in `select` block', p.tok.position()) + } + is_else = true + has_else = true + p.next() + } else if p.tok.kind == .gt { + if has_else { + p.error_with_pos('`else` and timeout `> t` are mutually exclusive `select` keys', p.tok.position()) + } + if has_timeout { + p.error_with_pos('at most one timeout `> t` branch allowed in `select` block', p.tok.position()) + } + is_timeout = true + has_timeout = true + p.next() + p.inside_match = true + expr := p.expr(0) + p.inside_match = false + stmt = ast.ExprStmt{ + expr: expr + pos: expr.position() + comments: [comment] + is_expr: true + } + } else { + p.inside_match = true + exprs, comments := p.expr_list() + if exprs.len != 1 { + p.error('only one expression allowed as `select` key') + } + if p.tok.kind in [.assign, .decl_assign] { + stmt = p.partial_assign_stmt(exprs, comments) + } else { + stmt = ast.ExprStmt{ + expr: exprs[0] + pos: exprs[0].position() + comments: [comment] + is_expr: true + } + } + p.inside_match = false + match stmt { + ast.ExprStmt { + if !stmt.is_expr { + p.error_with_pos('select: invalid expression', stmt.pos) + } else { + match stmt.expr as expr { + ast.InfixExpr { + if expr.op != .arrow { + p.error_with_pos('select key: `<-` operator expected', expr.pos) + } + } + else { + p.error_with_pos('select key: send expression (`ch <- x`) expected', stmt.pos) + } + } + } + } + ast.AssignStmt { + match stmt.right[0] as expr { + ast.PrefixExpr { + if expr.op != .arrow { + p.error_with_pos('select key: `<-` operator expected', expr.pos) + } + } else { + p.error_with_pos('select key: receive expression expected', stmt.right[0].position()) + } + } + } + else { + p.error_with_pos('select: transmission statement expected', stmt.position()) + } + } + } + branch_last_pos := p.tok.position() + p.inside_match_body = true + stmts := p.parse_block_no_scope(false) + p.close_scope() + p.inside_match_body = false + pos := token.Position{ + line_nr: branch_first_pos.line_nr + pos: branch_first_pos.pos + len: branch_last_pos.pos - branch_first_pos.pos + branch_last_pos.len + } + post_comments := p.eat_comments() + branches << ast.SelectBranch{ + stmt: stmt + stmts: stmts + pos: pos + comment: comment + is_else: is_else + is_timeout: is_timeout + post_comments: post_comments + } + if p.tok.kind == .rcbr || ((is_else || is_timeout) && no_lcbr) { + break + } + } + match_last_pos := p.tok.position() + pos := token.Position{ + line_nr: match_first_pos.line_nr + pos: match_first_pos.pos + len: match_last_pos.pos - match_first_pos.pos + match_last_pos.len + } + if p.tok.kind == .rcbr { + p.check(.rcbr) + } + return ast.SelectExpr{ + branches: branches + pos: pos + } +} diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 1ac520ce8..1b62df5f1 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -859,8 +859,9 @@ fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { } if p.tok.kind in [.assign, .decl_assign] || p.tok.kind.is_assign() { return p.partial_assign_stmt(left, left_comments) - } else if is_top_level && tok.kind !in [.key_if, .key_match, .key_lock, .key_rlock] && - left0 !is ast.CallExpr && left0 !is ast.PostfixExpr && !(left0 is ast.InfixExpr && + } else if is_top_level && tok.kind !in + [.key_if, .key_match, .key_lock, .key_rlock, .key_select] && left0 !is ast.CallExpr && + left0 !is ast.PostfixExpr && !(left0 is ast.InfixExpr && (left0 as ast.InfixExpr).op in [.left_shift, .arrow]) && left0 !is ast.ComptimeCall { p.error_with_pos('expression evaluated but not used', left0.position()) } diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index ec5876cb4..c9249dac6 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -74,6 +74,9 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { .key_match { node = p.match_expr() } + .key_select { + node = p.select_expr() + } .number { node = p.parse_number_literal() } diff --git a/vlib/v/parser/tests/select_bad_key_1.out b/vlib/v/parser/tests/select_bad_key_1.out new file mode 100644 index 000000000..6484827c1 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_1.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_1.vv:39:8: error: select key: receive expression expected + 37 | fn f3_bad(ch1 chan St) { + 38 | select { + 39 | b := 17 { + | ~~ + 40 | println(b) + 41 | } diff --git a/vlib/v/parser/tests/select_bad_key_1.vv b/vlib/v/parser/tests/select_bad_key_1.vv new file mode 100644 index 000000000..3e9da5d01 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_1.vv @@ -0,0 +1,47 @@ +import time + +struct St { + a int +} + +fn f1_good(ch1 chan St, ch2 chan int, ch3 chan int) { + mut a := 5 + select { + a = <- ch3 { + println(a) + } + b := <- ch1 { + println(b.a) + } + ch1 <- a { + a++ + } + > 50 * time.millisecond { + println('timeout') + } + } + println('done') +} + +fn f2_good(ch1 chan St) { + select { + b := <- ch1 { + println(b) + } + else { + println('no channel ready') + } + } +} + +fn f3_bad(ch1 chan St) { + select { + b := 17 { + println(b) + } + } +} + +fn main() {} + + diff --git a/vlib/v/parser/tests/select_bad_key_2.out b/vlib/v/parser/tests/select_bad_key_2.out new file mode 100644 index 000000000..9cb2d0698 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_2.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_2.vv:7:5: error: select key: `<-` operator expected + 5 | println(b) + 6 | } + 7 | a + 7 { + | ^ + 8 | println("shouldn't get here") + 9 | } diff --git a/vlib/v/parser/tests/select_bad_key_2.vv b/vlib/v/parser/tests/select_bad_key_2.vv new file mode 100644 index 000000000..52593814c --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_2.vv @@ -0,0 +1,13 @@ +fn f3_bad(ch1 chan int) { + a := 5 + select { + b := <-ch1 { + println(b) + } + a + 7 { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/vlib/v/parser/tests/select_bad_key_3.out b/vlib/v/parser/tests/select_bad_key_3.out new file mode 100644 index 000000000..41fc51522 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_3.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_3.vv:7:3: error: select key: send expression (`ch <- x`) expected + 5 | println(b) + 6 | } + 7 | println(7) { + | ~~~~~~~~~~ + 8 | println("shouldn't get here") + 9 | } diff --git a/vlib/v/parser/tests/select_bad_key_3.vv b/vlib/v/parser/tests/select_bad_key_3.vv new file mode 100644 index 000000000..82533dda5 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_3.vv @@ -0,0 +1,13 @@ +fn f3_bad(ch1 chan int) { + a := 5 + select { + b := <-ch1 { + println(b) + } + println(7) { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/vlib/v/parser/tests/select_bad_key_4.out b/vlib/v/parser/tests/select_bad_key_4.out new file mode 100644 index 000000000..3fac7dd16 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_4.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_bad_key_4.vv:7:8: error: select key: `<-` operator expected + 5 | println(b) + 6 | } + 7 | c := -a { + | ^ + 8 | println("shouldn't get here") + 9 | } diff --git a/vlib/v/parser/tests/select_bad_key_4.vv b/vlib/v/parser/tests/select_bad_key_4.vv new file mode 100644 index 000000000..bee7402f4 --- /dev/null +++ b/vlib/v/parser/tests/select_bad_key_4.vv @@ -0,0 +1,13 @@ +fn f3_bad(ch1 chan int) { + mut a := 5 + select { + a = <-ch1 { + println(b) + } + c := -a { + println("shouldn't get here") + } + } +} + +fn main() {} diff --git a/vlib/v/parser/tests/select_else_1.out b/vlib/v/parser/tests/select_else_1.out new file mode 100644 index 000000000..9f74c528d --- /dev/null +++ b/vlib/v/parser/tests/select_else_1.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_else_1.vv:12:3: error: `else` and timeout `> t` are mutually exclusive `select` keys + 10 | println("shouldn't get here") + 11 | } + 12 | > 30 * time.millisecond { + | ^ + 13 | println('bad') + 14 | } diff --git a/vlib/v/parser/tests/select_else_1.vv b/vlib/v/parser/tests/select_else_1.vv new file mode 100644 index 000000000..8c8c7a100 --- /dev/null +++ b/vlib/v/parser/tests/select_else_1.vv @@ -0,0 +1,18 @@ +import time + +fn f3_bad(ch1 chan int) { + a := 5 + select { + b := <-ch1 { + println(b) + } + else { + println("shouldn't get here") + } + > 30 * time.millisecond { + println('bad') + } + } +} + +fn main() {} diff --git a/vlib/v/parser/tests/select_else_2.out b/vlib/v/parser/tests/select_else_2.out new file mode 100644 index 000000000..afe53de4c --- /dev/null +++ b/vlib/v/parser/tests/select_else_2.out @@ -0,0 +1,7 @@ +vlib/v/parser/tests/select_else_2.vv:12:3: error: timeout `> t` and `else` are mutually exclusive `select` keys + 10 | println('bad') + 11 | } + 12 | else { + | ~~~~ + 13 | println("shouldn't get here") + 14 | } diff --git a/vlib/v/parser/tests/select_else_2.vv b/vlib/v/parser/tests/select_else_2.vv new file mode 100644 index 000000000..2666dbc59 --- /dev/null +++ b/vlib/v/parser/tests/select_else_2.vv @@ -0,0 +1,18 @@ +import time + +fn f3_bad(ch1 chan int) { + a := 5 + select { + b := <-ch1 { + println(b) + } + > 30 * time.millisecond { + println('bad') + } + else { + println("shouldn't get here") + } + } +} + +fn main() {} -- 2.39.5