From fdd4afa392afaae17acfb4b03d5cc095c5cb2ce3 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 6 Nov 2019 23:27:46 +0300 Subject: [PATCH] for.v and match.v; do not allow arrays in `match` --- vlib/compiler/for.v | 196 ++++++++++++++++++++ vlib/compiler/match.v | 221 +++++++++++++++++++++++ vlib/compiler/parser.v | 397 ----------------------------------------- 3 files changed, 417 insertions(+), 397 deletions(-) create mode 100644 vlib/compiler/for.v create mode 100644 vlib/compiler/match.v diff --git a/vlib/compiler/for.v b/vlib/compiler/for.v new file mode 100644 index 0000000000..2119d6b1c8 --- /dev/null +++ b/vlib/compiler/for.v @@ -0,0 +1,196 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module compiler + +fn (p mut Parser) for_st() { + p.check(.key_for) + p.fgen(' ') + p.for_expr_cnt++ + next_tok := p.peek() + //debug := p.scanner.file_path.contains('r_draw') + p.open_scope() + if p.tok == .lcbr { + // Infinite loop + p.gen('while (1) {') + } + else if p.tok == .key_mut { + p.error('`mut` is not required in for loops') + } + // for i := 0; i < 10; i++ { + else if next_tok == .decl_assign || next_tok == .assign || p.tok == .semicolon { + p.genln('for (') + if next_tok == .decl_assign { + p.var_decl() + } + else if p.tok != .semicolon { + // allow `for ;; i++ {` + // Allow `for i = 0; i < ...` + p.statement(false) + } + p.check(.semicolon) + p.gen(' ; ') + p.fgen(' ') + if p.tok != .semicolon { + p.bool_expression() + } + p.check(.semicolon) + p.gen(' ; ') + p.fgen(' ') + if p.tok != .lcbr { + p.statement(false) + } + p.genln(') { ') + } + // for i, val in array + else if p.peek() == .comma { + /* + `for i, val in array {` + ==> + ``` + array_int tmp = array; + for (int i = 0; i < tmp.len; i++) { + int val = tmp[i]; + ``` + */ + i := p.check_name() + p.check(.comma) + val := p.check_name() + if i == '_' && val == '_' { + p.error('no new variables on the left side of `in`') + } + p.fgen(' ') + p.check(.key_in) + p.fgen(' ') + tmp := p.get_tmp() + p.cgen.start_tmp() + mut typ := p.bool_expression() + is_arr := typ.starts_with('array_') + is_map := typ.starts_with('map_') + is_str := typ == 'string' + is_variadic_arg := typ.starts_with('varg_') + if !is_arr && !is_str && !is_map && !is_variadic_arg { + p.error('cannot range over type `$typ`') + } + expr := p.cgen.end_tmp() + if !is_variadic_arg { + if p.is_js { + p.genln('var $tmp = $expr;') + } else { + p.genln('$typ $tmp = $expr;') + } + } + // typ = strings.Replace(typ, "_ptr", "*", -1) + mut i_var_type := 'int' + if is_variadic_arg { + typ = typ[5..] + p.gen_for_varg_header(i, expr, typ, val) + } + else if is_arr { + typ = typ[6..] + p.gen_for_header(i, tmp, typ, val) + } + else if is_map { + i_var_type = 'string' + typ = typ[4..] + p.gen_for_map_header(i, tmp, typ, val, typ) + } + else if is_str { + typ = 'byte' + p.gen_for_str_header(i, tmp, typ, val) + } + // Register temp vars + if i != '_' { + p.register_var(Var { + name: i + typ: i_var_type + is_mut: true + is_changed: true + }) + } + if val != '_' { + p.register_var(Var { + name: val + typ: typ + ptr: typ.contains('*') + }) + } + } + // `for val in vals` + else if p.peek() == .key_in { + val := p.check_name() + p.fgen(' ') + p.check(.key_in) + p.fspace() + tmp := p.get_tmp() + p.cgen.start_tmp() + mut typ := p.bool_expression() + expr := p.cgen.end_tmp() + is_range := p.tok == .dotdot + is_variadic_arg := typ.starts_with('varg_') + mut range_end := '' + if is_range { + p.check_types(typ, 'int') + p.check_space(.dotdot) + p.cgen.start_tmp() + p.check_types(p.bool_expression(), 'int') + range_end = p.cgen.end_tmp() + } + is_arr := typ.contains('array') + is_str := typ == 'string' + if !is_arr && !is_str && !is_range && !is_variadic_arg { + p.error('cannot range over type `$typ`') + } + if !is_variadic_arg { + if p.is_js { + p.genln('var $tmp = $expr;') + } else { + p.genln('$typ $tmp = $expr;') + } + } + // TODO var_type := if... + i := p.get_tmp() + if is_variadic_arg { + typ = typ[5..] + p.gen_for_varg_header(i, expr, typ, val) + } + else if is_range { + typ = 'int' + p.gen_for_range_header(i, range_end, tmp, typ, val) + } + else if is_arr { + typ = typ[6..]// all after `array_` + p.gen_for_header(i, tmp, typ, val) + } + else if is_str { + typ = 'byte' + p.gen_for_str_header(i, tmp, typ, val) + } + // println('for typ=$typ vartyp=$var_typ') + // Register temp var + if val != '_' { + p.register_var(Var { + name: val + typ: typ + ptr: typ.contains('*') + is_changed: true + is_mut: false + is_for_var: true + }) + } + } else { + // `for a < b {` + p.gen('while (') + p.check_types(p.bool_expression(), 'bool') + p.genln(') {') + } + p.fspace() + p.check(.lcbr) + p.genln('') + p.statements() + p.close_scope() + p.for_expr_cnt-- + p.returns = false // TODO handle loops that are guaranteed to return +} + diff --git a/vlib/compiler/match.v b/vlib/compiler/match.v new file mode 100644 index 0000000000..7395b539ba --- /dev/null +++ b/vlib/compiler/match.v @@ -0,0 +1,221 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module compiler + +import ( + strings +) + +// Returns typ if used as expression +fn (p mut Parser) match_statement(is_expr bool) string { + p.check(.key_match) + p.cgen.start_tmp() + typ := p.bool_expression() + if typ.starts_with('array_') { + p.error('arrays cannot be compared') + } + 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) + if p.tok == .arrow { + p.warn(warn_match_arrow) + 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.returns = false + p.check(.lcbr) + + p.genln('{ ') + p.statements() + 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.returns = false + p.genln('else // default:') + + p.check(.lcbr) + + p.genln('{ ') + p.statements() + + 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 (') + } + + ph := p.cgen.add_placeholder() + + // Multiple checks separated by comma + mut got_comma := false + + for { + if got_comma { + p.gen(') || (') + } + + mut got_string := false + + if typ == 'string' { + got_string = true + p.gen('string_eq($tmp_var, ') + } + else { + p.gen('$tmp_var == ') + } + + p.expected_type = typ + p.check_types(p.bool_expression(), typ) + p.expected_type = '' + + if got_string { + p.gen(')') + } + + if p.tok != .comma { + if got_comma { + p.gen(') ') + p.cgen.set_placeholder(ph, '(') + } + break + } + p.check(.comma) + got_comma = true + } + p.gen(')') + + if p.tok == .arrow { + p.warn(warn_match_arrow) + 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.returns = false + p.check(.lcbr) + + p.genln('{ ') + p.statements() + + all_cases_return = all_cases_return && p.returns + // p.gen(')') + } + i++ + } + + if is_expr { + // we get here if no else found, ternary requires "else" branch + p.error('Match expression requires "else"') + } + + p.returns = false // only get here when no default, so return is not guaranteed + + return '' +} + +fn (p mut Parser) switch_statement() { + p.error('`switch` statement has been removed, use `match` instead:\n' + + 'https://vlang.io/docs#match') +} + + diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index e7bc06e0f4..0faac764e4 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -3246,403 +3246,6 @@ fn (p mut Parser) if_st(is_expr bool, elif_depth int) string { return typ } -fn (p mut Parser) for_st() { - p.check(.key_for) - p.fgen(' ') - p.for_expr_cnt++ - next_tok := p.peek() - //debug := p.scanner.file_path.contains('r_draw') - p.open_scope() - if p.tok == .lcbr { - // Infinite loop - p.gen('while (1) {') - } - else if p.tok == .key_mut { - p.error('`mut` is not required in for loops') - } - // for i := 0; i < 10; i++ { - else if next_tok == .decl_assign || next_tok == .assign || p.tok == .semicolon { - p.genln('for (') - if next_tok == .decl_assign { - p.var_decl() - } - else if p.tok != .semicolon { - // allow `for ;; i++ {` - // Allow `for i = 0; i < ...` - p.statement(false) - } - p.check(.semicolon) - p.gen(' ; ') - p.fgen(' ') - if p.tok != .semicolon { - p.bool_expression() - } - p.check(.semicolon) - p.gen(' ; ') - p.fgen(' ') - if p.tok != .lcbr { - p.statement(false) - } - p.genln(') { ') - } - // for i, val in array - else if p.peek() == .comma { - /* - `for i, val in array {` - ==> - ``` - array_int tmp = array; - for (int i = 0; i < tmp.len; i++) { - int val = tmp[i]; - ``` - */ - i := p.check_name() - p.check(.comma) - val := p.check_name() - if i == '_' && val == '_' { - p.error('no new variables on the left side of `in`') - } - p.fgen(' ') - p.check(.key_in) - p.fgen(' ') - tmp := p.get_tmp() - p.cgen.start_tmp() - mut typ := p.bool_expression() - is_arr := typ.starts_with('array_') - is_map := typ.starts_with('map_') - is_str := typ == 'string' - is_variadic_arg := typ.starts_with('varg_') - if !is_arr && !is_str && !is_map && !is_variadic_arg { - p.error('cannot range over type `$typ`') - } - expr := p.cgen.end_tmp() - if !is_variadic_arg { - if p.is_js { - p.genln('var $tmp = $expr;') - } else { - p.genln('$typ $tmp = $expr;') - } - } - // typ = strings.Replace(typ, "_ptr", "*", -1) - mut i_var_type := 'int' - if is_variadic_arg { - typ = typ[5..] - p.gen_for_varg_header(i, expr, typ, val) - } - else if is_arr { - typ = typ[6..] - p.gen_for_header(i, tmp, typ, val) - } - else if is_map { - i_var_type = 'string' - typ = typ[4..] - p.gen_for_map_header(i, tmp, typ, val, typ) - } - else if is_str { - typ = 'byte' - p.gen_for_str_header(i, tmp, typ, val) - } - // Register temp vars - if i != '_' { - p.register_var(Var { - name: i - typ: i_var_type - is_mut: true - is_changed: true - }) - } - if val != '_' { - p.register_var(Var { - name: val - typ: typ - ptr: typ.contains('*') - }) - } - } - // `for val in vals` - else if p.peek() == .key_in { - val := p.check_name() - p.fgen(' ') - p.check(.key_in) - p.fspace() - tmp := p.get_tmp() - p.cgen.start_tmp() - mut typ := p.bool_expression() - expr := p.cgen.end_tmp() - is_range := p.tok == .dotdot - is_variadic_arg := typ.starts_with('varg_') - mut range_end := '' - if is_range { - p.check_types(typ, 'int') - p.check_space(.dotdot) - p.cgen.start_tmp() - p.check_types(p.bool_expression(), 'int') - range_end = p.cgen.end_tmp() - } - is_arr := typ.contains('array') - is_str := typ == 'string' - if !is_arr && !is_str && !is_range && !is_variadic_arg { - p.error('cannot range over type `$typ`') - } - if !is_variadic_arg { - if p.is_js { - p.genln('var $tmp = $expr;') - } else { - p.genln('$typ $tmp = $expr;') - } - } - // TODO var_type := if... - i := p.get_tmp() - if is_variadic_arg { - typ = typ[5..] - p.gen_for_varg_header(i, expr, typ, val) - } - else if is_range { - typ = 'int' - p.gen_for_range_header(i, range_end, tmp, typ, val) - } - else if is_arr { - typ = typ[6..]// all after `array_` - p.gen_for_header(i, tmp, typ, val) - } - else if is_str { - typ = 'byte' - p.gen_for_str_header(i, tmp, typ, val) - } - // println('for typ=$typ vartyp=$var_typ') - // Register temp var - if val != '_' { - p.register_var(Var { - name: val - typ: typ - ptr: typ.contains('*') - is_changed: true - is_mut: false - is_for_var: true - }) - } - } else { - // `for a < b {` - p.gen('while (') - p.check_types(p.bool_expression(), 'bool') - p.genln(') {') - } - p.fspace() - p.check(.lcbr) - p.genln('') - p.statements() - p.close_scope() - p.for_expr_cnt-- - p.returns = false // TODO handle loops that are guaranteed to return -} - -fn (p mut Parser) switch_statement() { - p.error('`switch` statement has been removed, use `match` instead:\n' + - 'https://vlang.io/docs#match') -} - -// Returns typ if used as expression -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) - if p.tok == .arrow { - p.warn(warn_match_arrow) - 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.returns = false - p.check(.lcbr) - - p.genln('{ ') - p.statements() - 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.returns = false - p.genln('else // default:') - - p.check(.lcbr) - - p.genln('{ ') - p.statements() - - 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 (') - } - - ph := p.cgen.add_placeholder() - - // Multiple checks separated by comma - mut got_comma := false - - for { - if got_comma { - p.gen(') || (') - } - - mut got_string := false - - if typ == 'string' { - got_string = true - p.gen('string_eq($tmp_var, ') - } - else { - p.gen('$tmp_var == ') - } - - p.expected_type = typ - p.check_types(p.bool_expression(), typ) - p.expected_type = '' - - if got_string { - p.gen(')') - } - - if p.tok != .comma { - if got_comma { - p.gen(') ') - p.cgen.set_placeholder(ph, '(') - } - break - } - p.check(.comma) - got_comma = true - } - p.gen(')') - - if p.tok == .arrow { - p.warn(warn_match_arrow) - 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.returns = false - p.check(.lcbr) - - p.genln('{ ') - p.statements() - - all_cases_return = all_cases_return && p.returns - // p.gen(')') - } - i++ - } - - if is_expr { - // we get here if no else found, ternary requires "else" branch - p.error('Match expression requires "else"') - } - - p.returns = false // only get here when no default, so return is not guaranteed - - return '' -} - fn (p mut Parser) assert_statement() { if p.first_pass() { return