// Copyright (c) 2019-2022 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 c import v.ast import v.util fn (mut g Gen) index_expr(node ast.IndexExpr) { if node.index is ast.RangeExpr { g.range_expr(node, node.index) } else { sym := g.table.final_sym(g.unwrap_generic(node.left_type)) if sym.kind == .array { g.index_of_array(node, sym) } else if sym.kind == .array_fixed { g.index_of_fixed_array(node, sym) } else if sym.kind == .map { g.index_of_map(node, sym) } else if sym.kind == .string && !node.left_type.is_ptr() { is_direct_array_access := (g.fn_decl != 0 && g.fn_decl.is_direct_arr) || node.is_direct if is_direct_array_access { g.expr(node.left) g.write('.str[ ') g.expr(node.index) g.write(']') } else { gen_or := node.or_expr.kind != .absent || node.is_option if gen_or { tmp_opt := g.new_tmp_var() cur_line := g.go_before_stmt(0) g.out.write_string(util.tabs(g.indent)) opt_elem_type := g.typ(ast.byte_type.set_flag(.optional)) g.write('$opt_elem_type $tmp_opt = string_at_with_check(') g.expr(node.left) g.write(', ') g.expr(node.index) g.writeln(');') if !node.is_option { g.or_block(tmp_opt, node.or_expr, ast.byte_type) } g.write('\n$cur_line*(byte*)&${tmp_opt}.data') } else { g.write('string_at(') g.expr(node.left) g.write(', ') g.expr(node.index) g.write(')') } } } else { g.expr(node.left) g.write('[') g.expr(node.index) g.write(']') } } } fn (mut g Gen) range_expr(node ast.IndexExpr, range ast.RangeExpr) { sym := g.table.final_sym(node.left_type) mut tmp_opt := '' mut cur_line := '' mut gen_or := node.or_expr.kind != .absent || node.is_option mut tmp_left := '' if sym.kind == .string { if node.is_gated { g.write('string_substr_ni(') } else { if gen_or { tmp_opt = g.new_tmp_var() cur_line = g.go_before_stmt(0) g.out.write_string(util.tabs(g.indent)) opt_elem_type := g.typ(ast.string_type.set_flag(.optional)) g.write('$opt_elem_type $tmp_opt = string_substr_with_check(') } else { g.write('string_substr(') } } if node.left_type.is_ptr() { g.write('*') } g.expr(node.left) } else if sym.kind == .array { if !range.has_high { tmp_left = g.new_tmp_var() tmp_type := g.typ(node.left_type) g.insert_before_stmt('${util.tabs(g.indent)}$tmp_type $tmp_left;') // (tmp = expr, array_slice(...)) g.write('($tmp_left = ') g.expr(node.left) g.write(', ') } if node.is_gated { g.write('array_slice_ni(') } else { g.write('array_slice(') } if node.left_type.is_ptr() { g.write('*') } if range.has_high { g.expr(node.left) } else { g.write(tmp_left) } } else if sym.kind == .array_fixed { // Convert a fixed array to V array when doing `fixed_arr[start..end]` info := sym.info as ast.ArrayFixed noscan := g.check_noscan(info.elem_type) if node.is_gated { g.write('array_slice_ni(') } else { g.write('array_slice(') } g.write('new_array_from_c_array${noscan}(') ctype := g.typ(info.elem_type) g.write('$info.size, $info.size, sizeof($ctype), ') if node.left_type.is_ptr() { g.write('*') } g.expr(node.left) g.write(')') } else { g.expr(node.left) } g.write(', ') if range.has_low { g.expr(range.low) } else { g.write('0') } g.write(', ') if range.has_high { g.expr(range.high) } else if sym.kind == .array_fixed { info := sym.info as ast.ArrayFixed g.write('$info.size') } else if sym.kind == .array { if node.left_type.is_ptr() { g.write('$tmp_left->') } else { g.write('${tmp_left}.') } g.write('len)') } else { g.write('(') g.expr(node.left) g.write(').len') } g.write(')') if gen_or { if !node.is_option { g.or_block(tmp_opt, node.or_expr, ast.string_type) } g.write('\n$cur_line*(string*)&${tmp_opt}.data') } } fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { gen_or := node.or_expr.kind != .absent || node.is_option left_is_ptr := node.left_type.is_ptr() info := sym.info as ast.Array mut elem_type_str := g.typ(info.elem_type) elem_type := info.elem_type elem_sym := g.table.sym(elem_type) if elem_sym.kind == .function { elem_type_str = 'voidptr' } // `vals[i].field = x` is an exception and requires `array_get`: // `(*(Val*)array_get(vals, i)).field = x;` if g.is_assign_lhs && node.is_setter { is_direct_array_access := (g.fn_decl != 0 && g.fn_decl.is_direct_arr) || node.is_direct is_op_assign := g.assign_op != .assign && info.elem_type != ast.string_type if is_direct_array_access { g.write('(($elem_type_str*)') } else if is_op_assign { g.write('(*($elem_type_str*)array_get(') if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } } else { g.is_arraymap_set = true // special handling of assign_op and closing with '})' g.write('array_set(') if !left_is_ptr || node.left_type.has_flag(.shared_f) { g.write('&') } } g.expr(node.left) if node.left_type.has_flag(.shared_f) { if left_is_ptr { g.write('->val') } else { g.write('.val') } } if is_direct_array_access { if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('->') } else { g.write('.') } g.write('data)[') g.expr(node.index) g.write(']') } else { g.write(', ') g.expr(node.index) if !is_op_assign { mut need_wrapper := true /* match node.right { ast.EnumVal, ast.Ident { // `&x` is enough for variables and enums // `&(Foo[]){ ... }` is only needed for function calls and literals need_wrapper = false } else {} } */ if need_wrapper { g.write(', &($elem_type_str[]) { ') } else { g.write(', &') } } else { // `x[0] *= y` g.write('))') } } } else { is_direct_array_access := (g.fn_decl != 0 && g.fn_decl.is_direct_arr) || node.is_direct // do not clone inside `opt_ok(opt_ok(&(string[]) {..})` before returns needs_clone := info.elem_type == ast.string_type_idx && g.is_autofree && !(g.inside_return && g.fn_decl.return_type.has_flag(.optional)) && !g.is_assign_lhs is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result cur_line := if is_gen_or_and_assign_rhs { line := g.go_before_stmt(0) g.out.write_string(util.tabs(g.indent)) line } else { '' } tmp_opt := if gen_or { g.new_tmp_var() } else { '' } tmp_opt_ptr := if gen_or { g.new_tmp_var() } else { '' } if gen_or { g.write('$elem_type_str* $tmp_opt_ptr = ($elem_type_str*)/*ee elem_ptr_typ */(array_get_with_check(') } else { if needs_clone { g.write('/*2*/string_clone(') } if g.is_fn_index_call { if elem_sym.info is ast.FnType { g.write('((') g.write_fn_ptr_decl(&elem_sym.info, '') g.write(')(*($elem_type_str*)/*ee elem_sym */array_get(') } if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } } else if is_direct_array_access { g.write('(($elem_type_str*)') } else { g.write('(*($elem_type_str*)/*ee elem_sym */array_get(') if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('*') } } } g.expr(node.left) // TODO: test direct_array_access when 'shared' is implemented if node.left_type.has_flag(.shared_f) { if left_is_ptr { g.write('->val') } else { g.write('.val') } } if is_direct_array_access && !gen_or { if left_is_ptr && !node.left_type.has_flag(.shared_f) { g.write('->') } else { g.write('.') } g.write('data)[') g.expr(node.index) g.write(']') } else { g.write(', ') g.expr(node.index) if g.is_fn_index_call { g.write(')))') } else { g.write('))') } } if needs_clone { g.write(')') } if gen_or { g.writeln(';') opt_elem_type := g.typ(elem_type.set_flag(.optional)) g.writeln('$opt_elem_type $tmp_opt = {0};') g.writeln('if ($tmp_opt_ptr) {') g.writeln('\t*(($elem_type_str*)&${tmp_opt}.data) = *(($elem_type_str*)$tmp_opt_ptr);') g.writeln('} else {') g.writeln('\t${tmp_opt}.state = 2; ${tmp_opt}.err = _v_error(_SLIT("array index out of range"));') g.writeln('}') if !node.is_option { g.or_block(tmp_opt, node.or_expr, elem_type) } g.write('\n$cur_line*($elem_type_str*)${tmp_opt}.data') } } } fn (mut g Gen) index_of_fixed_array(node ast.IndexExpr, sym ast.TypeSymbol) { info := sym.info as ast.ArrayFixed elem_type := info.elem_type elem_sym := g.table.sym(elem_type) is_fn_index_call := g.is_fn_index_call && elem_sym.info is ast.FnType if is_fn_index_call { g.write('(*') } if node.left_type.is_ptr() { g.write('(*') g.expr(node.left) g.write(')') } else { g.expr(node.left) } g.write('[') direct := g.fn_decl != 0 && g.fn_decl.is_direct_arr if (direct || node.index is ast.IntegerLiteral) || g.pref.translated { g.expr(node.index) } else { // bounds check g.write('v_fixed_index(') g.expr(node.index) g.write(', $info.size)') } g.write(']') if is_fn_index_call { g.write(')') } } fn (mut g Gen) index_of_map(node ast.IndexExpr, sym ast.TypeSymbol) { gen_or := node.or_expr.kind != .absent || node.is_option left_is_ptr := node.left_type.is_ptr() info := sym.info as ast.Map key_type_str := g.typ(info.key_type) elem_type := info.value_type mut elem_type_str := g.typ(elem_type) elem_sym := g.table.sym(elem_type) if elem_sym.kind == .function { elem_type_str = 'voidptr' } get_and_set_types := elem_sym.kind in [.struct_, .map] if g.is_assign_lhs && !g.is_arraymap_set && !get_and_set_types { if g.assign_op == .assign || info.value_type == ast.string_type { g.is_arraymap_set = true g.write('map_set(') } else { if node.is_setter { g.write('(*(($elem_type_str*)map_get_and_set((map*)') } else { g.write('(*(($elem_type_str*)map_get((map*)') } } if !left_is_ptr || node.left_type.has_flag(.shared_f) { g.write('&') } if node.left is ast.IndexExpr { g.inside_map_index = true g.expr(node.left) g.inside_map_index = false } else { g.expr(node.left) } if node.left_type.has_flag(.shared_f) { g.write('->val') } g.write(', &($key_type_str[]){') old_is_arraymap_set := g.is_arraymap_set old_is_assign_lhs := g.is_assign_lhs g.is_arraymap_set = false g.is_assign_lhs = false g.expr(node.index) g.is_arraymap_set = old_is_arraymap_set g.is_assign_lhs = old_is_assign_lhs g.write('}') g.arraymap_set_pos = g.out.len g.write(', &($elem_type_str[]) { ') if g.assign_op != .assign && info.value_type != ast.string_type { zero := g.type_default(info.value_type) g.write('$zero })))') } } else if g.inside_map_postfix || g.inside_map_infix || g.inside_map_index || (g.is_assign_lhs && !g.is_arraymap_set && get_and_set_types) { zero := g.type_default(info.value_type) if node.is_setter { g.write('(*($elem_type_str*)map_get_and_set((map*)') } else { g.write('(*($elem_type_str*)map_get((map*)') } if !left_is_ptr || node.left_type.has_flag(.shared_f) { g.write('&') } g.expr(node.left) if node.left_type.has_flag(.shared_f) { g.write('->val') } g.write(', &($key_type_str[]){') g.expr(node.index) g.write('}, &($elem_type_str[]){ $zero }))') } else { zero := g.type_default(info.value_type) is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result cur_line := if is_gen_or_and_assign_rhs { line := g.go_before_stmt(0) g.out.write_string(util.tabs(g.indent)) line } else { '' } tmp_opt := if gen_or { g.new_tmp_var() } else { '' } tmp_opt_ptr := if gen_or { g.new_tmp_var() } else { '' } if gen_or { g.write('$elem_type_str* $tmp_opt_ptr = ($elem_type_str*)/*ee elem_ptr_typ */(map_get_check(') } else { if g.is_fn_index_call { if elem_sym.info is ast.FnType { g.write('((') g.write_fn_ptr_decl(&elem_sym.info, '') g.write(')(*(voidptr*)map_get(') } } else { g.write('(*($elem_type_str*)map_get(') } } if !left_is_ptr || node.left_type.has_flag(.shared_f) { g.write('ADDR(map, ') g.expr(node.left) } else { g.write('(') g.expr(node.left) } if node.left_type.has_flag(.shared_f) { if left_is_ptr { g.write('->val') } else { g.write('.val') } } g.write('), &($key_type_str[]){') g.expr(node.index) g.write('}') if gen_or { g.write('))') } else if g.is_fn_index_call { g.write(', &(voidptr[]){ $zero })))') } else { g.write(', &($elem_type_str[]){ $zero }))') } if gen_or { g.writeln(';') opt_elem_type := g.typ(elem_type.set_flag(.optional)) g.writeln('$opt_elem_type $tmp_opt = {0};') g.writeln('if ($tmp_opt_ptr) {') g.writeln('\t*(($elem_type_str*)&${tmp_opt}.data) = *(($elem_type_str*)$tmp_opt_ptr);') g.writeln('} else {') g.writeln('\t${tmp_opt}.state = 2; ${tmp_opt}.err = _v_error(_SLIT("array index out of range"));') g.writeln('}') if !node.is_option { g.or_block(tmp_opt, node.or_expr, elem_type) } g.write('\n$cur_line*($elem_type_str*)${tmp_opt}.data') } } }