diff --git a/compiler/parser.v b/compiler/parser.v index 2c0c6327d7..daa96cbf12 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -64,6 +64,7 @@ mut: builtin_mod bool vh_lines []string inside_if_expr bool + inside_unwrapping_match_statement bool is_struct_init bool if_expr_cnt int for_expr_cnt int // to detect whether `continue` can be used @@ -1019,6 +1020,7 @@ fn (p mut Parser) statements() string { fn (p mut Parser) statements_no_rcbr() string { p.cur_fn.open_scope() + if !p.inside_if_expr { p.genln('') } @@ -1049,6 +1051,7 @@ fn (p mut Parser) statements_no_rcbr() string { } //p.fmt_dec() // println('close scope line=$p.scanner.line_nr') + p.close_scope() return last_st_typ } @@ -1152,8 +1155,10 @@ fn (p mut Parser) statement(add_semi bool) string { p.if_st(false, 0) case Token.key_for: p.for_st() - case Token.key_switch, Token.key_match: + case Token.key_switch: p.switch_statement() + case Token.key_match: + p.match_statement(false) case Token.key_mut, Token.key_static: p.var_decl() case Token.key_return: @@ -2409,6 +2414,9 @@ fn (p mut Parser) factor() string { case Token.key_if: typ = p.if_st(true, 0) return typ + case Token.key_match: + typ = p.match_statement(true) + return typ default: if p.pref.is_verbose || p.pref.is_debug { next := p.peek() @@ -3350,6 +3358,186 @@ fn (p mut Parser) switch_statement() { p.returns = false // only get here when no default, so return is not guaranteed } +// Returns typ if used as expession +fn (p mut Parser) match_statement(is_expr bool) string { + p.check(.key_match) + p.cgen.start_tmp() + typ := p.bool_expression() + expr := p.cgen.end_tmp() + + // is it safe to use p.cgen.insert_before ??? + tmp_var := p.get_tmp() + p.cgen.insert_before('$typ $tmp_var = $expr;') + + p.check(.lcbr) + mut i := 0 + mut all_cases_return := true + + // stores typ of resulting variable + mut res_typ := '' + + defer { + p.check(.rcbr) + } + + for p.tok != .rcbr { + if p.tok == .key_else { + p.check(.key_else) + p.check(.arrow) + + // unwrap match if there is only else + if i == 0 { + if is_expr { + // statements are dissallowed (if match is expression) so user cant declare variables there and so on + + // allow braces is else + got_brace := p.tok == .lcbr + if got_brace { + p.check(.lcbr) + } + + p.gen('( ') + + res_typ = p.bool_expression() + + p.gen(' )') + + // allow braces in else + if got_brace { + p.check(.rcbr) + } + + return res_typ + } else { + p.match_parse_statement_branch() + p.returns = all_cases_return && p.returns + return '' + } + } + + if is_expr { + // statements are dissallowed (if match is expression) so user cant declare variables there and so on + p.gen(':(') + + // allow braces is else + got_brace := p.tok == .lcbr + if got_brace { + p.check(.lcbr) + } + + p.check_types(p.bool_expression(), res_typ) + + // allow braces in else + if got_brace { + p.check(.rcbr) + } + + p.gen(strings.repeat(`)`, i+1)) + + return res_typ + } else { + p.genln('else // default:') + p.match_parse_statement_branch() + p.returns = all_cases_return && p.returns + return '' + } + } + + if i > 0 { + if is_expr { + p.gen(': (') + } else { + p.gen('else ') + } + } else if is_expr { + p.gen('(') + } + + if is_expr { + p.gen('(') + } else { + p.gen('if (') + } + + // Multiple checks separated by comma + mut got_comma := false + + for { + if got_comma { + p.gen(') || (') + } + + if typ == 'string' { + // TODO: use tmp variable + // p.gen('string_eq($tmp_var, ') + p.gen('string_eq($tmp_var, ') + } + else { + // TODO: use tmp variable + // p.gen('($tmp_var == ') + p.gen('($tmp_var == ') + } + + p.expected_type = typ + p.check_types(p.bool_expression(), typ) + p.expected_type = '' + + if p.tok != .comma { + break + } + p.check(.comma) + got_comma = true + } + p.gen(') )') + + p.check(.arrow) + + // statements are dissallowed (if match is expression) so user cant declare variables there and so on + if is_expr { + p.gen('? (') + + // braces are required for now + p.check(.lcbr) + + if i == 0 { + // on the first iteration we set value of res_typ + res_typ = p.bool_expression() + } else { + // later on we check that the value is of res_typ type + p.check_types(p.bool_expression(), res_typ) + } + + // braces are required for now + p.check(.rcbr) + + p.gen(')') + } + else { + p.match_parse_statement_branch() + // p.gen(')') + } + + all_cases_return = all_cases_return && p.returns + i++ + } + + if is_expr { + // we get here if no else found, ternary requires "else" branch + p.error('Match expession requires "else"') + } + + p.returns = false // only get here when no default, so return is not guaranteed + + return '' +} + +fn (p mut Parser) match_parse_statement_branch(){ + p.check(.lcbr) + + p.genln('{ ') + p.statements() +} + fn (p mut Parser) assert_statement() { if p.first_pass() { return diff --git a/compiler/tests/match_test.v b/compiler/tests/match_test.v index 5f5d7df37d..dc2d6ed008 100644 --- a/compiler/tests/match_test.v +++ b/compiler/tests/match_test.v @@ -1,12 +1,88 @@ -fn test_match() { - a := 3 - mut b := 0 - match a { - 2 => println('two') - 3 => println('three') - b = 3 - 4 => println('four') - else => println('???') - } - assert b == 3 +enum Color{ + red, green, blue +} + +fn test_match_integers() { + // a := 3 + // mut b := 0 + // match a { + // 2 => println('two') + // 3 => println('three') + // b = 3 + // 4 => println('four') + // else => println('???') + // } + // assert b == 3 + + assert match 2 { + 1 => {2} + 2 => {3} + else => {5} + } == 3 + + assert match 0 { + 1 => {2} + 2 => {3} + else => 5 + } == 5 + + assert match 1 { + else => {5} + } == 5 + + mut a := 0 + match 2 { + 0 => {a = 1} + 1 => {a = 2} + else => { + a = 3 + println('a is $a') + } + } + assert a == 3 + + a = 0 + match 1 { + 0 => {a = 1} + 1 => { + a = 2 + a = a + 2 + a = a + 2 + } + } + assert a == 6 + + a = 0 + match 1 { + else => { + a = -2 + } + } + assert a == -2 } + +fn test_match_enums(){ + mut b := Color.red + match b{ + .red => { + b = .green + } + .green => {b = .blue} + else => { + println('b is ${b.str()}') + b = .red + } + } + assert b == .green + + match b{ + .red => { + b = .green + } + else => { + println('b is ${b.str()}') + b = .blue + } + } + assert b == .blue +}