v/vlib/compiler/if_match.v

369 lines
7.8 KiB
V

// Copyright (c) 2019-2020 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 type if used as expression
fn (p mut Parser) match_statement(is_expr bool) string {
p.check(.key_match)
p.fspace()
is_mut := p.tok == .key_mut
if is_mut {
p.next()
p.fspace()
}
typ,expr := p.tmp_expr()
if typ.starts_with('array_') {
p.error('arrays cannot be compared')
}
is_sum_type := typ in p.table.sum_types
mut sum_child_type := ''
// is it safe to use p.cgen.insert_before ???
tmp_var := p.get_tmp()
p.cgen.insert_before('$typ $tmp_var = $expr;')
p.fspace()
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.error(warn_match_arrow)
}
// unwrap match if there is only else
if i == 0 {
p.fspace()
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.fspace()
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.fspace()
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.fspace()
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
p.open_scope()
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 if is_sum_type {
p.gen('${tmp_var}.typ == ')
}
else {
p.gen('$tmp_var == ')
}
p.expected_type = typ
// `match node { ast.BoolExpr { it := node as BoolExpr ... } }`
if is_sum_type {
sum_child_type = p.get_type2().name
tt := sum_child_type.all_after('_')
p.gen('SumType_$tt')
// println('got child $sum_child_type')
p.register_var(Var{
name: 'it'
typ: sum_child_type+'*'
is_mut: is_mut
ptr: true
})
}
else {
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)
p.fspace()
got_comma = true
}
p.gen(')')
if p.tok == .arrow {
p.error(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.fgen_nl()
p.check(.rcbr)
p.gen(')')
}
else {
p.returns = false
p.fspace()
p.check(.lcbr)
p.genln('{ ')
if is_sum_type {
//p.genln(' $sum_child_type it = *($sum_child_type*)$tmp_var .obj ;')
p.genln(' $sum_child_type* it = ($sum_child_type*)${tmp_var}.obj ;')
}
p.statements()
all_cases_return = all_cases_return && p.returns
// p.gen(')')
}
i++
p.fgen_nl()
p.close_scope()
}
p.error('match must be exhaustive')
// 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')
}
fn (p mut Parser) if_statement(is_expr bool, elif_depth int) string {
if is_expr {
// if p.fileis('if_expr') {
// println('IF EXPR')
// }
p.inside_if_expr = true
p.gen('((')
}
else {
p.gen('if (')
}
p.next()
p.fspace()
if p.tok == .name && p.peek() == .assign {
p.error('cannot assign on if-else statement')
}
if p.tok == .name && (p.peek() == .inc || p.peek() == .dec) {
p.error('`${p.peek().str()}` is a statement')
}
// `if a := opt() { }` syntax
if p.tok == .name && p.peek() == .decl_assign {
p.check_not_reserved()
option_tmp := p.get_tmp()
var_name := p.lit
if p.known_var(var_name) {
p.error('redefinition of `$var_name`')
}
p.open_scope()
p.next()
p.fspace()
p.check(.decl_assign)
p.fspace()
p.is_var_decl = true
option_type,expr := p.tmp_expr() // := p.bool_expression()
if !option_type.starts_with('Option_') {
p.error('`if x := opt() {` syntax requires a function that returns an optional value')
}
p.is_var_decl = false
typ := parse_pointer(option_type[7..])
// Option_User tmp = get_user(1);
// if (tmp.ok) {
// User user = *(User*)tmp.data;
// [statements]
// }
p.cgen.insert_before('$option_type $option_tmp = $expr; ')
p.fspace()
p.check(.lcbr)
p.genln(option_tmp + '.ok) {')
p.genln('$typ $var_name = *($typ*) $option_tmp . data;')
p.register_var(Var{
name: var_name
typ: typ
is_mut: false // TODO
is_used: true // TODO
// is_alloc: p.is_alloc || typ.starts_with('array_')
// line_nr: p.tokens[ var_token_idx ].line_nr
// token_idx: var_token_idx
})
p.statements()
p.close_scope()
p.returns = false
if p.tok == .key_else {
p.next()
p.genln('else {')
p.check(.lcbr)
p.statements()
}
return 'void'
}
else {
p.check_types(p.bool_expression(), 'bool')
}
if is_expr {
p.gen(') ? (')
}
else {
p.genln(') {')
}
p.fspace()
p.check(.lcbr)
if p.inside_if_expr {
p.fspace()
}
mut typ := ''
// if { if hack
if p.tok == .key_if && p.inside_if_expr {
typ = p.factor()
p.next()
}
else {
typ = p.statements()
}
if_returns := p.returns
p.returns = false
if p.tok == .key_else {
if p.inside_if_expr {
p.fspace()
}
else {
p.fgen_nl()
}
p.check(.key_else)
p.fspace()
if p.tok == .key_if {
if is_expr {
p.gen(') : (')
nested := p.if_statement(is_expr, elif_depth + 1)
nested_returns := p.returns
p.returns = if_returns && nested_returns
return nested
}
else {
p.gen(' else ')
nested := p.if_statement(is_expr, 0)
nested_returns := p.returns
p.returns = if_returns && nested_returns
return nested
}
// return ''
}
if is_expr {
p.gen(') : (')
}
else {
p.genln(' else { ')
}
p.check(.lcbr)
if is_expr {
p.fspace()
}
// statements() returns the type of the last statement
first_typ := typ
typ = p.statements()
p.inside_if_expr = false
if is_expr {
p.check_types(first_typ, typ)
p.gen(strings.repeat(`)`, 2 * (elif_depth + 1)))
}
else_returns := p.returns
p.returns = if_returns && else_returns
return typ
}
p.inside_if_expr = false
if p.fileis('test_test') {
println('if ret typ="$typ" line=$p.scanner.line_nr')
}
return typ
}