diff options
author | Indrajith K L | 2022-12-03 17:00:20 +0530 |
---|---|---|
committer | Indrajith K L | 2022-12-03 17:00:20 +0530 |
commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/v/gen/c | |
download | cli-tools-windows-master.tar.gz cli-tools-windows-master.tar.bz2 cli-tools-windows-master.zip |
Diffstat (limited to 'v_windows/v/vlib/v/gen/c')
28 files changed, 15343 insertions, 0 deletions
diff --git a/v_windows/v/vlib/v/gen/c/array.v b/v_windows/v/vlib/v/gen/c/array.v new file mode 100644 index 0000000..74dc9aa --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/array.v @@ -0,0 +1,723 @@ +// Copyright (c) 2019-2021 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 strings +import v.ast + +fn (mut g Gen) array_init(node ast.ArrayInit) { + array_type := g.unwrap(node.typ) + mut array_styp := '' + elem_type := g.unwrap(node.elem_type) + mut shared_styp := '' // only needed for shared &[]{...} + is_amp := g.is_amp + g.is_amp = false + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared { + shared_styp = g.typ(array_type.typ.set_flag(.shared_f)) + g.writeln('($shared_styp*)__dup_shared_array(&($shared_styp){.mtx = {0}, .val =') + } else if is_amp { + array_styp = g.typ(array_type.typ) + g.write('HEAP($array_styp, ') + } + if array_type.unaliased_sym.kind == .array_fixed { + g.write('{') + if node.has_val { + for i, expr in node.exprs { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + if i != node.exprs.len - 1 { + g.write(', ') + } + } + } else if node.has_default { + g.expr(node.default_expr) + info := array_type.unaliased_sym.info as ast.ArrayFixed + for _ in 1 .. info.size { + g.write(', ') + g.expr(node.default_expr) + } + } else { + g.write('0') + } + g.write('}') + return + } + elem_styp := g.typ(elem_type.typ) + noscan := g.check_noscan(elem_type.typ) + if node.exprs.len == 0 { + is_default_array := elem_type.unaliased_sym.kind == .array && node.has_default + if is_default_array { + g.write('__new_array_with_array_default${noscan}(') + } else { + g.write('__new_array_with_default${noscan}(') + } + if node.has_len { + g.expr(node.len_expr) + g.write(', ') + } else { + g.write('0, ') + } + if node.has_cap { + g.expr(node.cap_expr) + g.write(', ') + } else { + g.write('0, ') + } + if elem_type.unaliased_sym.kind == .function { + g.write('sizeof(voidptr), ') + } else { + g.write('sizeof($elem_styp), ') + } + if is_default_array { + g.write('($elem_styp[]){') + g.expr(node.default_expr) + g.write('}[0])') + } else if node.has_default { + g.write('&($elem_styp[]){') + g.expr(node.default_expr) + g.write('})') + } else if node.has_len && node.elem_type == ast.string_type { + g.write('&($elem_styp[]){') + g.write('_SLIT("")') + g.write('})') + } else if node.has_len && elem_type.unaliased_sym.kind in [.array, .map] { + g.write('(voidptr)&($elem_styp[]){') + g.write(g.type_default(node.elem_type)) + g.write('}[0])') + } else { + g.write('0)') + } + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write(')') + } + return + } + len := node.exprs.len + if elem_type.unaliased_sym.kind == .function { + g.write('new_array_from_c_array($len, $len, sizeof(voidptr), _MOV((voidptr[$len]){') + } else { + g.write('new_array_from_c_array${noscan}($len, $len, sizeof($elem_styp), _MOV(($elem_styp[$len]){') + } + if len > 8 { + g.writeln('') + g.write('\t\t') + } + for i, expr in node.exprs { + if node.expr_types[i] == ast.string_type && expr !is ast.StringLiteral + && expr !is ast.StringInterLiteral { + g.write('string_clone(') + g.expr(expr) + g.write(')') + } else { + g.expr_with_cast(expr, node.expr_types[i], node.elem_type) + } + if i != len - 1 { + if i > 0 && i & 7 == 0 { // i > 0 && i % 8 == 0 + g.writeln(',') + g.write('\t\t') + } else { + g.write(', ') + } + } + } + g.write('}))') + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write('), sizeof($array_styp))') + } +} + +// `nums.map(it % 2 == 0)` +fn (mut g Gen) gen_array_map(node ast.CallExpr) { + g.inside_lambda = true + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + // println('filter s="$s"') + ret_typ := g.typ(node.return_type) + // inp_typ := g.typ(node.receiver_type) + ret_sym := g.table.get_type_symbol(node.return_type) + inp_sym := g.table.get_type_symbol(node.receiver_type) + ret_info := ret_sym.info as ast.Array + ret_elem_type := g.typ(ret_info.elem_type) + inp_info := inp_sym.info as ast.Array + inp_elem_type := g.typ(inp_info.elem_type) + if inp_sym.kind != .array { + verror('map() requires an array') + } + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + noscan := g.check_noscan(ret_info.elem_type) + g.writeln('$ret_typ $tmp = __new_array${noscan}(0, ${tmp}_len, sizeof($ret_elem_type));\n') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$inp_elem_type it = (($inp_elem_type*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\t$ret_elem_type ti = ') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\t$ret_elem_type ti = ') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym := g.table.get_type_symbol(var_info.typ) + if sym.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\t$ret_elem_type ti = ') + g.expr(node.args[0].expr) + } + else { + g.write('\t$ret_elem_type ti = ') + g.expr(node.args[0].expr) + } + } + g.writeln(';') + g.writeln('\tarray_push${noscan}((array*)&$tmp, &ti);') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) + g.inside_lambda = false +} + +// `users.sort(a.age < b.age)` +fn (mut g Gen) gen_array_sort(node ast.CallExpr) { + // println('filter s="$s"') + rec_sym := g.table.get_type_symbol(node.receiver_type) + if rec_sym.kind != .array { + println(node.name) + println(g.typ(node.receiver_type)) + // println(rec_sym.kind) + verror('.sort() is an array method') + } + if g.pref.is_bare { + g.writeln('bare_panic(_SLIT("sort does not work with -freestanding"))') + return + } + info := rec_sym.info as ast.Array + // `users.sort(a.age > b.age)` + // Generate a comparison function for a custom type + elem_stype := g.typ(info.elem_type) + mut compare_fn := 'compare_${elem_stype.replace('*', '_ptr')}' + mut comparison_type := g.unwrap(ast.void_type) + mut left_expr, mut right_expr := '', '' + // the only argument can only be an infix expression like `a < b` or `b.field > a.field` + if node.args.len == 0 { + comparison_type = g.unwrap(info.elem_type.set_nr_muls(0)) + if compare_fn in g.array_sort_fn { + g.gen_array_sort_call(node, compare_fn) + return + } + left_expr = '*a' + right_expr = '*b' + } else { + infix_expr := node.args[0].expr as ast.InfixExpr + comparison_type = g.unwrap(infix_expr.left_type.set_nr_muls(0)) + left_name := infix_expr.left.str() + if left_name.len > 1 { + compare_fn += '_by' + left_name[1..].replace_each(['.', '_', '[', '_', ']', '_']) + } + // is_reverse is `true` for `.sort(a > b)` and `.sort(b < a)` + is_reverse := (left_name.starts_with('a') && infix_expr.op == .gt) + || (left_name.starts_with('b') && infix_expr.op == .lt) + if is_reverse { + compare_fn += '_reverse' + } + if compare_fn in g.array_sort_fn { + g.gen_array_sort_call(node, compare_fn) + return + } + if left_name.starts_with('a') != is_reverse { + left_expr = g.expr_string(infix_expr.left) + right_expr = g.expr_string(infix_expr.right) + if infix_expr.left is ast.Ident { + left_expr = '*' + left_expr + } + if infix_expr.right is ast.Ident { + right_expr = '*' + right_expr + } + } else { + left_expr = g.expr_string(infix_expr.right) + right_expr = g.expr_string(infix_expr.left) + if infix_expr.left is ast.Ident { + right_expr = '*' + right_expr + } + if infix_expr.right is ast.Ident { + left_expr = '*' + left_expr + } + } + } + + // Register a new custom `compare_xxx` function for qsort() + // TODO: move to checker + g.table.register_fn(name: compare_fn, return_type: ast.int_type) + g.array_sort_fn[compare_fn] = true + + stype_arg := g.typ(info.elem_type) + g.definitions.writeln('int ${compare_fn}($stype_arg* a, $stype_arg* b) {') + c_condition := if comparison_type.sym.has_method('<') { + '${g.typ(comparison_type.typ)}__lt($left_expr, $right_expr)' + } else if comparison_type.unaliased_sym.has_method('<') { + '${g.typ(comparison_type.unaliased)}__lt($left_expr, $right_expr)' + } else { + '$left_expr < $right_expr' + } + g.definitions.writeln('\tif ($c_condition) return -1;') + g.definitions.writeln('\telse return 1;') + g.definitions.writeln('}\n') + + // write call to the generated function + g.gen_array_sort_call(node, compare_fn) +} + +fn (mut g Gen) gen_array_sort_call(node ast.CallExpr, compare_fn string) { + deref_field := if node.left_type.is_ptr() || node.left_type.is_pointer() { '->' } else { '.' } + // eprintln('> qsort: pointer $node.left_type | deref: `$deref`') + g.empty_line = true + g.write('qsort(') + g.expr(node.left) + g.write('${deref_field}data, ') + g.expr(node.left) + g.write('${deref_field}len, ') + g.expr(node.left) + g.write('${deref_field}element_size, (int (*)(const void *, const void *))&$compare_fn)') +} + +// `nums.filter(it % 2 == 0)` +fn (mut g Gen) gen_array_filter(node ast.CallExpr) { + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + // println('filter s="$s"') + sym := g.table.get_type_symbol(node.return_type) + if sym.kind != .array { + verror('filter() requires an array') + } + info := sym.info as ast.Array + styp := g.typ(node.return_type) + elem_type_str := g.typ(info.elem_type) + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + noscan := g.check_noscan(info.elem_type) + g.writeln('$styp $tmp = __new_array${noscan}(0, ${tmp}_len, sizeof($elem_type_str));\n') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\tif (') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\tif (') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym_t := g.table.get_type_symbol(var_info.typ) + if sym_t.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\tif (') + g.expr(node.args[0].expr) + } + else { + g.write('\tif (') + g.expr(node.args[0].expr) + } + } + g.writeln(') {') + g.writeln('\t\tarray_push${noscan}((array*)&$tmp, &it); \n\t\t}') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) +} + +// `nums.insert(0, 2)` `nums.insert(0, [2,3,4])` +fn (mut g Gen) gen_array_insert(node ast.CallExpr) { + left_sym := g.table.get_type_symbol(node.left_type) + left_info := left_sym.info as ast.Array + elem_type_str := g.typ(left_info.elem_type) + arg2_sym := g.table.get_type_symbol(node.args[1].typ) + is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type + noscan := g.check_noscan(left_info.elem_type) + if is_arg2_array { + g.write('array_insert_many${noscan}(&') + } else { + g.write('array_insert${noscan}(&') + } + g.expr(node.left) + g.write(', ') + g.expr(node.args[0].expr) + if is_arg2_array { + g.write(', ') + g.expr(node.args[1].expr) + g.write('.data, ') + g.expr(node.args[1].expr) + g.write('.len)') + } else { + g.write(', &($elem_type_str[]){') + if left_info.elem_type == ast.string_type { + g.write('string_clone(') + } + g.expr_with_cast(node.args[1].expr, node.args[1].typ, left_info.elem_type) + if left_info.elem_type == ast.string_type { + g.write(')') + } + g.write('})') + } +} + +// `nums.prepend(2)` `nums.prepend([2,3,4])` +fn (mut g Gen) gen_array_prepend(node ast.CallExpr) { + left_sym := g.table.get_type_symbol(node.left_type) + left_info := left_sym.info as ast.Array + elem_type_str := g.typ(left_info.elem_type) + arg_sym := g.table.get_type_symbol(node.args[0].typ) + is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type + noscan := g.check_noscan(left_info.elem_type) + if is_arg_array { + g.write('array_prepend_many${noscan}(&') + } else { + g.write('array_prepend${noscan}(&') + } + g.expr(node.left) + if is_arg_array { + g.write(', ') + g.expr(node.args[0].expr) + g.write('.data, ') + g.expr(node.args[0].expr) + g.write('.len)') + } else { + g.write(', &($elem_type_str[]){') + g.expr_with_cast(node.args[0].expr, node.args[0].typ, left_info.elem_type) + g.write('})') + } +} + +fn (mut g Gen) gen_array_contains_method(left_type ast.Type) string { + mut unwrap_left_type := g.unwrap_generic(left_type) + if unwrap_left_type.share() == .shared_t { + unwrap_left_type = unwrap_left_type.clear_flag(.shared_f) + } + mut left_sym := g.table.get_type_symbol(unwrap_left_type) + left_final_sym := g.table.get_final_type_symbol(unwrap_left_type) + mut left_type_str := g.typ(unwrap_left_type).replace('*', '') + fn_name := '${left_type_str}_contains' + if !left_sym.has_method('contains') { + left_info := left_final_sym.info as ast.Array + mut elem_type_str := g.typ(left_info.elem_type) + elem_sym := g.table.get_type_symbol(left_info.elem_type) + if elem_sym.kind == .function { + left_type_str = 'Array_voidptr' + elem_type_str = 'voidptr' + } + g.type_definitions.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v) {') + fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') + if elem_sym.kind == .string { + fn_builder.writeln('\t\tif (fast_string_eq(((string*)a.data)[i], v)) {') + } else if elem_sym.kind == .array && left_info.elem_type.nr_muls() == 0 { + ptr_typ := g.gen_array_equality_fn(left_info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq((($elem_type_str*)a.data)[i], v)) {') + } else if elem_sym.kind == .function { + fn_builder.writeln('\t\tif (((voidptr*)a.data)[i] == v) {') + } else if elem_sym.kind == .map && left_info.elem_type.nr_muls() == 0 { + ptr_typ := g.gen_map_equality_fn(left_info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_map_eq((($elem_type_str*)a.data)[i], v)) {') + } else if elem_sym.kind == .struct_ && left_info.elem_type.nr_muls() == 0 { + ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq((($elem_type_str*)a.data)[i], v)) {') + } else { + fn_builder.writeln('\t\tif ((($elem_type_str*)a.data)[i] == v) {') + } + fn_builder.writeln('\t\t\treturn true;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn false;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + left_sym.register_method(&ast.Fn{ + name: 'contains' + params: [ast.Param{ + typ: unwrap_left_type + }, ast.Param{ + typ: left_info.elem_type + }] + }) + } + return fn_name +} + +// `nums.contains(2)` +fn (mut g Gen) gen_array_contains(node ast.CallExpr) { + fn_name := g.gen_array_contains_method(node.left_type) + g.write('${fn_name}(') + if node.left_type.is_ptr() && node.left_type.share() != .shared_t { + g.write('*') + } + g.expr(node.left) + if node.left_type.share() == .shared_t { + g.write('->val') + } + g.write(', ') + g.expr(node.args[0].expr) + g.write(')') +} + +fn (mut g Gen) gen_array_index_method(left_type ast.Type) string { + unwrap_left_type := g.unwrap_generic(left_type) + mut left_sym := g.table.get_type_symbol(unwrap_left_type) + mut left_type_str := g.typ(unwrap_left_type).trim('*') + fn_name := '${left_type_str}_index' + if !left_sym.has_method('index') { + info := left_sym.info as ast.Array + mut elem_type_str := g.typ(info.elem_type) + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.kind == .function { + left_type_str = 'Array_voidptr' + elem_type_str = 'voidptr' + } + g.type_definitions.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v) {') + fn_builder.writeln('\t$elem_type_str* pelem = a.data;') + fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i, ++pelem) {') + if elem_sym.kind == .string { + fn_builder.writeln('\t\tif (fast_string_eq(*pelem, v)) {') + } else if elem_sym.kind == .array && !info.elem_type.is_ptr() { + ptr_typ := g.gen_array_equality_fn(info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq( *pelem, v)) {') + } else if elem_sym.kind == .function && !info.elem_type.is_ptr() { + fn_builder.writeln('\t\tif ( pelem == v) {') + } else if elem_sym.kind == .map && !info.elem_type.is_ptr() { + ptr_typ := g.gen_map_equality_fn(info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(( *pelem, v))) {') + } else if elem_sym.kind == .struct_ && !info.elem_type.is_ptr() { + ptr_typ := g.gen_struct_equality_fn(info.elem_type) + fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq( *pelem, v)) {') + } else { + fn_builder.writeln('\t\tif (*pelem == v) {') + } + fn_builder.writeln('\t\t\treturn i;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn -1;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + left_sym.register_method(&ast.Fn{ + name: 'index' + params: [ast.Param{ + typ: unwrap_left_type + }, ast.Param{ + typ: info.elem_type + }] + }) + } + return fn_name +} + +// `nums.index(2)` +fn (mut g Gen) gen_array_index(node ast.CallExpr) { + fn_name := g.gen_array_index_method(node.left_type) + g.write('${fn_name}(') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + g.expr(node.args[0].expr) + g.write(')') +} + +fn (mut g Gen) gen_array_wait(node ast.CallExpr) { + arr := g.table.get_type_symbol(node.receiver_type) + thread_type := arr.array_info().elem_type + thread_sym := g.table.get_type_symbol(thread_type) + thread_ret_type := thread_sym.thread_info().return_type + eltyp := g.table.get_type_symbol(thread_ret_type).cname + fn_name := g.register_thread_array_wait_call(eltyp) + g.write('${fn_name}(') + g.expr(node.left) + g.write(')') +} + +fn (mut g Gen) gen_array_any(node ast.CallExpr) { + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + sym := g.table.get_type_symbol(node.left_type) + info := sym.info as ast.Array + // styp := g.typ(node.return_type) + elem_type_str := g.typ(info.elem_type) + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + g.writeln('bool $tmp = false;') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\tif (') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\tif (') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym_t := g.table.get_type_symbol(var_info.typ) + if sym_t.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\tif (') + g.expr(node.args[0].expr) + } + else { + g.write('\tif (') + g.expr(node.args[0].expr) + } + } + g.writeln(') {') + g.writeln('\t\t$tmp = true;\n\t\t\tbreak;\n\t\t}') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) +} + +fn (mut g Gen) gen_array_all(node ast.CallExpr) { + tmp := g.new_tmp_var() + s := g.go_before_stmt(0) + sym := g.table.get_type_symbol(node.left_type) + info := sym.info as ast.Array + // styp := g.typ(node.return_type) + elem_type_str := g.typ(info.elem_type) + g.empty_line = true + g.write('${g.typ(node.left_type)} ${tmp}_orig = ') + g.expr(node.left) + g.writeln(';') + g.writeln('int ${tmp}_len = ${tmp}_orig.len;') + g.writeln('bool $tmp = true;') + i := g.new_tmp_var() + g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') + g.writeln('\t$elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') + mut is_embed_map_filter := false + mut expr := node.args[0].expr + match mut expr { + ast.AnonFn { + g.write('\tif (!(') + g.gen_anon_fn_decl(mut expr) + g.write('${expr.decl.name}(it)') + } + ast.Ident { + g.write('\tif (!(') + if expr.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else if expr.kind == .variable { + var_info := expr.var_info() + sym_t := g.table.get_type_symbol(var_info.typ) + if sym_t.kind == .function { + g.write('${c_name(expr.name)}(it)') + } else { + g.expr(node.args[0].expr) + } + } else { + g.expr(node.args[0].expr) + } + } + ast.CallExpr { + if expr.name in ['map', 'filter'] { + is_embed_map_filter = true + g.stmt_path_pos << g.out.len + } + g.write('\tif (!(') + g.expr(node.args[0].expr) + } + else { + g.write('\tif (!(') + g.expr(node.args[0].expr) + } + } + g.writeln(')) {') + g.writeln('\t\t$tmp = false;\n\t\t\tbreak;\n\t\t}') + g.writeln('}') + if !is_embed_map_filter { + g.stmt_path_pos << g.out.len + } + g.write('\n') + g.write(s) + g.write(tmp) +} diff --git a/v_windows/v/vlib/v/gen/c/assert.v b/v_windows/v/vlib/v/gen/c/assert.v new file mode 100644 index 0000000..46b48f7 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/assert.v @@ -0,0 +1,159 @@ +// Copyright (c) 2019-2021 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 + +fn (mut g Gen) gen_assert_stmt(original_assert_statement ast.AssertStmt) { + if !original_assert_statement.is_used { + return + } + mut node := original_assert_statement + g.writeln('// assert') + if mut node.expr is ast.InfixExpr { + if mut node.expr.left is ast.CallExpr { + node.expr.left = g.new_ctemp_var_then_gen(node.expr.left, node.expr.left_type) + } else if mut node.expr.left is ast.ParExpr { + if node.expr.left.expr is ast.CallExpr { + node.expr.left = g.new_ctemp_var_then_gen(node.expr.left.expr, node.expr.left_type) + } + } + if mut node.expr.right is ast.CallExpr { + node.expr.right = g.new_ctemp_var_then_gen(node.expr.right, node.expr.right_type) + } else if mut node.expr.right is ast.ParExpr { + if node.expr.right.expr is ast.CallExpr { + node.expr.right = g.new_ctemp_var_then_gen(node.expr.right.expr, node.expr.right_type) + } + } + } + g.inside_ternary++ + if g.is_test { + g.write('if (') + g.expr(node.expr) + g.write(')') + g.decrement_inside_ternary() + g.writeln(' {') + g.writeln('\tg_test_oks++;') + metaname_ok := g.gen_assert_metainfo(node) + g.writeln('\tmain__cb_assertion_ok(&$metaname_ok);') + g.writeln('} else {') + g.writeln('\tg_test_fails++;') + metaname_fail := g.gen_assert_metainfo(node) + g.writeln('\tmain__cb_assertion_failed(&$metaname_fail);') + g.gen_assert_postfailure_mode(node) + g.writeln('\tlongjmp(g_jump_buffer, 1);') + g.writeln('\t// TODO') + g.writeln('\t// Maybe print all vars in a test function if it fails?') + g.writeln('}') + } else { + g.write('if (!(') + g.expr(node.expr) + g.write('))') + g.decrement_inside_ternary() + g.writeln(' {') + metaname_panic := g.gen_assert_metainfo(node) + g.writeln('\t__print_assert_failure(&$metaname_panic);') + g.gen_assert_postfailure_mode(node) + g.writeln('\t_v_panic(_SLIT("Assertion failed..."));') + g.writeln('}') + } +} + +fn (mut g Gen) gen_assert_postfailure_mode(node ast.AssertStmt) { + g.write_v_source_line_info(node.pos) + match g.pref.assert_failure_mode { + .default {} + .aborts { + g.writeln('\tabort();') + } + .backtraces { + g.writeln('\tprint_backtrace();') + } + } +} + +fn (mut g Gen) gen_assert_metainfo(node ast.AssertStmt) string { + mod_path := cestring(g.file.path) + fn_name := g.fn_decl.name + line_nr := node.pos.line_nr + src := cestring(node.expr.str()) + metaname := 'v_assert_meta_info_$g.new_tmp_var()' + g.writeln('\tVAssertMetaInfo $metaname = {0};') + g.writeln('\t${metaname}.fpath = ${ctoslit(mod_path)};') + g.writeln('\t${metaname}.line_nr = $line_nr;') + g.writeln('\t${metaname}.fn_name = ${ctoslit(fn_name)};') + metasrc := cnewlines(ctoslit(src)) + g.writeln('\t${metaname}.src = $metasrc;') + match mut node.expr { + ast.InfixExpr { + expr_op_str := ctoslit(node.expr.op.str()) + expr_left_str := cnewlines(ctoslit(node.expr.left.str())) + expr_right_str := cnewlines(ctoslit(node.expr.right.str())) + g.writeln('\t${metaname}.op = $expr_op_str;') + g.writeln('\t${metaname}.llabel = $expr_left_str;') + g.writeln('\t${metaname}.rlabel = $expr_right_str;') + g.write('\t${metaname}.lvalue = ') + g.gen_assert_single_expr(node.expr.left, node.expr.left_type) + g.writeln(';') + g.write('\t${metaname}.rvalue = ') + g.gen_assert_single_expr(node.expr.right, node.expr.right_type) + g.writeln(';') + } + ast.CallExpr { + g.writeln('\t${metaname}.op = _SLIT("call");') + } + else {} + } + return metaname +} + +fn (mut g Gen) gen_assert_single_expr(expr ast.Expr, typ ast.Type) { + // eprintln('> gen_assert_single_expr typ: $typ | expr: $expr | typeof(expr): ${typeof(expr)}') + unknown_value := '*unknown value*' + match expr { + ast.CastExpr, ast.IfExpr, ast.IndexExpr, ast.MatchExpr { + g.write(ctoslit(unknown_value)) + } + ast.PrefixExpr { + if expr.right is ast.CastExpr { + // TODO: remove this check; + // vlib/builtin/map_test.v (a map of &int, set to &int(0)) fails + // without special casing ast.CastExpr here + g.write(ctoslit(unknown_value)) + } else { + g.gen_expr_to_string(expr, typ) + } + } + ast.TypeNode { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + g.write(ctoslit('$sym.name')) + } + else { + mut should_clone := true + if typ == ast.string_type && expr is ast.StringLiteral { + should_clone = false + } + if expr is ast.CTempVar { + if expr.orig is ast.CallExpr { + should_clone = false + if expr.orig.or_block.kind == .propagate { + should_clone = true + } + if expr.orig.is_method && expr.orig.args.len == 0 + && expr.orig.name == 'type_name' { + should_clone = true + } + } + } + if should_clone { + g.write('string_clone(') + } + g.gen_expr_to_string(expr, typ) + if should_clone { + g.write(')') + } + } + } + g.write(' /* typeof: ' + expr.type_name() + ' type: ' + typ.str() + ' */ ') +} diff --git a/v_windows/v/vlib/v/gen/c/auto_eq_methods.v b/v_windows/v/vlib/v/gen/c/auto_eq_methods.v new file mode 100644 index 0000000..16dd339 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/auto_eq_methods.v @@ -0,0 +1,343 @@ +// Copyright (c) 2019-2021 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 strings +import v.ast + +fn (mut g Gen) gen_sumtype_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.sumtype_fn_definitions { + return ptr_styp + } + g.sumtype_fn_definitions << ptr_styp + info := left.sym.sumtype_info() + g.type_definitions.writeln('static bool ${ptr_styp}_sumtype_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_sumtype_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tif (a._typ != b._typ) { return false; }') + for typ in info.variants { + variant := g.unwrap(typ) + fn_builder.writeln('\tif (a._typ == $variant.typ) {') + name := '_$variant.sym.cname' + if variant.sym.kind == .string { + fn_builder.writeln('\t\treturn string__eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .sum_type && !typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_sumtype_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .struct_ && !typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_struct_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .array && !typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_arr_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .array_fixed && !typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_arr_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .map && !typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_map_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .alias && !typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(typ) + fn_builder.writeln('\t\treturn ${eq_fn}_alias_eq(*a.$name, *b.$name);') + } else if variant.sym.kind == .function { + fn_builder.writeln('\t\treturn *((voidptr*)(*a.$name)) == *((voidptr*)(*b.$name));') + } else { + fn_builder.writeln('\t\treturn *a.$name == *b.$name;') + } + fn_builder.writeln('\t}') + } + fn_builder.writeln('\treturn false;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_struct_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.struct_fn_definitions { + return ptr_styp + } + g.struct_fn_definitions << ptr_styp + info := left.sym.struct_info() + g.type_definitions.writeln('static bool ${ptr_styp}_struct_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + defer { + g.auto_fn_definitions << fn_builder.str() + } + fn_builder.writeln('static bool ${ptr_styp}_struct_eq($ptr_styp a, $ptr_styp b) {') + + // overloaded + if left.sym.has_method('==') { + fn_builder.writeln('\treturn ${ptr_styp}__eq(a, b);') + fn_builder.writeln('}') + return ptr_styp + } + + fn_builder.write_string('\treturn ') + if info.fields.len > 0 { + for i, field in info.fields { + if i > 0 { + fn_builder.write_string('\n\t\t&& ') + } + field_type := g.unwrap(field.typ) + if field_type.sym.kind == .string { + fn_builder.write_string('string__eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .sum_type && !field.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_sumtype_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .struct_ && !field.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_struct_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .array && !field.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_arr_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .array_fixed && !field.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_arr_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .map && !field.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_map_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .alias && !field.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(field.typ) + fn_builder.write_string('${eq_fn}_alias_eq(a.$field.name, b.$field.name)') + } else if field_type.sym.kind == .function { + fn_builder.write_string('*((voidptr*)(a.$field.name)) == *((voidptr*)(b.$field.name))') + } else { + fn_builder.write_string('a.$field.name == b.$field.name') + } + } + } else { + fn_builder.write_string('true') + } + fn_builder.writeln(';') + fn_builder.writeln('}') + return ptr_styp +} + +fn (mut g Gen) gen_alias_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.alias_fn_definitions { + return ptr_styp + } + g.alias_fn_definitions << ptr_styp + info := left.sym.info as ast.Alias + g.type_definitions.writeln('static bool ${ptr_styp}_alias_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_alias_eq($ptr_styp a, $ptr_styp b) {') + sym := g.table.get_type_symbol(info.parent_type) + if sym.kind == .string { + fn_builder.writeln('\treturn string__eq(a, b);') + } else if sym.kind == .sum_type && !left.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_sumtype_eq(a, b);') + } else if sym.kind == .struct_ && !left.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_struct_eq(a, b);') + } else if sym.kind == .array && !left.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_arr_eq(a, b);') + } else if sym.kind == .array_fixed && !left.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_arr_eq(a, b);') + } else if sym.kind == .map && !left.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(info.parent_type) + fn_builder.writeln('\treturn ${eq_fn}_map_eq(a, b);') + } else if sym.kind == .function { + fn_builder.writeln('\treturn *((voidptr*)(a)) == *((voidptr*)(b));') + } else { + fn_builder.writeln('\treturn a == b;') + } + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_array_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.array_fn_definitions { + return ptr_styp + } + g.array_fn_definitions << ptr_styp + elem := g.unwrap(left.sym.array_info().elem_type) + ptr_elem_styp := g.typ(elem.typ) + g.type_definitions.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tif (a.len != b.len) {') + fn_builder.writeln('\t\treturn false;') + fn_builder.writeln('\t}') + fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') + // compare every pair of elements of the two arrays + if elem.sym.kind == .string { + fn_builder.writeln('\t\tif (!string__eq(*(($ptr_elem_styp*)((byte*)a.data+(i*a.element_size))), *(($ptr_elem_styp*)((byte*)b.data+(i*b.element_size))))) {') + } else if elem.sym.kind == .sum_type && !elem.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .struct_ && !elem.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .array && !elem.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .array_fixed && !elem.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .map && !elem.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .alias && !elem.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq((($ptr_elem_styp*)a.data)[i], (($ptr_elem_styp*)b.data)[i])) {') + } else if elem.sym.kind == .function { + fn_builder.writeln('\t\tif (*((voidptr*)((byte*)a.data+(i*a.element_size))) != *((voidptr*)((byte*)b.data+(i*b.element_size)))) {') + } else { + fn_builder.writeln('\t\tif (*(($ptr_elem_styp*)((byte*)a.data+(i*a.element_size))) != *(($ptr_elem_styp*)((byte*)b.data+(i*b.element_size)))) {') + } + fn_builder.writeln('\t\t\treturn false;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn true;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_fixed_array_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.array_fn_definitions { + return ptr_styp + } + g.array_fn_definitions << ptr_styp + elem_info := left.sym.array_fixed_info() + elem := g.unwrap(elem_info.elem_type) + size := elem_info.size + g.type_definitions.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_arr_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tfor (int i = 0; i < $size; ++i) {') + // compare every pair of elements of the two fixed arrays + if elem.sym.kind == .string { + fn_builder.writeln('\t\tif (!string__eq(a[i], b[i])) {') + } else if elem.sym.kind == .sum_type && !elem.typ.is_ptr() { + eq_fn := g.gen_sumtype_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(a[i], b[i])) {') + } else if elem.sym.kind == .struct_ && !elem.typ.is_ptr() { + eq_fn := g.gen_struct_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(a[i], b[i])) {') + } else if elem.sym.kind == .array && !elem.typ.is_ptr() { + eq_fn := g.gen_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a[i], b[i])) {') + } else if elem.sym.kind == .array_fixed && !elem.typ.is_ptr() { + eq_fn := g.gen_fixed_array_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(a[i], b[i])) {') + } else if elem.sym.kind == .map && !elem.typ.is_ptr() { + eq_fn := g.gen_map_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(a[i], b[i])) {') + } else if elem.sym.kind == .alias && !elem.typ.is_ptr() { + eq_fn := g.gen_alias_equality_fn(elem.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(a[i], b[i])) {') + } else if elem.sym.kind == .function { + fn_builder.writeln('\t\tif (a[i] != b[i]) {') + } else { + fn_builder.writeln('\t\tif (a[i] != b[i]) {') + } + fn_builder.writeln('\t\t\treturn false;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn true;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} + +fn (mut g Gen) gen_map_equality_fn(left_type ast.Type) string { + left := g.unwrap(left_type) + ptr_styp := g.typ(left.typ.set_nr_muls(0)) + if ptr_styp in g.map_fn_definitions { + return ptr_styp + } + g.map_fn_definitions << ptr_styp + value := g.unwrap(left.sym.map_info().value_type) + ptr_value_styp := g.typ(value.typ) + g.type_definitions.writeln('static bool ${ptr_styp}_map_eq($ptr_styp a, $ptr_styp b); // auto') + + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static bool ${ptr_styp}_map_eq($ptr_styp a, $ptr_styp b) {') + fn_builder.writeln('\tif (a.len != b.len) {') + fn_builder.writeln('\t\treturn false;') + fn_builder.writeln('\t}') + fn_builder.writeln('\tfor (int i = 0; i < a.key_values.len; ++i) {') + fn_builder.writeln('\t\tif (!DenseArray_has_index(&a.key_values, i)) continue;') + fn_builder.writeln('\t\tvoidptr k = DenseArray_key(&a.key_values, i);') + fn_builder.writeln('\t\tif (!map_exists(&b, k)) return false;') + kind := g.table.type_kind(value.typ) + if kind == .function { + func := value.sym.info as ast.FnType + ret_styp := g.typ(func.func.return_type) + fn_builder.write_string('\t\t$ret_styp (*v) (') + arg_len := func.func.params.len + for j, arg in func.func.params { + arg_styp := g.typ(arg.typ) + fn_builder.write_string('$arg_styp $arg.name') + if j < arg_len - 1 { + fn_builder.write_string(', ') + } + } + fn_builder.writeln(') = *(voidptr*)map_get(&a, k, &(voidptr[]){ 0 });') + } else { + fn_builder.writeln('\t\t$ptr_value_styp v = *($ptr_value_styp*)map_get(&a, k, &($ptr_value_styp[]){ 0 });') + } + match kind { + .string { + fn_builder.writeln('\t\tif (!fast_string_eq(*(string*)map_get(&b, k, &(string[]){_SLIT("")}), v)) {') + } + .sum_type { + eq_fn := g.gen_sumtype_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_sumtype_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .struct_ { + eq_fn := g.gen_struct_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_struct_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .array { + eq_fn := g.gen_array_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .array_fixed { + eq_fn := g.gen_fixed_array_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_arr_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .map { + eq_fn := g.gen_map_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_map_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .alias { + eq_fn := g.gen_alias_equality_fn(value.typ) + fn_builder.writeln('\t\tif (!${eq_fn}_alias_eq(*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }), v)) {') + } + .function { + fn_builder.writeln('\t\tif (*(voidptr*)map_get(&b, k, &(voidptr[]){ 0 }) != v) {') + } + else { + fn_builder.writeln('\t\tif (*($ptr_value_styp*)map_get(&b, k, &($ptr_value_styp[]){ 0 }) != v) {') + } + } + fn_builder.writeln('\t\t\treturn false;') + fn_builder.writeln('\t\t}') + fn_builder.writeln('\t}') + fn_builder.writeln('\treturn true;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + return ptr_styp +} diff --git a/v_windows/v/vlib/v/gen/c/auto_str_methods.v b/v_windows/v/vlib/v/gen/c/auto_str_methods.v new file mode 100644 index 0000000..ffec50c --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/auto_str_methods.v @@ -0,0 +1,900 @@ +// Copyright (c) 2019-2021 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 +import strings + +pub enum StrIntpType { + si_no_str = 0 // no parameter to print only fix string + si_c + si_u8 + si_i8 + si_u16 + si_i16 + si_u32 + si_i32 + si_u64 + si_i64 + si_e32 + si_e64 + si_f32 + si_f64 + si_g32 + si_g64 + si_s + si_p + si_vp +} + +pub fn type_to_str(x StrIntpType) string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'c' } + .si_u8 { return 'u8' } + .si_i8 { return 'i8' } + .si_u16 { return 'u16' } + .si_i16 { return 'i16' } + .si_u32 { return 'u32' } + .si_i32 { return 'i32' } + .si_u64 { return 'u64' } + .si_i64 { return 'i64' } + .si_f32 { return 'f32' } + .si_f64 { return 'f64' } + .si_g32 { return 'f32' } // g32 format use f32 data + .si_g64 { return 'f64' } // g64 format use f64 data + .si_e32 { return 'f32' } // e32 format use f32 data + .si_e64 { return 'f64' } // e64 format use f64 data + .si_s { return 's' } + .si_p { return 'p' } + .si_vp { return 'vp' } + } +} + +pub fn data_str(x StrIntpType) string { + match x { + .si_no_str { return 'no_str' } + .si_c { return 'd_c' } + .si_u8 { return 'd_u8' } + .si_i8 { return 'd_i8' } + .si_u16 { return 'd_u16' } + .si_i16 { return 'd_i16' } + .si_u32 { return 'd_u32' } + .si_i32 { return 'd_i32' } + .si_u64 { return 'd_u64' } + .si_i64 { return 'd_i64' } + .si_f32 { return 'd_f32' } + .si_f64 { return 'd_f64' } + .si_g32 { return 'd_f32' } // g32 format use f32 data + .si_g64 { return 'd_f64' } // g64 format use f64 data + .si_e32 { return 'd_f32' } // e32 format use f32 data + .si_e64 { return 'd_f64' } // e64 format use f64 data + .si_s { return 'd_s' } + .si_p { return 'd_p' } + .si_vp { return 'd_vp' } + } +} + +const ( + // BUG: this const is not released from the memory! use a const for now + // si_s_code = "0x" + int(StrIntpType.si_s).hex() // code for a simple string + si_s_code = '0xfe10' +) + +fn should_use_indent_func(kind ast.Kind) bool { + return kind in [.struct_, .alias, .array, .array_fixed, .map, .sum_type, .interface_] +} + +fn (mut g Gen) gen_str_default(sym ast.TypeSymbol, styp string, str_fn_name string) { + mut convertor := '' + mut typename_ := '' + if sym.parent_idx in ast.integer_type_idxs { + convertor = 'int' + typename_ = 'int' + } else if sym.parent_idx == ast.f32_type_idx { + convertor = 'float' + typename_ = 'f32' + } else if sym.parent_idx == ast.f64_type_idx { + convertor = 'double' + typename_ = 'f64' + } else if sym.parent_idx == ast.bool_type_idx { + convertor = 'bool' + typename_ = 'bool' + } else { + verror("could not generate string method for type '$styp'") + } + g.type_definitions.writeln('string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('string ${str_fn_name}($styp it) {') + if convertor == 'bool' { + g.auto_str_funcs.writeln('\tstring tmp1 = string__plus(_SLIT("${styp}("), ($convertor)it ? _SLIT("true") : _SLIT("false"));') + } else { + g.auto_str_funcs.writeln('\tstring tmp1 = string__plus(_SLIT("${styp}("), tos3(${typename_}_str(($convertor)it).str));') + } + g.auto_str_funcs.writeln('\tstring tmp2 = string__plus(tmp1, _SLIT(")"));') + g.auto_str_funcs.writeln('\tstring_free(&tmp1);') + g.auto_str_funcs.writeln('\treturn tmp2;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_type(typ ast.Type) string { + styp := g.typ(typ).replace('*', '') + mut sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut str_fn_name := styp_to_str_fn_name(styp) + if mut sym.info is ast.Alias { + if sym.info.is_import { + sym = g.table.get_type_symbol(sym.info.parent_type) + str_fn_name = styp_to_str_fn_name(sym.name) + } + } + sym_has_str_method, str_method_expects_ptr, str_nr_args := sym.str_method_info() + already_generated_key := '$styp:$str_fn_name' + if !sym_has_str_method && already_generated_key !in g.str_types && !typ.has_flag(.optional) { + $if debugautostr ? { + eprintln('> gen_str_for_type: |typ: ${typ:5}, ${sym.name:20}|has_str: ${sym_has_str_method:5}|expects_ptr: ${str_method_expects_ptr:5}|nr_args: ${str_nr_args:1}|fn_name: ${str_fn_name:20}') + } + g.str_types << already_generated_key + match mut sym.info { + ast.Alias { + if sym.info.is_import { + g.gen_str_default(sym, styp, str_fn_name) + } else { + g.gen_str_for_alias(sym.info, styp, str_fn_name) + } + } + ast.Array { + g.gen_str_for_array(sym.info, styp, str_fn_name) + } + ast.ArrayFixed { + g.gen_str_for_array_fixed(sym.info, styp, str_fn_name) + } + ast.Enum { + g.gen_str_for_enum(sym.info, styp, str_fn_name) + } + ast.FnType { + g.gen_str_for_fn_type(sym.info, styp, str_fn_name) + } + ast.Struct { + g.gen_str_for_struct(sym.info, styp, str_fn_name) + } + ast.Map { + g.gen_str_for_map(sym.info, styp, str_fn_name) + } + ast.MultiReturn { + g.gen_str_for_multi_return(sym.info, styp, str_fn_name) + } + ast.SumType { + g.gen_str_for_union_sum_type(sym.info, styp, str_fn_name) + } + ast.Interface { + g.gen_str_for_interface(sym.info, styp, str_fn_name) + } + ast.Chan { + g.gen_str_for_chan(sym.info, styp, str_fn_name) + } + ast.Thread { + g.gen_str_for_thread(sym.info, styp, str_fn_name) + } + else { + verror("could not generate string method $str_fn_name for type '$styp'") + } + } + } + if typ.has_flag(.optional) { + option_already_generated_key := 'option_$already_generated_key' + if option_already_generated_key !in g.str_types { + g.gen_str_for_option(typ, styp, str_fn_name) + g.str_types << option_already_generated_key + } + return str_fn_name + } + return str_fn_name +} + +fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) { + parent_type := typ.clear_flag(.optional) + sym := g.table.get_type_symbol(parent_type) + sym_has_str_method, _, _ := sym.str_method_info() + parent_str_fn_name := g.gen_str_for_type(parent_type) + + g.type_definitions.writeln('string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0); }') + g.type_definitions.writeln('string indent_${str_fn_name}($styp it, int indent_count); // auto') + g.auto_str_funcs.writeln('string indent_${str_fn_name}($styp it, int indent_count) {') + g.auto_str_funcs.writeln('\tstring res;') + g.auto_str_funcs.writeln('\tif (it.state == 0) {') + if sym.kind == .string { + tmp_res := '${parent_str_fn_name}(*($sym.cname*)it.data)' + g.auto_str_funcs.writeln('\t\tres = ${str_intp_sq(tmp_res)};') + } else if should_use_indent_func(sym.kind) && !sym_has_str_method { + g.auto_str_funcs.writeln('\t\tres = indent_${parent_str_fn_name}(*($sym.cname*)it.data, indent_count);') + } else { + g.auto_str_funcs.writeln('\t\tres = ${parent_str_fn_name}(*($sym.cname*)it.data);') + } + g.auto_str_funcs.writeln('\t} else {') + + tmp_str := str_intp_sub('error: %%', 'IError_str(it.err)') + g.auto_str_funcs.writeln('\t\tres = $tmp_str;') + g.auto_str_funcs.writeln('\t}') + + g.auto_str_funcs.writeln('\treturn ${str_intp_sub('Option(%%)', 'res')};') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string) { + parent_str_fn_name := g.gen_str_for_type(info.parent_type) + mut clean_type_v_type_name := util.strip_main_name(styp.replace('__', '.')) + g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0); }') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp it, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp it, int indent_count) {') + g.auto_str_funcs.writeln('\tstring indents = string_repeat(_SLIT(" "), indent_count);') + g.auto_str_funcs.writeln('\tstring tmp_ds = ${parent_str_fn_name}(it);') + g.auto_str_funcs.writeln('\tstring res = str_intp(3, _MOV((StrIntpData[]){ + {_SLIT0, $c.si_s_code, {.d_s = indents }}, + {_SLIT("${clean_type_v_type_name}("), $c.si_s_code, {.d_s = tmp_ds }}, + {_SLIT(")"), 0, {.d_c = 0 }} + }));') + g.auto_str_funcs.writeln('\tstring_free(&indents);') + g.auto_str_funcs.writeln('\tstring_free(&tmp_ds);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_multi_return(info ast.MultiReturn, styp string, str_fn_name string) { + g.type_definitions.writeln('static string ${str_fn_name}($styp a); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static string ${str_fn_name}($styp a) {') + fn_builder.writeln('\tstrings__Builder sb = strings__new_builder($info.types.len * 10);') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, _SLIT("("));') + for i, typ in info.types { + sym := g.table.get_type_symbol(typ) + is_arg_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + arg_str_fn_name := g.gen_str_for_type(typ) + + if should_use_indent_func(sym.kind) && !sym_has_str_method { + fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}(a.arg$i));') + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + tmp_val := str_intp_g32('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, $tmp_val);') + } else { + tmp_val := str_intp_g64('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, $tmp_val);') + } + } else if sym.kind == .string { + tmp_str := str_intp_sq('a.arg$i') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, $tmp_str);') + } else if sym.kind == .function { + fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}());') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_arg_ptr, typ) + fn_builder.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT("$deref_label"));') + fn_builder.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}( $deref a.arg$i));') + } + if i != info.types.len - 1 { + fn_builder.writeln('\tstrings__Builder_write_string(&sb, _SLIT(", "));') + } + } + fn_builder.writeln('\tstrings__Builder_write_string(&sb, _SLIT(")"));') + fn_builder.writeln('\tstring res = strings__Builder_str(&sb);') + fn_builder.writeln('\tstrings__Builder_free(&sb);') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() +} + +fn (mut g Gen) gen_str_for_enum(info ast.Enum, styp string, str_fn_name string) { + s := util.no_dots(styp) + g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { /* gen_str_for_enum */') + // Enums tagged with `[flag]` are special in that they can be a combination of enum values + if info.is_flag { + clean_name := util.strip_main_name(styp.replace('__', '.')) + g.auto_str_funcs.writeln('\tstring ret = _SLIT("$clean_name{");') + g.auto_str_funcs.writeln('\tint first = 1;') + for i, val in info.vals { + g.auto_str_funcs.writeln('\tif (it & (1 << $i)) {if (!first) {ret = string__plus(ret, _SLIT(" | "));} ret = string__plus(ret, _SLIT(".$val")); first = 0;}') + } + g.auto_str_funcs.writeln('\tret = string__plus(ret, _SLIT("}"));') + g.auto_str_funcs.writeln('\treturn ret;') + } else { + g.auto_str_funcs.writeln('\tswitch(it) {') + // Only use the first multi value on the lookup + mut seen := []string{len: info.vals.len} + for val in info.vals { + if info.is_multi_allowed && val in seen { + continue + } else if info.is_multi_allowed { + seen << val + } + g.auto_str_funcs.writeln('\t\tcase ${s}__$val: return _SLIT("$val");') + } + g.auto_str_funcs.writeln('\t\tdefault: return _SLIT("unknown enum value");') + g.auto_str_funcs.writeln('\t}') + } + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + g.type_definitions.writeln('static string ${str_fn_name}($styp x); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp x) { return indent_${str_fn_name}(x, 0); }') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp x, int indent_count); // auto') + mut fn_builder := strings.new_builder(512) + mut clean_interface_v_type_name := styp.replace('__', '.') + if styp.ends_with('*') { + clean_interface_v_type_name = '&' + clean_interface_v_type_name.replace('*', '') + } + if clean_interface_v_type_name.contains('_T_') { + clean_interface_v_type_name = + clean_interface_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_interface_v_type_name = util.strip_main_name(clean_interface_v_type_name) + fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) { /* gen_str_for_interface */') + for typ in info.types { + subtype := g.table.get_type_symbol(typ) + mut func_name := g.gen_str_for_type(typ) + sym_has_str_method, str_method_expects_ptr, _ := subtype.str_method_info() + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + func_name = 'indent_$func_name' + } + + // str_intp + deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' } + if typ == ast.string_type { + mut val := '${func_name}(${deref}($subtype.cname*)x._$subtype.cname' + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_interface_v_type_name}(\'"), $c.si_s_code, {.d_s = $val}}, + {_SLIT("\')"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + fn_builder.write_string(' return $res;') + } else { + mut val := '${func_name}(${deref}($subtype.cname*)x._$subtype.cname' + if should_use_indent_func(subtype.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_interface_v_type_name}("), $c.si_s_code, {.d_s = $val}}, + {_SLIT(")"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + fn_builder.write_string(' return $res;\n') + } + } + fn_builder.writeln('\treturn _SLIT("unknown interface value");') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() +} + +fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + g.type_definitions.writeln('static string ${str_fn_name}($styp x); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp x) { return indent_${str_fn_name}(x, 0); }') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp x, int indent_count); // auto') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('static string indent_${str_fn_name}($styp x, int indent_count) {') + mut clean_sum_type_v_type_name := styp.replace('__', '.') + if styp.ends_with('*') { + clean_sum_type_v_type_name = '&' + clean_sum_type_v_type_name.replace('*', '') + } + if clean_sum_type_v_type_name.contains('_T_') { + clean_sum_type_v_type_name = + clean_sum_type_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name) + fn_builder.writeln('\tswitch(x._typ) {') + for typ in info.variants { + typ_str := g.typ(typ) + mut func_name := g.gen_str_for_type(typ) + sym := g.table.get_type_symbol(typ) + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' } + if should_use_indent_func(sym.kind) && !sym_has_str_method { + func_name = 'indent_$func_name' + } + + // str_intp + if typ == ast.string_type { + mut val := '${func_name}(${deref}($typ_str*)x._$sym.cname' + if should_use_indent_func(sym.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_sum_type_v_type_name}(\'"), $c.si_s_code, {.d_s = $val}}, + {_SLIT("\')"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\t\tcase $typ: return $res;') + } else { + mut val := '${func_name}(${deref}($typ_str*)x._$sym.cname' + if should_use_indent_func(sym.kind) && !sym_has_str_method { + val += ', indent_count' + } + val += ')' + res := 'str_intp(2, _MOV((StrIntpData[]){ + {_SLIT("${clean_sum_type_v_type_name}("), $c.si_s_code, {.d_s = $val}}, + {_SLIT(")"), 0, {.d_c = 0 }} + }))' + fn_builder.write_string('\t\tcase $typ: return $res;') + } + } + fn_builder.writeln('\t\tdefault: return _SLIT("unknown sum type value");') + fn_builder.writeln('\t}') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() +} + +fn (mut g Gen) fn_decl_str(info ast.FnType) string { + mut fn_str := 'fn (' + for i, arg in info.func.params { + if arg.is_mut { + fn_str += 'mut ' + } + if i > 0 { + fn_str += ', ' + } + fn_str += util.strip_main_name(g.table.get_type_name(g.unwrap_generic(arg.typ))) + } + fn_str += ')' + if info.func.return_type == ast.ovoid_type { + fn_str += ' ?' + } else if info.func.return_type != ast.void_type { + x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type))) + if info.func.return_type.has_flag(.optional) { + fn_str += ' ?$x' + } else { + fn_str += ' $x' + } + } + return fn_str +} + +fn (mut g Gen) gen_str_for_fn_type(info ast.FnType, styp string, str_fn_name string) { + g.type_definitions.writeln('static string ${str_fn_name}(); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}() { return _SLIT("${g.fn_decl_str(info)}");}') +} + +fn (mut g Gen) gen_str_for_chan(info ast.Chan, styp string, str_fn_name string) { + elem_type_name := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.elem_type))) + g.type_definitions.writeln('static string ${str_fn_name}($styp x); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp x) { return sync__Channel_auto_str(x, _SLIT("$elem_type_name")); }') +} + +fn (mut g Gen) gen_str_for_thread(info ast.Thread, styp string, str_fn_name string) { + ret_type_name := util.strip_main_name(g.table.get_type_name(info.return_type)) + g.type_definitions.writeln('static string ${str_fn_name}($styp _); // auto}') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp _) { return _SLIT("thread($ret_type_name)");}') +} + +[inline] +fn styp_to_str_fn_name(styp string) string { + return styp.replace_each(['*', '', '.', '__', ' ', '__']) + '_str' +} + +fn deref_kind(str_method_expects_ptr bool, is_elem_ptr bool, typ ast.Type) (string, string) { + if str_method_expects_ptr != is_elem_ptr { + if is_elem_ptr { + return '*'.repeat(typ.nr_muls()), '&'.repeat(typ.nr_muls()) + } else { + return '&', '' + } + } + return '', '' +} + +fn (mut g Gen) gen_str_for_array(info ast.Array, styp string, str_fn_name string) { + mut typ := info.elem_type + mut sym := g.table.get_type_symbol(info.elem_type) + if mut sym.info is ast.Alias { + typ = sym.info.parent_type + sym = g.table.get_type_symbol(typ) + } + field_styp := g.typ(typ) + is_elem_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + mut elem_str_fn_name := g.gen_str_for_type(typ) + if sym.kind == .byte { + elem_str_fn_name = elem_str_fn_name + '_escaped' + } + + g.type_definitions.writeln('static string ${str_fn_name}($styp a); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp a) { return indent_${str_fn_name}(a, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp a, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp a, int indent_count) {') + g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(a.len * 10);') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("["));') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < a.len; ++i) {') + if sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();') + } else { + if sym.kind == .array_fixed { + g.auto_str_funcs.writeln('\t\t$field_styp it;') + g.auto_str_funcs.writeln('\t\tmemcpy(*($field_styp*)it, (byte*)array_get(a, i), sizeof($field_styp));') + } else { + g.auto_str_funcs.writeln('\t\t$field_styp it = *($field_styp*)array_get(a, i);') + } + if should_use_indent_func(sym.kind) && !sym_has_str_method { + if is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstring x = indent_${elem_str_fn_name}(*it, indent_count);') + } else { + g.auto_str_funcs.writeln('\t\tstring x = indent_${elem_str_fn_name}(it, indent_count);') + } + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + g.auto_str_funcs.writeln('\t\tstring x = ${str_intp_g32('it')};') + } else { + g.auto_str_funcs.writeln('\t\tstring x = ${str_intp_g64('it')};') + } + } else if sym.kind == .rune { + // Rune are managed at this level as strings + g.auto_str_funcs.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{_SLIT("\`"), $c.si_s_code, {.d_s = ${elem_str_fn_name}(it) }}, {_SLIT("\`"), 0, {.d_c = 0 }}}));\n') + } else if sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{_SLIT("\'"), $c.si_s_code, {.d_s = it }}, {_SLIT("\'"), 0, {.d_c = 0 }}}));\n') + } else { + // There is a custom .str() method, so use it. + // NB: we need to take account of whether the user has defined + // `fn (x T) str() {` or `fn (x &T) str() {`, and convert accordingly + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT("$deref_label"));') + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}( $deref it);') + } + } + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, x);') + if g.is_autofree && typ != ast.bool_type { + // no need to free "true"/"false" literals + g.auto_str_funcs.writeln('\t\tstring_free(&x);') + } + g.auto_str_funcs.writeln('\t\tif (i < a.len-1) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT(", "));') + g.auto_str_funcs.writeln('\t\t}') + g.auto_str_funcs.writeln('\t}') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("]"));') + g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);') + g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_array_fixed(info ast.ArrayFixed, styp string, str_fn_name string) { + mut typ := info.elem_type + mut sym := g.table.get_type_symbol(info.elem_type) + if mut sym.info is ast.Alias { + typ = sym.info.parent_type + sym = g.table.get_type_symbol(typ) + } + is_elem_ptr := typ.is_ptr() + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + elem_str_fn_name := g.gen_str_for_type(typ) + + g.type_definitions.writeln('static string ${str_fn_name}($styp a); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp a) { return indent_${str_fn_name}(a, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp a, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp a, int indent_count) {') + g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder($info.size * 10);') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("["));') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; ++i) {') + if sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, x);') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + if should_use_indent_func(sym.kind) && !sym_has_str_method { + if is_elem_ptr { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT("$deref_label"));') + g.auto_str_funcs.writeln('\t\tif ( 0 == a[i] ) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT("0"));') + g.auto_str_funcs.writeln('\t\t}else{') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}( $deref a[i]) );') + g.auto_str_funcs.writeln('\t\t}') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}( $deref a[i]) );') + } + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32('a[i]')} );') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64('a[i]')} );') + } + } else if sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_sq('a[i]')});') + } else if sym.kind == .rune { + tmp_str := str_intp_rune('${elem_str_fn_name}( $deref a[i])') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}( $deref a[i]));') + } + } + g.auto_str_funcs.writeln('\t\tif (i < ${info.size - 1}) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT(", "));') + g.auto_str_funcs.writeln('\t\t}') + g.auto_str_funcs.writeln('\t}') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("]"));') + g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);') + g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (mut g Gen) gen_str_for_map(info ast.Map, styp string, str_fn_name string) { + mut key_typ := info.key_type + mut key_sym := g.table.get_type_symbol(key_typ) + if mut key_sym.info is ast.Alias { + key_typ = key_sym.info.parent_type + key_sym = g.table.get_type_symbol(key_typ) + } + key_styp := g.typ(key_typ) + key_str_fn_name := key_styp.replace('*', '') + '_str' + if !key_sym.has_method('str') { + g.gen_str_for_type(key_typ) + } + + mut val_typ := info.value_type + mut val_sym := g.table.get_type_symbol(val_typ) + if mut val_sym.info is ast.Alias { + val_typ = val_sym.info.parent_type + val_sym = g.table.get_type_symbol(val_typ) + } + val_styp := g.typ(val_typ) + elem_str_fn_name := val_styp.replace('*', '') + '_str' + if !val_sym.has_method('str') { + g.gen_str_for_type(val_typ) + } + + g.type_definitions.writeln('static string ${str_fn_name}($styp m); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp m) { return indent_${str_fn_name}(m, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp m, int indent_count); // auto') + g.auto_str_funcs.writeln('static string indent_${str_fn_name}($styp m, int indent_count) { /* gen_str_for_map */') + g.auto_str_funcs.writeln('\tstrings__Builder sb = strings__new_builder(m.key_values.len*10);') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("{"));') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < m.key_values.len; ++i) {') + g.auto_str_funcs.writeln('\t\tif (!DenseArray_has_index(&m.key_values, i)) { continue; }') + + if key_sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstring key = *(string*)DenseArray_key(&m.key_values, i);') + } else { + g.auto_str_funcs.writeln('\t\t$key_styp key = *($key_styp*)DenseArray_key(&m.key_values, i);') + } + if key_sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_sq('key')});') + } else if key_sym.kind == .rune { + tmp_str := str_intp_rune('${key_str_fn_name}(key)') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${key_str_fn_name}(key));') + } + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _SLIT(": "));') + if val_sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}());') + } else if val_sym.kind == .string { + tmp_str := str_intp_sq('*($val_styp*)DenseArray_value(&m.key_values, i)') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else if should_use_indent_func(val_sym.kind) && !val_sym.has_method('str') { + ptr_str := '*'.repeat(val_typ.nr_muls()) + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(*${ptr_str}($val_styp*)DenseArray_value(&m.key_values, i), indent_count));') + } else if val_sym.kind in [.f32, .f64] { + tmp_val := '*($val_styp*)DenseArray_value(&m.key_values, i)' + if val_sym.kind == .f32 { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g32(tmp_val)});') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_g64(tmp_val)});') + } + } else if val_sym.kind == .rune { + tmp_str := str_intp_rune('${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i))') + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, $tmp_str);') + } else { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i)));') + } + g.auto_str_funcs.writeln('\t\tif (i != m.key_values.len-1) {') + g.auto_str_funcs.writeln('\t\t\tstrings__Builder_write_string(&sb, _SLIT(", "));') + g.auto_str_funcs.writeln('\t\t}') + g.auto_str_funcs.writeln('\t}') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _SLIT("}"));') + g.auto_str_funcs.writeln('\tstring res = strings__Builder_str(&sb);') + g.auto_str_funcs.writeln('\tstrings__Builder_free(&sb);') + g.auto_str_funcs.writeln('\treturn res;') + g.auto_str_funcs.writeln('}') +} + +fn (g &Gen) type_to_fmt1(typ ast.Type) StrIntpType { + if typ == ast.byte_type_idx { + return .si_u8 + } + if typ == ast.char_type_idx { + return .si_c + } + if typ in ast.voidptr_types || typ in ast.byteptr_types { + return .si_p + } + if typ in ast.charptr_types { + // return '%C\\000' // a C string + return .si_s + } + sym := g.table.get_type_symbol(typ) + if typ.is_ptr() && (typ.is_int_valptr() || typ.is_float_valptr()) { + return .si_s + } else if sym.kind in [.struct_, .array, .array_fixed, .map, .bool, .enum_, .interface_, + .sum_type, .function, .alias, .chan] { + return .si_s + } else if sym.kind == .string { + return .si_s + // return "'%.*s\\000'" + } else if sym.kind in [.f32, .f64] { + if sym.kind == .f32 { + return .si_g32 + } + return .si_g64 + } else if sym.kind == .int { + return .si_i32 + } else if sym.kind == .u32 { + return .si_u32 + } else if sym.kind == .u64 { + return .si_u64 + } else if sym.kind == .i64 { + return .si_i64 + } + return .si_i32 +} + +fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string) { + // _str() functions should have a single argument, the indenting ones take 2: + g.type_definitions.writeln('static string ${str_fn_name}($styp it); // auto') + g.auto_str_funcs.writeln('static string ${str_fn_name}($styp it) { return indent_${str_fn_name}(it, 0);}') + g.type_definitions.writeln('static string indent_${str_fn_name}($styp it, int indent_count); // auto') + mut fn_builder := strings.new_builder(512) + defer { + g.auto_fn_definitions << fn_builder.str() + } + fn_builder.writeln('static string indent_${str_fn_name}($styp it, int indent_count) {') + mut clean_struct_v_type_name := styp.replace('__', '.') + if clean_struct_v_type_name.contains('_T_') { + // TODO: this is a bit hacky. styp shouldn't be even parsed with _T_ + // use something different than g.typ for styp + clean_struct_v_type_name = + clean_struct_v_type_name.replace('Array_', '[]').replace('_T_', '<').replace('_', ', ') + + '>' + } + clean_struct_v_type_name = util.strip_main_name(clean_struct_v_type_name) + // generate ident / indent length = 4 spaces + if info.fields.len == 0 { + fn_builder.writeln('\treturn _SLIT("$clean_struct_v_type_name{}");') + fn_builder.writeln('}') + return + } + + fn_builder.writeln('\tstring indents = string_repeat(_SLIT(" "), indent_count);') + fn_builder.writeln('\tstring res = str_intp( ${info.fields.len * 4 + 3}, _MOV((StrIntpData[]){') + fn_builder.writeln('\t\t{_SLIT("$clean_struct_v_type_name{\\n"), 0, {.d_c=0}},') + + for i, field in info.fields { + mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' } + base_fmt := g.type_to_fmt1(g.unwrap_generic(field.typ)) + + // manage prefix and quote symbol for the filed + mut quote_str := '' + mut prefix := '' + sym := g.table.get_type_symbol(g.unwrap_generic(field.typ)) + if sym.kind == .string { + quote_str = "'" + } else if field.typ in ast.charptr_types { + quote_str = '\\"' + prefix = 'C' + } + + // first fields doesn't need \n + if i == 0 { + fn_builder.write_string('\t\t{_SLIT0, $c.si_s_code, {.d_s=indents}}, {_SLIT(" $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ') + } else { + fn_builder.write_string('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT(" $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ') + } + + // custom methods management + has_custom_str := sym.has_method('str') + mut field_styp := g.typ(field.typ).replace('*', '') + field_styp_fn_name := if has_custom_str { + '${field_styp}_str' + } else { + g.gen_str_for_type(field.typ) + } + + // manage the fact hat with float we use always the g representation + if sym.kind !in [.f32, .f64] { + fn_builder.write_string('{_SLIT("$quote_str"), ${int(base_fmt)}, {.${data_str(base_fmt)}=') + } else { + g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex() + fn_builder.write_string('{_SLIT("$quote_str"), $g_fmt, {.${data_str(base_fmt)}=') + } + + mut func := struct_auto_str_func1(sym, field.typ, field_styp_fn_name, field.name) + if field.typ in ast.cptr_types { + func = '(voidptr) it.$field.name' + } else if field.typ.is_ptr() { + // reference types can be "nil" + fn_builder.write_string('isnil(it.${c_name(field.name)})') + fn_builder.write_string(' ? _SLIT("nil") : ') + // struct, floats and ints have a special case through the _str function + if sym.kind != .struct_ && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() { + fn_builder.write_string('*') + } + } + // handle circular ref type of struct to the struct itself + if styp == field_styp { + fn_builder.write_string('_SLIT("<circular>")') + } else { + // manage C charptr + if field.typ in ast.charptr_types { + fn_builder.write_string('tos2((byteptr)$func)') + } else { + if field.typ.is_ptr() && sym.kind == .struct_ { + fn_builder.write_string('(indent_count > 25) ? _SLIT("<probably circular>") : ') + } + fn_builder.write_string(func) + } + } + + fn_builder.writeln('}}, {_SLIT("$quote_str"), 0, {.d_c=0}},') + } + fn_builder.writeln('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("}"), 0, {.d_c=0}},') + fn_builder.writeln('\t}));') + fn_builder.writeln('\tstring_free(&indents);') + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') +} + +fn struct_auto_str_func1(sym &ast.TypeSymbol, field_type ast.Type, fn_name string, field_name string) string { + has_custom_str, expects_ptr, _ := sym.str_method_info() + if sym.kind == .enum_ { + return '${fn_name}(it.${c_name(field_name)})' + } else if should_use_indent_func(sym.kind) { + mut obj := 'it.${c_name(field_name)}' + if field_type.is_ptr() && !expects_ptr { + obj = '*$obj' + } + if has_custom_str { + return '${fn_name}($obj)' + } + return 'indent_${fn_name}($obj, indent_count + 1)' + } else if sym.kind in [.array, .array_fixed, .map, .sum_type] { + if has_custom_str { + return '${fn_name}(it.${c_name(field_name)})' + } + return 'indent_${fn_name}(it.${c_name(field_name)}, indent_count + 1)' + } else if sym.kind == .function { + return '${fn_name}()' + } else { + if sym.kind == .chan { + return '${fn_name}(it.${c_name(field_name)})' + } + mut method_str := 'it.${c_name(field_name)}' + if sym.kind == .bool { + method_str += ' ? _SLIT("true") : _SLIT("false")' + } else if (field_type.is_int_valptr() || field_type.is_float_valptr()) + && field_type.is_ptr() && !expects_ptr { + // ptr int can be "nil", so this needs to be casted to a string + if sym.kind == .f32 { + return 'str_intp(1, _MOV((StrIntpData[]){ + {_SLIT0, $si_g32_code, {.d_f32 = *$method_str }} + }))' + } else if sym.kind == .f64 { + return 'str_intp(1, _MOV((StrIntpData[]){ + {_SLIT0, $si_g64_code, {.d_f64 = *$method_str }} + }))' + } else if sym.kind == .u64 { + fmt_type := StrIntpType.si_u64 + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_u64 = *$method_str }}}))' + } + fmt_type := StrIntpType.si_i32 + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *$method_str }}}))' + } + return method_str + } +} diff --git a/v_windows/v/vlib/v/gen/c/cgen.v b/v_windows/v/vlib/v/gen/c/cgen.v new file mode 100644 index 0000000..152d84d --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/cgen.v @@ -0,0 +1,6878 @@ +// Copyright (c) 2019-2021 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 os +import strings +import v.ast +import v.pref +import v.token +import v.util +import v.util.version +import v.depgraph + +const ( + // NB: some of the words in c_reserved, are not reserved in C, + // but are in C++, or have special meaning in V, thus need escaping too. + c_reserved = ['auto', 'break', 'calloc', 'case', 'char', 'class', 'const', 'continue', + 'default', 'delete', 'do', 'double', 'else', 'enum', 'error', 'exit', 'export', 'extern', + 'float', 'for', 'free', 'goto', 'if', 'inline', 'int', 'link', 'long', 'malloc', 'namespace', + 'new', 'panic', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', + 'struct', 'switch', 'typedef', 'typename', 'union', 'unix', 'unsigned', 'void', 'volatile', + 'while', 'template', 'stdout', 'stdin', 'stderr'] + c_reserved_map = string_array_to_map(c_reserved) + // same order as in token.Kind + cmp_str = ['eq', 'ne', 'gt', 'lt', 'ge', 'le'] + // when operands are switched + cmp_rev = ['eq', 'ne', 'lt', 'gt', 'le', 'ge'] +) + +fn string_array_to_map(a []string) map[string]bool { + mut res := map[string]bool{} + for x in a { + res[x] = true + } + return res +} + +struct Gen { + pref &pref.Preferences + module_built string + field_data_type ast.Type // cache her to avoid map lookups +mut: + table &ast.Table + out strings.Builder + cheaders strings.Builder + includes strings.Builder // all C #includes required by V modules + typedefs strings.Builder + typedefs2 strings.Builder + type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + global_inits map[string]strings.Builder // default initializers for globals (goes in _vinit()) + inits map[string]strings.Builder // contents of `void _vinit/2{}` + cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` + gowrappers strings.Builder // all go callsite wrappers + stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined + auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs + comptime_defines strings.Builder // custom defines, given by -d/-define flags on the CLI + pcs_declarations strings.Builder // -prof profile counter declarations for each function + hotcode_definitions strings.Builder // -live declarations & functions + embedded_data strings.Builder // data to embed in the executable/binary + shared_types strings.Builder // shared/lock types + shared_functions strings.Builder // shared constructors + channel_definitions strings.Builder // channel related code + options_typedefs strings.Builder // Option typedefs + options strings.Builder // `Option_xxxx` types + json_forward_decls strings.Builder // json type forward decls + enum_typedefs strings.Builder // enum types + sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc + file &ast.File + fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 + last_fn_c_name string + tmp_count int // counter for unique tmp vars (_tmp1, _tmp2 etc); resets at the start of each fn. + tmp_count2 int // a separate tmp var counter for autofree fn calls + tmp_count_declarations int // counter for unique tmp names (_d1, _d2 etc); does NOT reset, used for C declarations + global_tmp_count int // like tmp_count but global and not resetted in each function + is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) + discard_or_result bool // do not safe last ExprStmt of `or` block in tmp variable to defer ongoing expr usage + is_void_expr_stmt bool // ExprStmt whos result is discarded + is_arraymap_set bool // map or array set value state + is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc + is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) + is_shared bool // for initialization of hidden mutex in `[rw]shared` literals + is_vlines_enabled bool // is it safe to generate #line directives when -g is passed + inside_cast_in_heap int // inside cast to interface type in heap (resolve recursive calls) + arraymap_set_pos int // map or array set value position + vlines_path string // set to the proper path for generating #line directives + optionals []string // to avoid duplicates TODO perf, use map + chan_pop_optionals []string // types for `x := <-ch or {...}` + chan_push_optionals []string // types for `ch <- x or {...}` + cur_lock ast.LockExpr + mtxs string // array of mutexes if the `lock` has multiple variables + labeled_loops map[string]&ast.Stmt + inner_loop &ast.Stmt + shareds []int // types with hidden mutex for which decl has been emitted + inside_ternary int // ?: comma separated statements on a single line + inside_map_postfix bool // inside map++/-- postfix expr + inside_map_infix bool // inside map<</+=/-= infix expr + inside_map_index bool + inside_opt_data bool + inside_if_optional bool + ternary_names map[string]string + ternary_level_names map[string][]string + stmt_path_pos []int // positions of each statement start, for inserting C statements before the current statement + skip_stmt_pos bool // for handling if expressions + autofree (since both prepend C statements) + right_is_opt bool + is_autofree bool // false, inside the bodies of fns marked with [manualfree], otherwise === g.pref.autofree + indent int + empty_line bool + is_test bool + assign_op token.Kind // *=, =, etc (for array_set) + defer_stmts []ast.DeferStmt + defer_ifdef string + defer_profile_code string + str_types []string // types that need automatic str() generation + threaded_fns []string // for generating unique wrapper types and fns for `go xxx()` + waiter_fns []string // functions that wait for `go xxx()` to finish + array_fn_definitions []string // array equality functions that have been defined + map_fn_definitions []string // map equality functions that have been defined + struct_fn_definitions []string // struct equality functions that have been defined + sumtype_fn_definitions []string // sumtype equality functions that have been defined + alias_fn_definitions []string // alias equality functions that have been defined + auto_fn_definitions []string // auto generated functions defination list + anon_fn_definitions []string // anon generated functions defination list + sumtype_definitions map[int]bool // `_TypeA_to_sumtype_TypeB()` fns that have been generated + is_json_fn bool // inside json.encode() + json_types []string // to avoid json gen duplicates + pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name + is_builtin_mod bool + hotcode_fn_names []string + embedded_files []ast.EmbeddedFile + sql_i int + sql_stmt_name string + sql_bind_name string + sql_idents []string + sql_idents_types []ast.Type + sql_left_type ast.Type + sql_table_name string + sql_fkey string + sql_parent_id string + sql_side SqlExprSide // left or right, to distinguish idents in `name == name` + inside_vweb_tmpl bool + inside_return bool + inside_or_block bool + strs_to_free0 []string // strings.Builder + // strs_to_free []string // strings.Builder + inside_call bool + has_main bool + inside_const bool + comp_for_method string // $for method in T.methods {} + comp_for_field_var string // $for field in T.fields {}; the variable name + comp_for_field_value ast.StructField // value of the field variable + comp_for_field_type ast.Type // type of the field variable inferred from `$if field.typ is T {}` + comptime_var_type_map map[string]ast.Type + // tmp_arg_vars_to_free []string + // autofree_pregen map[string]string + // autofree_pregen_buf strings.Builder + // autofree_tmp_vars []string // to avoid redefining the same tmp vars in a single function + called_fn_name string + cur_mod ast.Module + is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` + is_fn_index_call bool + // nr_vars_to_free int + // doing_autofree_tmp bool + inside_lambda bool + prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type + // used in match multi branch + // TypeOne, TypeTwo {} + // where an aggregate (at least two types) is generated + // sum type deref needs to know which index to deref because unions take care of the correct field + aggregate_type_idx int + returned_var_name string // to detect that a var doesn't need to be freed since it's being returned + branch_parent_pos int // used in BranchStmt (continue/break) for autofree stop position + infix_left_var_name string // a && if expr + timers &util.Timers = util.new_timers(false) + force_main_console bool // true when [console] used on fn main() + as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast) + obf_table map[string]string + // main_fn_decl_node ast.FnDecl + expected_cast_type ast.Type // for match expr of sumtypes + defer_vars []string + anon_fn bool + array_sort_fn map[string]bool + nr_closures int +} + +pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) string { + // println('start cgen2') + mut module_built := '' + if pref.build_mode == .build_module { + for file in files { + if file.path.contains(pref.path) + && file.mod.short_name == pref.path.all_after_last(os.path_separator).trim_right(os.path_separator) { + module_built = file.mod.name + break + } + } + } + mut timers_should_print := false + $if time_cgening ? { + timers_should_print = true + } + mut g := Gen{ + file: 0 + out: strings.new_builder(512000) + cheaders: strings.new_builder(15000) + includes: strings.new_builder(100) + typedefs: strings.new_builder(100) + typedefs2: strings.new_builder(100) + type_definitions: strings.new_builder(100) + definitions: strings.new_builder(100) + gowrappers: strings.new_builder(100) + stringliterals: strings.new_builder(100) + auto_str_funcs: strings.new_builder(100) + comptime_defines: strings.new_builder(100) + pcs_declarations: strings.new_builder(100) + hotcode_definitions: strings.new_builder(100) + embedded_data: strings.new_builder(1000) + options_typedefs: strings.new_builder(100) + options: strings.new_builder(100) + shared_types: strings.new_builder(100) + shared_functions: strings.new_builder(100) + channel_definitions: strings.new_builder(100) + json_forward_decls: strings.new_builder(100) + enum_typedefs: strings.new_builder(100) + sql_buf: strings.new_builder(100) + table: table + pref: pref + fn_decl: 0 + is_autofree: true + indent: -1 + module_built: module_built + timers: util.new_timers(timers_should_print) + inner_loop: &ast.EmptyStmt{} + field_data_type: ast.Type(table.find_type_idx('FieldData')) + } + g.timers.start('cgen init') + for mod in g.table.modules { + g.inits[mod] = strings.new_builder(100) + g.global_inits[mod] = strings.new_builder(100) + g.cleanups[mod] = strings.new_builder(100) + } + g.init() + g.timers.show('cgen init') + mut tests_inited := false + mut autofree_used := false + for file in files { + g.timers.start('cgen_file $file.path') + g.file = file + if g.pref.is_vlines { + g.vlines_path = util.vlines_escape_path(file.path, g.pref.ccompiler) + } + // println('\ncgen "$g.file.path" nr_stmts=$file.stmts.len') + // building_v := true && (g.file.path.contains('/vlib/') || g.file.path.contains('cmd/v')) + g.is_test = g.pref.is_test + if g.file.path == '' || !g.pref.autofree { + // cgen test or building V + // println('autofree=false') + g.is_autofree = false + } else { + g.is_autofree = true + autofree_used = true + } + // anon fn may include assert and thus this needs + // to be included before any test contents are written + if g.is_test && !tests_inited { + g.write_tests_definitions() + tests_inited = true + } + g.stmts(file.stmts) + // Transfer embedded files + if file.embedded_files.len > 0 { + for path in file.embedded_files { + if path !in g.embedded_files { + g.embedded_files << path + } + } + } + g.timers.show('cgen_file $file.path') + } + g.timers.start('cgen common') + if autofree_used { + g.is_autofree = true // so that void _vcleanup is generated + } + // to make sure type idx's are the same in cached mods + if g.pref.build_mode == .build_module { + for idx, typ in g.table.type_symbols { + if idx == 0 { + continue + } + g.definitions.writeln('int _v_type_idx_${typ.cname}();') + } + } else if g.pref.use_cache { + for idx, typ in g.table.type_symbols { + if idx == 0 { + continue + } + g.definitions.writeln('int _v_type_idx_${typ.cname}() { return $idx; };') + } + } + // + g.dump_expr_definitions() + // v files are finished, what remains is pure C code + g.gen_vlines_reset() + if g.pref.build_mode != .build_module { + // no init in builtin.o + g.write_init_function() + } + + g.finish() + + mut b := strings.new_builder(640000) + b.write_string(g.hashes()) + b.writeln('\n// V comptime_defines:') + b.write_string(g.comptime_defines.str()) + b.writeln('\n// V typedefs:') + b.write_string(g.typedefs.str()) + b.writeln('\n// V typedefs2:') + b.write_string(g.typedefs2.str()) + b.writeln('\n// V cheaders:') + b.write_string(g.cheaders.str()) + if g.pcs_declarations.len > 0 { + b.writeln('\n// V profile counters:') + b.write_string(g.pcs_declarations.str()) + } + b.writeln('\n// V includes:') + b.write_string(g.includes.str()) + b.writeln('\n// Enum definitions:') + b.write_string(g.enum_typedefs.str()) + b.writeln('\n// V type definitions:') + b.write_string(g.type_definitions.str()) + b.writeln('\n// V shared types:') + b.write_string(g.shared_types.str()) + b.writeln('\n// V Option_xxx definitions:') + b.write_string(g.options.str()) + b.writeln('\n// V json forward decls:') + b.write_string(g.json_forward_decls.str()) + b.writeln('\n// V definitions:') + b.write_string(g.definitions.str()) + interface_table := g.interface_table() + if interface_table.len > 0 { + b.writeln('\n// V interface table:') + b.write_string(interface_table) + } + if g.gowrappers.len > 0 { + b.writeln('\n// V gowrappers:') + b.write_string(g.gowrappers.str()) + } + if g.hotcode_definitions.len > 0 { + b.writeln('\n// V hotcode definitions:') + b.write_string(g.hotcode_definitions.str()) + } + if g.embedded_data.len > 0 { + b.writeln('\n// V embedded data:') + b.write_string(g.embedded_data.str()) + } + if g.options_typedefs.len > 0 { + b.writeln('\n// V option typedefs:') + b.write_string(g.options_typedefs.str()) + } + if g.shared_functions.len > 0 { + b.writeln('\n// V shared type functions:') + b.write_string(g.shared_functions.str()) + b.write_string(c_concurrency_helpers) + } + if g.channel_definitions.len > 0 { + b.writeln('\n// V channel code:') + b.write_string(g.channel_definitions.str()) + } + if g.stringliterals.len > 0 { + b.writeln('\n// V stringliterals:') + b.write_string(g.stringliterals.str()) + } + if g.auto_str_funcs.len > 0 { + // if g.pref.build_mode != .build_module { + b.writeln('\n// V auto str functions:') + b.write_string(g.auto_str_funcs.str()) + // } + } + if g.auto_fn_definitions.len > 0 { + for fn_def in g.auto_fn_definitions { + b.writeln(fn_def) + } + } + if g.anon_fn_definitions.len > 0 { + if g.nr_closures > 0 { + b.writeln('\n// V closure helpers') + b.writeln(c_closure_helpers(g.pref)) + } + for fn_def in g.anon_fn_definitions { + b.writeln(fn_def) + } + } + b.writeln('\n// V out') + b.write_string(g.out.str()) + b.writeln('\n// THE END.') + g.timers.show('cgen common') + return b.str() +} + +pub fn (g &Gen) hashes() string { + mut res := c_commit_hash_default.replace('@@@', version.vhash()) + res += c_current_commit_hash_default.replace('@@@', version.githash(g.pref.building_v)) + return res +} + +pub fn (mut g Gen) init() { + if g.pref.custom_prelude != '' { + g.cheaders.writeln(g.pref.custom_prelude) + } else if !g.pref.no_preludes { + g.cheaders.writeln('// Generated by the V compiler') + tcc_undef_has_include := ' +#if defined(__TINYC__) && defined(__has_include) +// tcc does not support has_include properly yet, turn it off completely +#undef __has_include +#endif' + g.cheaders.writeln(tcc_undef_has_include) + g.includes.writeln(tcc_undef_has_include) + g.cheaders.writeln(get_guarded_include_text('<inttypes.h>', 'The C compiler can not find <inttypes.h> . Please install build-essentials')) // int64_t etc + g.cheaders.writeln(c_builtin_types) + if g.pref.is_bare { + g.cheaders.writeln(c_bare_headers) + } else { + g.cheaders.writeln(c_headers) + } + if !g.pref.skip_unused || g.table.used_maps > 0 { + g.cheaders.writeln(c_wyhash_headers) + } + } + if g.pref.os == .ios { + g.cheaders.writeln('#define __TARGET_IOS__ 1') + g.cheaders.writeln('#include <spawn.h>') + } + g.write_builtin_types() + g.write_typedef_types() + g.write_typeof_functions() + g.write_sorted_types() + g.write_multi_return_types() + g.definitions.writeln('// end of definitions #endif') + // + g.stringliterals.writeln('') + g.stringliterals.writeln('// >> string literal consts') + if g.pref.build_mode != .build_module { + g.stringliterals.writeln('void vinit_string_literals(){') + } + if g.pref.compile_defines_all.len > 0 { + g.comptime_defines.writeln('// V compile time defines by -d or -define flags:') + g.comptime_defines.writeln('// All custom defines : ' + + g.pref.compile_defines_all.join(',')) + g.comptime_defines.writeln('// Turned ON custom defines: ' + + g.pref.compile_defines.join(',')) + for cdefine in g.pref.compile_defines { + g.comptime_defines.writeln('#define CUSTOM_DEFINE_$cdefine') + } + g.comptime_defines.writeln('') + } + if g.table.gostmts > 0 { + g.comptime_defines.writeln('#define __VTHREADS__ (1)') + } + if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { + g.comptime_defines.writeln('#define _VGCBOEHM (1)') + } + if g.pref.is_debug || 'debug' in g.pref.compile_defines { + g.comptime_defines.writeln('#define _VDEBUG (1)') + } + if g.pref.is_prod || 'prod' in g.pref.compile_defines { + g.comptime_defines.writeln('#define _VPROD (1)') + } + if g.pref.is_test || 'test' in g.pref.compile_defines { + g.comptime_defines.writeln('#define _VTEST (1)') + } + if g.pref.autofree { + g.comptime_defines.writeln('#define _VAUTOFREE (1)') + // g.comptime_defines.writeln('unsigned char* g_cur_str;') + } + if g.pref.prealloc { + g.comptime_defines.writeln('#define _VPREALLOC (1)') + } + if g.pref.is_livemain || g.pref.is_liveshared { + g.generate_hotcode_reloading_declarations() + } + // Obfuscate only functions in the main module for now. + // Generate the obf_ast. + if g.pref.obfuscate { + mut i := 0 + // fns + for key, f in g.table.fns { + if f.mod != 'main' && key != 'main' { // !key.starts_with('main.') { + continue + } + g.obf_table[key] = '_f$i' + i++ + } + // methods + for type_sym in g.table.type_symbols { + if type_sym.mod != 'main' { + continue + } + for method in type_sym.methods { + g.obf_table[type_sym.name + '.' + method.name] = '_f$i' + i++ + } + } + } +} + +pub fn (mut g Gen) finish() { + if g.pref.build_mode != .build_module { + g.stringliterals.writeln('}') + } + g.stringliterals.writeln('// << string literal consts') + g.stringliterals.writeln('') + if g.pref.is_prof && g.pref.build_mode != .build_module { + g.gen_vprint_profile_stats() + } + if g.pref.is_livemain || g.pref.is_liveshared { + g.generate_hotcode_reloader_code() + } + if g.embed_file_is_prod_mode() && g.embedded_files.len > 0 { + g.gen_embedded_data() + } + if g.pref.is_test { + g.gen_c_main_for_tests() + } else { + g.gen_c_main() + } +} + +pub fn (mut g Gen) write_typeof_functions() { + g.writeln('') + g.writeln('// >> typeof() support for sum types / interfaces') + for ityp, typ in g.table.type_symbols { + if typ.kind == .sum_type { + sum_info := typ.info as ast.SumType + if sum_info.is_generic { + continue + } + g.writeln('static char * v_typeof_sumtype_${typ.cname}(int sidx) { /* $typ.name */ ') + if g.pref.build_mode == .build_module { + g.writeln('\t\tif( sidx == _v_type_idx_${typ.cname}() ) return "${util.strip_main_name(typ.name)}";') + for v in sum_info.variants { + subtype := g.table.get_type_symbol(v) + g.writeln('\tif( sidx == _v_type_idx_${subtype.cname}() ) return "${util.strip_main_name(subtype.name)}";') + } + g.writeln('\treturn "unknown ${util.strip_main_name(typ.name)}";') + } else { + tidx := g.table.find_type_idx(typ.name) + g.writeln('\tswitch(sidx) {') + g.writeln('\t\tcase $tidx: return "${util.strip_main_name(typ.name)}";') + for v in sum_info.variants { + subtype := g.table.get_type_symbol(v) + g.writeln('\t\tcase $v: return "${util.strip_main_name(subtype.name)}";') + } + g.writeln('\t\tdefault: return "unknown ${util.strip_main_name(typ.name)}";') + g.writeln('\t}') + } + g.writeln('}') + g.writeln('') + g.writeln('int v_typeof_sumtype_idx_${typ.cname}(int sidx) { /* $typ.name */ ') + if g.pref.build_mode == .build_module { + g.writeln('\t\tif( sidx == _v_type_idx_${typ.cname}() ) return ${int(ityp)};') + for v in sum_info.variants { + subtype := g.table.get_type_symbol(v) + g.writeln('\tif( sidx == _v_type_idx_${subtype.cname}() ) return ${int(v)};') + } + g.writeln('\treturn ${int(ityp)};') + } else { + tidx := g.table.find_type_idx(typ.name) + g.writeln('\tswitch(sidx) {') + g.writeln('\t\tcase $tidx: return ${int(ityp)};') + for v in sum_info.variants { + g.writeln('\t\tcase $v: return ${int(v)};') + } + g.writeln('\t\tdefault: return ${int(ityp)};') + g.writeln('\t}') + } + g.writeln('}') + } else if typ.kind == .interface_ { + inter_info := typ.info as ast.Interface + if inter_info.is_generic { + continue + } + g.definitions.writeln('static char * v_typeof_interface_${typ.cname}(int sidx);') + g.writeln('static char * v_typeof_interface_${typ.cname}(int sidx) { /* $typ.name */ ') + for t in inter_info.types { + subtype := g.table.get_type_symbol(t) + g.writeln('\tif (sidx == _${typ.cname}_${subtype.cname}_index) return "${util.strip_main_name(subtype.name)}";') + } + g.writeln('\treturn "unknown ${util.strip_main_name(typ.name)}";') + g.writeln('}') + g.writeln('') + g.writeln('static int v_typeof_interface_idx_${typ.cname}(int sidx) { /* $typ.name */ ') + for t in inter_info.types { + subtype := g.table.get_type_symbol(t) + g.writeln('\tif (sidx == _${typ.cname}_${subtype.cname}_index) return ${int(t)};') + } + g.writeln('\treturn ${int(ityp)};') + g.writeln('}') + } + } + g.writeln('// << typeof() support for sum types') + g.writeln('') +} + +// V type to C typecc +fn (mut g Gen) typ(t ast.Type) string { + styp := g.base_type(t) + if t.has_flag(.optional) { + // Register an optional if it's not registered yet + return g.register_optional(t) + } + /* + if styp.starts_with('C__') { + return styp[3..] + } + */ + return styp +} + +fn (mut g Gen) base_type(t ast.Type) string { + share := t.share() + mut styp := if share == .atomic_t { t.atomic_typename() } else { g.cc_type(t, true) } + if t.has_flag(.shared_f) { + styp = g.find_or_register_shared(t, styp) + } + if !t.has_flag(.variadic) { + nr_muls := g.unwrap_generic(t).nr_muls() + if nr_muls > 0 { + styp += strings.repeat(`*`, nr_muls) + } + } + return styp +} + +fn (mut g Gen) generic_fn_name(types []ast.Type, before string, is_decl bool) string { + if types.len == 0 { + return before + } + // Using _T_ to differentiate between get<string> and get_string + // `foo<int>()` => `foo_T_int()` + mut name := before + '_T' + for typ in types { + name += '_' + strings.repeat_string('__ptr__', typ.nr_muls()) + g.typ(typ.set_nr_muls(0)) + } + return name +} + +fn (mut g Gen) expr_string(expr ast.Expr) string { + pos := g.out.len + g.expr(expr) + return g.out.cut_to(pos).trim_space() +} + +// Surround a potentially multi-statement expression safely with `prepend` and `append`. +// (and create a statement) +fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string) string { + pos := g.out.len + g.stmt_path_pos << pos + defer { + g.stmt_path_pos.delete_last() + } + g.write(prepend) + g.expr(expr) + g.write(append) + return g.out.cut_to(pos) +} + +// TODO this really shouldnt be seperate from typ +// but I(emily) would rather have this generation +// all unified in one place so that it doesnt break +// if one location changes +fn (mut g Gen) optional_type_name(t ast.Type) (string, string) { + base := g.base_type(t) + mut styp := 'Option_$base' + if t.is_ptr() { + styp = styp.replace('*', '_ptr') + } + return styp, base +} + +fn (g &Gen) optional_type_text(styp string, base string) string { + // replace void with something else + size := if base == 'void' { 'byte' } else { base } + ret := 'struct $styp { + byte state; + IError err; + byte data[sizeof($size)]; +}' + return ret +} + +fn (mut g Gen) register_optional(t ast.Type) string { + styp, base := g.optional_type_name(t) + if styp !in g.optionals { + g.typedefs2.writeln('typedef struct $styp $styp;') + g.options.write_string(g.optional_type_text(styp, base)) + g.options.writeln(';\n') + g.optionals << styp.clone() + } + return styp +} + +fn (mut g Gen) find_or_register_shared(t ast.Type, base string) string { + sh_typ := '__shared__$base' + t_idx := t.idx() + if t_idx in g.shareds { + return sh_typ + } + mtx_typ := 'sync__RwMutex' + g.shared_types.writeln('struct $sh_typ { $base val; $mtx_typ mtx; };') + g.shared_functions.writeln('static inline voidptr __dup${sh_typ}(voidptr src, int sz) {') + g.shared_functions.writeln('\t$sh_typ* dest = memdup(src, sz);') + g.shared_functions.writeln('\tsync__RwMutex_init(&dest->mtx);') + g.shared_functions.writeln('\treturn dest;') + g.shared_functions.writeln('}') + g.typedefs2.writeln('typedef struct $sh_typ $sh_typ;') + // println('registered shared type $sh_typ') + g.shareds << t_idx + return sh_typ +} + +fn (mut g Gen) register_thread_array_wait_call(eltyp string) string { + is_void := eltyp == 'void' + thread_typ := if is_void { '__v_thread' } else { '__v_thread_$eltyp' } + ret_typ := if is_void { 'void' } else { 'Array_$eltyp' } + thread_arr_typ := 'Array_$thread_typ' + fn_name := '${thread_arr_typ}_wait' + if fn_name !in g.waiter_fns { + g.waiter_fns << fn_name + if is_void { + g.gowrappers.writeln(' +void ${fn_name}($thread_arr_typ a) { + for (int i = 0; i < a.len; ++i) { + $thread_typ t = (($thread_typ*)a.data)[i]; + __v_thread_wait(t); + } +}') + } else { + g.gowrappers.writeln(' +$ret_typ ${fn_name}($thread_arr_typ a) { + $ret_typ res = __new_array_with_default(a.len, a.len, sizeof($eltyp), 0); + for (int i = 0; i < a.len; ++i) { + $thread_typ t = (($thread_typ*)a.data)[i]; + (($eltyp*)res.data)[i] = __v_thread_${eltyp}_wait(t); + } + return res; +}') + } + } + return fn_name +} + +fn (mut g Gen) register_chan_pop_optional_call(opt_el_type string, styp string) { + if opt_el_type !in g.chan_pop_optionals { + g.chan_pop_optionals << opt_el_type + g.channel_definitions.writeln(' +static inline $opt_el_type __Option_${styp}_popval($styp ch) { + $opt_el_type _tmp = {0}; + if (sync__Channel_try_pop_priv(ch, _tmp.data, false)) { + return ($opt_el_type){ .state = 2, .err = _v_error(_SLIT("channel closed")), .data = {EMPTY_STRUCT_INITIALIZATION} }; + } + return _tmp; +}') + } +} + +fn (mut g Gen) register_chan_push_optional_call(el_type string, styp string) { + if styp !in g.chan_push_optionals { + g.chan_push_optionals << styp + g.register_optional(ast.void_type.set_flag(.optional)) + g.channel_definitions.writeln(' +static inline Option_void __Option_${styp}_pushval($styp ch, $el_type e) { + if (sync__Channel_try_push_priv(ch, &e, false)) { + return (Option_void){ .state = 2, .err = _v_error(_SLIT("channel closed")), .data = {EMPTY_STRUCT_INITIALIZATION} }; + } + return (Option_void){0}; +}') + } +} + +// cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo) +fn (mut g Gen) cc_type(typ ast.Type, is_prefix_struct bool) string { + sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut styp := sym.cname + // TODO: this needs to be removed; cgen shouldn't resolve generic types (job of checker) + match mut sym.info { + ast.Struct, ast.Interface, ast.SumType { + if sym.info.is_generic { + mut sgtyps := '_T' + for gt in sym.info.generic_types { + gts := g.table.get_type_symbol(g.unwrap_generic(gt)) + sgtyps += '_$gts.cname' + } + styp += sgtyps + } + } + ast.MultiReturn { + // TODO: this doesn't belong here, but makes it working for now + mut cname := 'multi_return' + for mr_typ in sym.info.types { + mr_type_sym := g.table.get_type_symbol(g.unwrap_generic(mr_typ)) + cname += '_$mr_type_sym.cname' + } + return cname + } + else {} + } + if is_prefix_struct && styp.starts_with('C__') { + styp = styp[3..] + if sym.kind == .struct_ { + info := sym.info as ast.Struct + if !info.is_typedef { + styp = 'struct $styp' + } + } + } + return styp +} + +[inline] +fn (g &Gen) type_sidx(t ast.Type) string { + if g.pref.build_mode == .build_module { + sym := g.table.get_type_symbol(t) + return '_v_type_idx_${sym.cname}()' + } + return '$t.idx()' +} + +// +pub fn (mut g Gen) write_typedef_types() { + for typ in g.table.type_symbols { + if typ.name in c.builtins { + continue + } + match typ.kind { + .array { + info := typ.info as ast.Array + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.kind != .placeholder { + g.type_definitions.writeln('typedef array $typ.cname;') + } + } + .array_fixed { + info := typ.info as ast.ArrayFixed + elem_sym := g.table.get_type_symbol(info.elem_type) + if elem_sym.is_builtin() { + // .array_fixed { + styp := typ.cname + // array_fixed_char_300 => char x[300] + mut fixed := styp[12..] + len := styp.after('_') + fixed = fixed[..fixed.len - len.len - 1] + if fixed.starts_with('C__') { + fixed = fixed[3..] + } + if elem_sym.info is ast.FnType { + pos := g.out.len + g.write_fn_ptr_decl(&elem_sym.info, '') + fixed = g.out.cut_to(pos) + mut def_str := 'typedef $fixed;' + def_str = def_str.replace_once('(*)', '(*$styp[$len])') + g.type_definitions.writeln(def_str) + } else { + g.type_definitions.writeln('typedef $fixed $styp [$len];') + } + } + } + .chan { + if typ.name != 'chan' { + g.type_definitions.writeln('typedef chan $typ.cname;') + chan_inf := typ.chan_info() + chan_elem_type := chan_inf.elem_type + if !chan_elem_type.has_flag(.generic) { + el_stype := g.typ(chan_elem_type) + g.channel_definitions.writeln(' +static inline $el_stype __${typ.cname}_popval($typ.cname ch) { + $el_stype val; + sync__Channel_try_pop_priv(ch, &val, false); + return val; +}') + g.channel_definitions.writeln(' +static inline void __${typ.cname}_pushval($typ.cname ch, $el_stype val) { + sync__Channel_try_push_priv(ch, &val, false); +}') + } + } + } + .map { + g.type_definitions.writeln('typedef map $typ.cname;') + } + else { + continue + } + } + } + for typ in g.table.type_symbols { + if typ.kind == .alias && typ.name !in c.builtins { + g.write_alias_typesymbol_declaration(typ) + } + } + for typ in g.table.type_symbols { + if typ.kind == .function && typ.name !in c.builtins { + g.write_fn_typesymbol_declaration(typ) + } + } + // Generating interfaces after all the common types have been defined + // to prevent generating interface struct before definition of field types + for typ in g.table.type_symbols { + if typ.kind == .interface_ && typ.name !in c.builtins { + g.write_interface_typedef(typ) + } + } + for typ in g.table.type_symbols { + if typ.kind == .interface_ && typ.name !in c.builtins { + g.write_interface_typesymbol_declaration(typ) + } + } +} + +pub fn (mut g Gen) write_alias_typesymbol_declaration(sym ast.TypeSymbol) { + parent := unsafe { &g.table.type_symbols[sym.parent_idx] } + is_c_parent := parent.name.len > 2 && parent.name[0] == `C` && parent.name[1] == `.` + mut is_typedef := false + if parent.info is ast.Struct { + is_typedef = parent.info.is_typedef + } + mut parent_styp := parent.cname + if is_c_parent { + if !is_typedef { + parent_styp = 'struct ' + parent.cname[3..] + } else { + parent_styp = parent.cname[3..] + } + } else { + if sym.info is ast.Alias { + parent_styp = g.typ(sym.info.parent_type) + } + } + if parent_styp == 'byte' && sym.cname == 'u8' { + // TODO: remove this check; it is here just to fix V rebuilding in -cstrict mode with clang-12 + return + } + g.type_definitions.writeln('typedef $parent_styp $sym.cname;') +} + +pub fn (mut g Gen) write_interface_typedef(sym ast.TypeSymbol) { + struct_name := c_name(sym.cname) + g.typedefs.writeln('typedef struct $struct_name $struct_name;') +} + +pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) { + info := sym.info as ast.Interface + if info.is_generic { + return + } + struct_name := c_name(sym.cname) + g.type_definitions.writeln('struct $struct_name {') + g.type_definitions.writeln('\tunion {') + g.type_definitions.writeln('\t\tvoid* _object;') + for variant in info.types { + vcname := g.table.get_type_symbol(variant).cname + g.type_definitions.writeln('\t\t$vcname* _$vcname;') + } + g.type_definitions.writeln('\t};') + g.type_definitions.writeln('\tint _typ;') + for field in info.fields { + styp := g.typ(field.typ) + cname := c_name(field.name) + g.type_definitions.writeln('\t$styp* $cname;') + } + g.type_definitions.writeln('};') +} + +pub fn (mut g Gen) write_fn_typesymbol_declaration(sym ast.TypeSymbol) { + info := sym.info as ast.FnType + func := info.func + is_fn_sig := func.name == '' + not_anon := !info.is_anon + mut has_generic_arg := false + for param in func.params { + if param.typ.has_flag(.generic) { + has_generic_arg = true + break + } + } + if !info.has_decl && (not_anon || is_fn_sig) && !func.return_type.has_flag(.generic) + && !has_generic_arg { + fn_name := sym.cname + g.type_definitions.write_string('typedef ${g.typ(func.return_type)} (*$fn_name)(') + for i, param in func.params { + g.type_definitions.write_string(g.typ(param.typ)) + if i < func.params.len - 1 { + g.type_definitions.write_string(',') + } + } + g.type_definitions.writeln(');') + } +} + +pub fn (mut g Gen) write_multi_return_types() { + g.typedefs.writeln('\n// BEGIN_multi_return_typedefs') + g.type_definitions.writeln('\n// BEGIN_multi_return_structs') + for sym in g.table.type_symbols { + if sym.kind != .multi_return { + continue + } + info := sym.mr_info() + if info.types.filter(it.has_flag(.generic)).len > 0 { + continue + } + g.typedefs.writeln('typedef struct $sym.cname $sym.cname;') + g.type_definitions.writeln('struct $sym.cname {') + for i, mr_typ in info.types { + type_name := g.typ(mr_typ) + g.type_definitions.writeln('\t$type_name arg$i;') + } + g.type_definitions.writeln('};\n') + } + g.typedefs.writeln('// END_multi_return_typedefs\n') + g.type_definitions.writeln('// END_multi_return_structs\n') +} + +pub fn (mut g Gen) write(s string) { + $if trace_gen ? { + eprintln('gen file: ${g.file.path:-30} | last_fn_c_name: ${g.last_fn_c_name:-45} | write: $s') + } + if g.indent > 0 && g.empty_line { + g.out.write_string(util.tabs(g.indent)) + } + g.out.write_string(s) + g.empty_line = false +} + +pub fn (mut g Gen) writeln(s string) { + $if trace_gen ? { + eprintln('gen file: ${g.file.path:-30} | last_fn_c_name: ${g.last_fn_c_name:-45} | writeln: $s') + } + if g.indent > 0 && g.empty_line { + g.out.write_string(util.tabs(g.indent)) + } + g.out.writeln(s) + g.empty_line = true +} + +pub fn (mut g Gen) new_tmp_var() string { + g.tmp_count++ + return '_t$g.tmp_count' +} + +pub fn (mut g Gen) new_global_tmp_var() string { + g.global_tmp_count++ + return '_t$g.global_tmp_count' +} + +pub fn (mut g Gen) new_tmp_declaration_name() string { + g.tmp_count_declarations++ + return '_d$g.tmp_count_declarations' +} + +pub fn (mut g Gen) current_tmp_var() string { + return '_t$g.tmp_count' +} + +/* +pub fn (mut g Gen) new_tmp_var2() string { + g.tmp_count2++ + return '_tt$g.tmp_count2' +} +*/ +pub fn (mut g Gen) reset_tmp_count() { + g.tmp_count = 0 +} + +fn (mut g Gen) decrement_inside_ternary() { + key := g.inside_ternary.str() + for name in g.ternary_level_names[key] { + g.ternary_names.delete(name) + } + g.ternary_level_names.delete(key) + g.inside_ternary-- +} + +fn (mut g Gen) stmts(stmts []ast.Stmt) { + g.stmts_with_tmp_var(stmts, '') +} + +fn is_noreturn_callexpr(expr ast.Expr) bool { + if expr is ast.CallExpr { + return expr.is_noreturn + } + return false +} + +// tmp_var is used in `if` expressions only +fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) { + g.indent++ + if g.inside_ternary > 0 { + g.write('(') + } + for i, stmt in stmts { + if i == stmts.len - 1 && tmp_var != '' { + // Handle if expressions, set the value of the last expression to the temp var. + if g.inside_if_optional { + g.stmt_path_pos << g.out.len + g.skip_stmt_pos = true + if stmt is ast.ExprStmt { + if stmt.typ == ast.error_type_idx || stmt.expr is ast.None { + g.writeln('${tmp_var}.state = 2;') + g.write('${tmp_var}.err = ') + g.expr(stmt.expr) + g.writeln(';') + } else { + mut styp := g.base_type(stmt.typ) + $if tinyc && x32 && windows { + if stmt.typ == ast.int_literal_type { + styp = 'int' + } else if stmt.typ == ast.float_literal_type { + styp = 'f64' + } + } + g.write('opt_ok(&($styp[]) { ') + g.stmt(stmt) + g.writeln(' }, (Option*)(&$tmp_var), sizeof($styp));') + } + } + } else { + g.stmt_path_pos << g.out.len + g.skip_stmt_pos = true + mut is_noreturn := false + if stmt is ast.ExprStmt { + is_noreturn = is_noreturn_callexpr(stmt.expr) + } + if !is_noreturn { + g.write('$tmp_var = ') + } + g.stmt(stmt) + if !g.out.last_n(2).contains(';') { + g.writeln(';') + } + } + } else { + g.stmt(stmt) + if g.inside_if_optional && stmt is ast.ExprStmt { + g.writeln(';') + } + } + g.skip_stmt_pos = false + if g.inside_ternary > 0 && i < stmts.len - 1 { + g.write(',') + } + } + g.indent-- + if g.inside_ternary > 0 { + g.write('') + g.write(')') + } + if g.is_autofree && !g.inside_vweb_tmpl && stmts.len > 0 { + // use the first stmt to get the scope + stmt := stmts[0] + // stmt := stmts[stmts.len-1] + if stmt !is ast.FnDecl && g.inside_ternary == 0 { + // g.trace_autofree('// autofree scope') + // g.trace_autofree('// autofree_scope_vars($stmt.pos.pos) | ${typeof(stmt)}') + // go back 1 position is important so we dont get the + // internal scope of for loops and possibly other nodes + // g.autofree_scope_vars(stmt.pos.pos - 1) + mut stmt_pos := stmt.pos + if stmt_pos.pos == 0 { + // Do not autofree if the position is 0, since the correct scope won't be found. + // Report a bug, since position shouldn't be 0 for most nodes. + if stmt is ast.Module { + return + } + if stmt is ast.ExprStmt { + // For some reason ExprStmt.pos is 0 when ExprStmt.expr is comp if expr + // Extract the pos. TODO figure out why and fix. + stmt_pos = stmt.expr.position() + } + if stmt_pos.pos == 0 { + $if trace_autofree ? { + println('autofree: first stmt pos = 0. $stmt.type_name()') + } + return + } + } + g.autofree_scope_vars(stmt_pos.pos - 1, stmt_pos.line_nr, false) + } + } +} + +[inline] +fn (mut g Gen) write_v_source_line_info(pos token.Position) { + if g.inside_ternary == 0 && g.pref.is_vlines && g.is_vlines_enabled { + nline := pos.line_nr + 1 + lineinfo := '\n#line $nline "$g.vlines_path"' + g.writeln(lineinfo) + } +} + +fn (mut g Gen) stmt(node ast.Stmt) { + if !g.skip_stmt_pos { + g.stmt_path_pos << g.out.len + } + defer { + } + // println('g.stmt()') + // g.writeln('//// stmt start') + match node { + ast.EmptyStmt {} + ast.AsmStmt { + g.write_v_source_line_info(node.pos) + g.gen_asm_stmt(node) + } + ast.AssertStmt { + g.write_v_source_line_info(node.pos) + g.gen_assert_stmt(node) + } + ast.AssignStmt { + g.write_v_source_line_info(node.pos) + g.gen_assign_stmt(node) + } + ast.Block { + g.write_v_source_line_info(node.pos) + if node.is_unsafe { + g.writeln('{ // Unsafe block') + } else { + g.writeln('{') + } + g.stmts(node.stmts) + g.writeln('}') + } + ast.BranchStmt { + g.write_v_source_line_info(node.pos) + + if node.label != '' { + x := g.labeled_loops[node.label] or { + panic('$node.label doesn\'t exist $g.file.path, $node.pos') + } + match x { + ast.ForCStmt { + if x.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForInStmt { + if x.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForStmt { + if x.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + else {} + } + + if node.kind == .key_break { + g.writeln('goto ${node.label}__break;') + } else { + // assert node.kind == .key_continue + g.writeln('goto ${node.label}__continue;') + } + } else { + inner_loop := g.inner_loop + match inner_loop { + ast.ForCStmt { + if inner_loop.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForInStmt { + if inner_loop.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + ast.ForStmt { + if inner_loop.scope.contains(g.cur_lock.pos.pos) { + g.unlock_locks() + } + } + else {} + } + // continue or break + if g.is_autofree && !g.is_builtin_mod { + g.trace_autofree('// free before continue/break') + g.autofree_scope_vars_stop(node.pos.pos - 1, node.pos.line_nr, true, + g.branch_parent_pos) + } + g.writeln('$node.kind;') + } + } + ast.ConstDecl { + g.write_v_source_line_info(node.pos) + // if g.pref.build_mode != .build_module { + g.const_decl(node) + // } + } + ast.CompFor { + g.comp_for(node) + } + ast.DeferStmt { + mut defer_stmt := node + defer_stmt.ifdef = g.defer_ifdef + g.writeln('${g.defer_flag_var(defer_stmt)} = true;') + g.defer_stmts << defer_stmt + } + ast.EnumDecl { + enum_name := util.no_dots(node.name) + is_flag := node.is_flag + g.enum_typedefs.writeln('typedef enum {') + mut cur_enum_expr := '' + mut cur_enum_offset := 0 + for i, field in node.fields { + g.enum_typedefs.write_string('\t${enum_name}__$field.name') + if field.has_expr { + g.enum_typedefs.write_string(' = ') + expr_str := g.expr_string(field.expr) + g.enum_typedefs.write_string(expr_str) + cur_enum_expr = expr_str + cur_enum_offset = 0 + } else if is_flag { + g.enum_typedefs.write_string(' = ') + cur_enum_expr = '1 << $i' + g.enum_typedefs.write_string((1 << i).str()) + cur_enum_offset = 0 + } + cur_value := if cur_enum_offset > 0 { + '$cur_enum_expr+$cur_enum_offset' + } else { + cur_enum_expr + } + g.enum_typedefs.writeln(', // $cur_value') + cur_enum_offset++ + } + g.enum_typedefs.writeln('} $enum_name;\n') + } + ast.ExprStmt { + g.write_v_source_line_info(node.pos) + // af := g.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod + // if af { + // g.autofree_call_pregen(node.expr as ast.CallExpr) + // } + old_is_void_expr_stmt := g.is_void_expr_stmt + g.is_void_expr_stmt = !node.is_expr + if node.typ != ast.void_type && g.expected_cast_type != 0 { + g.expr_with_cast(node.expr, node.typ, g.expected_cast_type) + } else { + g.expr(node.expr) + } + g.is_void_expr_stmt = old_is_void_expr_stmt + // if af { + // g.autofree_call_postgen() + // } + if g.inside_ternary == 0 && !g.inside_if_optional && !node.is_expr + && node.expr !is ast.IfExpr { + g.writeln(';') + } + } + ast.FnDecl { + g.process_fn_decl(node) + } + ast.ForCStmt { + prev_branch_parent_pos := g.branch_parent_pos + g.branch_parent_pos = node.pos.pos + save_inner_loop := g.inner_loop + g.inner_loop = unsafe { &node } + if node.label != '' { + g.labeled_loops[node.label] = unsafe { &node } + } + g.write_v_source_line_info(node.pos) + g.for_c_stmt(node) + g.branch_parent_pos = prev_branch_parent_pos + g.labeled_loops.delete(node.label) + g.inner_loop = save_inner_loop + } + ast.ForInStmt { + prev_branch_parent_pos := g.branch_parent_pos + g.branch_parent_pos = node.pos.pos + save_inner_loop := g.inner_loop + g.inner_loop = unsafe { &node } + if node.label != '' { + g.labeled_loops[node.label] = unsafe { &node } + } + g.write_v_source_line_info(node.pos) + g.for_in_stmt(node) + g.branch_parent_pos = prev_branch_parent_pos + g.labeled_loops.delete(node.label) + g.inner_loop = save_inner_loop + } + ast.ForStmt { + prev_branch_parent_pos := g.branch_parent_pos + g.branch_parent_pos = node.pos.pos + save_inner_loop := g.inner_loop + g.inner_loop = unsafe { &node } + if node.label != '' { + g.labeled_loops[node.label] = unsafe { &node } + } + g.write_v_source_line_info(node.pos) + g.for_stmt(node) + g.branch_parent_pos = prev_branch_parent_pos + g.labeled_loops.delete(node.label) + g.inner_loop = save_inner_loop + } + ast.GlobalDecl { + g.global_decl(node) + } + ast.GotoLabel { + g.writeln('$node.name: {}') + } + ast.GotoStmt { + g.write_v_source_line_info(node.pos) + g.writeln('goto $node.name;') + } + ast.HashStmt { + mut ct_condition := '' + if node.ct_conds.len > 0 { + ct_condition_start := g.out.len + for idx, ct_expr in node.ct_conds { + g.comp_if_cond(ct_expr, false) + if idx < node.ct_conds.len - 1 { + g.write(' && ') + } + } + ct_condition = g.out.cut_to(ct_condition_start).trim_space() + // dump(node) + // dump(ct_condition) + } + // #include etc + if node.kind == 'include' { + mut missing_message := 'Header file $node.main, needed for module `$node.mod` was not found.' + if node.msg != '' { + missing_message += ' ${node.msg}.' + } else { + missing_message += ' Please install the corresponding development headers.' + } + mut guarded_include := get_guarded_include_text(node.main, missing_message) + if node.main == '<errno.h>' { + // fails with musl-gcc and msvc; but an unguarded include works: + guarded_include = '#include $node.main' + } + if node.main.contains('.m') { + g.definitions.writeln('\n') + if ct_condition.len > 0 { + g.definitions.writeln('#if $ct_condition') + } + // Objective C code import, include it after V types, so that e.g. `string` is + // available there + g.definitions.writeln('// added by module `$node.mod`') + g.definitions.writeln(guarded_include) + if ct_condition.len > 0 { + g.definitions.writeln('#endif // \$if $ct_condition') + } + g.definitions.writeln('\n') + } else { + g.includes.writeln('\n') + if ct_condition.len > 0 { + g.includes.writeln('#if $ct_condition') + } + g.includes.writeln('// added by module `$node.mod`') + g.includes.writeln(guarded_include) + if ct_condition.len > 0 { + g.includes.writeln('#endif // \$if $ct_condition') + } + g.includes.writeln('\n') + } + } else if node.kind == 'define' { + if ct_condition.len > 0 { + g.includes.writeln('#if $ct_condition') + } + g.includes.writeln('// defined by module `$node.mod`') + g.includes.writeln('#define $node.main') + if ct_condition.len > 0 { + g.includes.writeln('#endif // \$if $ct_condition') + } + } + } + ast.Import {} + ast.InterfaceDecl { + // definitions are sorted and added in write_types + ts := g.table.get_type_symbol(node.typ) + if !(ts.info as ast.Interface).is_generic { + for method in node.methods { + if method.return_type.has_flag(.optional) { + // Register an optional if it's not registered yet + g.register_optional(method.return_type) + } + } + } + } + ast.Module { + // g.is_builtin_mod = node.name == 'builtin' + g.is_builtin_mod = node.name in ['builtin', 'os', 'strconv', 'strings', 'gg'] + // g.cur_mod = node.name + g.cur_mod = node + } + ast.NodeError {} + ast.Return { + g.return_stmt(node) + } + ast.SqlStmt { + g.sql_stmt(node) + } + ast.StructDecl { + name := if node.language == .c { util.no_dots(node.name) } else { c_name(node.name) } + // TODO For some reason, build fails with autofree with this line + // as it's only informative, comment it for now + // g.gen_attrs(node.attrs) + // g.writeln('typedef struct {') + // for field in it.fields { + // field_type_sym := g.table.get_type_symbol(field.typ) + // g.writeln('\t$field_type_sym.name $field.name;') + // } + // g.writeln('} $name;') + if node.language == .c { + return + } + if node.is_union { + g.typedefs.writeln('typedef union $name $name;') + } else { + /* + attrs := if node.attrs.contains('packed') { + '__attribute__((__packed__))' + } else { + '' + } + */ + g.typedefs.writeln('typedef struct $name $name;') + } + } + ast.TypeDecl { + if !g.pref.skip_unused { + g.writeln('// TypeDecl') + } + } + } + if !g.skip_stmt_pos { // && g.stmt_path_pos.len > 0 { + g.stmt_path_pos.delete_last() + } + // If we have temporary string exprs to free after this statement, do it. e.g.: + // `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);` + if g.is_autofree { + // if node is ast.ExprStmt {&& node.expr is ast.CallExpr { + if node !is ast.FnDecl { + // p := node.position() + // g.autofree_call_postgen(p.pos) + } + } +} + +fn (mut g Gen) write_defer_stmts() { + for i := g.defer_stmts.len - 1; i >= 0; i-- { + defer_stmt := g.defer_stmts[i] + g.writeln('// Defer begin') + g.writeln('if (${g.defer_flag_var(defer_stmt)}) {') + g.indent++ + if defer_stmt.ifdef.len > 0 { + g.writeln(defer_stmt.ifdef) + g.stmts(defer_stmt.stmts) + g.writeln('') + g.writeln('#endif') + } else { + g.indent-- + g.stmts(defer_stmt.stmts) + g.indent++ + } + g.indent-- + g.writeln('}') + g.writeln('// Defer end') + } +} + +fn (mut g Gen) for_c_stmt(node ast.ForCStmt) { + if node.is_multi { + g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } + g.writeln('{') + g.indent++ + if node.has_init { + g.stmt(node.init) + } + g.writeln('bool _is_first = true;') + g.writeln('while (true) {') + g.writeln('\tif (_is_first) {') + g.writeln('\t\t_is_first = false;') + g.writeln('\t} else {') + if node.has_inc { + g.indent++ + g.stmt(node.inc) + g.writeln(';') + g.indent-- + } + g.writeln('}') + if node.has_cond { + g.write('if (!(') + g.expr(node.cond) + g.writeln(')) break;') + } + g.is_vlines_enabled = true + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('${node.label}__continue: {}') + } + g.writeln('}') + g.indent-- + g.writeln('}') + if node.label.len > 0 { + g.writeln('${node.label}__break: {}') + } + } else { + g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } + g.write('for (') + if !node.has_init { + g.write('; ') + } else { + g.stmt(node.init) + // Remove excess return and add space + if g.out.last_n(1) == '\n' { + g.out.go_back(1) + g.empty_line = false + g.write(' ') + } + } + if node.has_cond { + g.expr(node.cond) + } + g.write('; ') + if node.has_inc { + g.stmt(node.inc) + } + g.writeln(') {') + g.is_vlines_enabled = true + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('${node.label}__continue: {}') + } + g.writeln('}') + if node.label.len > 0 { + g.writeln('${node.label}__break: {}') + } + } +} + +fn (mut g Gen) for_stmt(node ast.ForStmt) { + g.is_vlines_enabled = false + if node.label.len > 0 { + g.writeln('$node.label:') + } + g.writeln('for (;;) {') + if !node.is_inf { + g.indent++ + g.stmt_path_pos << g.out.len + g.write('if (!(') + g.expr(node.cond) + g.writeln(')) break;') + g.indent-- + } + g.is_vlines_enabled = true + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('\t${node.label}__continue: {}') + } + g.writeln('}') + if node.label.len > 0 { + g.writeln('${node.label}__break: {}') + } +} + +fn (mut g Gen) for_in_stmt(node ast.ForInStmt) { + if node.label.len > 0 { + g.writeln('\t$node.label: {}') + } + if node.is_range { + // `for x in 1..10 {` + i := if node.val_var == '_' { g.new_tmp_var() } else { c_name(node.val_var) } + val_typ := g.table.mktyp(node.val_type) + g.write('for (${g.typ(val_typ)} $i = ') + g.expr(node.cond) + g.write('; $i < ') + g.expr(node.high) + g.writeln('; ++$i) {') + } else if node.kind == .array { + // `for num in nums {` + // g.writeln('// FOR IN array') + styp := g.typ(node.val_type) + val_sym := g.table.get_type_symbol(node.val_type) + mut cond_var := '' + if node.cond is ast.Ident || node.cond is ast.SelectorExpr { + cond_var = g.expr_string(node.cond) + } else { + cond_var = g.new_tmp_var() + g.write(g.typ(node.cond_type)) + g.write(' $cond_var = ') + g.expr(node.cond) + g.writeln(';') + } + i := if node.key_var in ['', '_'] { g.new_tmp_var() } else { node.key_var } + field_accessor := if node.cond_type.is_ptr() { '->' } else { '.' } + share_accessor := if node.cond_type.share() == .shared_t { 'val.' } else { '' } + op_field := field_accessor + share_accessor + g.empty_line = true + g.writeln('for (int $i = 0; $i < $cond_var${op_field}len; ++$i) {') + if node.val_var != '_' { + if val_sym.kind == .function { + g.write('\t') + g.write_fn_ptr_decl(val_sym.info as ast.FnType, c_name(node.val_var)) + g.writeln(' = ((voidptr*)$cond_var${op_field}data)[$i];') + } else if val_sym.kind == .array_fixed && !node.val_is_mut { + right := '(($styp*)$cond_var${op_field}data)[$i]' + g.writeln('\t$styp ${c_name(node.val_var)};') + g.writeln('\tmemcpy(*($styp*)${c_name(node.val_var)}, (byte*)$right, sizeof($styp));') + } else { + // If val is mutable (pointer behind the scenes), we need to generate + // `int* val = ((int*)arr.data) + i;` + // instead of + // `int* val = ((int**)arr.data)[i];` + // right := if node.val_is_mut { styp } else { styp + '*' } + right := if node.val_is_mut { + '(($styp)$cond_var${op_field}data) + $i' + } else { + '(($styp*)$cond_var${op_field}data)[$i]' + } + g.writeln('\t$styp ${c_name(node.val_var)} = $right;') + } + } + } else if node.kind == .array_fixed { + mut cond_var := '' + cond_type_is_ptr := node.cond_type.is_ptr() + cond_is_literal := node.cond is ast.ArrayInit + if cond_is_literal { + cond_var = g.new_tmp_var() + g.write(g.typ(node.cond_type)) + g.write(' $cond_var = ') + g.expr(node.cond) + g.writeln(';') + } else if cond_type_is_ptr { + cond_var = g.new_tmp_var() + cond_var_type := g.typ(node.cond_type).trim('*') + if !node.cond.is_lvalue() { + g.write('$cond_var_type *$cond_var = (($cond_var_type)') + } else { + g.write('$cond_var_type *$cond_var = (') + } + g.expr(node.cond) + g.writeln(');') + } else { + cond_var = g.expr_string(node.cond) + } + idx := if node.key_var in ['', '_'] { g.new_tmp_var() } else { node.key_var } + cond_sym := g.table.get_type_symbol(node.cond_type) + info := cond_sym.info as ast.ArrayFixed + g.writeln('for (int $idx = 0; $idx != $info.size; ++$idx) {') + if node.val_var != '_' { + val_sym := g.table.get_type_symbol(node.val_type) + is_fixed_array := val_sym.kind == .array_fixed && !node.val_is_mut + if val_sym.kind == .function { + g.write('\t') + g.write_fn_ptr_decl(val_sym.info as ast.FnType, c_name(node.val_var)) + } else if is_fixed_array { + styp := g.typ(node.val_type) + g.writeln('\t$styp ${c_name(node.val_var)};') + g.writeln('\tmemcpy(*($styp*)${c_name(node.val_var)}, (byte*)$cond_var[$idx], sizeof($styp));') + } else { + styp := g.typ(node.val_type) + g.write('\t$styp ${c_name(node.val_var)}') + } + if !is_fixed_array { + addr := if node.val_is_mut { '&' } else { '' } + if cond_type_is_ptr { + g.writeln(' = ${addr}(*$cond_var)[$idx];') + } else if cond_is_literal { + g.writeln(' = $addr$cond_var[$idx];') + } else { + g.write(' = $addr') + g.expr(node.cond) + g.writeln('[$idx];') + } + } + } + } else if node.kind == .map { + // `for key, val in map { + // g.writeln('// FOR IN map') + mut cond_var := '' + if node.cond is ast.Ident { + cond_var = g.expr_string(node.cond) + } else { + cond_var = g.new_tmp_var() + g.write(g.typ(node.cond_type)) + g.write(' $cond_var = ') + g.expr(node.cond) + g.writeln(';') + } + mut arw_or_pt := if node.cond_type.is_ptr() { '->' } else { '.' } + if node.cond_type.has_flag(.shared_f) { + arw_or_pt = '->val.' + } + idx := g.new_tmp_var() + map_len := g.new_tmp_var() + g.empty_line = true + g.writeln('int $map_len = $cond_var${arw_or_pt}key_values.len;') + g.writeln('for (int $idx = 0; $idx < $map_len; ++$idx ) {') + // TODO: don't have this check when the map has no deleted elements + g.indent++ + diff := g.new_tmp_var() + g.writeln('int $diff = $cond_var${arw_or_pt}key_values.len - $map_len;') + g.writeln('$map_len = $cond_var${arw_or_pt}key_values.len;') + // TODO: optimize this + g.writeln('if ($diff < 0) {') + g.writeln('\t$idx = -1;') + g.writeln('\tcontinue;') + g.writeln('}') + g.writeln('if (!DenseArray_has_index(&$cond_var${arw_or_pt}key_values, $idx)) {continue;}') + if node.key_var != '_' { + key_styp := g.typ(node.key_type) + key := c_name(node.key_var) + g.writeln('$key_styp $key = /*key*/ *($key_styp*)DenseArray_key(&$cond_var${arw_or_pt}key_values, $idx);') + // TODO: analyze whether node.key_type has a .clone() method and call .clone() for all types: + if node.key_type == ast.string_type { + g.writeln('$key = string_clone($key);') + } + } + if node.val_var != '_' { + val_sym := g.table.get_type_symbol(node.val_type) + if val_sym.kind == .function { + g.write_fn_ptr_decl(val_sym.info as ast.FnType, c_name(node.val_var)) + g.write(' = (*(voidptr*)') + g.writeln('DenseArray_value(&$cond_var${arw_or_pt}key_values, $idx));') + } else if val_sym.kind == .array_fixed && !node.val_is_mut { + val_styp := g.typ(node.val_type) + g.writeln('$val_styp ${c_name(node.val_var)};') + g.writeln('memcpy(*($val_styp*)${c_name(node.val_var)}, (byte*)DenseArray_value(&$cond_var${arw_or_pt}key_values, $idx), sizeof($val_styp));') + } else { + val_styp := g.typ(node.val_type) + if node.val_type.is_ptr() { + g.write('$val_styp ${c_name(node.val_var)} = &(*($val_styp)') + } else { + g.write('$val_styp ${c_name(node.val_var)} = (*($val_styp*)') + } + g.writeln('DenseArray_value(&$cond_var${arw_or_pt}key_values, $idx));') + } + } + g.indent-- + } else if node.kind == .string { + cond := if node.cond is ast.StringLiteral || node.cond is ast.StringInterLiteral { + ast.Expr(g.new_ctemp_var_then_gen(node.cond, ast.string_type)) + } else { + node.cond + } + i := if node.key_var in ['', '_'] { g.new_tmp_var() } else { node.key_var } + g.write('for (int $i = 0; $i < ') + g.expr(cond) + g.writeln('.len; ++$i) {') + if node.val_var != '_' { + g.write('\tbyte ${c_name(node.val_var)} = ') + g.expr(cond) + g.writeln('.str[$i];') + } + } else if node.kind == .struct_ { + cond_type_sym := g.table.get_type_symbol(node.cond_type) + next_fn := cond_type_sym.find_method('next') or { + verror('`next` method not found') + return + } + ret_typ := next_fn.return_type + t_expr := g.new_tmp_var() + g.write('${g.typ(node.cond_type)} $t_expr = ') + g.expr(node.cond) + g.writeln(';') + g.writeln('while (1) {') + t_var := g.new_tmp_var() + receiver_typ := next_fn.params[0].typ + receiver_styp := g.typ(receiver_typ) + fn_name := receiver_styp.replace_each(['*', '', '.', '__']) + '_next' + g.write('\t${g.typ(ret_typ)} $t_var = ${fn_name}(') + if !node.cond_type.is_ptr() && receiver_typ.is_ptr() { + g.write('&') + } + g.writeln('$t_expr);') + g.writeln('\tif (${t_var}.state != 0) break;') + val := if node.val_var in ['', '_'] { g.new_tmp_var() } else { node.val_var } + val_styp := g.typ(node.val_type) + g.writeln('\t$val_styp $val = *($val_styp*)${t_var}.data;') + } else { + typ_str := g.table.type_to_str(node.cond_type) + g.error('for in: unhandled symbol `$node.cond` of type `$typ_str`', node.pos) + } + g.stmts(node.stmts) + if node.label.len > 0 { + g.writeln('\t${node.label}__continue: {}') + } + + if node.kind == .map { + // diff := g.new_tmp_var() + // g.writeln('int $diff = $cond_var${arw_or_pt}key_values.len - $map_len;') + // g.writeln('if ($diff < 0) {') + // g.writeln('\t$idx = -1;') + // g.writeln('\t$map_len = $cond_var${arw_or_pt}key_values.len;') + // g.writeln('}') + } + + g.writeln('}') + if node.label.len > 0 { + g.writeln('\t${node.label}__break: {}') + } +} + +fn (mut g Gen) write_sumtype_casting_fn(got_ ast.Type, exp_ ast.Type) { + got, exp := got_.idx(), exp_.idx() + i := got | (exp << 16) + if got == exp || g.sumtype_definitions[i] { + return + } + g.sumtype_definitions[i] = true + got_sym := g.table.get_type_symbol(got) + exp_sym := g.table.get_type_symbol(exp) + mut sb := strings.new_builder(128) + got_cname, exp_cname := got_sym.cname, exp_sym.cname + sb.writeln('static inline $exp_cname ${got_cname}_to_sumtype_${exp_cname}($got_cname* x) {') + sb.writeln('\t$got_cname* ptr = memdup(x, sizeof($got_cname));') + for embed_hierarchy in g.table.get_embeds(got_sym) { + // last embed in the hierarchy + mut embed_cname := '' + mut embed_name := '' + mut accessor := '&x->' + for j, embed in embed_hierarchy { + embed_sym := g.table.get_type_symbol(embed) + embed_cname = embed_sym.cname + embed_name = embed_sym.embed_name() + if j > 0 { + accessor += '.' + } + accessor += embed_name + } + // if the variable is not used, the C compiler will optimize it away + sb.writeln('\t$embed_cname* ${embed_name}_ptr = memdup($accessor, sizeof($embed_cname));') + } + sb.write_string('\treturn ($exp_cname){ ._$got_cname = ptr, ._typ = ${g.type_sidx(got)}') + for field in (exp_sym.info as ast.SumType).fields { + mut ptr := 'ptr' + mut type_cname := got_cname + _, embed_types := g.table.find_field_from_embeds_recursive(got_sym, field.name) or { + ast.StructField{}, []ast.Type{} + } + if embed_types.len > 0 { + embed_sym := g.table.get_type_symbol(embed_types.last()) + ptr = '${embed_sym.embed_name()}_ptr' + type_cname = embed_sym.cname + } + field_styp := g.typ(field.typ) + if got_sym.kind in [.sum_type, .interface_] { + // the field is already a wrapped pointer; we shouldn't wrap it once again + sb.write_string(', .$field.name = ptr->$field.name') + } else { + sb.write_string(', .$field.name = ($field_styp*)((char*)$ptr + __offsetof_ptr($ptr, $type_cname, $field.name))') + } + } + sb.writeln('};\n}') + g.auto_fn_definitions << sb.str() +} + +fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp_is_ptr bool, exp_styp string, got_is_ptr bool, got_styp string) { + mut rparen_n := 1 + if exp_is_ptr { + g.write('HEAP($exp_styp, ') + rparen_n++ + } + g.write('${fname}(') + if !got_is_ptr { + if !expr.is_lvalue() + || (expr is ast.Ident && (expr as ast.Ident).obj.is_simple_define_const()) { + g.write('ADDR($got_styp, (') + rparen_n += 2 + } else { + g.write('&') + } + } + g.expr(expr) + g.write(')'.repeat(rparen_n)) +} + +// use instead of expr() when you need to cast to a different type +fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_type ast.Type) { + got_type := g.table.mktyp(got_type_raw) + exp_sym := g.table.get_type_symbol(expected_type) + expected_is_ptr := expected_type.is_ptr() + got_is_ptr := got_type.is_ptr() + got_sym := g.table.get_type_symbol(got_type) + // allow using the new Error struct as a string, to avoid a breaking change + // TODO: temporary to allow people to migrate their code; remove soon + if got_type == ast.error_type_idx && expected_type == ast.string_type_idx { + g.write('(*(') + g.expr(expr) + g.write('.msg))') + return + } + if got_sym.kind == .none_ && exp_sym.name == 'IError' { + g.expr(expr) + return + } + if exp_sym.info is ast.Interface && got_type_raw.idx() != expected_type.idx() + && !expected_type.has_flag(.optional) { + if expr is ast.StructInit && !got_type.is_ptr() { + g.inside_cast_in_heap++ + got_styp := g.cc_type(got_type.to_ptr(), true) + // TODO: why does cc_type even add this in the first place? + exp_styp := exp_sym.cname + mut fname := 'I_${got_styp}_to_Interface_$exp_styp' + if exp_sym.info.is_generic { + fname = g.generic_fn_name(exp_sym.info.concrete_types, fname, false) + } + g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, true, + got_styp) + g.inside_cast_in_heap-- + } else { + got_styp := g.cc_type(got_type, true) + exp_styp := exp_sym.cname + mut fname := '/*$exp_sym*/I_${got_styp}_to_Interface_$exp_styp' + if exp_sym.info.is_generic { + fname = g.generic_fn_name(exp_sym.info.concrete_types, fname, false) + } + g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, got_is_ptr, + got_styp) + } + return + } + // cast to sum type + exp_styp := g.typ(expected_type) + got_styp := g.typ(got_type) + if expected_type != ast.void_type { + expected_deref_type := if expected_is_ptr { expected_type.deref() } else { expected_type } + got_deref_type := if got_is_ptr { got_type.deref() } else { got_type } + if g.table.sumtype_has_variant(expected_deref_type, got_deref_type) { + mut is_already_sum_type := false + scope := g.file.scope.innermost(expr.position().pos) + if expr is ast.Ident { + if v := scope.find_var(expr.name) { + if v.smartcasts.len > 0 { + is_already_sum_type = true + } + } + } else if expr is ast.SelectorExpr { + if _ := scope.find_struct_field(expr.expr.str(), expr.expr_type, expr.field_name) { + is_already_sum_type = true + } + } + if is_already_sum_type { + // Don't create a new sum type wrapper if there is already one + g.prevent_sum_type_unwrapping_once = true + g.expr(expr) + } else { + g.write_sumtype_casting_fn(got_type, expected_type) + fname := '${got_sym.cname}_to_sumtype_$exp_sym.cname' + g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_sym.cname, + got_is_ptr, got_styp) + } + return + } + } + // Generic dereferencing logic + neither_void := ast.voidptr_type !in [got_type, expected_type] + to_shared := expected_type.has_flag(.shared_f) && !got_type_raw.has_flag(.shared_f) + && !expected_type.has_flag(.optional) + // from_shared := got_type_raw.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) + if to_shared { + shared_styp := exp_styp[0..exp_styp.len - 1] // `shared` implies ptr, so eat one `*` + if got_type_raw.is_ptr() { + g.error('cannot convert reference to `shared`', expr.position()) + } + if exp_sym.kind == .array { + g.writeln('($shared_styp*)__dup_shared_array(&($shared_styp){.mtx = {0}, .val =') + } else if exp_sym.kind == .map { + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.mtx = {0}, .val =') + } else { + g.writeln('($shared_styp*)__dup${shared_styp}(&($shared_styp){.mtx = {0}, .val =') + } + old_is_shared := g.is_shared + g.is_shared = false + g.expr(expr) + g.is_shared = old_is_shared + g.writeln('}, sizeof($shared_styp))') + return + } + if got_is_ptr && !expected_is_ptr && neither_void + && exp_sym.kind !in [.interface_, .placeholder] && expr !is ast.InfixExpr { + got_deref_type := got_type.deref() + deref_sym := g.table.get_type_symbol(got_deref_type) + deref_will_match := expected_type in [got_type, got_deref_type, deref_sym.parent_idx] + got_is_opt := got_type.has_flag(.optional) + if deref_will_match || got_is_opt { + g.write('*') + } + } + if expected_type.has_flag(.optional) && expr is ast.None { + g.gen_optional_error(expected_type, expr) + return + } + if expr is ast.IntegerLiteral { + if expected_type in [ast.u64_type, ast.u32_type, ast.u16_type] && expr.val[0] != `-` { + g.expr(expr) + g.write('U') + return + } + } + if exp_sym.kind == .function { + g.write('(voidptr)') + } + // no cast + g.expr(expr) +} + +// cestring returns a V string, properly escaped for embeddeding in a C string literal. +fn cestring(s string) string { + return s.replace('\\', '\\\\').replace('"', "'") +} + +// ctoslit returns a '_SLIT("$s")' call, where s is properly escaped. +fn ctoslit(s string) string { + return '_SLIT("' + cestring(s) + '")' +} + +fn (mut g Gen) gen_attrs(attrs []ast.Attr) { + if g.pref.skip_unused { + return + } + for attr in attrs { + g.writeln('// Attr: [$attr.name]') + } +} + +fn (mut g Gen) gen_asm_stmt(stmt ast.AsmStmt) { + g.write('__asm__') + if stmt.is_volatile { + g.write(' volatile') + } + if stmt.is_goto { + g.write(' goto') + } + g.writeln(' (') + g.indent++ + for template_tmp in stmt.templates { + mut template := template_tmp + g.write('"') + if template.is_directive { + g.write('.') + } + g.write(template.name) + if template.is_label { + g.write(':') + } else { + g.write(' ') + } + // swap destionation and operands for att syntax + if template.args.len != 0 && !template.is_directive { + template.args.prepend(template.args[template.args.len - 1]) + template.args.delete(template.args.len - 1) + } + + for i, arg in template.args { + if stmt.arch == .amd64 && (template.name == 'call' || template.name[0] == `j`) + && arg is ast.AsmRegister { + g.write('*') // indirect branching + } + + g.asm_arg(arg, stmt) + if i + 1 < template.args.len { + g.write(', ') + } + } + + if !template.is_label { + g.write(';') + } + g.writeln('"') + } + + if stmt.output.len != 0 || stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + g.gen_asm_ios(stmt.output) + if stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + g.gen_asm_ios(stmt.input) + if stmt.clobbered.len != 0 || stmt.is_goto { + g.write(': ') + } + for i, clob in stmt.clobbered { + g.write('"') + g.write(clob.reg.name) + g.write('"') + if i + 1 < stmt.clobbered.len { + g.writeln(',') + } else { + g.writeln('') + } + } + if stmt.is_goto { + g.write(': ') + } + for i, label in stmt.global_labels { + g.write(label) + if i + 1 < stmt.clobbered.len { + g.writeln(',') + } else { + g.writeln('') + } + } + g.indent-- + g.writeln(');') +} + +fn (mut g Gen) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt) { + match arg { + ast.AsmAlias { + name := arg.name + if name in stmt.local_labels || name in stmt.global_labels + || name in g.file.global_labels || stmt.is_basic + || (name !in stmt.input.map(it.alias) && name !in stmt.output.map(it.alias)) { + asm_formatted_name := if name in stmt.global_labels { '%l[$name]' } else { name } + g.write(asm_formatted_name) + } else { + g.write('%[$name]') + } + } + ast.CharLiteral { + g.write("'$arg.val'") + } + ast.IntegerLiteral, ast.FloatLiteral { + g.write('\$$arg.val') + } + ast.BoolLiteral { + g.write('\$$arg.val.str()') + } + ast.AsmRegister { + if !stmt.is_basic { + g.write('%') // escape percent with percent in extended assembly + } + g.write('%$arg.name') + } + ast.AsmAddressing { + base := arg.base + index := arg.index + displacement := arg.displacement + scale := arg.scale + match arg.mode { + .base { + g.write('(') + g.asm_arg(base, stmt) + g.write(')') + } + .displacement { + g.asm_arg(displacement, stmt) + g.write('()') + } + .base_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(')') + } + .index_times_scale_plus_displacement { + if displacement is ast.AsmDisp { + g.asm_arg(displacement, stmt) + g.write('(, ') + } else if displacement is ast.AsmRegister { + g.write('(') + g.asm_arg(displacement, stmt) + g.write(',') + } else { + panic('unexpected $displacement.type_name()') + } + g.asm_arg(index, stmt) + g.write(',$scale)') + } + .base_plus_index_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(',') + g.asm_arg(index, stmt) + g.write(',1)') + } + .base_plus_index_times_scale_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(',') + g.asm_arg(index, stmt) + g.write(',$scale)') + } + .rip_plus_displacement { + g.asm_arg(displacement, stmt) + g.write('(') + g.asm_arg(base, stmt) + g.write(')') + } + .invalid { + g.error('invalid addressing mode', arg.pos) + } + } + } + ast.AsmDisp { + g.write(arg.val) + } + string { + g.write(arg) + } + } +} + +fn (mut g Gen) gen_asm_ios(ios []ast.AsmIO) { + for i, io in ios { + if io.alias != '' { + g.write('[$io.alias] ') + } + g.write('"$io.constraint" (') + g.expr(io.expr) + g.write(')') + if i + 1 < ios.len { + g.writeln(',') + } else { + g.writeln('') + } + } +} + +fn cnewlines(s string) string { + return s.replace('\n', r'\n') +} + +fn (mut g Gen) write_fn_ptr_decl(func &ast.FnType, ptr_name string) { + ret_styp := g.typ(func.func.return_type) + g.write('$ret_styp (*$ptr_name) (') + arg_len := func.func.params.len + for i, arg in func.func.params { + arg_styp := g.typ(arg.typ) + g.write('$arg_styp $arg.name') + if i < arg_len - 1 { + g.write(', ') + } + } + g.write(')') +} + +// TODO this function is scary. Simplify/split up. +fn (mut g Gen) gen_assign_stmt(assign_stmt ast.AssignStmt) { + if assign_stmt.is_static { + g.write('static ') + } + mut return_type := ast.void_type + is_decl := assign_stmt.op == .decl_assign + g.assign_op = assign_stmt.op + op := if is_decl { token.Kind.assign } else { assign_stmt.op } + right_expr := assign_stmt.right[0] + match right_expr { + ast.CallExpr { return_type = right_expr.return_type } + ast.LockExpr { return_type = right_expr.typ } + ast.MatchExpr { return_type = right_expr.return_type } + ast.IfExpr { return_type = right_expr.typ } + else {} + } + // Free the old value assigned to this string var (only if it's `str = [new value]` + // or `x.str = [new value]` ) + mut af := g.is_autofree && !g.is_builtin_mod && assign_stmt.op == .assign + && assign_stmt.left_types.len == 1 + && (assign_stmt.left[0] is ast.Ident || assign_stmt.left[0] is ast.SelectorExpr) + // assign_stmt.left_types[0] in [ast.string_type, ast.array_type] && + mut sref_name := '' + mut type_to_free := '' + if af { + first_left_type := assign_stmt.left_types[0] + first_left_sym := g.table.get_type_symbol(assign_stmt.left_types[0]) + if first_left_type == ast.string_type || first_left_sym.kind == .array { + type_to_free = if first_left_type == ast.string_type { 'string' } else { 'array' } + mut ok := true + left0 := assign_stmt.left[0] + if left0 is ast.Ident { + if left0.name == '_' { + ok = false + } + } + if ok { + sref_name = '_sref$assign_stmt.pos.pos' + g.write('$type_to_free $sref_name = (') // TODO we are copying the entire string here, optimize + // we can't just do `.str` since we need the extra data from the string struct + // doing `&string` is also not an option since the stack memory with the data will be overwritten + g.expr(left0) // assign_stmt.left[0]) + g.writeln('); // free $type_to_free on re-assignment2') + defer { + if af { + g.writeln('${type_to_free}_free(&$sref_name);') + } + } + } else { + af = false + } + } else { + af = false + } + } + // Autofree tmp arg vars + // first_right := assign_stmt.right[0] + // af := g.autofree && first_right is ast.CallExpr && !g.is_builtin_mod + // if af { + // g.autofree_call_pregen(first_right as ast.CallExpr) + // } + // + // + // Handle optionals. We need to declare a temp variable for them, that's why they are handled + // here, not in call_expr(). + // `pos := s.index('x') or { return }` + // ==========> + // Option_int _t190 = string_index(s, _STR("x")); // _STR() no more used!! + // if (_t190.state != 2) { + // Error err = _t190.err; + // return; + // } + // int pos = *(int*)_t190.data; + // mut tmp_opt := '' + /* + is_optional := false && g.is_autofree && (assign_stmt.op in [.decl_assign, .assign]) + && assign_stmt.left_types.len == 1 && assign_stmt.right[0] is ast.CallExpr + if is_optional { + // g.write('/* optional assignment */') + call_expr := assign_stmt.right[0] as ast.CallExpr + if call_expr.or_block.kind != .absent { + styp := g.typ(call_expr.return_type.set_flag(.optional)) + tmp_opt = g.new_tmp_var() + g.write('/*AF opt*/$styp $tmp_opt = ') + g.expr(assign_stmt.right[0]) + g.or_block(tmp_opt, call_expr.or_block, call_expr.return_type) + g.writeln('/*=============ret*/') + // if af && is_optional { + // g.autofree_call_postgen() + // } + // return + } + } + */ + // json_test failed w/o this check + if return_type != ast.void_type && return_type != 0 { + sym := g.table.get_type_symbol(return_type) + if sym.kind == .multi_return { + // multi return + // TODO Handle in if_expr + is_opt := return_type.has_flag(.optional) + mr_var_name := 'mr_$assign_stmt.pos.pos' + mr_styp := g.typ(return_type) + g.write('$mr_styp $mr_var_name = ') + g.expr(assign_stmt.right[0]) + g.writeln(';') + for i, lx in assign_stmt.left { + mut is_auto_heap := false + mut ident := ast.Ident{ + scope: 0 + } + if lx is ast.Ident { + ident = lx + if lx.kind == .blank_ident { + continue + } + if lx.obj is ast.Var { + is_auto_heap = lx.obj.is_auto_heap + } + } + styp := if ident.name in g.defer_vars { + '' + } else { + g.typ(assign_stmt.left_types[i]) + } + if assign_stmt.op == .decl_assign { + g.write('$styp ') + if is_auto_heap { + g.write('*') + } + } + if lx.is_auto_deref_var() { + g.write('*') + } + g.expr(lx) + noscan := if is_auto_heap { g.check_noscan(return_type) } else { '' } + if g.is_arraymap_set { + if is_opt { + mr_base_styp := g.base_type(return_type) + if is_auto_heap { + g.writeln('HEAP${noscan}($mr_base_styp, *($mr_base_styp*)${mr_var_name}.data).arg$i) });') + } else { + g.writeln('(*($mr_base_styp*)${mr_var_name}.data).arg$i });') + } + } else { + if is_auto_heap { + g.writeln('HEAP${noscan}($styp, ${mr_var_name}.arg$i) });') + } else { + g.writeln('${mr_var_name}.arg$i });') + } + } + } else { + if is_opt { + mr_base_styp := g.base_type(return_type) + if is_auto_heap { + g.writeln(' = HEAP${noscan}($mr_base_styp, *($mr_base_styp*)${mr_var_name}.data).arg$i);') + } else { + g.writeln(' = (*($mr_base_styp*)${mr_var_name}.data).arg$i;') + } + } else { + if is_auto_heap { + g.writeln(' = HEAP${noscan}($styp, ${mr_var_name}.arg$i);') + } else { + g.writeln(' = ${mr_var_name}.arg$i;') + } + } + } + } + if g.is_arraymap_set { + g.is_arraymap_set = false + } + return + } + } + // TODO: non idents on left (exprs) + if assign_stmt.has_cross_var { + for i, left in assign_stmt.left { + match left { + ast.Ident { + left_typ := assign_stmt.left_types[i] + left_sym := g.table.get_type_symbol(left_typ) + if left_sym.kind == .function { + g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_$left.pos.pos') + g.writeln(' = $left.name;') + } else { + styp := g.typ(left_typ) + g.writeln('$styp _var_$left.pos.pos = $left.name;') + } + } + ast.IndexExpr { + sym := g.table.get_type_symbol(left.left_type) + if sym.kind == .array { + info := sym.info as ast.Array + elem_typ := g.table.get_type_symbol(info.elem_type) + if elem_typ.kind == .function { + left_typ := assign_stmt.left_types[i] + left_sym := g.table.get_type_symbol(left_typ) + g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_$left.pos.pos') + g.write(' = *(voidptr*)array_get(') + } else { + styp := g.typ(info.elem_type) + g.write('$styp _var_$left.pos.pos = *($styp*)array_get(') + } + if left.left_type.is_ptr() { + g.write('*') + } + needs_clone := info.elem_type == ast.string_type && g.is_autofree + if needs_clone { + g.write('/*1*/string_clone(') + } + g.expr(left.left) + if needs_clone { + g.write(')') + } + g.write(', ') + g.expr(left.index) + g.writeln(');') + } else if sym.kind == .map { + info := sym.info as ast.Map + skeytyp := g.typ(info.key_type) + styp := g.typ(info.value_type) + zero := g.type_default(info.value_type) + val_typ := g.table.get_type_symbol(info.value_type) + if val_typ.kind == .function { + left_type := assign_stmt.left_types[i] + left_sym := g.table.get_type_symbol(left_type) + g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_$left.pos.pos') + g.write(' = *(voidptr*)map_get(') + } else { + g.write('$styp _var_$left.pos.pos = *($styp*)map_get(') + } + if !left.left_type.is_ptr() { + g.write('ADDR(map, ') + g.expr(left.left) + g.write(')') + } else { + g.expr(left.left) + } + g.write(', &($skeytyp[]){') + g.expr(left.index) + g.write('}') + if val_typ.kind == .function { + g.writeln(', &(voidptr[]){ $zero });') + } else { + g.writeln(', &($styp[]){ $zero });') + } + } + } + ast.SelectorExpr { + styp := g.typ(left.typ) + g.write('$styp _var_$left.pos.pos = ') + g.expr(left.expr) + mut sel := '.' + if left.expr_type.is_ptr() { + if left.expr_type.has_flag(.shared_f) { + sel = '->val.' + } else { + sel = '->' + } + } + g.writeln('$sel$left.field_name;') + } + else {} + } + } + } + // `a := 1` | `a,b := 1,2` + for i, left in assign_stmt.left { + mut is_auto_heap := false + mut var_type := assign_stmt.left_types[i] + mut val_type := assign_stmt.right_types[i] + val := assign_stmt.right[i] + mut is_call := false + mut blank_assign := false + mut ident := ast.Ident{ + scope: 0 + } + left_sym := g.table.get_type_symbol(var_type) + if left is ast.Ident { + ident = left + // id_info := ident.var_info() + // var_type = id_info.typ + blank_assign = left.kind == .blank_ident + // TODO: temporary, remove this + left_info := left.info + if left_info is ast.IdentVar { + share := left_info.share + if share == .shared_t { + var_type = var_type.set_flag(.shared_f) + } + if share == .atomic_t { + var_type = var_type.set_flag(.atomic_f) + } + } + if left.obj is ast.Var { + is_auto_heap = left.obj.is_auto_heap + } + } + styp := g.typ(var_type) + mut is_fixed_array_init := false + mut has_val := false + match val { + ast.ArrayInit { + is_fixed_array_init = val.is_fixed + has_val = val.has_val + } + ast.CallExpr { + is_call = true + return_type = val.return_type + } + // TODO: no buffer fiddling + ast.AnonFn { + if blank_assign { + g.write('{') + } + // if it's a decl assign (`:=`) or a blank assignment `_ =`/`_ :=` then generate `void (*ident) (args) =` + if (is_decl || blank_assign) && left is ast.Ident { + ret_styp := g.typ(val.decl.return_type) + g.write('$ret_styp (*$ident.name) (') + def_pos := g.definitions.len + g.fn_args(val.decl.params, voidptr(0)) + g.definitions.go_back(g.definitions.len - def_pos) + g.write(') = ') + } else { + g.is_assign_lhs = true + g.assign_op = assign_stmt.op + g.expr(left) + g.is_assign_lhs = false + g.is_arraymap_set = false + if left is ast.IndexExpr { + sym := g.table.get_type_symbol(left.left_type) + if sym.kind in [.map, .array] { + g.expr(val) + g.writeln('});') + continue + } + } + g.write(' = ') + } + g.expr(val) + g.writeln(';') + if blank_assign { + g.write('}') + } + continue + } + else {} + } + unwrapped_val_type := g.unwrap_generic(val_type) + right_sym := g.table.get_type_symbol(unwrapped_val_type) + unaliased_right_sym := g.table.get_final_type_symbol(unwrapped_val_type) + is_fixed_array_var := unaliased_right_sym.kind == .array_fixed && val !is ast.ArrayInit + && (val is ast.Ident || val is ast.IndexExpr || val is ast.CallExpr + || (val is ast.CastExpr && (val as ast.CastExpr).expr !is ast.ArrayInit) + || val is ast.SelectorExpr) + g.is_assign_lhs = true + g.assign_op = assign_stmt.op + if val_type.has_flag(.optional) { + g.right_is_opt = true + } + if blank_assign { + if is_call { + old_is_void_expr_stmt := g.is_void_expr_stmt + g.is_void_expr_stmt = true + g.expr(val) + g.is_void_expr_stmt = old_is_void_expr_stmt + } else { + g.write('{$styp _ = ') + g.expr(val) + g.writeln(';}') + } + } else if assign_stmt.op == .assign + && (is_fixed_array_init || (right_sym.kind == .array_fixed && val is ast.Ident)) { + mut v_var := '' + arr_typ := styp.trim('*') + if is_fixed_array_init { + right := val as ast.ArrayInit + v_var = g.new_tmp_var() + g.write('$arr_typ $v_var = ') + g.expr(right) + g.writeln(';') + } else { + right := val as ast.Ident + v_var = right.name + } + pos := g.out.len + g.expr(left) + + if g.is_arraymap_set && g.arraymap_set_pos > 0 { + g.out.go_back_to(g.arraymap_set_pos) + g.write(', &$v_var)') + g.is_arraymap_set = false + g.arraymap_set_pos = 0 + } else { + g.out.go_back_to(pos) + is_var_mut := !is_decl && left.is_auto_deref_var() + addr := if is_var_mut { '' } else { '&' } + g.writeln('') + g.write('memcpy($addr') + g.expr(left) + g.writeln(', &$v_var, sizeof($arr_typ));') + } + g.is_assign_lhs = false + } else { + is_inside_ternary := g.inside_ternary != 0 + cur_line := if is_inside_ternary && is_decl { + g.register_ternary_name(ident.name) + g.empty_line = false + g.go_before_ternary() + } else { + '' + } + mut str_add := false + mut op_overloaded := false + mut op_expected_left := ast.Type(0) + mut op_expected_right := ast.Type(0) + if var_type == ast.string_type_idx && assign_stmt.op == .plus_assign { + if left is ast.IndexExpr { + // a[0] += str => `array_set(&a, 0, &(string[]) {string__plus(...))})` + g.expr(left) + g.write('string__plus(') + } else { + // str += str2 => `str = string__plus(str, str2)` + g.expr(left) + g.write(' = /*f*/string__plus(') + } + g.is_assign_lhs = false + str_add = true + } + // Assignment Operator Overloading + if ((left_sym.kind == .struct_ && right_sym.kind == .struct_) + || (left_sym.kind == .alias && right_sym.kind == .alias)) + && assign_stmt.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] { + extracted_op := match assign_stmt.op { + .plus_assign { '+' } + .minus_assign { '-' } + .div_assign { '/' } + .mod_assign { '%' } + .mult_assign { '*' } + else { 'unknown op' } + } + g.expr(left) + g.write(' = ${styp}_${util.replace_op(extracted_op)}(') + method := g.table.type_find_method(left_sym, extracted_op) or { + // the checker will most likely have found this, already... + g.error('assignemnt operator `$extracted_op=` used but no `$extracted_op` method defined', + assign_stmt.pos) + ast.Fn{} + } + op_expected_left = method.params[0].typ + op_expected_right = method.params[1].typ + op_overloaded = true + } + if right_sym.kind == .function && is_decl { + if is_inside_ternary && is_decl { + g.out.write_string(util.tabs(g.indent - g.inside_ternary)) + } + func := right_sym.info as ast.FnType + ret_styp := g.typ(func.func.return_type) + g.write('$ret_styp (*${g.get_ternary_name(ident.name)}) (') + def_pos := g.definitions.len + g.fn_args(func.func.params, voidptr(0)) + g.definitions.go_back(g.definitions.len - def_pos) + g.write(')') + } else { + if is_decl { + if is_inside_ternary { + g.out.write_string(util.tabs(g.indent - g.inside_ternary)) + } + mut is_used_var_styp := false + if ident.name !in g.defer_vars { + val_sym := g.table.get_type_symbol(val_type) + if val_sym.info is ast.Struct { + if val_sym.info.generic_types.len > 0 { + if val is ast.StructInit { + var_styp := g.typ(val.typ) + g.write('$var_styp ') + is_used_var_styp = true + } else if val is ast.PrefixExpr { + if val.op == .amp && val.right is ast.StructInit { + var_styp := g.typ(val.right.typ.to_ptr()) + g.write('$var_styp ') + is_used_var_styp = true + } + } + } + } + if !is_used_var_styp { + g.write('$styp ') + } + if is_auto_heap { + g.write('*') + } + } + } + if left is ast.Ident || left is ast.SelectorExpr { + g.prevent_sum_type_unwrapping_once = true + } + if !is_fixed_array_var || is_decl { + if op_overloaded { + g.op_arg(left, op_expected_left, var_type) + } else { + if !is_decl && left.is_auto_deref_var() { + g.write('*') + } + g.expr(left) + } + } + } + if is_inside_ternary && is_decl { + g.write(';\n$cur_line') + g.out.write_string(util.tabs(g.indent)) + g.expr(left) + } + g.is_assign_lhs = false + if is_fixed_array_var { + if is_decl { + g.writeln(';') + } + } else if !g.is_arraymap_set && !str_add && !op_overloaded { + g.write(' $op ') + } else if str_add || op_overloaded { + g.write(', ') + } + mut cloned := false + if g.is_autofree && right_sym.kind in [.array, .string] { + if g.gen_clone_assignment(val, right_sym, false) { + cloned = true + } + } + unwrap_optional := !var_type.has_flag(.optional) && val_type.has_flag(.optional) + if unwrap_optional { + // Unwrap the optional now that the testing code has been prepended. + // `pos := s.index(... + // `int pos = *(int)_t10.data;` + // if g.is_autofree { + /* + if is_optional { + g.write('*($styp*)') + g.write(tmp_opt + '.data/*FFz*/') + g.right_is_opt = false + if g.inside_ternary == 0 && !assign_stmt.is_simple { + g.writeln(';') + } + return + } + */ + } + g.is_shared = var_type.has_flag(.shared_f) + if !cloned { + if is_fixed_array_var { + typ_str := g.typ(val_type).trim('*') + ref_str := if val_type.is_ptr() { '' } else { '&' } + g.write('memcpy(($typ_str*)') + g.expr(left) + g.write(', (byte*)$ref_str') + g.expr(val) + g.write(', sizeof($typ_str))') + } else if is_decl { + if is_fixed_array_init && !has_val { + if val is ast.ArrayInit { + if val.has_default { + g.write('{') + g.expr(val.default_expr) + info := right_sym.info as ast.ArrayFixed + for _ in 1 .. info.size { + g.write(', ') + g.expr(val.default_expr) + } + g.write('}') + } else { + g.write('{0}') + } + } else { + g.write('{0}') + } + } else { + if is_auto_heap { + g.write('HEAP($styp, (') + } + if val.is_auto_deref_var() { + g.write('*') + } + g.expr(val) + if is_auto_heap { + g.write('))') + } + } + } else { + if assign_stmt.has_cross_var { + g.gen_cross_tmp_variable(assign_stmt.left, val) + } else { + if op_overloaded { + g.op_arg(val, op_expected_right, val_type) + } else { + g.expr_with_cast(val, val_type, var_type) + } + } + } + } + if str_add || op_overloaded { + g.write(')') + } + if g.is_arraymap_set { + g.write(' })') + g.is_arraymap_set = false + } + g.is_shared = false + } + g.right_is_opt = false + if g.inside_ternary == 0 && (assign_stmt.left.len > 1 || !assign_stmt.is_simple) { + g.writeln(';') + } + } +} + +fn (mut g Gen) gen_cross_tmp_variable(left []ast.Expr, val ast.Expr) { + val_ := val + match val { + ast.Ident { + mut has_var := false + for lx in left { + if lx is ast.Ident { + if val.name == lx.name { + g.write('_var_') + g.write(lx.pos.pos.str()) + has_var = true + break + } + } + } + if !has_var { + g.expr(val_) + } + } + ast.IndexExpr { + mut has_var := false + for lx in left { + if val_.str() == lx.str() { + g.write('_var_') + g.write(lx.position().pos.str()) + has_var = true + break + } + } + if !has_var { + g.expr(val_) + } + } + ast.InfixExpr { + g.gen_cross_tmp_variable(left, val.left) + g.write(val.op.str()) + g.gen_cross_tmp_variable(left, val.right) + } + ast.PrefixExpr { + g.write(val.op.str()) + g.gen_cross_tmp_variable(left, val.right) + } + ast.PostfixExpr { + g.gen_cross_tmp_variable(left, val.expr) + g.write(val.op.str()) + } + ast.SelectorExpr { + mut has_var := false + for lx in left { + if val_.str() == lx.str() { + g.write('_var_') + g.write(lx.position().pos.str()) + has_var = true + break + } + } + if !has_var { + g.expr(val_) + } + } + else { + g.expr(val_) + } + } +} + +fn (mut g Gen) register_ternary_name(name string) { + level_key := g.inside_ternary.str() + if level_key !in g.ternary_level_names { + g.ternary_level_names[level_key] = []string{} + } + new_name := g.new_tmp_var() + g.ternary_names[name] = new_name + g.ternary_level_names[level_key] << name +} + +fn (mut g Gen) get_ternary_name(name string) string { + if g.inside_ternary == 0 { + return name + } + if name !in g.ternary_names { + return name + } + return g.ternary_names[name] +} + +fn (mut g Gen) gen_clone_assignment(val ast.Expr, right_sym ast.TypeSymbol, add_eq bool) bool { + if val !is ast.Ident && val !is ast.SelectorExpr { + return false + } + if g.is_autofree && right_sym.kind == .array { + // `arr1 = arr2` => `arr1 = arr2.clone()` + if add_eq { + g.write('=') + } + g.write(' array_clone_static_to_depth(') + g.expr(val) + elem_type := (right_sym.info as ast.Array).elem_type + array_depth := g.get_array_depth(elem_type) + g.write(', $array_depth)') + } else if g.is_autofree && right_sym.kind == .string { + if add_eq { + g.write('=') + } + // `str1 = str2` => `str1 = str2.clone()` + g.write(' string_clone_static(') + g.expr(val) + g.write(')') + } + return true +} + +fn (mut g Gen) autofree_scope_vars(pos int, line_nr int, free_parent_scopes bool) { + g.autofree_scope_vars_stop(pos, line_nr, free_parent_scopes, -1) +} + +fn (mut g Gen) autofree_scope_vars_stop(pos int, line_nr int, free_parent_scopes bool, stop_pos int) { + if g.is_builtin_mod { + // In `builtin` everything is freed manually. + return + } + if pos == -1 { + // TODO why can pos be -1? + return + } + // eprintln('> free_scope_vars($pos)') + scope := g.file.scope.innermost(pos) + if scope.start_pos == 0 { + // TODO why can scope.pos be 0? (only outside fns?) + return + } + g.trace_autofree('// autofree_scope_vars(pos=$pos line_nr=$line_nr scope.pos=$scope.start_pos scope.end_pos=$scope.end_pos)') + g.autofree_scope_vars2(scope, scope.start_pos, scope.end_pos, line_nr, free_parent_scopes, + stop_pos) +} + +[if trace_autofree ?] +fn (mut g Gen) trace_autofree(line string) { + g.writeln(line) +} + +// fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, end_pos int) { +fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int, line_nr int, free_parent_scopes bool, stop_pos int) { + if isnil(scope) { + return + } + for _, obj in scope.objects { + match obj { + ast.Var { + g.trace_autofree('// var "$obj.name" var.pos=$obj.pos.pos var.line_nr=$obj.pos.line_nr') + if obj.name == g.returned_var_name { + g.trace_autofree('// skipping returned var') + continue + } + if obj.is_or { + // Skip vars inited with the `or {}`, since they are generated + // after the or block in C. + g.trace_autofree('// skipping `or{}` var "$obj.name"') + continue + } + if obj.is_tmp { + // Skip for loop vars + g.trace_autofree('// skipping tmp var "$obj.name"') + continue + } + if obj.is_inherited { + g.trace_autofree('// skipping inherited var "$obj.name"') + continue + } + // if var.typ == 0 { + // // TODO why 0? + // continue + // } + // if v.pos.pos > end_pos { + if obj.pos.pos > end_pos || (obj.pos.pos < start_pos && obj.pos.line_nr == line_nr) { + // Do not free vars that were declared after this scope + continue + } + is_optional := obj.typ.has_flag(.optional) + if is_optional { + // TODO: free optionals + continue + } + g.autofree_variable(obj) + } + else {} + } + } + // Free all vars in parent scopes as well: + // ``` + // s := ... + // if ... { + // s.free() + // return + // } + // ``` + // if !isnil(scope.parent) && line_nr > 0 { + if free_parent_scopes && !isnil(scope.parent) + && (stop_pos == -1 || scope.parent.start_pos >= stop_pos) { + g.trace_autofree('// af parent scope:') + g.autofree_scope_vars2(scope.parent, start_pos, end_pos, line_nr, true, stop_pos) + } +} + +fn (mut g Gen) autofree_variable(v ast.Var) { + sym := g.table.get_type_symbol(v.typ) + // if v.name.contains('output2') { + // eprintln(' > var name: ${v.name:-20s} | is_arg: ${v.is_arg.str():6} | var type: ${int(v.typ):8} | type_name: ${sym.name:-33s}') + // } + if sym.kind == .array { + if sym.has_method('free') { + free_method_name := g.typ(v.typ) + '_free' + g.autofree_var_call(free_method_name, v) + return + } + g.autofree_var_call('array_free', v) + return + } + if sym.kind == .string { + // Don't free simple string literals. + match v.expr { + ast.StringLiteral { + g.trace_autofree('// str literal') + } + else { + // NOTE/TODO: assign_stmt multi returns variables have no expr + // since the type comes from the called fns return type + /* + f := v.name[0] + if + //!(f >= `a` && f <= `d`) { + //f != `c` { + v.name!='cvar_name' { + t := typeof(v.expr) + return '// other ' + t + '\n' + } + */ + } + } + g.autofree_var_call('string_free', v) + return + } + if sym.has_method('free') { + g.autofree_var_call(c_name(sym.name) + '_free', v) + } +} + +fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { + if v.is_arg { + // fn args should not be autofreed + return + } + if v.is_used && v.is_autofree_tmp { + // tmp expr vars do not need to be freed again here + return + } + if g.is_builtin_mod { + return + } + if !g.is_autofree { + return + } + // if v.is_autofree_tmp && !g.doing_autofree_tmp { + // return + // } + if v.name.contains('expr_write_string_1_') { + // TODO remove this temporary hack + return + } + if v.typ.is_ptr() { + g.writeln('\t${free_fn_name}(${c_name(v.name)}); // autofreed ptr var') + } else { + if v.typ == ast.error_type && !v.is_autofree_tmp { + return + } + g.writeln('\t${free_fn_name}(&${c_name(v.name)}); // autofreed var $g.cur_mod.name $g.is_builtin_mod') + } +} + +fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) { + mut hash_fn := '' + mut key_eq_fn := '' + mut clone_fn := '' + mut free_fn := '&map_free_nop' + match key_typ.kind { + .byte, .i8, .char { + hash_fn = '&map_hash_int_1' + key_eq_fn = '&map_eq_int_1' + clone_fn = '&map_clone_int_1' + } + .i16, .u16 { + hash_fn = '&map_hash_int_2' + key_eq_fn = '&map_eq_int_2' + clone_fn = '&map_clone_int_2' + } + .int, .u32, .rune, .f32, .enum_ { + hash_fn = '&map_hash_int_4' + key_eq_fn = '&map_eq_int_4' + clone_fn = '&map_clone_int_4' + } + .voidptr { + ts := if g.pref.m64 { + unsafe { &g.table.type_symbols[ast.u64_type_idx] } + } else { + unsafe { &g.table.type_symbols[ast.u32_type_idx] } + } + return g.map_fn_ptrs(ts) + } + .u64, .i64, .f64 { + hash_fn = '&map_hash_int_8' + key_eq_fn = '&map_eq_int_8' + clone_fn = '&map_clone_int_8' + } + .string { + hash_fn = '&map_hash_string' + key_eq_fn = '&map_eq_string' + clone_fn = '&map_clone_string' + free_fn = '&map_free_string' + } + else { + verror('map key type not supported') + } + } + return hash_fn, key_eq_fn, clone_fn, free_fn +} + +fn (mut g Gen) expr(node ast.Expr) { + // println('cgen expr() line_nr=$node.pos.line_nr') + old_discard_or_result := g.discard_or_result + old_is_void_expr_stmt := g.is_void_expr_stmt + if g.is_void_expr_stmt { + g.discard_or_result = true + g.is_void_expr_stmt = false + } else { + g.discard_or_result = false + } + // NB: please keep the type names in the match here in alphabetical order: + match mut node { + ast.EmptyExpr { + g.error('g.expr(): unhandled EmptyExpr', token.Position{}) + } + ast.AnonFn { + g.gen_anon_fn(mut node) + } + ast.ArrayDecompose { + g.expr(node.expr) + } + ast.ArrayInit { + g.array_init(node) + } + ast.AsCast { + g.as_cast(node) + } + ast.Assoc { + g.assoc(node) + } + ast.BoolLiteral { + g.write(node.val.str()) + } + ast.CallExpr { + // if g.fileis('1.strings') { + // println('\ncall_expr()()') + // } + ret_type := if node.or_block.kind == .absent { + node.return_type + } else { + node.return_type.clear_flag(.optional) + } + mut shared_styp := '' + if g.is_shared && !ret_type.has_flag(.shared_f) { + ret_sym := g.table.get_type_symbol(ret_type) + shared_typ := ret_type.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + if ret_sym.kind == .array { + g.writeln('($shared_styp*)__dup_shared_array(&($shared_styp){.mtx = {0}, .val =') + } else if ret_sym.kind == .map { + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.mtx = {0}, .val =') + } else { + g.writeln('($shared_styp*)__dup${shared_styp}(&($shared_styp){.mtx = {0}, .val =') + } + } + last_stmt_pos := g.stmt_path_pos.last() + g.call_expr(node) + // if g.fileis('1.strings') { + // println('before:' + node.autofree_pregen) + // } + if g.is_autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len == 0 + && !g.inside_lambda { // && g.inside_ternary == + // if len != 0, that means we are handling call expr inside call expr (arg) + // and it'll get messed up here, since it's handled recursively in autofree_call_pregen() + // so just skip it + g.autofree_call_pregen(node) + if g.strs_to_free0.len > 0 { + g.insert_at(last_stmt_pos, g.strs_to_free0.join('\n') + '/* inserted before */') + } + g.strs_to_free0 = [] + // println('pos=$node.pos.pos') + } + if g.is_shared && !ret_type.has_flag(.shared_f) { + g.writeln('}, sizeof($shared_styp))') + } + // if g.autofree && node.autofree_pregen != '' { // g.strs_to_free0.len != 0 { + /* + if g.autofree { + s := g.autofree_pregen[node.pos.pos.str()] + if s != '' { + // g.insert_before_stmt('/*START2*/' + g.strs_to_free0.join('\n') + '/*END*/') + // g.insert_before_stmt('/*START3*/' + node.autofree_pregen + '/*END*/') + g.insert_before_stmt('/*START3*/' + s + '/*END*/') + // for s in g.strs_to_free0 { + } + // //g.writeln(s) + // } + g.strs_to_free0 = [] + } + */ + } + ast.CastExpr { + g.cast_expr(node) + } + ast.ChanInit { + elem_typ_str := g.typ(node.elem_type) + noscan := g.check_noscan(node.elem_type) + g.write('sync__new_channel_st${noscan}(') + if node.has_cap { + g.expr(node.cap_expr) + } else { + g.write('0') + } + g.write(', sizeof(') + g.write(elem_typ_str) + g.write('))') + } + ast.CharLiteral { + if node.val == r'\`' { + g.write("'`'") + } else { + // TODO: optimize use L-char instead of u32 when possible + if utf8_str_len(node.val) < node.val.len { + g.write('((rune)0x$node.val.utf32_code().hex() /* `$node.val` */)') + } else { + g.write("'$node.val'") + } + } + } + ast.DumpExpr { + g.dump_expr(node) + } + ast.AtExpr { + g.comp_at(node) + } + ast.ComptimeCall { + g.comptime_call(node) + } + ast.ComptimeSelector { + g.comptime_selector(node) + } + ast.Comment {} + ast.ConcatExpr { + g.concat_expr(node) + } + ast.CTempVar { + // g.write('/*ctmp .orig: $node.orig.str() , ._typ: $node.typ, .is_ptr: $node.is_ptr */ ') + g.write(node.name) + } + ast.EnumVal { + // g.write('${it.mod}${it.enum_name}_$it.val') + // g.enum_expr(node) + styp := g.typ(node.typ) + g.write('${styp}__$node.val') + } + ast.FloatLiteral { + g.write(node.val) + } + ast.GoExpr { + g.go_expr(node) + } + ast.Ident { + g.ident(node) + } + ast.IfExpr { + g.if_expr(node) + } + ast.IfGuardExpr { + g.write('/* guard */') + } + ast.IndexExpr { + g.index_expr(node) + } + ast.InfixExpr { + if node.op in [.left_shift, .plus_assign, .minus_assign] { + g.inside_map_infix = true + g.infix_expr(node) + g.inside_map_infix = false + } else { + g.infix_expr(node) + } + } + ast.IntegerLiteral { + if node.val.starts_with('0o') { + g.write('0') + g.write(node.val[2..]) + } else if node.val.starts_with('-0o') { + g.write('-0') + g.write(node.val[3..]) + } else { + g.write(node.val) // .int().str()) + } + } + ast.LockExpr { + g.lock_expr(node) + } + ast.MatchExpr { + g.match_expr(node) + } + ast.MapInit { + g.map_init(node) + } + ast.NodeError {} + ast.None { + g.write('_const_none__') + } + ast.OrExpr { + // this should never appear here + } + ast.ParExpr { + g.write('(') + g.expr(node.expr) + g.write(')') + } + ast.PostfixExpr { + if node.auto_locked != '' { + g.writeln('sync__RwMutex_lock(&$node.auto_locked->mtx);') + } + g.inside_map_postfix = true + if node.expr.is_auto_deref_var() { + g.write('(*') + g.expr(node.expr) + g.write(')') + } else { + g.expr(node.expr) + } + g.inside_map_postfix = false + g.write(node.op.str()) + if node.auto_locked != '' { + g.writeln(';') + g.write('sync__RwMutex_unlock(&$node.auto_locked->mtx)') + } + } + ast.PrefixExpr { + gen_or := node.op == .arrow && (node.or_block.kind != .absent || node.is_option) + if node.op == .amp { + g.is_amp = true + } + if node.op == .arrow { + styp := g.typ(node.right_type) + right_sym := g.table.get_type_symbol(node.right_type) + mut right_inf := right_sym.info as ast.Chan + elem_type := right_inf.elem_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 { '' } + if gen_or { + opt_elem_type := g.typ(elem_type.set_flag(.optional)) + g.register_chan_pop_optional_call(opt_elem_type, styp) + g.write('$opt_elem_type $tmp_opt = __Option_${styp}_popval(') + } else { + g.write('__${styp}_popval(') + } + g.expr(node.right) + g.write(')') + if gen_or { + if !node.is_option { + g.or_block(tmp_opt, node.or_block, elem_type) + } + if is_gen_or_and_assign_rhs { + elem_styp := g.typ(elem_type) + g.write(';\n$cur_line*($elem_styp*)${tmp_opt}.data') + } + } + } else { + // g.write('/*pref*/') + if !(g.is_amp && node.right.is_auto_deref_var()) { + g.write(node.op.str()) + } + // g.write('(') + g.expr(node.right) + } + g.is_amp = false + } + ast.RangeExpr { + // Only used in IndexExpr + } + ast.SelectExpr { + g.select_expr(node) + } + ast.SizeOf { + typ := if node.typ == g.field_data_type { g.comp_for_field_value.typ } else { node.typ } + node_typ := g.unwrap_generic(typ) + sym := g.table.get_type_symbol(node_typ) + if sym.language == .v && sym.kind in [.placeholder, .any] { + g.error('unknown type `$sym.name`', node.pos) + } + styp := g.typ(node_typ) + g.write('/*SizeOf*/ sizeof(${util.no_dots(styp)})') + } + ast.IsRefType { + typ := if node.typ == g.field_data_type { g.comp_for_field_value.typ } else { node.typ } + node_typ := g.unwrap_generic(typ) + sym := g.table.get_type_symbol(node_typ) + if sym.language == .v && sym.kind in [.placeholder, .any] { + g.error('unknown type `$sym.name`', node.pos) + } + is_ref_type := g.contains_ptr(node_typ) + g.write('/*IsRefType*/ $is_ref_type') + } + ast.OffsetOf { + styp := g.typ(node.struct_type) + g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))') + } + ast.SqlExpr { + g.sql_select_expr(node) + } + ast.StringLiteral { + g.string_literal(node) + } + ast.StringInterLiteral { + g.string_inter_literal(node) + } + ast.StructInit { + if node.unresolved { + g.expr(ast.resolve_init(node, g.unwrap_generic(node.typ), g.table)) + } else { + // `user := User{name: 'Bob'}` + g.struct_init(node) + } + } + ast.SelectorExpr { + g.selector_expr(node) + } + ast.TypeNode { + // match sum Type + // g.write('/* Type */') + // type_idx := node.typ.idx() + typ := g.unwrap_generic(node.typ) + sym := g.table.get_type_symbol(typ) + sidx := g.type_sidx(typ) + // g.write('$type_idx /* $sym.name */') + g.write('$sidx /* $sym.name */') + } + ast.TypeOf { + g.typeof_expr(node) + } + ast.Likely { + if node.is_likely { + g.write('_likely_') + } else { + g.write('_unlikely_') + } + g.write('(') + g.expr(node.expr) + g.write(')') + } + ast.UnsafeExpr { + g.expr(node.expr) + } + } + g.discard_or_result = old_discard_or_result + g.is_void_expr_stmt = old_is_void_expr_stmt +} + +// T.name, typeof(expr).name +fn (mut g Gen) type_name(raw_type ast.Type) { + typ := if raw_type == g.field_data_type { g.comp_for_field_value.typ } else { raw_type } + sym := g.table.get_type_symbol(typ) + mut s := '' + if sym.kind == .function { + if typ.is_ptr() { + s = '&' + g.fn_decl_str(sym.info as ast.FnType) + } else { + s = g.fn_decl_str(sym.info as ast.FnType) + } + } else { + s = g.table.type_to_str(g.unwrap_generic(typ)) + } + g.write('_SLIT("${util.strip_main_name(s)}")') +} + +fn (mut g Gen) typeof_expr(node ast.TypeOf) { + typ := if node.expr_type == g.field_data_type { + g.comp_for_field_value.typ + } else { + node.expr_type + } + sym := g.table.get_type_symbol(typ) + if sym.kind == .sum_type { + // When encountering a .sum_type, typeof() should be done at runtime, + // because the subtype of the expression may change: + g.write('charptr_vstring_literal( /* $sym.name */ v_typeof_sumtype_${sym.cname}( (') + g.expr(node.expr) + g.write(')._typ ))') + } else if sym.kind == .array_fixed { + fixed_info := sym.info as ast.ArrayFixed + typ_name := g.table.get_type_name(fixed_info.elem_type) + g.write('_SLIT("[$fixed_info.size]${util.strip_main_name(typ_name)}")') + } else if sym.kind == .function { + info := sym.info as ast.FnType + g.write('_SLIT("${g.fn_decl_str(info)}")') + } else if typ.has_flag(.variadic) { + varg_elem_type_sym := g.table.get_type_symbol(g.table.value_type(typ)) + g.write('_SLIT("...${util.strip_main_name(varg_elem_type_sym.name)}")') + } else { + x := g.table.type_to_str(typ) + y := util.strip_main_name(x) + g.write('_SLIT("$y")') + } +} + +fn (mut g Gen) selector_expr(node ast.SelectorExpr) { + prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once + g.prevent_sum_type_unwrapping_once = false + if node.name_type > 0 { + match node.gkind_field { + .name { + g.type_name(node.name_type) + return + } + .typ { + g.write(int(g.unwrap_generic(node.name_type)).str()) + return + } + .unknown { + if node.field_name == 'name' { + // typeof(expr).name + g.type_name(node.name_type) + return + } else if node.field_name == 'idx' { + // typeof(expr).idx + g.write(int(g.unwrap_generic(node.name_type)).str()) + return + } + g.error('unknown generic field', node.pos) + } + } + } + if node.expr_type == 0 { + g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos) + } + sym := g.table.get_type_symbol(g.unwrap_generic(node.expr_type)) + // if node expr is a root ident and an optional + mut is_optional := node.expr is ast.Ident && node.expr_type.has_flag(.optional) + if is_optional { + opt_base_typ := g.base_type(node.expr_type) + g.writeln('(*($opt_base_typ*)') + } + if sym.kind in [.interface_, .sum_type] { + g.write('(*(') + } + if sym.kind == .array_fixed { + if node.field_name != 'len' { + g.error('field_name should be `len`', node.pos) + } + info := sym.info as ast.ArrayFixed + g.write('$info.size') + return + } + if sym.kind == .chan && (node.field_name == 'len' || node.field_name == 'closed') { + g.write('sync__Channel_${node.field_name}(') + g.expr(node.expr) + g.write(')') + return + } + mut sum_type_deref_field := '' + mut sum_type_dot := '.' + if f := g.table.find_field(sym, node.field_name) { + field_sym := g.table.get_type_symbol(f.typ) + if field_sym.kind in [.sum_type, .interface_] { + if !prevent_sum_type_unwrapping_once { + // check first if field is sum type because scope searching is expensive + scope := g.file.scope.innermost(node.pos.pos) + if field := scope.find_struct_field(node.expr.str(), node.expr_type, node.field_name) { + if field.orig_type.is_ptr() { + sum_type_dot = '->' + } + for i, typ in field.smartcasts { + g.write('(') + if field_sym.kind == .sum_type { + g.write('*') + } + cast_sym := g.table.get_type_symbol(typ) + if i != 0 { + dot := if field.typ.is_ptr() { '->' } else { '.' } + sum_type_deref_field += ')$dot' + } + if mut cast_sym.info is ast.Aggregate { + agg_sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx]) + sum_type_deref_field += '_$agg_sym.cname' + } else { + sum_type_deref_field += '_$cast_sym.cname' + } + } + } + } + } + } + n_ptr := node.expr_type.nr_muls() - 1 + if n_ptr > 0 { + g.write('(') + g.write('*'.repeat(n_ptr)) + g.expr(node.expr) + g.write(')') + } else { + g.expr(node.expr) + } + if is_optional { + g.write('.data)') + } + // struct embedding + if sym.info is ast.Struct || sym.info is ast.Aggregate { + if node.from_embed_type != 0 { + embed_sym := g.table.get_type_symbol(node.from_embed_type) + embed_name := embed_sym.embed_name() + if node.expr_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(embed_name) + } + } + if (node.expr_type.is_ptr() || sym.kind == .chan) && node.from_embed_type == 0 { + g.write('->') + } else { + // g.write('. /*typ= $it.expr_type */') // ${g.typ(it.expr_type)} /') + g.write('.') + } + if node.expr_type.has_flag(.shared_f) { + g.write('val.') + } + if node.expr_type == 0 { + verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `$node.expr` | field: `$node.field_name` | file: $g.file.path | line: $node.pos.line_nr') + } + field_name := if sym.language == .v { c_name(node.field_name) } else { node.field_name } + g.write(field_name) + if sum_type_deref_field != '' { + g.write('$sum_type_dot$sum_type_deref_field)') + } + if sym.kind in [.interface_, .sum_type] { + g.write('))') + } +} + +fn (mut g Gen) enum_expr(node ast.Expr) { + match node { + ast.EnumVal { + g.write(node.val) + } + else { + g.expr(node) + } + } +} + +fn (mut g Gen) lock_expr(node ast.LockExpr) { + g.cur_lock = unsafe { node } // is ok because it is discarded at end of fn + defer { + g.cur_lock = ast.LockExpr{ + scope: 0 + } + } + tmp_result := if node.is_expr { g.new_tmp_var() } else { '' } + mut cur_line := '' + if node.is_expr { + styp := g.typ(node.typ) + cur_line = g.go_before_stmt(0) + g.writeln('$styp $tmp_result;') + } + mut mtxs := '' + if node.lockeds.len == 0 { + // this should not happen + } else if node.lockeds.len == 1 { + lock_prefix := if node.is_rlock[0] { 'r' } else { '' } + g.write('sync__RwMutex_${lock_prefix}lock(&') + g.expr(node.lockeds[0]) + g.writeln('->mtx);') + } else { + mtxs = g.new_tmp_var() + g.writeln('uintptr_t _arr_$mtxs[$node.lockeds.len];') + g.writeln('bool _isrlck_$mtxs[$node.lockeds.len];') + mut j := 0 + for i, is_rlock in node.is_rlock { + if !is_rlock { + g.write('_arr_$mtxs[$j] = (uintptr_t)&') + g.expr(node.lockeds[i]) + g.writeln('->mtx;') + g.writeln('_isrlck_$mtxs[$j] = false;') + j++ + } + } + for i, is_rlock in node.is_rlock { + if is_rlock { + g.write('_arr_$mtxs[$j] = (uintptr_t)&') + g.expr(node.lockeds[i]) + g.writeln('->mtx;') + g.writeln('_isrlck_$mtxs[$j] = true;') + j++ + } + } + if node.lockeds.len == 2 { + g.writeln('if (_arr_$mtxs[0] > _arr_$mtxs[1]) {') + g.writeln('\tuintptr_t _ptr_$mtxs = _arr_$mtxs[0];') + g.writeln('\t_arr_$mtxs[0] = _arr_$mtxs[1];') + g.writeln('\t_arr_$mtxs[1] = _ptr_$mtxs;') + g.writeln('\tbool _bool_$mtxs = _isrlck_$mtxs[0];') + g.writeln('\t_isrlck_$mtxs[0] = _isrlck_$mtxs[1];') + g.writeln('\t_isrlck_$mtxs[1] = _bool_$mtxs;') + g.writeln('}') + } else { + g.writeln('__sort_ptr(_arr_$mtxs, _isrlck_$mtxs, $node.lockeds.len);') + } + g.writeln('for (int $mtxs=0; $mtxs<$node.lockeds.len; $mtxs++) {') + g.writeln('\tif ($mtxs && _arr_$mtxs[$mtxs] == _arr_$mtxs[$mtxs-1]) continue;') + g.writeln('\tif (_isrlck_$mtxs[$mtxs])') + g.writeln('\t\tsync__RwMutex_rlock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') + g.writeln('\telse') + g.writeln('\t\tsync__RwMutex_lock((sync__RwMutex*)_arr_$mtxs[$mtxs]);') + g.writeln('}') + } + g.mtxs = mtxs + defer { + g.mtxs = '' + } + g.writeln('/*lock*/ {') + g.stmts_with_tmp_var(node.stmts, tmp_result) + if node.is_expr { + g.writeln(';') + } + g.writeln('}') + g.unlock_locks() + if node.is_expr { + g.writeln('') + g.write(cur_line) + g.write('$tmp_result') + } +} + +fn (mut g Gen) unlock_locks() { + if g.cur_lock.lockeds.len == 0 { + } else if g.cur_lock.lockeds.len == 1 { + lock_prefix := if g.cur_lock.is_rlock[0] { 'r' } else { '' } + g.write('sync__RwMutex_${lock_prefix}unlock(&') + g.expr(g.cur_lock.lockeds[0]) + g.write('->mtx);') + } else { + g.writeln('for (int $g.mtxs=${g.cur_lock.lockeds.len - 1}; $g.mtxs>=0; $g.mtxs--) {') + g.writeln('\tif ($g.mtxs && _arr_$g.mtxs[$g.mtxs] == _arr_$g.mtxs[$g.mtxs-1]) continue;') + g.writeln('\tif (_isrlck_$g.mtxs[$g.mtxs])') + g.writeln('\t\tsync__RwMutex_runlock((sync__RwMutex*)_arr_$g.mtxs[$g.mtxs]);') + g.writeln('\telse') + g.writeln('\t\tsync__RwMutex_unlock((sync__RwMutex*)_arr_$g.mtxs[$g.mtxs]);') + g.write('}') + } +} + +fn (mut g Gen) need_tmp_var_in_match(node ast.MatchExpr) bool { + if node.is_expr && node.return_type != ast.void_type && node.return_type != 0 { + sym := g.table.get_type_symbol(node.return_type) + if sym.kind == .multi_return { + return false + } + for branch in node.branches { + if branch.stmts.len > 1 { + return true + } + if branch.stmts.len == 1 { + if branch.stmts[0] is ast.ExprStmt { + stmt := branch.stmts[0] as ast.ExprStmt + if stmt.expr is ast.CallExpr || stmt.expr is ast.IfExpr + || stmt.expr is ast.MatchExpr || (stmt.expr is ast.IndexExpr + && (stmt.expr as ast.IndexExpr).or_expr.kind != .absent) { + return true + } + } + } + } + } + return false +} + +fn (mut g Gen) match_expr(node ast.MatchExpr) { + // println('match expr typ=$it.expr_type') + // TODO + if node.cond_type == 0 { + g.writeln('// match 0') + return + } + need_tmp_var := g.need_tmp_var_in_match(node) + is_expr := (node.is_expr && node.return_type != ast.void_type) || g.inside_ternary > 0 + mut cond_var := '' + mut tmp_var := '' + mut cur_line := '' + if is_expr && !need_tmp_var { + g.inside_ternary++ + } + if node.cond is ast.Ident || node.cond is ast.SelectorExpr || node.cond is ast.IntegerLiteral + || node.cond is ast.StringLiteral || node.cond is ast.FloatLiteral { + cond_var = g.expr_string(node.cond) + } else { + line := if is_expr { + g.empty_line = true + g.go_before_stmt(0) + } else { + '' + } + cond_var = g.new_tmp_var() + g.write('${g.typ(node.cond_type)} $cond_var = ') + g.expr(node.cond) + g.writeln(';') + g.stmt_path_pos << g.out.len + g.write(line) + } + if need_tmp_var { + g.empty_line = true + cur_line = g.go_before_stmt(0).trim_left(' \t') + tmp_var = g.new_tmp_var() + g.writeln('${g.typ(node.return_type)} $tmp_var = ${g.type_default(node.return_type)};') + } + + if is_expr && !need_tmp_var { + // brackets needed otherwise '?' will apply to everything on the left + g.write('(') + } + if node.is_sum_type { + g.match_expr_sumtype(node, is_expr, cond_var, tmp_var) + } else { + g.match_expr_classic(node, is_expr, cond_var, tmp_var) + } + g.write(cur_line) + if need_tmp_var { + g.write('$tmp_var') + } + if is_expr && !need_tmp_var { + g.write(')') + g.decrement_inside_ternary() + } +} + +fn (mut g Gen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { + for j, branch in node.branches { + mut sumtype_index := 0 + // iterates through all types in sumtype branches + for { + g.aggregate_type_idx = sumtype_index + is_last := j == node.branches.len - 1 + sym := g.table.get_type_symbol(node.cond_type) + if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) { + if is_expr && tmp_var.len == 0 { + // TODO too many branches. maybe separate ?: matches + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.writeln('else {') + } + } else { + if j > 0 || sumtype_index > 0 { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.write_v_source_line_info(branch.pos) + g.write('else ') + } + } + if is_expr && tmp_var.len == 0 { + g.write('(') + } else { + if j == 0 && sumtype_index == 0 { + g.empty_line = true + } + g.write_v_source_line_info(branch.pos) + g.write('if (') + } + g.write(cond_var) + dot_or_ptr := if node.cond_type.is_ptr() { '->' } else { '.' } + if sym.kind == .sum_type { + g.write('${dot_or_ptr}_typ == ') + g.expr(branch.exprs[sumtype_index]) + } else if sym.kind == .interface_ { + if branch.exprs[sumtype_index] is ast.TypeNode { + typ := branch.exprs[sumtype_index] as ast.TypeNode + branch_sym := g.table.get_type_symbol(g.unwrap_generic(typ.typ)) + g.write('${dot_or_ptr}_typ == _${sym.cname}_${branch_sym.cname}_index') + } else if branch.exprs[sumtype_index] is ast.None && sym.name == 'IError' { + g.write('${dot_or_ptr}_typ == _IError_None___index') + } + } + if is_expr && tmp_var.len == 0 { + g.write(') ? ') + } else { + g.writeln(') {') + } + } + if is_expr && tmp_var.len > 0 + && g.table.get_type_symbol(node.return_type).kind == .sum_type { + g.expected_cast_type = node.return_type + } + g.stmts_with_tmp_var(branch.stmts, tmp_var) + g.expected_cast_type = 0 + if g.inside_ternary == 0 { + g.writeln('}') + g.stmt_path_pos << g.out.len + } + sumtype_index++ + if branch.exprs.len == 0 || sumtype_index == branch.exprs.len { + break + } + } + // reset global field for next use + g.aggregate_type_idx = 0 + } +} + +fn (mut g Gen) match_expr_classic(node ast.MatchExpr, is_expr bool, cond_var string, tmp_var string) { + type_sym := g.table.get_type_symbol(node.cond_type) + for j, branch in node.branches { + is_last := j == node.branches.len - 1 + if branch.is_else || (node.is_expr && is_last && tmp_var.len == 0) { + if node.branches.len > 1 { + if is_expr && tmp_var.len == 0 { + // TODO too many branches. maybe separate ?: matches + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.writeln('else {') + } + } + } else { + if j > 0 { + if is_expr && tmp_var.len == 0 { + g.write(' : ') + } else { + g.writeln('') + g.write_v_source_line_info(branch.pos) + g.write('else ') + } + } + if is_expr && tmp_var.len == 0 { + g.write('(') + } else { + if j == 0 { + g.writeln('') + } + g.write_v_source_line_info(branch.pos) + g.write('if (') + } + for i, expr in branch.exprs { + if i > 0 { + g.write(' || ') + } + match type_sym.kind { + .array { + ptr_typ := g.gen_array_equality_fn(node.cond_type) + g.write('${ptr_typ}_arr_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .array_fixed { + ptr_typ := g.gen_fixed_array_equality_fn(node.cond_type) + g.write('${ptr_typ}_arr_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .map { + ptr_typ := g.gen_map_equality_fn(node.cond_type) + g.write('${ptr_typ}_map_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .string { + g.write('string__eq($cond_var, ') + g.expr(expr) + g.write(')') + } + .struct_ { + ptr_typ := g.gen_struct_equality_fn(node.cond_type) + g.write('${ptr_typ}_struct_eq($cond_var, ') + g.expr(expr) + g.write(')') + } + else { + if expr is ast.RangeExpr { + // if type is unsigned and low is 0, check is unneeded + mut skip_low := false + if expr.low is ast.IntegerLiteral { + if node.cond_type in [ast.u16_type, ast.u32_type, ast.u64_type] + && expr.low.val == '0' { + skip_low = true + } + } + g.write('(') + if !skip_low { + g.write('$cond_var >= ') + g.expr(expr.low) + g.write(' && ') + } + g.write('$cond_var <= ') + g.expr(expr.high) + g.write(')') + } else { + g.write('$cond_var == (') + g.expr(expr) + g.write(')') + } + } + } + } + if is_expr && tmp_var.len == 0 { + g.write(') ? ') + } else { + g.writeln(') {') + } + } + g.stmts_with_tmp_var(branch.stmts, tmp_var) + if g.inside_ternary == 0 && node.branches.len >= 1 { + g.write('}') + } + } +} + +fn (mut g Gen) map_init(node ast.MapInit) { + key_typ_str := g.typ(node.key_type) + value_typ_str := g.typ(node.value_type) + value_typ := g.table.get_type_symbol(node.value_type) + key_typ := g.table.get_final_type_symbol(node.key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) + size := node.vals.len + mut shared_styp := '' // only needed for shared &[]{...} + mut styp := '' + is_amp := g.is_amp + g.is_amp = false + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared { + mut shared_typ := node.typ.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + g.writeln('($shared_styp*)__dup_shared_map(&($shared_styp){.mtx = {0}, .val =') + } else if is_amp { + styp = g.typ(node.typ) + g.write('($styp*)memdup(ADDR($styp, ') + } + noscan_key := g.check_noscan(node.key_type) + noscan_value := g.check_noscan(node.value_type) + mut noscan := if noscan_key.len != 0 || noscan_value.len != 0 { '_noscan' } else { '' } + if noscan.len != 0 { + if noscan_key.len != 0 { + noscan += '_key' + } + if noscan_value.len != 0 { + noscan += '_value' + } + } + if size > 0 { + if value_typ.kind == .function { + g.write('new_map_init${noscan}($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof(voidptr), _MOV(($key_typ_str[$size]){') + } else { + g.write('new_map_init${noscan}($hash_fn, $key_eq_fn, $clone_fn, $free_fn, $size, sizeof($key_typ_str), sizeof($value_typ_str), _MOV(($key_typ_str[$size]){') + } + for expr in node.keys { + g.expr(expr) + g.write(', ') + } + if value_typ.kind == .function { + g.write('}), _MOV((voidptr[$size]){') + } else { + g.write('}), _MOV(($value_typ_str[$size]){') + } + for expr in node.vals { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + g.write(', ') + } + g.write('}))') + } else { + g.write('new_map${noscan}(sizeof($key_typ_str), sizeof($value_typ_str), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)') + } + if g.is_shared { + g.write('}, sizeof($shared_styp))') + } else if is_amp { + g.write('), sizeof($styp))') + } +} + +fn (mut g Gen) select_expr(node ast.SelectExpr) { + is_expr := node.is_expr || g.inside_ternary > 0 + cur_line := if is_expr { + g.empty_line = true + g.go_before_stmt(0) + } else { + '' + } + n_channels := if node.has_exception { node.branches.len - 1 } else { node.branches.len } + mut channels := []ast.Expr{cap: n_channels} + mut objs := []ast.Expr{cap: n_channels} + mut tmp_objs := []string{cap: n_channels} + mut elem_types := []string{cap: n_channels} + mut is_push := []bool{cap: n_channels} + mut has_else := false + mut has_timeout := false + mut timeout_expr := ast.empty_expr() + mut exception_branch := -1 + for j, branch in node.branches { + if branch.is_else { + has_else = true + exception_branch = j + } else if branch.is_timeout { + has_timeout = true + exception_branch = j + timeout_expr = (branch.stmt as ast.ExprStmt).expr + } else { + match branch.stmt { + ast.ExprStmt { + // send expression + expr := branch.stmt.expr as ast.InfixExpr + channels << expr.left + if expr.right is ast.Ident || expr.right is ast.IndexExpr + || expr.right is ast.SelectorExpr || expr.right is ast.StructInit { + // addressable objects in the `C` output + objs << expr.right + tmp_objs << '' + elem_types << '' + } else { + // must be evaluated to tmp var before real `select` is performed + objs << ast.empty_expr() + tmp_obj := g.new_tmp_var() + tmp_objs << tmp_obj + el_stype := g.typ(g.table.mktyp(expr.right_type)) + g.writeln('$el_stype $tmp_obj;') + } + is_push << true + } + ast.AssignStmt { + rec_expr := branch.stmt.right[0] as ast.PrefixExpr + channels << rec_expr.right + is_push << false + // create tmp unless the object with *exactly* the type we need exists already + if branch.stmt.op == .decl_assign + || branch.stmt.right_types[0] != branch.stmt.left_types[0] { + tmp_obj := g.new_tmp_var() + tmp_objs << tmp_obj + el_stype := g.typ(branch.stmt.right_types[0]) + elem_types << if branch.stmt.op == .decl_assign { + el_stype + ' ' + } else { + '' + } + g.writeln('$el_stype $tmp_obj;') + } else { + tmp_objs << '' + elem_types << '' + } + objs << branch.stmt.left[0] + } + else {} + } + } + } + chan_array := g.new_tmp_var() + g.write('Array_sync__Channel_ptr $chan_array = new_array_from_c_array($n_channels, $n_channels, sizeof(sync__Channel*), _MOV((sync__Channel*[$n_channels]){') + for i in 0 .. n_channels { + if i > 0 { + g.write(', ') + } + g.write('(sync__Channel*)(') + g.expr(channels[i]) + g.write(')') + } + g.writeln('}));\n') + directions_array := g.new_tmp_var() + g.write('Array_sync__Direction $directions_array = new_array_from_c_array($n_channels, $n_channels, sizeof(sync__Direction), _MOV((sync__Direction[$n_channels]){') + for i in 0 .. n_channels { + if i > 0 { + g.write(', ') + } + if is_push[i] { + g.write('sync__Direction__push') + } else { + g.write('sync__Direction__pop') + } + } + g.writeln('}));\n') + objs_array := g.new_tmp_var() + g.write('Array_voidptr $objs_array = new_array_from_c_array($n_channels, $n_channels, sizeof(voidptr), _MOV((voidptr[$n_channels]){') + for i in 0 .. n_channels { + if i > 0 { + g.write(', &') + } else { + g.write('&') + } + if tmp_objs[i] == '' { + g.expr(objs[i]) + } else { + g.write(tmp_objs[i]) + } + } + g.writeln('}));\n') + select_result := g.new_tmp_var() + g.write('int $select_result = sync__channel_select(&/*arr*/$chan_array, $directions_array, &/*arr*/$objs_array, ') + if has_timeout { + g.expr(timeout_expr) + } else if has_else { + g.write('0') + } else { + g.write('_const_time__infinite') + } + g.writeln(');') + // free the temps that were created + g.writeln('array_free(&$objs_array);') + g.writeln('array_free(&$directions_array);') + g.writeln('array_free(&$chan_array);') + mut i := 0 + for j in 0 .. node.branches.len { + if j > 0 { + g.write('} else ') + } + g.write('if ($select_result == ') + if j == exception_branch { + g.writeln('-1) {') + } else { + g.writeln('$i) {') + if !is_push[i] && tmp_objs[i] != '' { + g.write('\t${elem_types[i]}') + g.expr(objs[i]) + g.writeln(' = ${tmp_objs[i]};') + } + i++ + } + g.stmts(node.branches[j].stmts) + } + g.writeln('}') + if is_expr { + g.empty_line = false + g.write(cur_line) + g.write('($select_result != -2)') + } +} + +fn (mut g Gen) ident(node ast.Ident) { + prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once + g.prevent_sum_type_unwrapping_once = false + if node.name == 'lld' { + return + } + if node.name.starts_with('C.') { + g.write(util.no_dots(node.name[2..])) + return + } + if node.kind == .constant { // && !node.name.starts_with('g_') { + // TODO globals hack + g.write('_const_') + } + mut name := c_name(node.name) + // TODO: temporary, remove this + node_info := node.info + mut is_auto_heap := false + if node_info is ast.IdentVar { + // x ?int + // `x = 10` => `x.data = 10` (g.right_is_opt == false) + // `x = new_opt()` => `x = new_opt()` (g.right_is_opt == true) + // `println(x)` => `println(*(int*)x.data)` + if node_info.is_optional && !(g.is_assign_lhs && g.right_is_opt) { + g.write('/*opt*/') + styp := g.base_type(node_info.typ) + g.write('(*($styp*)${name}.data)') + return + } + if !g.is_assign_lhs && node_info.share == .shared_t { + g.write('${name}.val') + return + } + scope := g.file.scope.innermost(node.pos.pos) + if v := scope.find_var(node.name) { + is_auto_heap = v.is_auto_heap && (!g.is_assign_lhs || g.assign_op != .decl_assign) + if is_auto_heap { + g.write('(*(') + } + if v.smartcasts.len > 0 { + v_sym := g.table.get_type_symbol(v.typ) + if !prevent_sum_type_unwrapping_once { + for _ in v.smartcasts { + g.write('(') + if v_sym.kind == .sum_type && !is_auto_heap { + g.write('*') + } + } + for i, typ in v.smartcasts { + cast_sym := g.table.get_type_symbol(g.unwrap_generic(typ)) + mut is_ptr := false + if i == 0 { + g.write(name) + if v.orig_type.is_ptr() { + is_ptr = true + } + } + dot := if is_ptr || is_auto_heap { '->' } else { '.' } + if mut cast_sym.info is ast.Aggregate { + sym := g.table.get_type_symbol(cast_sym.info.types[g.aggregate_type_idx]) + g.write('${dot}_$sym.cname') + } else { + g.write('${dot}_$cast_sym.cname') + } + g.write(')') + } + if is_auto_heap { + g.write('))') + } + return + } + } + if v.is_inherited { + g.write(closure_ctx + '->') + } + } + } else if node_info is ast.IdentFn { + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { + key := node.name + g.write('/* obf identfn: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + } + g.write(g.get_ternary_name(name)) + if is_auto_heap { + g.write('))') + } +} + +fn (mut g Gen) cast_expr(node ast.CastExpr) { + if g.is_amp { + // &Foo(0) => ((Foo*)0) + g.out.go_back(1) + } + g.is_amp = false + sym := g.table.get_type_symbol(node.typ) + if sym.kind == .string && !node.typ.is_ptr() { + // `string(x)` needs `tos()`, but not `&string(x) + // `tos(str, len)`, `tos2(str)` + if node.has_arg { + g.write('tos((byteptr)') + } else { + g.write('tos2((byteptr)') + } + g.expr(node.expr) + expr_sym := g.table.get_type_symbol(node.expr_type) + if expr_sym.kind == .array { + // if we are casting an array, we need to add `.data` + g.write('.data') + } + if node.has_arg { + // len argument + g.write(', ') + g.expr(node.arg) + } + g.write(')') + } else if sym.kind in [.sum_type, .interface_] { + g.expr_with_cast(node.expr, node.expr_type, node.typ) + } else if sym.kind == .struct_ && !node.typ.is_ptr() && !(sym.info as ast.Struct).is_typedef { + // deprecated, replaced by Struct{...exr} + styp := g.typ(node.typ) + g.write('*(($styp *)(&') + g.expr(node.expr) + g.write('))') + } else if sym.kind == .alias && g.table.get_final_type_symbol(node.typ).kind == .array_fixed { + g.expr(node.expr) + } else { + styp := g.typ(node.typ) + mut cast_label := '' + // `ast.string_type` is done for MSVC's bug + if sym.kind != .alias + || (sym.info as ast.Alias).parent_type !in [node.expr_type, ast.string_type] { + cast_label = '($styp)' + } + if node.typ.has_flag(.optional) && node.expr is ast.None { + g.gen_optional_error(node.typ, node.expr) + } else { + g.write('(${cast_label}(') + g.expr(node.expr) + if node.expr is ast.IntegerLiteral { + if node.typ in [ast.u64_type, ast.u32_type, ast.u16_type] { + if !node.expr.val.starts_with('-') { + g.write('U') + } + } + } + g.write('))') + } + } +} + +fn (mut g Gen) concat_expr(node ast.ConcatExpr) { + styp := g.typ(node.return_type) + sym := g.table.get_type_symbol(node.return_type) + is_multi := sym.kind == .multi_return + if !is_multi { + g.expr(node.vals[0]) + } else { + g.write('($styp){') + for i, expr in node.vals { + g.write('.arg$i=') + g.expr(expr) + if i < node.vals.len - 1 { + g.write(',') + } + } + g.write('}') + } +} + +fn (mut g Gen) need_tmp_var_in_if(node ast.IfExpr) bool { + if node.is_expr && g.inside_ternary == 0 { + if g.is_autofree || node.typ.has_flag(.optional) { + return true + } + for branch in node.branches { + if branch.cond is ast.IfGuardExpr || branch.stmts.len > 1 { + return true + } + if branch.stmts.len == 1 { + if branch.stmts[0] is ast.ExprStmt { + stmt := branch.stmts[0] as ast.ExprStmt + if stmt.expr is ast.CallExpr { + if stmt.expr.is_method { + left_sym := g.table.get_type_symbol(stmt.expr.receiver_type) + if left_sym.kind in [.array, .array_fixed, .map] { + return true + } + } + } + } + } + } + } + return false +} + +fn (mut g Gen) if_expr(node ast.IfExpr) { + if node.is_comptime { + g.comp_if(node) + return + } + // For simpe if expressions we can use C's `?:` + // `if x > 0 { 1 } else { 2 }` => `(x > 0) ? (1) : (2)` + // For if expressions with multiple statements or another if expression inside, it's much + // easier to use a temp var, than do C tricks with commas, introduce special vars etc + // (as it used to be done). + // Always use this in -autofree, since ?: can have tmp expressions that have to be freed. + needs_tmp_var := g.need_tmp_var_in_if(node) + tmp := if needs_tmp_var { g.new_tmp_var() } else { '' } + mut cur_line := '' + if needs_tmp_var { + if node.typ.has_flag(.optional) { + g.inside_if_optional = true + } + styp := g.typ(node.typ) + cur_line = g.go_before_stmt(0) + g.empty_line = true + g.writeln('$styp $tmp; /* if prepend */') + if g.infix_left_var_name.len > 0 { + g.writeln('if ($g.infix_left_var_name) {') + g.indent++ + } + } else if node.is_expr || g.inside_ternary != 0 { + g.inside_ternary++ + g.write('(') + for i, branch in node.branches { + if i > 0 { + g.write(' : ') + } + if i < node.branches.len - 1 || !node.has_else { + g.expr(branch.cond) + g.write(' ? ') + } + g.stmts(branch.stmts) + } + if node.branches.len == 1 { + g.write(': 0') + } + g.write(')') + g.decrement_inside_ternary() + return + } + mut is_guard := false + mut guard_idx := 0 + mut guard_vars := []string{} + for i, branch in node.branches { + cond := branch.cond + if cond is ast.IfGuardExpr { + if !is_guard { + is_guard = true + guard_idx = i + guard_vars = []string{len: node.branches.len} + } + if cond.expr !is ast.IndexExpr && cond.expr !is ast.PrefixExpr { + var_name := g.new_tmp_var() + guard_vars[i] = var_name + g.writeln('${g.typ(cond.expr_type)} $var_name;') + } else { + guard_vars[i] = '' + } + } + } + for i, branch in node.branches { + if i > 0 { + g.write('} else ') + } + // if last branch is `else {` + if i == node.branches.len - 1 && node.has_else { + g.writeln('{') + // define `err` only for simple `if val := opt {...} else {` + if is_guard && guard_idx == i - 1 { + cvar_name := guard_vars[guard_idx] + g.writeln('\tIError err = ${cvar_name}.err;') + } + } else { + match branch.cond { + ast.IfGuardExpr { + mut var_name := guard_vars[i] + mut short_opt := false + if var_name == '' { + short_opt = true // we don't need a further tmp, so use the one we'll get later + var_name = g.new_tmp_var() + guard_vars[i] = var_name // for `else` + g.tmp_count-- + g.writeln('if (${var_name}.state == 0) {') + } else { + g.write('if ($var_name = ') + g.expr(branch.cond.expr) + g.writeln(', ${var_name}.state == 0) {') + } + if short_opt || branch.cond.var_name != '_' { + base_type := g.base_type(branch.cond.expr_type) + if short_opt { + cond_var_name := if branch.cond.var_name == '_' { + '_dummy_${g.tmp_count + 1}' + } else { + branch.cond.var_name + } + g.write('\t$base_type $cond_var_name = ') + g.expr(branch.cond.expr) + g.writeln(';') + } else { + mut is_auto_heap := false + if branch.stmts.len > 0 { + scope := g.file.scope.innermost(ast.Node(branch.stmts[branch.stmts.len - 1]).position().pos) + if v := scope.find_var(branch.cond.var_name) { + is_auto_heap = v.is_auto_heap + } + } + if is_auto_heap { + g.writeln('\t$base_type* $branch.cond.var_name = HEAP($base_type, *($base_type*)${var_name}.data);') + } else { + g.writeln('\t$base_type $branch.cond.var_name = *($base_type*)${var_name}.data;') + } + } + } + } + else { + g.write('if (') + g.expr(branch.cond) + g.writeln(') {') + } + } + } + if needs_tmp_var { + g.stmts_with_tmp_var(branch.stmts, tmp) + } else { + // restore if_expr stmt header pos + stmt_pos := g.nth_stmt_pos(0) + g.stmts(branch.stmts) + g.stmt_path_pos << stmt_pos + } + } + g.writeln('}') + if needs_tmp_var { + if g.infix_left_var_name.len > 0 { + g.indent-- + g.writeln('}') + } + g.empty_line = false + g.write('$cur_line $tmp') + } + if node.typ.has_flag(.optional) { + g.inside_if_optional = false + } +} + +[inline] +fn (g &Gen) expr_is_multi_return_call(expr ast.Expr) bool { + match expr { + ast.CallExpr { return g.table.get_type_symbol(expr.return_type).kind == .multi_return } + else { return false } + } +} + +fn (mut g Gen) gen_optional_error(target_type ast.Type, expr ast.Expr) { + styp := g.typ(target_type) + g.write('($styp){ .state=2, .err=') + g.expr(expr) + g.write(', .data={EMPTY_STRUCT_INITIALIZATION} }') +} + +fn (mut g Gen) return_stmt(node ast.Return) { + g.write_v_source_line_info(node.pos) + if node.exprs.len > 0 { + // skip `return $vweb.html()` + if node.exprs[0] is ast.ComptimeCall { + g.expr(node.exprs[0]) + g.writeln(';') + return + } + } + + g.inside_return = true + defer { + g.inside_return = false + } + // got to do a correct check for multireturn + sym := g.table.get_type_symbol(g.fn_decl.return_type) + fn_return_is_multi := sym.kind == .multi_return + fn_return_is_optional := g.fn_decl.return_type.has_flag(.optional) + mut has_semicolon := false + if node.exprs.len == 0 { + g.write_defer_stmts_when_needed() + if fn_return_is_optional { + styp := g.typ(g.fn_decl.return_type) + g.writeln('return ($styp){0};') + } else { + if g.is_autofree { + g.trace_autofree('// free before return (no values returned)') + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + } + g.writeln('return;') + } + return + } + tmpvar := g.new_tmp_var() + ret_typ := g.typ(g.fn_decl.return_type) + mut use_tmp_var := g.defer_stmts.len > 0 || g.defer_profile_code.len > 0 + // handle promoting none/error/function returning 'Option' + if fn_return_is_optional { + optional_none := node.exprs[0] is ast.None + ftyp := g.typ(node.types[0]) + mut is_regular_option := ftyp == 'Option' + if optional_none || is_regular_option || node.types[0] == ast.error_type_idx { + if !isnil(g.fn_decl) && g.fn_decl.is_test { + test_error_var := g.new_tmp_var() + g.write('$ret_typ $test_error_var = ') + g.gen_optional_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') + g.write_defer_stmts_when_needed() + g.gen_failing_return_error_for_test_fn(node, test_error_var) + return + } + if use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + g.write('return ') + } + g.gen_optional_error(g.fn_decl.return_type, node.exprs[0]) + g.writeln(';') + if use_tmp_var { + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + } + return + } + } + // regular cases + if fn_return_is_multi && node.exprs.len > 0 && !g.expr_is_multi_return_call(node.exprs[0]) { + if node.exprs.len == 1 && node.exprs[0] is ast.IfExpr { + // use a temporary for `return if cond { x,y } else { a,b }` + g.write('$ret_typ $tmpvar = ') + g.expr(node.exprs[0]) + g.writeln(';') + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + return + } + // typ_sym := g.table.get_type_symbol(g.fn_decl.return_type) + // mr_info := typ_sym.info as ast.MultiReturn + mut styp := '' + if fn_return_is_optional { + g.writeln('$ret_typ $tmpvar;') + styp = g.base_type(g.fn_decl.return_type) + g.write('opt_ok(&($styp/*X*/[]) { ') + } else { + if use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + g.write('return ') + } + styp = g.typ(g.fn_decl.return_type) + } + // Use this to keep the tmp assignments in order + mut multi_unpack := '' + g.write('($styp){') + mut arg_idx := 0 + for i, expr in node.exprs { + // Check if we are dealing with a multi return and handle it seperately + if g.expr_is_multi_return_call(expr) { + c := expr as ast.CallExpr + expr_sym := g.table.get_type_symbol(c.return_type) + // Create a tmp for this call + mut tmp := g.new_tmp_var() + if !c.return_type.has_flag(.optional) { + s := g.go_before_stmt(0) + expr_styp := g.typ(c.return_type) + g.write('$expr_styp $tmp=') + g.expr(expr) + g.writeln(';') + multi_unpack += g.go_before_stmt(0) + g.write(s) + } else { + s := g.go_before_stmt(0) + // TODO + // I (emily) am sorry for doing this + // I cant find another way to do this so right now + // this will have to do. + g.tmp_count-- + g.expr(expr) + multi_unpack += g.go_before_stmt(0) + g.write(s) + // modify tmp so that it is the opt deref + // TODO copy-paste from cgen.v:2397 + expr_styp := g.base_type(c.return_type) + tmp = ('/*opt*/(*($expr_styp*)${tmp}.data)') + } + expr_types := expr_sym.mr_info().types + for j, _ in expr_types { + g.write('.arg$arg_idx=${tmp}.arg$j') + if j < expr_types.len || i < node.exprs.len - 1 { + g.write(',') + } + arg_idx++ + } + continue + } + g.write('.arg$arg_idx=') + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + arg_idx++ + if i < node.exprs.len - 1 { + g.write(', ') + } + } + g.write('}') + if fn_return_is_optional { + g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));') + g.write_defer_stmts_when_needed() + g.write('return $tmpvar') + } + // Make sure to add our unpacks + if multi_unpack.len > 0 { + g.insert_before_stmt(multi_unpack) + } + if use_tmp_var && !fn_return_is_optional { + if !has_semicolon { + g.writeln(';') + } + g.write_defer_stmts_when_needed() + g.writeln('return $tmpvar;') + has_semicolon = true + } + } else if node.exprs.len >= 1 { + // normal return + return_sym := g.table.get_type_symbol(node.types[0]) + expr0 := node.exprs[0] + // `return opt_ok(expr)` for functions that expect an optional + expr_type_is_opt := match expr0 { + ast.CallExpr { + expr0.return_type.has_flag(.optional) && expr0.or_block.kind == .absent + } + else { + node.types[0].has_flag(.optional) + } + } + if fn_return_is_optional && !expr_type_is_opt && return_sym.name != 'Option' { + styp := g.base_type(g.fn_decl.return_type) + g.writeln('$ret_typ $tmpvar;') + g.write('opt_ok(&($styp[]) { ') + if !g.fn_decl.return_type.is_ptr() && node.types[0].is_ptr() { + if !(node.exprs[0] is ast.Ident && !g.is_amp) { + g.write('*') + } + } + for i, expr in node.exprs { + g.expr_with_cast(expr, node.types[i], g.fn_decl.return_type.clear_flag(.optional)) + if i < node.exprs.len - 1 { + g.write(', ') + } + } + g.writeln(' }, (Option*)(&$tmpvar), sizeof($styp));') + g.write_defer_stmts_when_needed() + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + g.writeln('return $tmpvar;') + return + } + // autofree before `return` + // set free_parent_scopes to true, since all variables defined in parent + // scopes need to be freed before the return + if g.is_autofree { + expr := node.exprs[0] + if expr is ast.Ident { + g.returned_var_name = expr.name + } + } + // free := g.is_autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr + // Create a temporary variable for the return expression + use_tmp_var = use_tmp_var || !g.is_builtin_mod // node.exprs[0] is ast.CallExpr + if use_tmp_var { + // `return foo(a, b, c)` + // `tmp := foo(a, b, c); free(a); free(b); free(c); return tmp;` + // Save return value in a temp var so that all args (a,b,c) can be freed + // Don't use a tmp var if a variable is simply returned: `return x` + // Just in case of defer statements exists, that the return values cannot + // be modified. + if node.exprs[0] !is ast.Ident || use_tmp_var { + g.write('$ret_typ $tmpvar = ') + } else { + use_tmp_var = false + g.write_defer_stmts_when_needed() + if !g.is_builtin_mod { + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + } + g.write('return ') + } + } else { + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + g.write('return ') + } + if expr0.is_auto_deref_var() { + if g.fn_decl.return_type.is_ptr() { + var_str := g.expr_string(expr0) + g.write(var_str.trim('&')) + } else { + g.write('*') + g.expr(expr0) + } + } else { + g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) + } + if use_tmp_var { + g.writeln(';') + has_semicolon = true + g.write_defer_stmts_when_needed() + if !g.is_builtin_mod { + g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) + } + g.write('return $tmpvar') + has_semicolon = false + } + } else { // if node.exprs.len == 0 { + println('this should never happen') + g.write('/*F*/return') + } + if !has_semicolon { + g.writeln(';') + } +} + +fn (mut g Gen) const_decl(node ast.ConstDecl) { + g.inside_const = true + defer { + g.inside_const = false + } + for field in node.fields { + if g.pref.skip_unused { + if field.name !in g.table.used_consts { + $if trace_skip_unused_consts ? { + eprintln('>> skipping unused const name: $field.name') + } + continue + } + } + name := c_name(field.name) + field_expr := field.expr + match field.expr { + ast.ArrayInit { + if field.expr.is_fixed { + styp := g.typ(field.expr.typ) + if g.pref.build_mode != .build_module { + val := g.expr_string(field.expr) + g.definitions.writeln('$styp _const_$name = $val; // fixed array const') + } else { + g.definitions.writeln('$styp _const_$name; // fixed array const') + } + } else { + g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + } + } + ast.StringLiteral { + g.definitions.writeln('string _const_$name; // a string literal, inited later') + if g.pref.build_mode != .build_module { + val := g.expr_string(field.expr) + g.stringliterals.writeln('\t_const_$name = $val;') + } + } + ast.CallExpr { + if field.expr.return_type.has_flag(.optional) { + unwrap_option := field.expr.or_block.kind != .absent + g.const_decl_init_later(field.mod, name, field.expr, field.typ, unwrap_option) + } else { + g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + } + } + else { + if g.pref.build_mode != .build_module { + if ct_value := field.comptime_expr_value() { + if g.const_decl_precomputed(field.mod, name, ct_value, field.typ) { + continue + } + } + } + if field.is_simple_define_const() { + // "Simple" expressions are not going to need multiple statements, + // only the ones which are inited later, so it's safe to use expr_string + g.const_decl_simple_define(name, g.expr_string(field_expr)) + } else { + g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) + } + } + } + } +} + +fn (mut g Gen) const_decl_precomputed(mod string, name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { + mut styp := g.typ(typ) + cname := '_const_$name' + $if trace_const_precomputed ? { + eprintln('> styp: $styp | cname: $cname | ct_value: $ct_value | $ct_value.type_name()') + } + match ct_value { + i8 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + i16 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + int { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + i64 { + if typ == ast.i64_type { + return false + } + if typ == ast.int_type { + // TODO: use g.const_decl_write_precomputed here too. + // For now, use #define macros, so existing code compiles + // with -cstrict. Add checker errors for overflows instead, + // so V can catch them earlier, instead of relying on the + // C compiler for that. + g.const_decl_simple_define(name, ct_value.str()) + return true + } + if typ == ast.u64_type { + g.const_decl_write_precomputed(styp, cname, ct_value.str() + 'U') + } else { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + } + byte { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + u16 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + u32 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + u64 { + g.const_decl_write_precomputed(styp, cname, ct_value.str() + 'U') + } + f32 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + f64 { + g.const_decl_write_precomputed(styp, cname, ct_value.str()) + } + rune { + rune_code := u32(ct_value) + if rune_code <= 255 { + if rune_code in [`"`, `\\`, `'`] { + return false + } + escval := util.smart_quote(byte(rune_code).ascii_str(), false) + g.const_decl_write_precomputed(styp, cname, "'$escval'") + } else { + g.const_decl_write_precomputed(styp, cname, u32(ct_value).str()) + } + } + string { + escaped_val := util.smart_quote(ct_value, false) + // g.const_decl_write_precomputed(styp, cname, '_SLIT("$escaped_val")') + // TODO: ^ the above for strings, cause: + // `error C2099: initializer is not a constant` errors in MSVC, + // so fall back to the delayed initialisation scheme: + g.definitions.writeln('$styp $cname; // inited later') + g.inits[mod].writeln('\t$cname = _SLIT("$escaped_val");') + if g.is_autofree { + g.cleanups[mod].writeln('\tstring_free(&$cname);') + } + } + ast.EmptyExpr { + return false + } + } + return true +} + +fn (mut g Gen) const_decl_write_precomputed(styp string, cname string, ct_value string) { + g.definitions.writeln('$styp $cname = $ct_value; // precomputed') +} + +fn (mut g Gen) const_decl_simple_define(name string, val string) { + // Simple expressions should use a #define + // so that we don't pollute the binary with unnecessary global vars + // Do not do this when building a module, otherwise the consts + // will not be accessible. + g.definitions.write_string('#define _const_$name ') + g.definitions.writeln(val) +} + +fn (mut g Gen) const_decl_init_later(mod string, name string, expr ast.Expr, typ ast.Type, unwrap_option bool) { + // Initialize more complex consts in `void _vinit/2{}` + // (C doesn't allow init expressions that can't be resolved at compile time). + mut styp := g.typ(typ) + cname := '_const_$name' + g.definitions.writeln('$styp $cname; // inited later') + if cname == '_const_os__args' { + if g.pref.os == .windows { + g.inits[mod].writeln('\t_const_os__args = os__init_os_args_wide(___argc, (byteptr*)___argv);') + } else { + g.inits[mod].writeln('\t_const_os__args = os__init_os_args(___argc, (byte**)___argv);') + } + } else { + if unwrap_option { + g.inits[mod].writeln(g.expr_string_surround('\t$cname = *($styp*)', expr, + '.data;')) + } else { + g.inits[mod].writeln(g.expr_string_surround('\t$cname = ', expr, ';')) + } + } + if g.is_autofree { + sym := g.table.get_type_symbol(typ) + if styp.starts_with('Array_') { + g.cleanups[mod].writeln('\tarray_free(&$cname);') + } else if styp == 'string' { + g.cleanups[mod].writeln('\tstring_free(&$cname);') + } else if sym.kind == .map { + g.cleanups[mod].writeln('\tmap_free(&$cname);') + } else if styp == 'IError' { + g.cleanups[mod].writeln('\tIError_free(&$cname);') + } + } +} + +fn (mut g Gen) global_decl(node ast.GlobalDecl) { + mod := if g.pref.build_mode == .build_module && g.is_builtin_mod { 'static ' } else { '' } + key := node.mod // module name + for field in node.fields { + if g.pref.skip_unused { + if field.name !in g.table.used_globals { + $if trace_skip_unused_globals ? { + eprintln('>> skipping unused global name: $field.name') + } + continue + } + } + styp := g.typ(field.typ) + if field.has_expr { + g.definitions.write_string('$mod$styp $field.name') + if field.expr.is_literal() { + g.definitions.writeln(' = ${g.expr_string(field.expr)}; // global') + } else { + g.definitions.writeln(';') + g.global_inits[key].writeln('\t$field.name = ${g.expr_string(field.expr)}; // global') + } + } else { + default_initializer := g.type_default(field.typ) + if default_initializer == '{0}' { + g.definitions.writeln('$mod$styp $field.name = {0}; // global') + } else { + g.definitions.writeln('$mod$styp $field.name; // global') + if field.name !in ['as_cast_type_indexes', 'g_memory_block'] { + g.global_inits[key].writeln('\t$field.name = *($styp*)&(($styp[]){${g.type_default(field.typ)}}[0]); // global') + } + } + } + } +} + +fn (mut g Gen) go_back_out(n int) { + g.out.go_back(n) +} + +const ( + skip_struct_init = ['struct stat', 'struct addrinfo'] +) + +fn (mut g Gen) struct_init(struct_init ast.StructInit) { + styp := g.typ(struct_init.typ) + mut shared_styp := '' // only needed for shared x := St{... + if styp in c.skip_struct_init { + // needed for c++ compilers + g.go_back_out(3) + return + } + sym := g.table.get_final_type_symbol(g.unwrap_generic(struct_init.typ)) + is_amp := g.is_amp + is_multiline := struct_init.fields.len > 5 + g.is_amp = false // reset the flag immediately so that other struct inits in this expr are handled correctly + if is_amp { + g.out.go_back(1) // delete the `&` already generated in `prefix_expr() + } + if g.is_shared && !g.inside_opt_data && !g.is_arraymap_set { + mut shared_typ := struct_init.typ.set_flag(.shared_f) + shared_styp = g.typ(shared_typ) + g.writeln('($shared_styp*)__dup${shared_styp}(&($shared_styp){.mtx = {0}, .val =($styp){') + } else if is_amp || g.inside_cast_in_heap > 0 { + g.write('($styp*)memdup(&($styp){') + } else if struct_init.typ.is_ptr() { + basetyp := g.typ(struct_init.typ.set_nr_muls(0)) + if is_multiline { + g.writeln('&($basetyp){') + } else { + g.write('&($basetyp){') + } + } else { + if is_multiline { + g.writeln('($styp){') + } else { + g.write('($styp){') + } + } + // mut fields := []string{} + mut inited_fields := map[string]int{} // TODO this is done in checker, move to ast node + /* + if struct_init.fields.len == 0 && struct_init.exprs.len > 0 { + // Get fields for {a,b} short syntax. Fields array wasn't set in the parser. + for f in info.fields { + fields << f.name + } + } else { + fields = struct_init.fields + } + */ + if is_multiline { + g.indent++ + } + // User set fields + mut initialized := false + mut old_is_shared := g.is_shared + for i, field in struct_init.fields { + if !field.typ.has_flag(.shared_f) { + g.is_shared = false + } + inited_fields[field.name] = i + if sym.kind != .struct_ { + field_name := if sym.language == .v { c_name(field.name) } else { field.name } + g.write('.$field_name = ') + if field.typ == 0 { + g.checker_bug('struct init, field.typ is 0', field.pos) + } + field_type_sym := g.table.get_type_symbol(field.typ) + mut cloned := false + if g.is_autofree && !field.typ.is_ptr() && field_type_sym.kind in [.array, .string] { + g.write('/*clone1*/') + if g.gen_clone_assignment(field.expr, field_type_sym, false) { + cloned = true + } + } + if !cloned { + if (field.expected_type.is_ptr() && !field.expected_type.has_flag(.shared_f)) + && !(field.typ.is_ptr() || field.typ.is_pointer()) && !field.typ.is_number() { + g.write('/* autoref */&') + } + g.expr_with_cast(field.expr, field.typ, field.expected_type) + } + if i != struct_init.fields.len - 1 { + if is_multiline { + g.writeln(',') + } else { + g.write(', ') + } + } + initialized = true + } + g.is_shared = old_is_shared + } + g.is_shared = old_is_shared + // The rest of the fields are zeroed. + // `inited_fields` is a list of fields that have been init'ed, they are skipped + mut nr_fields := 1 + if sym.kind == .struct_ { + info := sym.info as ast.Struct + nr_fields = info.fields.len + if info.is_union && struct_init.fields.len > 1 { + verror('union must not have more than 1 initializer') + } + if !info.is_union { + old_is_shared2 := g.is_shared + mut used_embed_fields := []string{} + init_field_names := info.fields.map(it.name) + // fields that are initialized but belong to the embedding + init_fields_to_embed := struct_init.fields.filter(it.name !in init_field_names) + for embed in info.embeds { + embed_sym := g.table.get_type_symbol(embed) + embed_name := embed_sym.embed_name() + if embed_name !in inited_fields { + embed_info := embed_sym.info as ast.Struct + embed_field_names := embed_info.fields.map(it.name) + fields_to_embed := init_fields_to_embed.filter(it.name !in used_embed_fields + && it.name in embed_field_names) + used_embed_fields << fields_to_embed.map(it.name) + default_init := ast.StructInit{ + typ: embed + fields: fields_to_embed + } + g.write('.$embed_name = ') + g.struct_init(default_init) + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + } + } + g.is_shared = old_is_shared2 + } + // g.zero_struct_fields(info, inited_fields) + // nr_fields = info.fields.len + for mut field in info.fields { + if !field.typ.has_flag(.shared_f) { + g.is_shared = false + } + if mut sym.info is ast.Struct { + mut found_equal_fields := 0 + for mut sifield in sym.info.fields { + if sifield.name == field.name { + found_equal_fields++ + break + } + } + if found_equal_fields == 0 { + continue + } + } + if field.name in inited_fields { + sfield := struct_init.fields[inited_fields[field.name]] + field_name := if sym.language == .v { c_name(field.name) } else { field.name } + if sfield.typ == 0 { + continue + } + g.write('.$field_name = ') + field_type_sym := g.table.get_type_symbol(sfield.typ) + mut cloned := false + if g.is_autofree && !sfield.typ.is_ptr() && field_type_sym.kind in [.array, .string] { + g.write('/*clone1*/') + if g.gen_clone_assignment(sfield.expr, field_type_sym, false) { + cloned = true + } + } + if !cloned { + if field_type_sym.kind == .array_fixed && sfield.expr is ast.Ident { + fixed_array_info := field_type_sym.info as ast.ArrayFixed + g.write('{') + for i in 0 .. fixed_array_info.size { + g.expr(sfield.expr) + g.write('[$i]') + if i != fixed_array_info.size - 1 { + g.write(', ') + } + } + g.write('}') + } else { + if (sfield.expected_type.is_ptr() + && !sfield.expected_type.has_flag(.shared_f)) && !(sfield.typ.is_ptr() + || sfield.typ.is_pointer()) && !sfield.typ.is_number() { + g.write('/* autoref */&') + } + g.expr_with_cast(sfield.expr, sfield.typ, sfield.expected_type) + } + } + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + continue + } + if info.is_union { + // unions thould have exactly one explicit initializer + continue + } + if field.typ.has_flag(.optional) { + field_name := c_name(field.name) + g.write('.$field_name = {EMPTY_STRUCT_INITIALIZATION},') + initialized = true + continue + } + if field.typ in info.embeds { + continue + } + if struct_init.has_update_expr { + g.expr(struct_init.update_expr) + if struct_init.update_expr_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(field.name) + } else { + if !g.zero_struct_field(field) { + nr_fields-- + continue + } + } + if is_multiline { + g.writeln(',') + } else { + g.write(',') + } + initialized = true + g.is_shared = old_is_shared + } + g.is_shared = old_is_shared + } + if is_multiline { + g.indent-- + } + + if !initialized { + if nr_fields > 0 { + g.write('0') + } else { + g.write('EMPTY_STRUCT_INITIALIZATION') + } + } + + g.write('}') + if g.is_shared && !g.inside_opt_data && !g.is_arraymap_set { + g.write('}, sizeof($shared_styp))') + } else if is_amp || g.inside_cast_in_heap > 0 { + g.write(', sizeof($styp))') + } +} + +fn (mut g Gen) zero_struct_field(field ast.StructField) bool { + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ { + info := sym.info as ast.Struct + if info.fields.len == 0 { + return false + } + } + field_name := if sym.language == .v { c_name(field.name) } else { field.name } + g.write('.$field_name = ') + if field.has_default_expr { + if sym.kind in [.sum_type, .interface_] { + g.expr_with_cast(field.default_expr, field.default_expr_typ, field.typ) + return true + } + g.expr(field.default_expr) + } else { + g.write(g.type_default(field.typ)) + } + return true +} + +// fn (mut g Gen) zero_struct_fields(info ast.Struct, inited_fields map[string]int) { +// } +// { user | name: 'new name' } +fn (mut g Gen) assoc(node ast.Assoc) { + g.writeln('// assoc') + if node.typ == 0 { + return + } + styp := g.typ(node.typ) + g.writeln('($styp){') + mut inited_fields := map[string]int{} + for i, field in node.fields { + inited_fields[field] = i + } + // Merge inited_fields in the rest of the fields. + sym := g.table.get_type_symbol(node.typ) + info := sym.info as ast.Struct + for field in info.fields { + field_name := c_name(field.name) + if field.name in inited_fields { + g.write('\t.$field_name = ') + g.expr(node.exprs[inited_fields[field.name]]) + g.writeln(', ') + } else { + g.writeln('\t.$field_name = ${node.var_name}.$field_name,') + } + } + g.write('}') + if g.is_amp { + g.write(', sizeof($styp))') + } +} + +[noreturn] +fn verror(s string) { + util.verror('cgen error', s) +} + +[noreturn] +fn (g &Gen) error(s string, pos token.Position) { + ferror := util.formatted_error('cgen error:', s, g.file.path, pos) + eprintln(ferror) + exit(1) +} + +fn (g &Gen) checker_bug(s string, pos token.Position) { + g.error('checker bug; $s', pos) +} + +fn (mut g Gen) write_init_function() { + if g.pref.is_liveshared { + return + } + fn_vinit_start_pos := g.out.len + // ___argv is declared as voidptr here, because that unifies the windows/unix logic + g.writeln('void _vinit(int ___argc, voidptr ___argv) {') + if g.pref.prealloc { + g.writeln('prealloc_vinit();') + } + // NB: the as_cast table should be *before* the other constant initialize calls, + // because it may be needed during const initialization of builtin and during + // calling module init functions too, just in case they do fail... + g.write('\tas_cast_type_indexes = ') + g.writeln(g.as_cast_name_table()) + // + g.writeln('\tbuiltin_init();') + g.writeln('\tvinit_string_literals();') + // + for mod_name in g.table.modules { + g.writeln('\t// Initializations for module $mod_name :') + g.write(g.inits[mod_name].str()) + g.write(g.global_inits[mod_name].str()) + init_fn_name := '${mod_name}.init' + if initfn := g.table.find_fn(init_fn_name) { + if initfn.return_type == ast.void_type && initfn.params.len == 0 { + mod_c_name := util.no_dots(mod_name) + init_fn_c_name := '${mod_c_name}__init' + g.writeln('\t${init_fn_c_name}();') + } + } + } + g.writeln('}') + if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list { + println(g.out.after(fn_vinit_start_pos)) + } + // + fn_vcleanup_start_pos := g.out.len + g.writeln('void _vcleanup() {') + if g.is_autofree { + // g.writeln('puts("cleaning up...");') + reversed_table_modules := g.table.modules.reverse() + for mod_name in reversed_table_modules { + g.writeln('\t// Cleanups for module $mod_name :') + g.writeln(g.cleanups[mod_name].str()) + } + g.writeln('\tarray_free(&as_cast_type_indexes);') + } + g.writeln('}') + if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list { + println(g.out.after(fn_vcleanup_start_pos)) + } + // + needs_constructor := g.pref.is_shared && g.pref.os != .windows + if needs_constructor { + // shared libraries need a way to call _vinit/2. For that purpose, + // provide a constructor/destructor pair, ensuring that all constants + // are initialized just once, and that they will be freed too. + // NB: os.args in this case will be []. + g.writeln('__attribute__ ((constructor))') + g.writeln('void _vinit_caller() {') + g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') + g.writeln('\t_vinit(0,0);') + g.writeln('}') + + g.writeln('__attribute__ ((destructor))') + g.writeln('void _vcleanup_caller() {') + g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') + g.writeln('\t_vcleanup();') + g.writeln('}') + } +} + +const ( + builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', 'Option'] +) + +fn (mut g Gen) write_builtin_types() { + mut builtin_types := []ast.TypeSymbol{} // builtin types + // builtin types need to be on top + // everything except builtin will get sorted + for builtin_name in c.builtins { + sym := g.table.type_symbols[g.table.type_idxs[builtin_name]] + if sym.kind == .interface_ { + g.write_interface_typedef(sym) + g.write_interface_typesymbol_declaration(sym) + } else { + builtin_types << sym + } + } + g.write_types(builtin_types) +} + +// C struct definitions, ordered +// Sort the types, make sure types that are referenced by other types +// are added before them. +fn (mut g Gen) write_sorted_types() { + mut types := []ast.TypeSymbol{} // structs that need to be sorted + for typ in g.table.type_symbols { + if typ.name !in c.builtins { + types << typ + } + } + // sort structs + types_sorted := g.sort_structs(types) + // Generate C code + g.type_definitions.writeln('// builtin types:') + g.type_definitions.writeln('//------------------ #endbuiltin') + g.write_types(types_sorted) +} + +fn (mut g Gen) write_types(types []ast.TypeSymbol) { + for typ in types { + if typ.name.starts_with('C.') { + continue + } + // sym := g.table.get_type_symbol(typ) + mut name := typ.cname + match mut typ.info { + ast.Struct { + if typ.info.is_generic { + continue + } + if name.contains('_T_') { + g.typedefs.writeln('typedef struct $name $name;') + } + // TODO avoid buffer manip + start_pos := g.type_definitions.len + + mut pre_pragma := '' + mut post_pragma := '' + + for attr in typ.info.attrs { + match attr.name { + '_pack' { + pre_pragma += '#pragma pack(push, $attr.arg)\n' + post_pragma += '#pragma pack(pop)' + } + else {} + } + } + + g.type_definitions.writeln(pre_pragma) + + if typ.info.is_union { + g.type_definitions.writeln('union $name {') + } else { + g.type_definitions.writeln('struct $name {') + } + if typ.info.fields.len > 0 || typ.info.embeds.len > 0 { + for field in typ.info.fields { + // Some of these structs may want to contain + // optionals that may not be defined at this point + // if this is the case then we are going to + // buffer manip out in front of the struct + // write the optional in and then continue + if field.typ.has_flag(.optional) { + // Dont use g.typ() here becuase it will register + // optional and we dont want that + styp, base := g.optional_type_name(field.typ) + if styp !in g.optionals { + last_text := g.type_definitions.cut_to(start_pos).clone() + g.optionals << styp + g.typedefs2.writeln('typedef struct $styp $styp;') + g.type_definitions.writeln('${g.optional_type_text(styp, + base)};') + g.type_definitions.write_string(last_text) + } + } + type_name := g.typ(field.typ) + field_name := c_name(field.name) + g.type_definitions.writeln('\t$type_name $field_name;') + } + } else { + g.type_definitions.writeln('\tEMPTY_STRUCT_DECLARATION;') + } + // g.type_definitions.writeln('} $name;\n') + // + ti_attrs := if typ.info.attrs.contains('packed') { + '__attribute__((__packed__))' + } else { + '' + } + g.type_definitions.writeln('}$ti_attrs;\n') + g.type_definitions.writeln(post_pragma) + } + ast.Alias { + // ast.Alias { TODO + } + ast.Thread { + if g.pref.os == .windows { + if name == '__v_thread' { + g.type_definitions.writeln('typedef HANDLE $name;') + } else { + // Windows can only return `u32` (no void*) from a thread, so the + // V gohandle must maintain a pointer to the return value + g.type_definitions.writeln('typedef struct {') + g.type_definitions.writeln('\tvoid* ret_ptr;') + g.type_definitions.writeln('\tHANDLE handle;') + g.type_definitions.writeln('} $name;') + } + } else { + if !g.pref.is_bare { + g.type_definitions.writeln('typedef pthread_t $name;') + } + } + } + ast.SumType { + if typ.info.is_generic { + continue + } + g.typedefs.writeln('typedef struct $name $name;') + g.type_definitions.writeln('') + g.type_definitions.writeln('// Union sum type $name = ') + for variant in typ.info.variants { + g.type_definitions.writeln('// | ${variant:4d} = ${g.typ(variant.idx()):-20s}') + } + g.type_definitions.writeln('struct $name {') + g.type_definitions.writeln('\tunion {') + for variant in typ.info.variants { + variant_sym := g.table.get_type_symbol(variant) + g.type_definitions.writeln('\t\t${g.typ(variant.to_ptr())} _$variant_sym.cname;') + } + g.type_definitions.writeln('\t};') + g.type_definitions.writeln('\tint _typ;') + if typ.info.fields.len > 0 { + g.writeln('\t// pointers to common sumtype fields') + for field in typ.info.fields { + g.type_definitions.writeln('\t${g.typ(field.typ.to_ptr())} $field.name;') + } + } + g.type_definitions.writeln('};') + g.type_definitions.writeln('') + } + ast.ArrayFixed { + elem_sym := g.table.get_type_symbol(typ.info.elem_type) + if !elem_sym.is_builtin() && !typ.info.elem_type.has_flag(.generic) { + // .array_fixed { + styp := typ.cname + // array_fixed_char_300 => char x[300] + // [16]&&&EventListener{} => Array_fixed_main__EventListener_16_ptr3 + // => typedef main__EventListener*** Array_fixed_main__EventListener_16_ptr3 [16] + mut fixed_elem_name := g.typ(typ.info.elem_type.set_nr_muls(0)) + if typ.info.elem_type.is_ptr() { + fixed_elem_name += '*'.repeat(typ.info.elem_type.nr_muls()) + } + len := typ.info.size + if fixed_elem_name.starts_with('C__') { + fixed_elem_name = fixed_elem_name[3..] + } + if elem_sym.info is ast.FnType { + pos := g.out.len + g.write_fn_ptr_decl(&elem_sym.info, '') + fixed_elem_name = g.out.cut_to(pos) + mut def_str := 'typedef $fixed_elem_name;' + def_str = def_str.replace_once('(*)', '(*$styp[$len])') + g.type_definitions.writeln(def_str) + } else { + g.type_definitions.writeln('typedef $fixed_elem_name $styp [$len];') + } + } + } + else {} + } + } +} + +// sort structs by dependant fields +fn (g &Gen) sort_structs(typesa []ast.TypeSymbol) []ast.TypeSymbol { + mut dep_graph := depgraph.new_dep_graph() + // types name list + mut type_names := []string{} + for typ in typesa { + type_names << typ.name + } + // loop over types + for t in typesa { + if t.kind == .interface_ { + dep_graph.add(t.name, []) + continue + } + // create list of deps + mut field_deps := []string{} + match mut t.info { + ast.ArrayFixed { + dep := g.table.get_type_symbol(t.info.elem_type).name + if dep in type_names { + field_deps << dep + } + } + ast.Struct { + for embed in t.info.embeds { + dep := g.table.get_type_symbol(embed).name + // skip if not in types list or already in deps + if dep !in type_names || dep in field_deps { + continue + } + field_deps << dep + } + for field in t.info.fields { + dep := g.table.get_type_symbol(field.typ).name + // skip if not in types list or already in deps + if dep !in type_names || dep in field_deps || field.typ.is_ptr() { + continue + } + field_deps << dep + } + } + // ast.Interface {} + else {} + } + // add type and dependant types to graph + dep_graph.add(t.name, field_deps) + } + // sort graph + dep_graph_sorted := dep_graph.resolve() + if !dep_graph_sorted.acyclic { + // this should no longer be called since it's catched in the parser + // TODO: should it be removed? + verror('cgen.sort_structs(): the following structs form a dependency cycle:\n' + + dep_graph_sorted.display_cycles() + + '\nyou can solve this by making one or both of the dependant struct fields references, eg: field &MyStruct' + + '\nif you feel this is an error, please create a new issue here: https://github.com/vlang/v/issues and tag @joe-conigliaro') + } + // sort types + mut types_sorted := []ast.TypeSymbol{} + for node in dep_graph_sorted.nodes { + types_sorted << g.table.type_symbols[g.table.type_idxs[node.name]] + } + return types_sorted +} + +[inline] +fn (g &Gen) nth_stmt_pos(n int) int { + return g.stmt_path_pos[g.stmt_path_pos.len - (1 + n)] +} + +fn (mut g Gen) go_before_stmt(n int) string { + stmt_pos := g.nth_stmt_pos(n) + return g.out.cut_to(stmt_pos) +} + +[inline] +fn (mut g Gen) go_before_ternary() string { + return g.go_before_stmt(g.inside_ternary) +} + +fn (mut g Gen) insert_before_stmt(s string) { + cur_line := g.go_before_stmt(0) + g.writeln(s) + g.write(cur_line) +} + +fn (mut g Gen) insert_at(pos int, s string) { + cur_line := g.out.cut_to(pos) + g.writeln(s) + g.write(cur_line) +} + +// fn (mut g Gen) start_tmp() { +// } +// If user is accessing the return value eg. in assigment, pass the variable name. +// If the user is not using the optional return value. We need to pass a temp var +// to access its fields (`.ok`, `.error` etc) +// `os.cp(...)` => `Option bool tmp = os__cp(...); if (tmp.state != 0) { ... }` +// Returns the type of the last stmt +fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Type) { + cvar_name := c_name(var_name) + mr_styp := g.base_type(return_type) + is_none_ok := return_type == ast.ovoid_type + g.writeln(';') + if is_none_ok { + g.writeln('if (${cvar_name}.state != 0 && ${cvar_name}.err._typ != _IError_None___index) {') + } else { + g.writeln('if (${cvar_name}.state != 0) { /*or block*/ ') + } + if or_block.kind == .block { + if g.inside_or_block { + g.writeln('\terr = ${cvar_name}.err;') + } else { + g.writeln('\tIError err = ${cvar_name}.err;') + } + g.inside_or_block = true + defer { + g.inside_or_block = false + } + stmts := or_block.stmts + if stmts.len > 0 && stmts[or_block.stmts.len - 1] is ast.ExprStmt + && (stmts[stmts.len - 1] as ast.ExprStmt).typ != ast.void_type { + g.indent++ + for i, stmt in stmts { + if i == stmts.len - 1 { + expr_stmt := stmt as ast.ExprStmt + g.stmt_path_pos << g.out.len + g.write('*($mr_styp*) ${cvar_name}.data = ') + old_inside_opt_data := g.inside_opt_data + g.inside_opt_data = true + g.expr_with_cast(expr_stmt.expr, expr_stmt.typ, return_type.clear_flag(.optional)) + g.inside_opt_data = old_inside_opt_data + if g.inside_ternary == 0 { + g.writeln(';') + } + g.stmt_path_pos.delete_last() + } else { + g.stmt(stmt) + } + } + g.indent-- + } else { + g.stmts(stmts) + if stmts.len > 0 && stmts[or_block.stmts.len - 1] is ast.ExprStmt { + g.writeln(';') + } + } + } else if or_block.kind == .propagate { + if g.file.mod.name == 'main' && (isnil(g.fn_decl) || g.fn_decl.is_main) { + // In main(), an `opt()?` call is sugar for `opt() or { panic(err) }` + if g.pref.is_debug { + paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) + g.writeln('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), *${cvar_name}.err.msg );') + } else { + g.writeln('\tpanic_optional_not_set(*${cvar_name}.err.msg);') + } + } else if !isnil(g.fn_decl) && g.fn_decl.is_test { + g.gen_failing_error_propagation_for_test_fn(or_block, cvar_name) + } else { + // In ordinary functions, `opt()?` call is sugar for: + // `opt() or { return err }` + // Since we *do* return, first we have to ensure that + // the defered statements are generated. + g.write_defer_stmts() + // Now that option types are distinct we need a cast here + if g.fn_decl.return_type == ast.void_type { + g.writeln('\treturn;') + } else { + styp := g.typ(g.fn_decl.return_type) + err_obj := g.new_tmp_var() + g.writeln('\t$styp $err_obj;') + g.writeln('\tmemcpy(&$err_obj, &$cvar_name, sizeof(Option));') + g.writeln('\treturn $err_obj;') + } + } + } + g.writeln('}') + g.stmt_path_pos << g.out.len +} + +[inline] +fn c_name(name_ string) string { + name := util.no_dots(name_) + if name in c.c_reserved_map { + return '_v_$name' + } + return name +} + +fn (mut g Gen) type_default(typ_ ast.Type) string { + typ := g.unwrap_generic(typ_) + if typ.has_flag(.optional) { + return '{0}' + } + // Always set pointers to 0 + if typ.is_ptr() && !typ.has_flag(.shared_f) { + return '0' + } + if typ.idx() < ast.string_type_idx { + // Default values for other types are not needed because of mandatory initialization + return '0' + } + sym := g.table.get_type_symbol(typ) + match sym.kind { + .string { + return '(string){.str=(byteptr)"", .is_lit=1}' + } + .interface_, .sum_type, .array_fixed, .multi_return { + return '{0}' + } + .alias { + return g.type_default((sym.info as ast.Alias).parent_type) + } + .chan { + elem_type := sym.chan_info().elem_type + elemtypstr := g.typ(elem_type) + noscan := g.check_noscan(elem_type) + return 'sync__new_channel_st${noscan}(0, sizeof($elemtypstr))' + } + .array { + elem_typ := sym.array_info().elem_type + elem_sym := g.typ(elem_typ) + mut elem_type_str := util.no_dots(elem_sym) + if elem_type_str.starts_with('C__') { + elem_type_str = elem_type_str[3..] + } + noscan := g.check_noscan(elem_typ) + init_str := '__new_array${noscan}(0, 0, sizeof($elem_type_str))' + if typ.has_flag(.shared_f) { + atyp := '__shared__Array_${g.table.get_type_symbol(elem_typ).cname}' + return '($atyp*)__dup_shared_array(&($atyp){.mtx = {0}, .val =$init_str}, sizeof($atyp))' + } else { + return init_str + } + } + .map { + info := sym.map_info() + key_typ := g.table.get_type_symbol(info.key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) + noscan_key := g.check_noscan(info.key_type) + noscan_value := g.check_noscan(info.value_type) + mut noscan := if noscan_key.len != 0 || noscan_value.len != 0 { '_noscan' } else { '' } + if noscan.len != 0 { + if noscan_key.len != 0 { + noscan += '_key' + } + if noscan_value.len != 0 { + noscan += '_value' + } + } + init_str := 'new_map${noscan}(sizeof(${g.typ(info.key_type)}), sizeof(${g.typ(info.value_type)}), $hash_fn, $key_eq_fn, $clone_fn, $free_fn)' + if typ.has_flag(.shared_f) { + mtyp := '__shared__Map_${key_typ.cname}_${g.table.get_type_symbol(info.value_type).cname}' + return '($mtyp*)__dup_shared_map(&($mtyp){.mtx = {0}, .val =$init_str}, sizeof($mtyp))' + } else { + return init_str + } + } + .struct_ { + mut has_none_zero := false + mut init_str := '{' + info := sym.info as ast.Struct + typ_is_shared_f := typ.has_flag(.shared_f) + if sym.language == .v && !typ_is_shared_f { + for field in info.fields { + field_sym := g.table.get_type_symbol(field.typ) + if field.has_default_expr + || field_sym.kind in [.array, .map, .string, .bool, .alias, .size_t, .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .char, .voidptr, .byteptr, .charptr, .struct_] { + field_name := c_name(field.name) + if field.has_default_expr { + expr_str := g.expr_string(field.default_expr) + init_str += '.$field_name = $expr_str,' + } else { + init_str += '.$field_name = ${g.type_default(field.typ)},' + } + has_none_zero = true + } + } + } + if has_none_zero { + init_str += '}' + type_name := g.typ(typ) + init_str = '($type_name)' + init_str + } else { + init_str += '0}' + } + if typ.has_flag(.shared_f) { + styp := '__shared__${g.table.get_type_symbol(typ).cname}' + return '($styp*)__dup${styp}(&($styp){.mtx = {0}, .val =$init_str}, sizeof($styp))' + } else { + return init_str + } + } + else { + return '0' + } + } +} + +fn (g &Gen) get_all_test_function_names() []string { + mut tfuncs := []string{} + mut tsuite_begin := '' + mut tsuite_end := '' + for _, f in g.table.fns { + if f.name.ends_with('.testsuite_begin') { + tsuite_begin = f.name + continue + } + if f.name.contains('.test_') { + tfuncs << f.name + continue + } + if f.name.ends_with('.testsuite_end') { + tsuite_end = f.name + continue + } + } + mut all_tfuncs := []string{} + if tsuite_begin.len > 0 { + all_tfuncs << tsuite_begin + } + all_tfuncs << tfuncs + if tsuite_end.len > 0 { + all_tfuncs << tsuite_end + } + return all_tfuncs +} + +fn (g &Gen) is_importing_os() bool { + return 'os' in g.table.imports +} + +fn (mut g Gen) go_expr(node ast.GoExpr) { + line := g.go_before_stmt(0) + mut handle := '' + tmp := g.new_tmp_var() + mut expr := node.call_expr + mut name := expr.name // util.no_dots(expr.name) + // TODO: fn call is duplicated. merge with fn_call(). + for i, concrete_type in expr.concrete_types { + if concrete_type != ast.void_type && concrete_type != 0 { + // Using _T_ to differentiate between get<string> and get_string + // `foo<int>()` => `foo_T_int()` + if i == 0 { + name += '_T' + } + name += '_' + g.typ(concrete_type) + } + } + if expr.is_method { + receiver_sym := g.table.get_type_symbol(expr.receiver_type) + name = receiver_sym.name + '_' + name + } else if mut expr.left is ast.AnonFn { + g.gen_anon_fn_decl(mut expr.left) + name = expr.left.decl.name + } + name = util.no_dots(name) + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { + mut key := expr.name + if expr.is_method { + sym := g.table.get_type_symbol(expr.receiver_type) + key = sym.name + '.' + expr.name + } + g.write('/* obf go: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + g.writeln('// go') + wrapper_struct_name := 'thread_arg_' + name + wrapper_fn_name := name + '_thread_wrapper' + arg_tmp_var := 'arg_' + tmp + g.writeln('$wrapper_struct_name *$arg_tmp_var = malloc(sizeof(thread_arg_$name));') + if expr.is_method { + g.write('$arg_tmp_var->arg0 = ') + // TODO is this needed? + /* + if false && !expr.return_type.is_ptr() { + g.write('&') + } + */ + g.expr(expr.left) + g.writeln(';') + } + for i, arg in expr.args { + g.write('$arg_tmp_var->arg${i + 1} = ') + g.expr(arg.expr) + g.writeln(';') + } + s_ret_typ := g.typ(node.call_expr.return_type) + if g.pref.os == .windows && node.call_expr.return_type != ast.void_type { + g.writeln('$arg_tmp_var->ret_ptr = malloc(sizeof($s_ret_typ));') + } + is_opt := node.call_expr.return_type.has_flag(.optional) + mut gohandle_name := '' + if node.call_expr.return_type == ast.void_type { + gohandle_name = if is_opt { '__v_thread_Option_void' } else { '__v_thread' } + } else { + opt := if is_opt { 'Option_' } else { '' } + gohandle_name = '__v_thread_$opt${g.table.get_type_symbol(g.unwrap_generic(node.call_expr.return_type)).cname}' + } + if g.pref.os == .windows { + simple_handle := if node.is_expr && node.call_expr.return_type != ast.void_type { + 'thread_handle_$tmp' + } else { + 'thread_$tmp' + } + g.writeln('HANDLE $simple_handle = CreateThread(0,0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0,0);') + g.writeln('if (!$simple_handle) panic_lasterr(tos3("`go ${name}()`: "));') + if node.is_expr && node.call_expr.return_type != ast.void_type { + g.writeln('$gohandle_name thread_$tmp = {') + g.writeln('\t.ret_ptr = $arg_tmp_var->ret_ptr,') + g.writeln('\t.handle = thread_handle_$tmp') + g.writeln('};') + } + if !node.is_expr { + g.writeln('CloseHandle(thread_$tmp);') + } + } else { + g.writeln('pthread_t thread_$tmp;') + g.writeln('int ${tmp}_thr_res = pthread_create(&thread_$tmp, NULL, (void*)$wrapper_fn_name, $arg_tmp_var);') + g.writeln('if (${tmp}_thr_res) panic_error_number(tos3("`go ${name}()`: "), ${tmp}_thr_res);') + if !node.is_expr { + g.writeln('pthread_detach(thread_$tmp);') + } + } + g.writeln('// endgo\n') + if node.is_expr { + handle = 'thread_$tmp' + // create wait handler for this return type if none exists + waiter_fn_name := gohandle_name + '_wait' + if waiter_fn_name !in g.waiter_fns { + g.gowrappers.writeln('\n$s_ret_typ ${waiter_fn_name}($gohandle_name thread) {') + mut c_ret_ptr_ptr := 'NULL' + if node.call_expr.return_type != ast.void_type { + g.gowrappers.writeln('\t$s_ret_typ* ret_ptr;') + c_ret_ptr_ptr = '&ret_ptr' + } + if g.pref.os == .windows { + if node.call_expr.return_type == ast.void_type { + g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);') + } else { + g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread.handle, INFINITE);') + g.gowrappers.writeln('\tret_ptr = thread.ret_ptr;') + } + } else { + g.gowrappers.writeln('\tint stat = pthread_join(thread, (void **)$c_ret_ptr_ptr);') + } + g.gowrappers.writeln('\tif (stat != 0) { _v_panic(_SLIT("unable to join thread")); }') + if g.pref.os == .windows { + if node.call_expr.return_type == ast.void_type { + g.gowrappers.writeln('\tCloseHandle(thread);') + } else { + g.gowrappers.writeln('\tCloseHandle(thread.handle);') + } + } + if node.call_expr.return_type != ast.void_type { + g.gowrappers.writeln('\t$s_ret_typ ret = *ret_ptr;') + g.gowrappers.writeln('\tfree(ret_ptr);') + g.gowrappers.writeln('\treturn ret;') + } else { + g.gowrappers.writeln('\treturn;') + } + g.gowrappers.writeln('}') + g.waiter_fns << waiter_fn_name + } + } + // Register the wrapper type and function + if name !in g.threaded_fns { + g.type_definitions.writeln('\ntypedef struct $wrapper_struct_name {') + if expr.is_method { + styp := g.typ(expr.receiver_type) + g.type_definitions.writeln('\t$styp arg0;') + } + need_return_ptr := g.pref.os == .windows && node.call_expr.return_type != ast.void_type + if expr.args.len == 0 && !need_return_ptr { + g.type_definitions.writeln('EMPTY_STRUCT_DECLARATION;') + } else { + for i, arg in expr.args { + styp := g.typ(arg.typ) + g.type_definitions.writeln('\t$styp arg${i + 1};') + } + } + if need_return_ptr { + g.type_definitions.writeln('\tvoid* ret_ptr;') + } + g.type_definitions.writeln('} $wrapper_struct_name;') + thread_ret_type := if g.pref.os == .windows { 'u32' } else { 'void*' } + g.type_definitions.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg);') + g.gowrappers.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg) {') + if node.call_expr.return_type != ast.void_type { + if g.pref.os == .windows { + g.gowrappers.write_string('\t*(($s_ret_typ*)(arg->ret_ptr)) = ') + } else { + g.gowrappers.writeln('\t$s_ret_typ* ret_ptr = malloc(sizeof($s_ret_typ));') + g.gowrappers.write_string('\t*ret_ptr = ') + } + } else { + g.gowrappers.write_string('\t') + } + if expr.is_method { + unwrapped_rec_type := g.unwrap_generic(expr.receiver_type) + typ_sym := g.table.get_type_symbol(unwrapped_rec_type) + if typ_sym.kind == .interface_ + && (typ_sym.info as ast.Interface).defines_method(expr.name) { + rec_cc_type := g.cc_type(unwrapped_rec_type, false) + receiver_type_name := util.no_dots(rec_cc_type) + g.gowrappers.write_string('${c_name(receiver_type_name)}_name_table[') + g.gowrappers.write_string('arg->arg0') + dot := if expr.left_type.is_ptr() { '->' } else { '.' } + mname := c_name(expr.name) + g.gowrappers.write_string('${dot}_typ]._method_${mname}(') + g.gowrappers.write_string('arg->arg0') + g.gowrappers.write_string('${dot}_object') + } else { + g.gowrappers.write_string('${name}(') + g.gowrappers.write_string('arg->arg0') + } + if expr.args.len > 0 { + g.gowrappers.write_string(', ') + } + } else { + g.gowrappers.write_string('${name}(') + } + if expr.args.len > 0 { + mut has_cast := false + for i in 0 .. expr.args.len { + if g.table.get_type_symbol(expr.expected_arg_types[i]).kind == .interface_ + && g.table.get_type_symbol(expr.args[i].typ).kind != .interface_ { + has_cast = true + break + } + } + if has_cast { + pos := g.out.len + g.call_args(expr) + mut call_args_str := g.out.after(pos) + g.out.go_back(call_args_str.len) + mut rep_group := []string{cap: 2 * expr.args.len} + for i in 0 .. expr.args.len { + rep_group << g.expr_string(expr.args[i].expr) + rep_group << 'arg->arg${i + 1}' + } + call_args_str = call_args_str.replace_each(rep_group) + g.gowrappers.write_string(call_args_str) + } else { + for i in 0 .. expr.args.len { + g.gowrappers.write_string('arg->arg${i + 1}') + if i != expr.args.len - 1 { + g.gowrappers.write_string(', ') + } + } + } + } + g.gowrappers.writeln(');') + g.gowrappers.writeln('\tfree(arg);') + if g.pref.os != .windows && node.call_expr.return_type != ast.void_type { + g.gowrappers.writeln('\treturn ret_ptr;') + } else { + g.gowrappers.writeln('\treturn 0;') + } + g.gowrappers.writeln('}') + g.threaded_fns << name + } + if node.is_expr { + g.empty_line = false + g.write(line) + g.write(handle) + } +} + +fn (mut g Gen) as_cast(node ast.AsCast) { + // Make sure the sum type can be cast to this type (the types + // are the same), otherwise panic. + // g.insert_before(' + styp := g.typ(node.typ) + sym := g.table.get_type_symbol(node.typ) + mut expr_type_sym := g.table.get_type_symbol(node.expr_type) + if mut expr_type_sym.info is ast.SumType { + dot := if node.expr_type.is_ptr() { '->' } else { '.' } + g.write('/* as */ *($styp*)__as_cast(') + g.write('(') + g.expr(node.expr) + g.write(')') + g.write(dot) + g.write('_$sym.cname,') + g.write('(') + g.expr(node.expr) + g.write(')') + g.write(dot) + // g.write('typ, /*expected:*/$node.typ)') + sidx := g.type_sidx(node.typ) + expected_sym := g.table.get_type_symbol(node.typ) + g.write('_typ, $sidx) /*expected idx: $sidx, name: $expected_sym.name */ ') + + // fill as cast name table + for variant in expr_type_sym.info.variants { + idx := u32(variant).str() + if idx in g.as_cast_type_names { + continue + } + variant_sym := g.table.get_type_symbol(variant) + g.as_cast_type_names[idx] = variant_sym.name + } + } else if expr_type_sym.kind == .interface_ && sym.kind == .interface_ { + g.write('I_${expr_type_sym.cname}_as_I_${sym.cname}(') + if node.expr_type.is_ptr() { + g.write('*') + } + g.expr(node.expr) + g.write(')') + + mut info := expr_type_sym.info as ast.Interface + if node.typ !in info.conversions { + left_variants := g.table.iface_types[expr_type_sym.name] + right_variants := g.table.iface_types[sym.name] + info.conversions[node.typ] = left_variants.filter(it in right_variants) + } + expr_type_sym.info = info + } else { + g.expr(node.expr) + } +} + +fn (g Gen) as_cast_name_table() string { + if g.as_cast_type_names.len == 0 { + return 'new_array_from_c_array(1, 1, sizeof(VCastTypeIndexName), _MOV((VCastTypeIndexName[1]){(VCastTypeIndexName){.tindex = 0,.tname = _SLIT("unknown")}}));\n' + } + mut name_ast := strings.new_builder(1024) + casts_len := g.as_cast_type_names.len + 1 + name_ast.writeln('new_array_from_c_array($casts_len, $casts_len, sizeof(VCastTypeIndexName), _MOV((VCastTypeIndexName[$casts_len]){') + name_ast.writeln('\t\t (VCastTypeIndexName){.tindex = 0, .tname = _SLIT("unknown")}') + for key, value in g.as_cast_type_names { + name_ast.writeln('\t\t, (VCastTypeIndexName){.tindex = $key, .tname = _SLIT("$value")}') + } + name_ast.writeln('\t}));\n') + return name_ast.str() +} + +// Generates interface table and interface indexes +fn (mut g Gen) interface_table() string { + mut sb := strings.new_builder(100) + mut conversion_functions := strings.new_builder(100) + for ityp in g.table.type_symbols { + if ityp.kind != .interface_ { + continue + } + inter_info := ityp.info as ast.Interface + if inter_info.is_generic { + continue + } + // interface_name is for example Speaker + interface_name := ityp.cname + // generate a struct that references interface methods + methods_struct_name := 'struct _${interface_name}_interface_methods' + mut methods_struct_def := strings.new_builder(100) + methods_struct_def.writeln('$methods_struct_name {') + mut methodidx := map[string]int{} + for k, method in inter_info.methods { + methodidx[method.name] = k + ret_styp := g.typ(method.return_type) + methods_struct_def.write_string('\t$ret_styp (*_method_${c_name(method.name)})(void* _') + // the first param is the receiver, it's handled by `void*` above + for i in 1 .. method.params.len { + arg := method.params[i] + methods_struct_def.write_string(', ${g.typ(arg.typ)} $arg.name') + } + // TODO g.fn_args(method.args[1..]) + methods_struct_def.writeln(');') + } + methods_struct_def.writeln('};') + // generate an array of the interface methods for the structs using the interface + // as well as case functions from the struct to the interface + mut methods_struct := strings.new_builder(100) + // + iname_table_length := inter_info.types.len + if iname_table_length == 0 { + // msvc can not process `static struct x[0] = {};` + methods_struct.writeln('$methods_struct_name ${interface_name}_name_table[1];') + } else { + if g.pref.build_mode != .build_module { + methods_struct.writeln('$methods_struct_name ${interface_name}_name_table[$iname_table_length] = {') + } else { + methods_struct.writeln('$methods_struct_name ${interface_name}_name_table[$iname_table_length];') + } + } + mut cast_functions := strings.new_builder(100) + mut methods_wrapper := strings.new_builder(100) + methods_wrapper.writeln('// Methods wrapper for interface "$interface_name"') + mut already_generated_mwrappers := map[string]int{} + iinidx_minimum_base := 1000 // NB: NOT 0, to avoid map entries set to 0 later, so `if already_generated_mwrappers[name] > 0 {` works. + mut current_iinidx := iinidx_minimum_base + for st in inter_info.types { + st_sym := g.table.get_type_symbol(st) + // cctype is the Cleaned Concrete Type name, *without ptr*, + // i.e. cctype is always just Cat, not Cat_ptr: + cctype := g.cc_type(st, true) + $if debug_interface_table ? { + eprintln( + '>> interface name: $ityp.name | concrete type: $st.debug() | st symname: ' + + st_sym.name) + } + // Speaker_Cat_index = 0 + interface_index_name := '_${interface_name}_${cctype}_index' + if already_generated_mwrappers[interface_index_name] > 0 { + continue + } + already_generated_mwrappers[interface_index_name] = current_iinidx + current_iinidx++ + if ityp.name != 'vweb.DbInterface' { // TODO remove this + // eprintln('>>> current_iinidx: ${current_iinidx-iinidx_minimum_base} | interface_index_name: $interface_index_name') + sb.writeln('static $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x);') + mut cast_struct := strings.new_builder(100) + cast_struct.writeln('($interface_name) {') + cast_struct.writeln('\t\t._$cctype = x,') + cast_struct.writeln('\t\t._typ = $interface_index_name,') + for field in inter_info.fields { + cname := c_name(field.name) + field_styp := g.typ(field.typ) + if _ := st_sym.find_field(field.name) { + cast_struct.writeln('\t\t.$cname = ($field_styp*)((char*)x + __offsetof_ptr(x, $cctype, $cname)),') + } else { + // the field is embedded in another struct + cast_struct.write_string('\t\t.$cname = ($field_styp*)((char*)x') + if st == ast.voidptr_type { + cast_struct.write_string('/*.... ast.voidptr_type */') + } else { + for embed_type in st_sym.struct_info().embeds { + embed_sym := g.table.get_type_symbol(embed_type) + if _ := embed_sym.find_field(field.name) { + cast_struct.write_string(' + __offsetof_ptr(x, $cctype, $embed_sym.embed_name()) + __offsetof_ptr(x, $embed_sym.cname, $cname)') + break + } + } + } + cast_struct.writeln('),') + } + } + cast_struct.write_string('\t}') + cast_struct_str := cast_struct.str() + + cast_functions.writeln(' +// Casting functions for converting "$cctype" to interface "$interface_name" +static inline $interface_name I_${cctype}_to_Interface_${interface_name}($cctype* x) { + return $cast_struct_str; +}') + } + + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t{') + } + if st == ast.voidptr_type { + for mname, _ in methodidx { + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t\t._method_${c_name(mname)} = (void*) 0,') + } + } + } + mut methods := st_sym.methods + match st_sym.info { + ast.Struct, ast.Interface, ast.SumType { + if st_sym.info.parent_type.has_flag(.generic) { + parent_sym := g.table.get_type_symbol(st_sym.info.parent_type) + for method in parent_sym.methods { + if method.name in methodidx { + methods << st_sym.find_method_with_generic_parent(method.name) or { + continue + } + } + } + } + } + else {} + } + for method in methods { + mut name := method.name + if inter_info.parent_type.has_flag(.generic) { + parent_sym := g.table.get_type_symbol(inter_info.parent_type) + match mut parent_sym.info { + ast.Struct, ast.Interface, ast.SumType { + name = g.generic_fn_name(parent_sym.info.concrete_types, method.name, + false) + } + else {} + } + } + + if method.name !in methodidx { + // a method that is not part of the interface should be just skipped + continue + } + // .speak = Cat_speak + if st_sym.info is ast.Struct { + if st_sym.info.parent_type.has_flag(.generic) { + name = g.generic_fn_name(st_sym.info.concrete_types, method.name, + false) + } + } + mut method_call := '${cctype}_$name' + if !method.params[0].typ.is_ptr() { + // inline void Cat_speak_Interface_Animal_method_wrapper(Cat c) { return Cat_speak(*c); } + iwpostfix := '_Interface_${interface_name}_method_wrapper' + methods_wrapper.write_string('static inline ${g.typ(method.return_type)} $method_call${iwpostfix}(') + // + params_start_pos := g.out.len + mut params := method.params.clone() + // hack to mutate typ + params[0] = ast.Param{ + ...params[0] + typ: params[0].typ.set_nr_muls(1) + } + fargs, _, _ := g.fn_args(params, voidptr(0)) + methods_wrapper.write_string(g.out.cut_last(g.out.len - params_start_pos)) + methods_wrapper.writeln(') {') + methods_wrapper.write_string('\t') + if method.return_type != ast.void_type { + methods_wrapper.write_string('return ') + } + methods_wrapper.writeln('${method_call}(*${fargs.join(', ')});') + methods_wrapper.writeln('}') + // .speak = Cat_speak_Interface_Animal_method_wrapper + method_call += iwpostfix + } + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t\t._method_${c_name(method.name)} = (void*) $method_call,') + } + } + if g.pref.build_mode != .build_module { + methods_struct.writeln('\t},') + } + iin_idx := already_generated_mwrappers[interface_index_name] - iinidx_minimum_base + if g.pref.build_mode != .build_module { + sb.writeln('const int $interface_index_name = $iin_idx;') + } else { + sb.writeln('extern const int $interface_index_name;') + } + } + for vtyp, variants in inter_info.conversions { + vsym := g.table.get_type_symbol(vtyp) + conversion_functions.write_string('static inline bool I_${interface_name}_is_I_${vsym.cname}($interface_name x) {\n\treturn ') + for i, variant in variants { + variant_sym := g.table.get_type_symbol(variant) + if i > 0 { + conversion_functions.write_string(' || ') + } + conversion_functions.write_string('(x._typ == _${interface_name}_${variant_sym.cname}_index)') + } + conversion_functions.writeln(';\n}') + + conversion_functions.writeln('static inline $vsym.cname I_${interface_name}_as_I_${vsym.cname}($interface_name x) {') + for variant in variants { + variant_sym := g.table.get_type_symbol(variant) + conversion_functions.writeln('\tif (x._typ == _${interface_name}_${variant_sym.cname}_index) return I_${variant_sym.cname}_to_Interface_${vsym.cname}(x._$variant_sym.cname);') + } + pmessage := 'string__plus(string__plus(tos3("`as_cast`: cannot convert "), tos3(v_typeof_interface_${interface_name}(x._typ))), tos3(" to ${util.strip_main_name(vsym.name)}"))' + if g.pref.is_debug { + // TODO: actually return a valid position here + conversion_functions.write_string('\tpanic_debug(1, tos3("builtin.v"), tos3("builtin"), tos3("__as_cast"), ') + conversion_functions.write_string(pmessage) + conversion_functions.writeln(');') + } else { + conversion_functions.write_string('\t_v_panic(') + conversion_functions.write_string(pmessage) + conversion_functions.writeln(');') + } + conversion_functions.writeln('\treturn ($vsym.cname){0};') + conversion_functions.writeln('}') + } + sb.writeln('// ^^^ number of types for interface $interface_name: ${current_iinidx - iinidx_minimum_base}') + if iname_table_length == 0 { + methods_struct.writeln('') + } else { + if g.pref.build_mode != .build_module { + methods_struct.writeln('};') + } + } + // add line return after interface index declarations + sb.writeln('') + if inter_info.methods.len > 0 { + sb.writeln(methods_wrapper.str()) + sb.writeln(methods_struct_def.str()) + sb.writeln(methods_struct.str()) + } + sb.writeln(cast_functions.str()) + } + sb.writeln(conversion_functions.str()) + return sb.str() +} + +fn (mut g Gen) panic_debug_info(pos token.Position) (int, string, string, string) { + paline := pos.line_nr + 1 + if isnil(g.fn_decl) { + return paline, '', 'main', 'C._vinit' + } + pafile := g.fn_decl.file.replace('\\', '/') + pafn := g.fn_decl.name.after('.') + pamod := g.fn_decl.modname() + return paline, pafile, pamod, pafn +} + +pub fn get_guarded_include_text(iname string, imessage string) string { + res := ' + |#if defined(__has_include) + | + |#if __has_include($iname) + |#include $iname + |#else + |#error VERROR_MESSAGE $imessage + |#endif + | + |#else + |#include $iname + |#endif + '.strip_margin() + return res +} + +fn (mut g Gen) trace(fbase string, message string) { + if g.file.path_base == fbase { + println('> g.trace | ${fbase:-10s} | $message') + } +} + +pub fn (mut g Gen) get_array_depth(el_typ ast.Type) int { + typ := g.unwrap_generic(el_typ) + sym := g.table.get_final_type_symbol(typ) + if sym.kind == .array { + info := sym.info as ast.Array + return 1 + g.get_array_depth(info.elem_type) + } else { + return 0 + } +} + +// returns true if `t` includes any pointer(s) - during garbage collection heap regions +// that contain no pointers do not have to be scanned +pub fn (mut g Gen) contains_ptr(el_typ ast.Type) bool { + if el_typ.is_ptr() || el_typ.is_pointer() { + return true + } + typ := g.unwrap_generic(el_typ) + if typ.is_ptr() { + return true + } + sym := g.table.get_final_type_symbol(typ) + if sym.language != .v { + return true + } + match sym.kind { + .i8, .i16, .int, .i64, .byte, .u16, .u32, .u64, .f32, .f64, .char, .size_t, .rune, .bool, + .enum_ { + return false + } + .array_fixed { + info := sym.info as ast.ArrayFixed + return g.contains_ptr(info.elem_type) + } + .struct_ { + info := sym.info as ast.Struct + for embed in info.embeds { + if g.contains_ptr(embed) { + return true + } + } + for field in info.fields { + if g.contains_ptr(field.typ) { + return true + } + } + return false + } + .aggregate { + info := sym.info as ast.Aggregate + for atyp in info.types { + if g.contains_ptr(atyp) { + return true + } + } + return false + } + .multi_return { + info := sym.info as ast.MultiReturn + for mrtyp in info.types { + if g.contains_ptr(mrtyp) { + return true + } + } + return false + } + else { + return true + } + } +} + +fn (mut g Gen) check_noscan(elem_typ ast.Type) string { + if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { + if !g.contains_ptr(elem_typ) { + return '_noscan' + } + } + return '' +} diff --git a/v_windows/v/vlib/v/gen/c/cheaders.v b/v_windows/v/vlib/v/gen/c/cheaders.v new file mode 100644 index 0000000..1c03c6f --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/cheaders.v @@ -0,0 +1,718 @@ +module c + +import strings +import v.pref + +// NB: @@@ here serve as placeholders. +// They will be replaced with correct strings +// for each constant, during C code generation. + +// V_COMMIT_HASH is generated by cmd/tools/gen_vc.v . +const c_commit_hash_default = ' +#ifndef V_COMMIT_HASH + #define V_COMMIT_HASH "@@@" +#endif +' + +// V_CURRENT_COMMIT_HASH is updated, when V is rebuilt inside a git repo. +const c_current_commit_hash_default = ' +#ifndef V_CURRENT_COMMIT_HASH + #define V_CURRENT_COMMIT_HASH "@@@" +#endif +' + +const c_concurrency_helpers = ' +typedef struct __shared_map __shared_map; +struct __shared_map { map val; sync__RwMutex mtx; }; +static inline voidptr __dup_shared_map(voidptr src, int sz) { + __shared_map* dest = memdup(src, sz); + sync__RwMutex_init(&dest->mtx); + return dest; +} +typedef struct __shared_array __shared_array; +struct __shared_array { array val; sync__RwMutex mtx; }; +static inline voidptr __dup_shared_array(voidptr src, int sz) { + __shared_array* dest = memdup(src, sz); + sync__RwMutex_init(&dest->mtx); + return dest; +} +static inline void __sort_ptr(uintptr_t a[], bool b[], int l) { + for (int i=1; i<l; i++) { + uintptr_t ins = a[i]; + bool insb = b[i]; + int j = i; + while(j>0 && a[j-1] > ins) { + a[j] = a[j-1]; + b[j] = b[j-1]; + j--; + } + a[j] = ins; + b[j] = insb; + } +} +' + +// Heavily based on Chris Wellons's work +// https://nullprogram.com/blog/2017/01/08/ + +fn c_closure_helpers(pref &pref.Preferences) string { + if pref.os == .windows { + verror('closures are not implemented on Windows yet') + } + if pref.arch != .amd64 { + verror('closures are not implemented on this architecture yet: $pref.arch') + } + mut builder := strings.new_builder(2048) + if pref.os != .windows { + builder.writeln('#include <sys/mman.h>') + } + if pref.arch == .amd64 { + builder.write_string(' +static unsigned char __closure_thunk[6][13] = { + { + 0x48, 0x8b, 0x3d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x35, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x15, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x48, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x4C, 0x8b, 0x05, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, { + 0x4C, 0x8b, 0x0d, 0xe9, 0xff, 0xff, 0xff, + 0xff, 0x25, 0xeb, 0xff, 0xff, 0xff + }, +}; +') + } + builder.write_string(' +static void __closure_set_data(void *closure, void *data) { + void **p = closure; + p[-2] = data; +} + +static void __closure_set_function(void *closure, void *f) { + void **p = closure; + p[-1] = f; +} +') + if pref.os != .windows { + builder.write_string(' +static void * __closure_create(void *f, int nargs, void *userdata) { + long page_size = sysconf(_SC_PAGESIZE); + int prot = PROT_READ | PROT_WRITE; + int flags = MAP_ANONYMOUS | MAP_PRIVATE; + char *p = mmap(0, page_size * 2, prot, flags, -1, 0); + if (p == MAP_FAILED) + return 0; + void *closure = p + page_size; + memcpy(closure, __closure_thunk[nargs - 1], sizeof(__closure_thunk[0])); + mprotect(closure, page_size, PROT_READ | PROT_EXEC); + __closure_set_function(closure, f); + __closure_set_data(closure, userdata); + return closure; +} + +static void __closure_destroy(void *closure) { + long page_size = sysconf(_SC_PAGESIZE); + munmap((char *)closure - page_size, page_size * 2); +} +') + } + return builder.str() +} + +const c_common_macros = ' +#define EMPTY_VARG_INITIALIZATION 0 +#define EMPTY_STRUCT_DECLARATION +#define EMPTY_STRUCT_INITIALIZATION +// Due to a tcc bug, the length of an array needs to be specified, but GCC crashes if it is... +#define EMPTY_ARRAY_OF_ELEMS(x,n) (x[]) +#define TCCSKIP(x) x + +#define __NOINLINE __attribute__((noinline)) +#define __IRQHANDLER __attribute__((interrupt)) + +#define __V_architecture 0 +#if defined(__x86_64__) + #define __V_amd64 1 + #undef __V_architecture + #define __V_architecture 1 +#endif + +#if defined(__aarch64__) || defined(__arm64__) + #define __V_arm64 1 + #undef __V_architecture + #define __V_architecture 2 +#endif + +// Using just __GNUC__ for detecting gcc, is not reliable because other compilers define it too: +#ifdef __GNUC__ + #define __V_GCC__ +#endif +#ifdef __TINYC__ + #undef __V_GCC__ +#endif +#ifdef __cplusplus + #undef __V_GCC__ +#endif +#ifdef __clang__ + #undef __V_GCC__ +#endif +#ifdef _MSC_VER + #undef __V_GCC__ + #undef EMPTY_STRUCT_INITIALIZATION + #define EMPTY_STRUCT_INITIALIZATION 0 +#endif + +#ifdef __TINYC__ + #undef EMPTY_STRUCT_DECLARATION + #define EMPTY_STRUCT_DECLARATION char _dummy + #undef EMPTY_ARRAY_OF_ELEMS + #define EMPTY_ARRAY_OF_ELEMS(x,n) (x[n]) + #undef __NOINLINE + #undef __IRQHANDLER + // tcc does not support inlining at all + #define __NOINLINE + #define __IRQHANDLER + #undef TCCSKIP + #define TCCSKIP(x) + // #include <byteswap.h> + #ifndef _WIN32 + #include <execinfo.h> + int tcc_backtrace(const char *fmt, ...); + #endif +#endif + +// Use __offsetof_ptr instead of __offset_of, when you *do* have a valid pointer, to avoid UB: +#ifndef __offsetof_ptr + #define __offsetof_ptr(ptr,PTYPE,FIELDNAME) ((size_t)((byte *)&((PTYPE *)ptr)->FIELDNAME - (byte *)ptr)) +#endif + +// for __offset_of +#ifndef __offsetof + #define __offsetof(PTYPE,FIELDNAME) ((size_t)((char *)&((PTYPE *)0)->FIELDNAME - (char *)0)) +#endif + +#define OPTION_CAST(x) (x) + +#ifndef V64_PRINTFORMAT + #ifdef PRIx64 + #define V64_PRINTFORMAT "0x%"PRIx64 + #elif defined(__WIN32__) + #define V64_PRINTFORMAT "0x%I64x" + #elif defined(__linux__) && defined(__LP64__) + #define V64_PRINTFORMAT "0x%lx" + #else + #define V64_PRINTFORMAT "0x%llx" + #endif +#endif + +#if defined(_WIN32) || defined(__CYGWIN__) + #define VV_EXPORTED_SYMBOL extern __declspec(dllexport) + #define VV_LOCAL_SYMBOL static +#else + // 4 < gcc < 5 is used by some older Ubuntu LTS and Centos versions, + // and does not support __has_attribute(visibility) ... + #ifndef __has_attribute + #define __has_attribute(x) 0 // Compatibility with non-clang compilers. + #endif + #if (defined(__GNUC__) && (__GNUC__ >= 4)) || (defined(__clang__) && __has_attribute(visibility)) + #ifdef ARM + #define VV_EXPORTED_SYMBOL extern __attribute__((externally_visible,visibility("default"))) + #else + #define VV_EXPORTED_SYMBOL extern __attribute__((visibility("default"))) + #endif + #define VV_LOCAL_SYMBOL __attribute__ ((visibility ("hidden"))) + #else + #define VV_EXPORTED_SYMBOL extern + #define VV_LOCAL_SYMBOL static + #endif +#endif + +#ifdef __cplusplus + #include <utility> + #define _MOV std::move +#else + #define _MOV +#endif + +// tcc does not support has_include properly yet, turn it off completely +#if defined(__TINYC__) && defined(__has_include) +#undef __has_include +#endif + +#if !defined(VNORETURN) + #if defined(__TINYC__) + #include <stdnoreturn.h> + #define VNORETURN noreturn + #endif + # if !defined(__TINYC__) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + # define VNORETURN _Noreturn + # elif defined(__GNUC__) && __GNUC__ >= 2 + # define VNORETURN __attribute__((noreturn)) + # endif + #ifndef VNORETURN + #define VNORETURN + #endif +#endif + +#if !defined(VUNREACHABLE) + #if defined(__GNUC__) && !defined(__clang__) + #define V_GCC_VERSION (__GNUC__ * 10000L + __GNUC_MINOR__ * 100L + __GNUC_PATCHLEVEL__) + #if (V_GCC_VERSION >= 40500L) + #define VUNREACHABLE() do { __builtin_unreachable(); } while (0) + #endif + #endif + #if defined(__clang__) && defined(__has_builtin) + #if __has_builtin(__builtin_unreachable) + #define VUNREACHABLE() do { __builtin_unreachable(); } while (0) + #endif + #endif + #ifndef VUNREACHABLE + #define VUNREACHABLE() do { } while (0) + #endif + #if defined(__FreeBSD__) && defined(__TINYC__) + #define VUNREACHABLE() do { } while (0) + #endif +#endif + +//likely and unlikely macros +#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + #define _likely_(x) __builtin_expect(x,1) + #define _unlikely_(x) __builtin_expect(x,0) +#else + #define _likely_(x) (x) + #define _unlikely_(x) (x) +#endif + +' + +const c_unsigned_comparison_functions = ' +// unsigned/signed comparisons +static inline bool _us32_gt(uint32_t a, int32_t b) { return a > INT32_MAX || (int32_t)a > b; } +static inline bool _us32_ge(uint32_t a, int32_t b) { return a >= INT32_MAX || (int32_t)a >= b; } +static inline bool _us32_eq(uint32_t a, int32_t b) { return a <= INT32_MAX && (int32_t)a == b; } +static inline bool _us32_ne(uint32_t a, int32_t b) { return a > INT32_MAX || (int32_t)a != b; } +static inline bool _us32_le(uint32_t a, int32_t b) { return a <= INT32_MAX && (int32_t)a <= b; } +static inline bool _us32_lt(uint32_t a, int32_t b) { return a < INT32_MAX && (int32_t)a < b; } +static inline bool _us64_gt(uint64_t a, int64_t b) { return a > INT64_MAX || (int64_t)a > b; } +static inline bool _us64_ge(uint64_t a, int64_t b) { return a >= INT64_MAX || (int64_t)a >= b; } +static inline bool _us64_eq(uint64_t a, int64_t b) { return a <= INT64_MAX && (int64_t)a == b; } +static inline bool _us64_ne(uint64_t a, int64_t b) { return a > INT64_MAX || (int64_t)a != b; } +static inline bool _us64_le(uint64_t a, int64_t b) { return a <= INT64_MAX && (int64_t)a <= b; } +static inline bool _us64_lt(uint64_t a, int64_t b) { return a < INT64_MAX && (int64_t)a < b; } +' + +const c_helper_macros = '//============================== HELPER C MACROS =============================*/ +// _SLIT0 is used as NULL string for literal arguments +// `"" s` is used to enforce a string literal argument +#define _SLIT0 (string){.str=(byteptr)(""), .len=0, .is_lit=1} +#define _SLIT(s) ((string){.str=(byteptr)("" s), .len=(sizeof(s)-1), .is_lit=1}) +#define _SLEN(s, n) ((string){.str=(byteptr)("" s), .len=n, .is_lit=1}) + +// take the address of an rvalue +#define ADDR(type, expr) (&((type[]){expr}[0])) + +// copy something to the heap +#define HEAP(type, expr) ((type*)memdup((void*)&((type[]){expr}[0]), sizeof(type))) +#define HEAP_noscan(type, expr) ((type*)memdup_noscan((void*)&((type[]){expr}[0]), sizeof(type))) + +#define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many(arr, tmp.data, tmp.len);} +#define _PUSH_MANY_noscan(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array_push_many_noscan(arr, tmp.data, tmp.len);} +' + +const c_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_macros + + r' +// c_headers +typedef int (*qsort_callback_func)(const void*, const void*); +#include <stdio.h> // TODO remove all these includes, define all function signatures and types manually +#include <stdlib.h> +#include <string.h> + +#if defined(_WIN32) || defined(__CYGWIN__) + #define VV_EXPORTED_SYMBOL extern __declspec(dllexport) + #define VV_LOCAL_SYMBOL static +#else + // 4 < gcc < 5 is used by some older Ubuntu LTS and Centos versions, + // and does not support __has_attribute(visibility) ... + #ifndef __has_attribute + #define __has_attribute(x) 0 // Compatibility with non-clang compilers. + #endif + #if (defined(__GNUC__) && (__GNUC__ >= 4)) || (defined(__clang__) && __has_attribute(visibility)) + #ifdef ARM + #define VV_EXPORTED_SYMBOL extern __attribute__((externally_visible,visibility("default"))) + #else + #define VV_EXPORTED_SYMBOL extern __attribute__((visibility("default"))) + #endif + #define VV_LOCAL_SYMBOL __attribute__ ((visibility ("hidden"))) + #else + #define VV_EXPORTED_SYMBOL extern + #define VV_LOCAL_SYMBOL static + #endif +#endif + +#if defined(__TINYC__) && defined(__has_include) +// tcc does not support has_include properly yet, turn it off completely +#undef __has_include +#endif + +#ifndef _WIN32 + #if defined __has_include + #if __has_include (<execinfo.h>) + #include <execinfo.h> + #else + // Most probably musl OR __ANDROID__ ... + int backtrace (void **__array, int __size) { return 0; } + char **backtrace_symbols (void *const *__array, int __size){ return 0; } + void backtrace_symbols_fd (void *const *__array, int __size, int __fd){} + #endif + #endif +#endif + +#include <stdarg.h> // for va_list + +//================================== GLOBALS =================================*/ +int load_so(byteptr); +void reload_so(); +void _vinit(int ___argc, voidptr ___argv); +void _vcleanup(); +#define sigaction_size sizeof(sigaction); +#define _ARR_LEN(a) ( (sizeof(a)) / (sizeof(a[0])) ) + +void v_free(voidptr ptr); +voidptr memdup(voidptr src, int sz); + +#if INTPTR_MAX == INT32_MAX + #define TARGET_IS_32BIT 1 +#elif INTPTR_MAX == INT64_MAX + #define TARGET_IS_64BIT 1 +#else + #error "The environment is not 32 or 64-bit." +#endif + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) + #define TARGET_ORDER_IS_BIG 1 +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ || defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_AMD64) || defined(_M_X64) || defined(_M_IX86) + #define TARGET_ORDER_IS_LITTLE 1 +#else + #error "Unknown architecture endianness" +#endif + +#ifndef _WIN32 + #include <ctype.h> + #include <locale.h> // tolower + #include <sys/time.h> + #include <unistd.h> // sleep + extern char **environ; +#endif + +#if defined(__CYGWIN__) && !defined(_WIN32) + #error Cygwin is not supported, please use MinGW or Visual Studio. +#endif + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__vinix__) || defined(__serenity__) || defined(__sun) + #include <sys/types.h> + #include <sys/wait.h> // os__wait uses wait on nix +#endif + +#ifdef __OpenBSD__ + #include <sys/types.h> + #include <sys/resource.h> + #include <sys/wait.h> // os__wait uses wait on nix +#endif + +#ifdef __NetBSD__ + #include <sys/wait.h> // os__wait uses wait on nix +#endif + +#ifdef _WIN32 + #define WINVER 0x0600 + #ifdef _WIN32_WINNT + #undef _WIN32_WINNT + #endif + #define _WIN32_WINNT 0x0600 + #ifndef WIN32_FULL + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef _UNICODE + #define _UNICODE + #endif + #ifndef UNICODE + #define UNICODE + #endif + #include <windows.h> + + #include <io.h> // _waccess + #include <direct.h> // _wgetcwd + + #ifdef _MSC_VER + // On MSVC these are the same (as long as /volatile:ms is passed) + #define _Atomic volatile + + // MSVC cannot parse some things properly + #undef EMPTY_STRUCT_DECLARATION + #undef OPTION_CAST + + #define EMPTY_STRUCT_DECLARATION char __pad + #define OPTION_CAST(x) + #undef __NOINLINE + #undef __IRQHANDLER + #define __NOINLINE __declspec(noinline) + #define __IRQHANDLER __declspec(naked) + + #include <dbghelp.h> + #pragma comment(lib, "Dbghelp") + #endif +#else + #include <pthread.h> + #ifndef PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP + // musl does not have that + #define pthread_rwlockattr_setkind_np(a, b) + #endif +#endif + +// g_live_info is used by live.info() +static void* g_live_info = NULL; + +#if defined(__MINGW32__) || defined(__MINGW64__) || (defined(_WIN32) && defined(__TINYC__)) + #undef PRId64 + #undef PRIi64 + #undef PRIo64 + #undef PRIu64 + #undef PRIx64 + #undef PRIX64 + #define PRId64 "lld" + #define PRIi64 "lli" + #define PRIo64 "llo" + #define PRIu64 "llu" + #define PRIx64 "llx" + #define PRIX64 "llX" +#endif + +#ifdef _VFREESTANDING +#undef _VFREESTANDING +#endif +' + +const c_builtin_types = ' +//================================== builtin types ================================*/ +typedef int64_t i64; +typedef int16_t i16; +typedef int8_t i8; +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint8_t byte; +typedef uint32_t rune; +typedef float f32; +typedef double f64; +typedef int64_t int_literal; +typedef double float_literal; +typedef unsigned char* byteptr; +typedef void* voidptr; +typedef char* charptr; +typedef byte array_fixed_byte_300 [300]; + +typedef struct sync__Channel* chan; + +#ifndef __cplusplus + #ifndef bool + typedef byte bool; + #define true 1 + #define false 0 + #endif +#endif + +typedef u64 (*MapHashFn)(voidptr); +typedef bool (*MapEqFn)(voidptr, voidptr); +typedef void (*MapCloneFn)(voidptr, voidptr); +typedef void (*MapFreeFn)(voidptr); +' + +const c_bare_headers = c_helper_macros + c_unsigned_comparison_functions + c_common_macros + + ' +#define _VFREESTANDING + +typedef long unsigned int size_t; + +// Memory allocation related headers +void *malloc(size_t size); +void *calloc(size_t nitems, size_t size); +void *realloc(void *ptr, size_t size); +void *memcpy(void *dest, void *src, size_t n); +void *memset(void *s, int c, size_t n); +void *memmove(void *dest, void *src, size_t n); + +// varargs implementation, TODO: works on tcc and gcc, but is very unportable and hacky +typedef __builtin_va_list va_list; +#define va_start(a, b) __builtin_va_start(a, b) +#define va_end(a) __builtin_va_end(a) +#define va_arg(a, b) __builtin_va_arg(a, b) +#define va_copy(a, b) __builtin_va_copy(a, b) + +//================================== GLOBALS =================================*/ +int load_so(byteptr); +void reload_so(); +void _vinit(int ___argc, voidptr ___argv); +void _vcleanup(); +#define sigaction_size sizeof(sigaction); +#define _ARR_LEN(a) ( (sizeof(a)) / (sizeof(a[0])) ) + +void v_free(voidptr ptr); +voidptr memdup(voidptr src, int sz); + +' + +const c_wyhash_headers = ' +// ============== wyhash ============== +#ifndef wyhash_final_version_3 +#define wyhash_final_version_3 + +#ifndef WYHASH_CONDOM +// protections that produce different results: +// 1: normal valid behavior +// 2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" +#define WYHASH_CONDOM 1 +#endif + +#ifndef WYHASH_32BIT_MUM +// 0: normal version, slow on 32 bit systems +// 1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function +#define WYHASH_32BIT_MUM 0 +#endif + +// includes +#include <stdint.h> +#if defined(_MSC_VER) && defined(_M_X64) + #include <intrin.h> + #pragma intrinsic(_umul128) +#endif + +// 128bit multiply function +static inline uint64_t _wyrot(uint64_t x) { return (x>>32)|(x<<32); } +static inline void _wymum(uint64_t *A, uint64_t *B){ +#if(WYHASH_32BIT_MUM) + uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; + #if(WYHASH_CONDOM>1) + *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; + #else + *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; + #endif +#elif defined(__SIZEOF_INT128__) + __uint128_t r=*A; r*=*B; + #if(WYHASH_CONDOM>1) + *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); + #else + *A=(uint64_t)r; *B=(uint64_t)(r>>64); + #endif +#elif defined(_MSC_VER) && defined(_M_X64) + #if(WYHASH_CONDOM>1) + uint64_t a, b; + a=_umul128(*A,*B,&b); + *A^=a; *B^=b; + #else + *A=_umul128(*A,*B,B); + #endif +#else + uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; + uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t<rl; + lo=t+(rm1<<32); c+=lo<t; hi=rh+(rm0>>32)+(rm1>>32)+c; + #if(WYHASH_CONDOM>1) + *A^=lo; *B^=hi; + #else + *A=lo; *B=hi; + #endif +#endif +} + +// multiply and xor mix function, aka MUM +static inline uint64_t _wymix(uint64_t A, uint64_t B){ _wymum(&A,&B); return A^B; } + +// endian macros +#ifndef WYHASH_LITTLE_ENDIAN + #ifdef TARGET_ORDER_IS_LITTLE + #define WYHASH_LITTLE_ENDIAN 1 + #else + #define WYHASH_LITTLE_ENDIAN 0 + #endif +#endif + +// read functions +#if (WYHASH_LITTLE_ENDIAN) + static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} + static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} +#elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) + static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} + static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} +#elif defined(_MSC_VER) + static inline uint64_t _wyr8(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} + static inline uint64_t _wyr4(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} +#else + static inline uint64_t _wyr8(const uint8_t *p) { + uint64_t v; memcpy(&v, p, 8); + return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); + } + static inline uint64_t _wyr4(const uint8_t *p) { + uint32_t v; memcpy(&v, p, 4); + return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); + } +#endif +static inline uint64_t _wyr3(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} +// wyhash main function +static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ + const uint8_t *p=(const uint8_t *)key; seed^=*secret; uint64_t a, b; + if (_likely_(len<=16)) { + if (_likely_(len>=4)) { a=(_wyr4(p)<<32)|_wyr4(p+((len>>3)<<2)); b=(_wyr4(p+len-4)<<32)|_wyr4(p+len-4-((len>>3)<<2)); } + else if (_likely_(len>0)) { a=_wyr3(p,len); b=0; } + else a=b=0; + } else { + size_t i=len; + if (_unlikely_(i>48)) { + uint64_t see1=seed, see2=seed; + do { + seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); + see1=_wymix(_wyr8(p+16)^secret[2],_wyr8(p+24)^see1); + see2=_wymix(_wyr8(p+32)^secret[3],_wyr8(p+40)^see2); + p+=48; i-=48; + } while(_likely_(i>48)); + seed^=see1^see2; + } + while(_unlikely_(i>16)) { seed=_wymix(_wyr8(p)^secret[1],_wyr8(p+8)^seed); i-=16; p+=16; } + a=_wyr8(p+i-16); b=_wyr8(p+i-8); + } + return _wymix(secret[1]^len,_wymix(a^secret[1],b^seed)); +} +// the default secret parameters +static const uint64_t _wyp[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; + +// a useful 64bit-64bit mix function to produce deterministic pseudo random numbers that can pass BigCrush and PractRand +static inline uint64_t wyhash64(uint64_t A, uint64_t B){ A^=0xa0761d6478bd642full; B^=0xe7037ed1a0b428dbull; _wymum(&A,&B); return _wymix(A^0xa0761d6478bd642full,B^0xe7037ed1a0b428dbull);} + +// the wyrand PRNG that pass BigCrush and PractRand +static inline uint64_t wyrand(uint64_t *seed){ *seed+=0xa0761d6478bd642full; return _wymix(*seed,*seed^0xe7037ed1a0b428dbull);} + +#ifndef __vinix__ +// convert any 64 bit pseudo random numbers to uniform distribution [0,1). It can be combined with wyrand, wyhash64 or wyhash. +static inline double wy2u01(uint64_t r){ const double _wynorm=1.0/(1ull<<52); return (r>>12)*_wynorm;} + +// convert any 64 bit pseudo random numbers to APPROXIMATE Gaussian distribution. It can be combined with wyrand, wyhash64 or wyhash. +static inline double wy2gau(uint64_t r){ const double _wynorm=1.0/(1ull<<20); return ((r&0x1fffff)+((r>>21)&0x1fffff)+((r>>42)&0x1fffff))*_wynorm-3.0;} +#endif + +#if(!WYHASH_32BIT_MUM) +// fast range integer random number generation on [0,k) credit to Daniel Lemire. May not work when WYHASH_32BIT_MUM=1. It can be combined with wyrand, wyhash64 or wyhash. +static inline uint64_t wy2u0k(uint64_t r, uint64_t k){ _wymum(&r,&k); return k; } +#endif +#endif + +#define _IN_MAP(val, m) map_exists(m, val) + +' diff --git a/v_windows/v/vlib/v/gen/c/cmain.v b/v_windows/v/vlib/v/gen/c/cmain.v new file mode 100644 index 0000000..6e741a6 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/cmain.v @@ -0,0 +1,214 @@ +module c + +import v.util +import v.ast + +pub fn (mut g Gen) gen_c_main() { + if !g.has_main { + return + } + if g.pref.is_liveshared { + return + } + g.out.writeln('') + main_fn_start_pos := g.out.len + + is_sokol := 'sokol' in g.table.imports + + if (g.pref.os == .android && g.pref.is_apk) || (g.pref.os == .ios && is_sokol) { + g.gen_c_android_sokol_main() + } else { + g.gen_c_main_header() + g.writeln('\tmain__main();') + g.gen_c_main_footer() + if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list { + println(g.out.after(main_fn_start_pos)) + } + } +} + +fn (mut g Gen) gen_vlines_reset() { + if g.pref.is_vlines { + // At this point, the v files are transpiled. + // The rest is auto generated code, which will not have + // different .v source file/line numbers. + // + // TODO: calculate the proper line here, based on + // the actual C lines in all the buffers + lines_so_far := 1000000 + g.vlines_path = util.vlines_escape_path(g.pref.out_name_c, g.pref.ccompiler) + g.writeln('') + g.writeln('\n// Reset the file/line numbers') + g.writeln('\n#line $lines_so_far "$g.vlines_path"') + g.writeln('') + } +} + +fn (mut g Gen) gen_c_main_function_header() { + if g.pref.os == .windows { + if g.is_gui_app() { + $if msvc { + // This is kinda bad but I dont see a way that is better + g.writeln('#pragma comment(linker, "/SUBSYSTEM:WINDOWS")') + } + // GUI application + g.writeln('int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance, LPWSTR cmd_line, int show_cmd){') + g.writeln('\tLPWSTR full_cmd_line = GetCommandLineW(); // NB: do not use cmd_line') + g.writeln('\ttypedef LPWSTR*(WINAPI *cmd_line_to_argv)(LPCWSTR, int*);') + g.writeln('\tHMODULE shell32_module = LoadLibrary(L"shell32.dll");') + g.writeln('\tcmd_line_to_argv CommandLineToArgvW = (cmd_line_to_argv)GetProcAddress(shell32_module, "CommandLineToArgvW");') + g.writeln('\tint ___argc;') + g.writeln('\twchar_t** ___argv = CommandLineToArgvW(full_cmd_line, &___argc);') + } else { + // Console application + g.writeln('int wmain(int ___argc, wchar_t* ___argv[], wchar_t* ___envp[]){') + } + } else { + g.writeln('int main(int ___argc, char** ___argv){') + } +} + +fn (mut g Gen) gen_c_main_header() { + g.gen_c_main_function_header() + if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { + g.writeln('#if defined(_VGCBOEHM)') + if g.pref.gc_mode == .boehm_leak { + g.writeln('\tGC_set_find_leak(1);') + } + g.writeln('\tGC_INIT();') + if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] { + g.writeln('\tGC_enable_incremental();') + } + g.writeln('#endif') + } + g.writeln('\t_vinit(___argc, (voidptr)___argv);') + if g.pref.is_prof { + g.writeln('') + g.writeln('\tatexit(vprint_profile_stats);') + g.writeln('') + } + if g.pref.is_livemain { + g.generate_hotcode_reloading_main_caller() + } +} + +pub fn (mut g Gen) gen_c_main_footer() { + g.writeln('\t_vcleanup();') + g.writeln('\treturn 0;') + g.writeln('}') +} + +pub fn (mut g Gen) gen_c_android_sokol_main() { + // Weave autofree into sokol lifecycle callback(s) + if g.is_autofree { + g.writeln('// Wrapping cleanup/free callbacks for sokol to include _vcleanup() +void (*_vsokol_user_cleanup_ptr)(void); +void (*_vsokol_user_cleanup_cb_ptr)(void *); + +void (_vsokol_cleanup_cb)(void) { + if (_vsokol_user_cleanup_ptr) { + _vsokol_user_cleanup_ptr(); + } + _vcleanup(); +} + +void (_vsokol_cleanup_userdata_cb)(void* user_data) { + if (_vsokol_user_cleanup_cb_ptr) { + _vsokol_user_cleanup_cb_ptr(g_desc.user_data); + } + _vcleanup(); +} +') + } + g.writeln('// The sokol_main entry point on Android +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; (void)argv; + + _vinit(argc, (voidptr)argv); + main__main(); +') + if g.is_autofree { + g.writeln(' // Wrap user provided cleanup/free functions for sokol to be able to call _vcleanup() + if (g_desc.cleanup_cb) { + _vsokol_user_cleanup_ptr = g_desc.cleanup_cb; + g_desc.cleanup_cb = _vsokol_cleanup_cb; + } + else if (g_desc.cleanup_userdata_cb) { + _vsokol_user_cleanup_cb_ptr = g_desc.cleanup_userdata_cb; + g_desc.cleanup_userdata_cb = _vsokol_cleanup_userdata_cb; + } +') + } + g.writeln(' return g_desc;') + g.writeln('}') +} + +pub fn (mut g Gen) write_tests_definitions() { + g.includes.writeln('#include <setjmp.h> // write_tests_main') + g.definitions.writeln('int g_test_oks = 0;') + g.definitions.writeln('int g_test_fails = 0;') + g.definitions.writeln('jmp_buf g_jump_buffer;') +} + +pub fn (mut g Gen) gen_failing_error_propagation_for_test_fn(or_block ast.OrExpr, cvar_name string) { + // in test_() functions, an `opt()?` call is sugar for + // `or { cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg) }` + // and the test is considered failed + paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) + g.writeln('\tmain__cb_propagate_test_error($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), *(${cvar_name}.err.msg) );') + g.writeln('\tg_test_fails++;') + g.writeln('\tlongjmp(g_jump_buffer, 1);') +} + +pub fn (mut g Gen) gen_failing_return_error_for_test_fn(return_stmt ast.Return, cvar_name string) { + // in test_() functions, a `return error('something')` is sugar for + // `or { err := error('something') cb_propagate_test_error(@LINE, @FILE, @MOD, @FN, err.msg) return err }` + // and the test is considered failed + paline, pafile, pamod, pafn := g.panic_debug_info(return_stmt.pos) + g.writeln('\tmain__cb_propagate_test_error($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), *(${cvar_name}.err.msg) );') + g.writeln('\tg_test_fails++;') + g.writeln('\tlongjmp(g_jump_buffer, 1);') +} + +pub fn (mut g Gen) gen_c_main_for_tests() { + main_fn_start_pos := g.out.len + g.writeln('') + g.gen_c_main_function_header() + if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { + g.writeln('#if defined(_VGCBOEHM)') + if g.pref.gc_mode == .boehm_leak { + g.writeln('\tGC_set_find_leak(1);') + } + g.writeln('\tGC_INIT();') + if g.pref.gc_mode in [.boehm_incr, .boehm_incr_opt] { + g.writeln('\tGC_enable_incremental();') + } + g.writeln('#endif') + } + g.writeln('\t_vinit(___argc, (voidptr)___argv);') + all_tfuncs := g.get_all_test_function_names() + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests bt = main__start_testing($all_tfuncs.len, _SLIT("$g.pref.path"));') + } + g.writeln('') + for tname in all_tfuncs { + tcname := util.no_dots(tname) + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests_testing_step_start(&bt, _SLIT("$tcname"));') + } + g.writeln('\tif (!setjmp(g_jump_buffer)) ${tcname}();') + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests_testing_step_end(&bt);') + } + } + g.writeln('') + if g.pref.is_stats { + g.writeln('\tmain__BenchedTests_end_testing(&bt);') + } + g.writeln('\t_vcleanup();') + g.writeln('\treturn g_test_fails > 0;') + g.writeln('}') + if g.pref.printfn_list.len > 0 && 'main' in g.pref.printfn_list { + println(g.out.after(main_fn_start_pos)) + } +} diff --git a/v_windows/v/vlib/v/gen/c/comptime.v b/v_windows/v/vlib/v/gen/c/comptime.v new file mode 100644 index 0000000..9689a8e --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/comptime.v @@ -0,0 +1,676 @@ +// Copyright (c) 2019-2021 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 os +import v.ast +import v.util +import v.pref + +fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) { + g.expr(node.left) + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + // check for field.name + if node.field_expr is ast.SelectorExpr { + if node.field_expr.expr is ast.Ident { + if node.field_expr.expr.name == g.comp_for_field_var + && node.field_expr.field_name == 'name' { + g.write(c_name(g.comp_for_field_value.name)) + return + } + } + } + g.expr(node.field_expr) +} + +fn (mut g Gen) comptime_call(node ast.ComptimeCall) { + if node.is_embed { + // $embed_file('/path/to/file') + g.gen_embed_file_init(node) + return + } + if node.method_name == 'env' { + // $env('ENV_VAR_NAME') + val := util.cescaped_path(os.getenv(node.args_var)) + g.write('_SLIT("$val")') + return + } + if node.is_vweb { + is_html := node.method_name == 'html' + for stmt in node.vweb_tmpl.stmts { + if stmt is ast.FnDecl { + // insert stmts from vweb_tmpl fn + if stmt.name.starts_with('main.vweb_tmpl') { + if is_html { + g.inside_vweb_tmpl = true + } + g.stmts(stmt.stmts) + g.inside_vweb_tmpl = false + break + } + } + } + if is_html { + // return vweb html template + g.writeln('vweb__Context_html(&app->Context, _tmpl_res_$g.fn_decl.name); strings__Builder_free(&sb); string_free(&_tmpl_res_$g.fn_decl.name);') + } else { + // return $tmpl string + fn_name := g.fn_decl.name.replace('.', '__') + g.writeln('return _tmpl_res_$fn_name;') + } + return + } + g.trace_autofree('// \$method call. sym="$node.sym.name"') + if node.method_name == 'method' { + // `app.$method()` + m := node.sym.find_method(g.comp_for_method) or { return } + /* + vals := m.attrs[0].split('/') + args := vals.filter(it.starts_with(':')).map(it[1..]) + println(vals) + for val in vals { + } + */ + expand_strs := if node.args.len > 0 && m.params.len - 1 >= node.args.len { + arg := node.args[node.args.len - 1] + param := m.params[node.args.len] + + arg.expr is ast.Ident && g.table.type_to_str(arg.typ) == '[]string' + && g.table.type_to_str(param.typ) != '[]string' + } else { + false + } + // check argument length and types + if m.params.len - 1 != node.args.len && !expand_strs { + // do not generate anything if the argument lengths don't match + g.writeln('/* skipping ${node.sym.name}.$m.name due to mismatched arguments list */') + // verror('expected ${m.params.len-1} arguments to method ${node.sym.name}.$m.name, but got $node.args.len') + return + } + // TODO: check argument types + g.write('${util.no_dots(node.sym.name)}_${g.comp_for_method}(') + + // try to see if we need to pass a pointer + if node.left is ast.Ident { + scope := g.file.scope.innermost(node.pos.pos) + if v := scope.find_var(node.left.name) { + if m.params[0].typ.is_ptr() && !v.typ.is_ptr() { + g.write('&') + } + } + } + g.expr(node.left) + if m.params.len > 1 { + g.write(', ') + } + for i in 1 .. m.params.len { + if node.left is ast.Ident { + if m.params[i].name == node.left.name { + continue + } + } + if i - 1 < node.args.len - 1 { + g.expr(node.args[i - 1].expr) + g.write(', ') + } else if !expand_strs && i == node.args.len { + g.expr(node.args[i - 1].expr) + break + } else { + // last argument; try to expand if it's []string + idx := i - node.args.len + if m.params[i].typ.is_int() || m.params[i].typ.idx() == ast.bool_type_idx { + // Gets the type name and cast the string to the type with the string_<type> function + type_name := g.table.type_symbols[int(m.params[i].typ)].str() + g.write('string_${type_name}(((string*)${node.args[node.args.len - 1]}.data) [$idx])') + } else { + g.write('((string*)${node.args[node.args.len - 1]}.data) [$idx] ') + } + if i < m.params.len - 1 { + g.write(', ') + } + } + } + g.write(' ); // vweb action call with args') + return + } + mut j := 0 + for method in node.sym.methods { + // if method.return_type != ast.void_type { + if method.return_type != node.result_type { + continue + } + if method.params.len != 1 { + continue + } + // receiver := method.args[0] + // if !p.expr_var.ptr { + // p.error('`$p.expr_var.name` needs to be a reference') + // } + amp := '' // if receiver.is_mut && !p.expr_var.ptr { '&' } else { '' } + if node.is_vweb { + if j > 0 { + g.write(' else ') + } + g.write('if (string__eq($node.method_name, _SLIT("$method.name"))) ') + } + g.write('${util.no_dots(node.sym.name)}_${method.name}($amp ') + g.expr(node.left) + g.writeln(');') + j++ + } +} + +fn cgen_attrs(attrs []ast.Attr) []string { + mut res := []string{cap: attrs.len} + for attr in attrs { + // we currently don't quote 'arg' (otherwise we could just use `s := attr.str()`) + mut s := attr.name + if attr.arg.len > 0 { + s += ': $attr.arg' + } + res << '_SLIT("$s")' + } + return res +} + +fn (mut g Gen) comp_at(node ast.AtExpr) { + if node.kind == .vmod_file { + val := cnewlines(node.val.replace('\r', '')) + g.write('_SLIT("$val")') + } else { + val := node.val.replace('\\', '\\\\') + g.write('_SLIT("$val")') + } +} + +fn (mut g Gen) comp_if(node ast.IfExpr) { + if !node.is_expr && !node.has_else && node.branches.len == 1 { + if node.branches[0].stmts.len == 0 { + // empty ifdef; result of target OS != conditional => skip + return + } + if !g.pref.output_cross_c { + if node.branches[0].cond is ast.Ident { + if g.pref.os == (pref.os_from_string(node.branches[0].cond.name) or { + pref.OS._auto + }) { + // Same target OS as the conditional... + // => skip the #if defined ... #endif wrapper + // and just generate the branch statements: + g.indent-- + g.stmts(node.branches[0].stmts) + g.indent++ + return + } + } + } + } + line := if node.is_expr { + stmt_str := g.go_before_stmt(0) + g.write(util.tabs(g.indent)) + stmt_str.trim_space() + } else { + '' + } + mut comp_if_stmts_skip := false // don't write any statements if the condition is false + // (so that for example windows calls don't get generated inside `$if macos` which + // will lead to compilation errors) + + for i, branch in node.branches { + start_pos := g.out.len + if i == node.branches.len - 1 && node.has_else { + g.writeln('#else') + comp_if_stmts_skip = false + } else { + if i == 0 { + g.write('#if ') + } else { + g.write('#elif ') + } + comp_if_stmts_skip = !g.comp_if_cond(branch.cond, branch.pkg_exist) + g.writeln('') + } + expr_str := g.out.last_n(g.out.len - start_pos).trim_space() + g.defer_ifdef = expr_str + if node.is_expr { + len := branch.stmts.len + if len > 0 { + last := branch.stmts[len - 1] as ast.ExprStmt + if len > 1 { + tmp := g.new_tmp_var() + styp := g.typ(last.typ) + g.indent++ + g.writeln('$styp $tmp;') + g.writeln('{') + g.stmts(branch.stmts[0..len - 1]) + g.write('\t$tmp = ') + g.stmt(last) + g.writeln('}') + g.indent-- + g.writeln('$line $tmp;') + } else { + g.write('$line ') + g.stmt(last) + } + } + } else { + // Only wrap the contents in {} if we're inside a function, not on the top level scope + should_create_scope := g.fn_decl != 0 + if should_create_scope { + g.writeln('{') + } + if !comp_if_stmts_skip { + g.stmts(branch.stmts) + } + if should_create_scope { + g.writeln('}') + } + } + g.defer_ifdef = '' + } + g.writeln('#endif') +} + +/* +// returning `false` means the statements inside the $if can be skipped +*/ +// returns the value of the bool comptime expression +fn (mut g Gen) comp_if_cond(cond ast.Expr, pkg_exist bool) bool { + match cond { + ast.BoolLiteral { + g.expr(cond) + return true + } + ast.ParExpr { + g.write('(') + is_cond_true := g.comp_if_cond(cond.expr, pkg_exist) + g.write(')') + return is_cond_true + } + ast.PrefixExpr { + g.write(cond.op.str()) + return g.comp_if_cond(cond.right, pkg_exist) + } + ast.PostfixExpr { + ifdef := g.comp_if_to_ifdef((cond.expr as ast.Ident).name, true) or { + verror(err.msg) + return false + } + g.write('defined($ifdef)') + return true + } + ast.InfixExpr { + match cond.op { + .and, .logical_or { + l := g.comp_if_cond(cond.left, pkg_exist) + g.write(' $cond.op ') + r := g.comp_if_cond(cond.right, pkg_exist) + return if cond.op == .and { l && r } else { l || r } + } + .key_is, .not_is { + left := cond.left + mut name := '' + mut exp_type := ast.Type(0) + got_type := (cond.right as ast.TypeNode).typ + // Handle `$if x is Interface {` + // mut matches_interface := 'false' + if left is ast.TypeNode && cond.right is ast.TypeNode + && g.table.get_type_symbol(got_type).kind == .interface_ { + // `$if Foo is Interface {` + interface_sym := g.table.get_type_symbol(got_type) + if interface_sym.info is ast.Interface { + // q := g.table.get_type_symbol(interface_sym.info.types[0]) + checked_type := g.unwrap_generic(left.typ) + // TODO PERF this check is run twice (also in the checker) + // store the result in a field + is_true := g.table.does_type_implement_interface(checked_type, + got_type) + // true // exp_type in interface_sym.info.types + if cond.op == .key_is { + if is_true { + g.write('1') + } else { + g.write('0') + } + return is_true + } else if cond.op == .not_is { + if is_true { + g.write('0') + } else { + g.write('1') + } + return !is_true + } + // matches_interface = '/*iface:$got_type $exp_type*/ true' + //} + } + } else if left is ast.SelectorExpr { + name = '${left.expr}.$left.field_name' + exp_type = g.comptime_var_type_map[name] + } else if left is ast.TypeNode { + name = left.str() + // this is only allowed for generics currently, otherwise blocked by checker + exp_type = g.unwrap_generic(left.typ) + } + + if cond.op == .key_is { + g.write('$exp_type == $got_type') + return exp_type == got_type + } else { + g.write('$exp_type != $got_type') + return exp_type != got_type + } + } + .eq, .ne { + // TODO Implement `$if method.args.len == 1` + g.write('1') + return true + } + else { + return true + } + } + } + ast.Ident { + ifdef := g.comp_if_to_ifdef(cond.name, false) or { 'true' } // handled in checker + g.write('defined($ifdef)') + return true + } + ast.ComptimeCall { + g.write('$pkg_exist') + return true + } + else { + // should be unreachable, but just in case + g.write('1') + return true + } + } +} + +fn (mut g Gen) comp_for(node ast.CompFor) { + sym := g.table.get_type_symbol(g.unwrap_generic(node.typ)) + g.writeln('/* \$for $node.val_var in ${sym.name}($node.kind.str()) */ {') + g.indent++ + // vweb_result_type := ast.new_type(g.table.find_type_idx('vweb.Result')) + mut i := 0 + // g.writeln('string method = _SLIT("");') + if node.kind == .methods { + mut methods := sym.methods.filter(it.attrs.len == 0) // methods without attrs first + methods_with_attrs := sym.methods.filter(it.attrs.len > 0) // methods with attrs second + methods << methods_with_attrs + if methods.len > 0 { + g.writeln('FunctionData $node.val_var = {0};') + } + for method in methods { // sym.methods { + /* + if method.return_type != vweb_result_type { // ast.void_type { + continue + } + */ + g.comp_for_method = method.name + g.writeln('/* method $i */ {') + g.writeln('\t${node.val_var}.name = _SLIT("$method.name");') + if method.attrs.len == 0 { + g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') + } else { + attrs := cgen_attrs(method.attrs) + g.writeln( + '\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + + attrs.join(', ') + '}));\n') + } + if method.params.len < 2 { + // 0 or 1 (the receiver) args + g.writeln('\t${node.val_var}.args = __new_array_with_default(0, 0, sizeof(MethodArgs), 0);') + } else { + len := method.params.len - 1 + g.write('\t${node.val_var}.args = new_array_from_c_array($len, $len, sizeof(MethodArgs), _MOV((MethodArgs[$len]){') + // Skip receiver arg + for j, arg in method.params[1..] { + typ := arg.typ.idx() + g.write('{$typ.str(), _SLIT("$arg.name")}') + if j < len - 1 { + g.write(', ') + } + g.comptime_var_type_map['${node.val_var}.args[$j].typ'] = typ + } + g.writeln('}));\n') + } + mut sig := 'anon_fn_' + // skip the first (receiver) arg + for j, arg in method.params[1..] { + // TODO: ignore mut/pts in sig for now + typ := arg.typ.set_nr_muls(0) + sig += '$typ' + if j < method.params.len - 2 { + sig += '_' + } + } + sig += '_$method.return_type' + styp := g.table.find_type_idx(sig) + // println(styp) + // if styp == 0 { } + // TODO: type aliases + ret_typ := method.return_type.idx() + g.writeln('\t${node.val_var}.typ = $styp;') + g.writeln('\t${node.val_var}.return_type = $ret_typ;') + // + g.comptime_var_type_map['${node.val_var}.return_type'] = ret_typ + g.comptime_var_type_map['${node.val_var}.typ'] = styp + g.stmts(node.stmts) + i++ + g.writeln('}') + // + mut delete_keys := []string{} + for key, _ in g.comptime_var_type_map { + if key.starts_with(node.val_var) { + delete_keys << key + } + } + for key in delete_keys { + g.comptime_var_type_map.delete(key) + } + } + } else if node.kind == .fields { + // TODO add fields + if sym.info is ast.Struct { + mut fields := sym.info.fields.filter(it.attrs.len == 0) + fields_with_attrs := sym.info.fields.filter(it.attrs.len > 0) + fields << fields_with_attrs + if fields.len > 0 { + g.writeln('\tFieldData $node.val_var = {0};') + } + for field in fields { + g.comp_for_field_var = node.val_var + g.comp_for_field_value = field + g.writeln('/* field $i */ {') + g.writeln('\t${node.val_var}.name = _SLIT("$field.name");') + if field.attrs.len == 0 { + g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') + } else { + attrs := cgen_attrs(field.attrs) + g.writeln( + '\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + + attrs.join(', ') + '}));\n') + } + // field_sym := g.table.get_type_symbol(field.typ) + // g.writeln('\t${node.val_var}.typ = _SLIT("$field_sym.name");') + styp := field.typ + g.writeln('\t${node.val_var}.typ = $styp;') + g.writeln('\t${node.val_var}.is_pub = $field.is_pub;') + g.writeln('\t${node.val_var}.is_mut = $field.is_mut;') + g.writeln('\t${node.val_var}.is_shared = ${field.typ.has_flag(.shared_f)};') + g.comptime_var_type_map['${node.val_var}.typ'] = styp + g.stmts(node.stmts) + i++ + g.writeln('}') + } + g.comptime_var_type_map.delete(node.val_var) + } + } else if node.kind == .attributes { + if sym.info is ast.Struct { + if sym.info.attrs.len > 0 { + g.writeln('\tStructAttribute $node.val_var = {0};') + } + for attr in sym.info.attrs { + g.writeln('/* attribute $i */ {') + g.writeln('\t${node.val_var}.name = _SLIT("$attr.name");') + g.writeln('\t${node.val_var}.has_arg = $attr.has_arg;') + g.writeln('\t${node.val_var}.arg = _SLIT("$attr.arg");') + g.writeln('\t${node.val_var}.kind = AttributeKind__$attr.kind;') + g.stmts(node.stmts) + g.writeln('}') + } + } + } + g.indent-- + g.writeln('}// \$for') +} + +fn (mut g Gen) comp_if_to_ifdef(name string, is_comptime_optional bool) ?string { + match name { + // platforms/os-es: + 'windows' { + return '_WIN32' + } + 'ios' { + return '__TARGET_IOS__' + } + 'macos' { + return '__APPLE__' + } + 'mach' { + return '__MACH__' + } + 'darwin' { + return '__DARWIN__' + } + 'hpux' { + return '__HPUX__' + } + 'gnu' { + return '__GNU__' + } + 'qnx' { + return '__QNX__' + } + 'linux' { + return '__linux__' + } + 'serenity' { + return '__serenity__' + } + 'vinix' { + return '__vinix__' + } + 'freebsd' { + return '__FreeBSD__' + } + 'openbsd' { + return '__OpenBSD__' + } + 'netbsd' { + return '__NetBSD__' + } + 'bsd' { + return '__BSD__' + } + 'dragonfly' { + return '__DragonFly__' + } + 'android' { + return '__ANDROID__' + } + 'solaris' { + return '__sun' + } + 'haiku' { + return '__HAIKU__' + } + // + 'js' { + return '_VJS' + } + // compilers: + 'gcc' { + return '__V_GCC__' + } + 'tinyc' { + return '__TINYC__' + } + 'clang' { + return '__clang__' + } + 'mingw' { + return '__MINGW32__' + } + 'msvc' { + return '_MSC_VER' + } + 'cplusplus' { + return '__cplusplus' + } + // other: + 'threads' { + return '__VTHREADS__' + } + 'gcboehm' { + return '_VGCBOEHM' + } + 'debug' { + return '_VDEBUG' + } + 'prod' { + return '_VPROD' + } + 'test' { + return '_VTEST' + } + 'glibc' { + return '__GLIBC__' + } + 'prealloc' { + return '_VPREALLOC' + } + 'no_bounds_checking' { + return 'CUSTOM_DEFINE_no_bounds_checking' + } + 'freestanding' { + return '_VFREESTANDING' + } + // architectures: + 'amd64' { + return '__V_amd64' + } + 'aarch64', 'arm64' { + return '__V_arm64' + } + // bitness: + 'x64' { + return 'TARGET_IS_64BIT' + } + 'x32' { + return 'TARGET_IS_32BIT' + } + // endianness: + 'little_endian' { + return 'TARGET_ORDER_IS_LITTLE' + } + 'big_endian' { + return 'TARGET_ORDER_IS_BIG' + } + else { + if is_comptime_optional + || (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) { + return 'CUSTOM_DEFINE_$name' + } + return error('bad os ifdef name "$name"') // should never happen, caught in the checker + } + } + return none +} diff --git a/v_windows/v/vlib/v/gen/c/coutput_test.v b/v_windows/v/vlib/v/gen/c/coutput_test.v new file mode 100644 index 0000000..b8e2729 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/coutput_test.v @@ -0,0 +1,174 @@ +import os +import rand +import term +import v.util.diff +import v.util.vtest + +const vexe = @VEXE + +const vroot = os.real_path(@VMODROOT) + +const testdata_folder = os.join_path(vroot, 'vlib', 'v', 'gen', 'c', 'testdata') + +const diff_cmd = diff.find_working_diff_command() or { '' } + +fn test_out_files() ? { + println(term.colorize(term.green, '> testing whether .out files match:')) + os.chdir(vroot) or {} + output_path := os.join_path(os.temp_dir(), 'coutput', 'out') + os.mkdir_all(output_path) ? + defer { + os.rmdir_all(output_path) or {} + } + files := os.ls(testdata_folder) or { [] } + tests := files.filter(it.ends_with('.out')) + if tests.len == 0 { + eprintln('no `.out` tests found in $testdata_folder') + return + } + paths := vtest.filter_vtest_only(tests, basepath: testdata_folder) + mut total_errors := 0 + for out_path in paths { + basename, path, relpath, out_relpath := target2paths(out_path, '.out') + print(term.colorize(term.magenta, 'v run $relpath') + ' == ' + + term.colorize(term.magenta, out_relpath) + ' ') + pexe := os.join_path(output_path, '${basename}.exe') + compilation := os.execute('$vexe -o $pexe $path') + ensure_compilation_succeeded(compilation) + res := os.execute(pexe) + if res.exit_code < 0 { + println('nope') + panic(res.output) + } + mut found := res.output.trim_right('\r\n').replace('\r\n', '\n') + mut expected := os.read_file(out_path) ? + expected = expected.trim_right('\r\n').replace('\r\n', '\n') + if expected.contains('================ V panic ================') { + // panic include backtraces and absolute file paths, so can't do char by char comparison + n_found := normalize_panic_message(found, vroot) + n_expected := normalize_panic_message(expected, vroot) + if found.contains('================ V panic ================') { + if n_found.starts_with(n_expected) { + println(term.green('OK (panic)')) + continue + } else { + // Both have panics, but there was a difference... + // Pass the normalized strings for further reporting. + // There is no point in comparing the backtraces too. + found = n_found + expected = n_expected + } + } + } + if expected != found { + println(term.red('FAIL')) + println(term.header('expected:', '-')) + println(expected) + println(term.header('found:', '-')) + println(found) + if diff_cmd != '' { + println(term.header('difference:', '-')) + println(diff.color_compare_strings(diff_cmd, rand.ulid(), expected, found)) + } else { + println(term.h_divider('-')) + } + total_errors++ + } else { + println(term.green('OK')) + } + } + assert total_errors == 0 +} + +fn test_c_must_have_files() ? { + println(term.colorize(term.green, '> testing whether `.c.must_have` files match:')) + os.chdir(vroot) or {} + output_path := os.join_path(os.temp_dir(), 'coutput', 'c_must_have') + os.mkdir_all(output_path) ? + defer { + os.rmdir_all(output_path) or {} + } + files := os.ls(testdata_folder) or { [] } + tests := files.filter(it.ends_with('.c.must_have')) + if tests.len == 0 { + eprintln('no `.c.must_have` files found in $testdata_folder') + return + } + paths := vtest.filter_vtest_only(tests, basepath: testdata_folder) + mut total_errors := 0 + for must_have_path in paths { + basename, path, relpath, must_have_relpath := target2paths(must_have_path, '.c.must_have') + print(term.colorize(term.magenta, 'v -o - $relpath') + ' matches all line paterns in ' + + term.colorize(term.magenta, must_have_relpath) + ' ') + compilation := os.execute('$vexe -o - $path') + ensure_compilation_succeeded(compilation) + expected_lines := os.read_lines(must_have_path) or { [] } + generated_c_lines := compilation.output.split_into_lines() + mut nmatches := 0 + for idx_expected_line, eline in expected_lines { + if does_line_match_one_of_generated_lines(eline, generated_c_lines) { + nmatches++ + // eprintln('> testing: $must_have_path has line: $eline') + } else { + println(term.red('FAIL')) + eprintln('$must_have_path:${idx_expected_line + 1}: expected match error:') + eprintln('`$vexe -o - $path` does NOT produce expected line:') + eprintln(term.colorize(term.red, eline)) + total_errors++ + continue + } + } + if nmatches == expected_lines.len { + println(term.green('OK')) + } else { + eprintln('> ALL lines:') + eprintln(compilation.output) + } + } + assert total_errors == 0 +} + +fn does_line_match_one_of_generated_lines(line string, generated_c_lines []string) bool { + for cline in generated_c_lines { + if line == cline { + return true + } + if cline.contains(line) { + return true + } + } + return false +} + +fn normalize_panic_message(message string, vroot string) string { + mut msg := message.all_before('=========================================') + // change windows to nix path + s := vroot.replace(os.path_separator, '/') + msg = msg.replace(s + '/', '') + msg = msg.trim_space() + return msg +} + +fn vroot_relative(opath string) string { + nvroot := vroot.replace(os.path_separator, '/') + '/' + npath := opath.replace(os.path_separator, '/') + return npath.replace(nvroot, '') +} + +fn ensure_compilation_succeeded(compilation os.Result) { + if compilation.exit_code < 0 { + panic(compilation.output) + } + if compilation.exit_code != 0 { + panic('compilation failed: $compilation.output') + } +} + +fn target2paths(target_path string, postfix string) (string, string, string, string) { + basename := os.file_name(target_path).replace(postfix, '') + target_dir := os.dir(target_path) + path := os.join_path(target_dir, '${basename}.vv') + relpath := vroot_relative(path) + target_relpath := vroot_relative(target_path) + return basename, path, relpath, target_relpath +} diff --git a/v_windows/v/vlib/v/gen/c/ctempvars.v b/v_windows/v/vlib/v/gen/c/ctempvars.v new file mode 100644 index 0000000..f0484ed --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/ctempvars.v @@ -0,0 +1,25 @@ +module c + +import v.ast + +fn (mut g Gen) new_ctemp_var(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + return ast.CTempVar{ + name: g.new_tmp_var() + typ: expr_type + is_ptr: expr_type.is_ptr() + orig: expr + } +} + +fn (mut g Gen) new_ctemp_var_then_gen(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + x := g.new_ctemp_var(expr, expr_type) + g.gen_ctemp_var(x) + return x +} + +fn (mut g Gen) gen_ctemp_var(tvar ast.CTempVar) { + styp := g.typ(tvar.typ) + g.write('$styp $tvar.name = ') + g.expr(tvar.orig) + g.writeln(';') +} diff --git a/v_windows/v/vlib/v/gen/c/dumpexpr.v b/v_windows/v/vlib/v/gen/c/dumpexpr.v new file mode 100644 index 0000000..fcb6bbd --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/dumpexpr.v @@ -0,0 +1,72 @@ +module c + +import v.ast +import strings + +fn (mut g Gen) dump_expr(node ast.DumpExpr) { + sexpr := ctoslit(node.expr.str()) + fpath := cestring(g.file.path) + line := node.pos.line_nr + 1 + if 'nop_dump' in g.pref.compile_defines { + g.expr(node.expr) + return + } + dump_fn_name := '_v_dump_expr_$node.cname' + (if node.expr_type.is_ptr() { '_ptr' } else { '' }) + g.write(' ${dump_fn_name}(${ctoslit(fpath)}, $line, $sexpr, ') + g.expr(node.expr) + g.write(' )') +} + +fn (mut g Gen) dump_expr_definitions() { + mut dump_typedefs := map[string]bool{} + mut dump_fns := strings.new_builder(100) + for dump_type, cname in g.table.dumps { + to_string_fn_name := g.gen_str_for_type(dump_type) + is_ptr := ast.Type(dump_type).is_ptr() + ptr_asterisk := if is_ptr { '*' } else { '' } + dump_sym := g.table.get_type_symbol(dump_type) + mut str_dumparg_type := '$cname$ptr_asterisk' + if dump_sym.kind == .function { + fninfo := dump_sym.info as ast.FnType + str_dumparg_type = 'DumpFNType_$cname' + tdef_pos := g.out.len + g.write_fn_ptr_decl(&fninfo, str_dumparg_type) + str_tdef := g.out.after(tdef_pos) + g.out.go_back(str_tdef.len) + dump_typedefs['typedef $str_tdef;'] = true + } + dump_fn_name := '_v_dump_expr_$cname' + (if is_ptr { '_ptr' } else { '' }) + if g.writeln_fn_header('$str_dumparg_type ${dump_fn_name}(string fpath, int line, string sexpr, $str_dumparg_type x)', mut + dump_fns) + { + continue + } + dump_fns.writeln('\teprint(${ctoslit('[')});') + dump_fns.writeln('\teprint(fpath);') + dump_fns.writeln('\teprint(${ctoslit(':')});') + dump_fns.writeln('\teprint(int_str(line));') + dump_fns.writeln('\teprint(${ctoslit('] ')});') + // dump_fns.writeln('\t/* dump_type: $dump_type | to_string_fn_name: $to_string_fn_name | is_ptr: $is_ptr | ptr_asterisk: $ptr_asterisk | dump_fn_name: $dump_fn_name | cnam: $cname */') + dump_fns.writeln('\teprint(sexpr);') + dump_fns.writeln('\teprint(${ctoslit(': ')});') + if is_ptr { + dump_fns.writeln('\teprint(${ctoslit('&')});') + } + dump_fns.writeln('\teprintln(${to_string_fn_name}(${ptr_asterisk}x));') + dump_fns.writeln('\treturn x;') + dump_fns.writeln('}') + } + for tdef, _ in dump_typedefs { + g.definitions.writeln(tdef) + } + g.definitions.writeln(dump_fns.str()) +} + +fn (mut g Gen) writeln_fn_header(s string, mut sb strings.Builder) bool { + if g.pref.build_mode == .build_module { + sb.writeln('$s;') + return true + } + sb.writeln('$s {') + return false +} diff --git a/v_windows/v/vlib/v/gen/c/embed.v b/v_windows/v/vlib/v/gen/c/embed.v new file mode 100644 index 0000000..4ee02eb --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/embed.v @@ -0,0 +1,69 @@ +module c + +import os +import v.ast + +fn (mut g Gen) embed_file_is_prod_mode() bool { + if g.pref.is_prod || 'debug_embed_file_in_prod' in g.pref.compile_defines { + return true + } + return false +} + +// gen_embed_file_struct generates C code for `$embed_file('...')` calls. +fn (mut g Gen) gen_embed_file_init(node ast.ComptimeCall) { + g.writeln('(v__embed_file__EmbedFileData){') + g.writeln('\t\t.path = ${ctoslit(node.embed_file.rpath)},') + g.writeln('\t\t.apath = ${ctoslit(node.embed_file.apath)},') + file_size := os.file_size(node.embed_file.apath) + if file_size > 5242880 { + eprintln('Warning: embedding of files >= ~5MB is currently not supported') + } + if g.embed_file_is_prod_mode() { + // Use function generated in Gen.gen_embedded_data() + g.writeln('\t\t.compressed = v__embed_file__find_index_entry_by_path((voidptr)_v_embed_file_index, ${ctoslit(node.embed_file.rpath)})->data,') + } + g.writeln('\t\t.uncompressed = NULL,') + g.writeln('\t\t.free_compressed = 0,') + g.writeln('\t\t.free_uncompressed = 0,') + g.writeln('\t\t.len = $file_size') + g.writeln('} // \$embed_file("$node.embed_file.apath")') +} + +// gen_embedded_data embeds data into the V target executable. +fn (mut g Gen) gen_embedded_data() { + /* + TODO implement compression. + See also the vlib/embed module where decompression should occur. + */ + /* + TODO implement support for large files - right now the setup has problems + // with even just 10 - 50 MB files - the problem is both in V and C compilers. + // maybe we need to write to separate files or have an external tool for large files + // like the `rcc` tool in Qt? + */ + for i, emfile in g.embedded_files { + fbytes := os.read_bytes(emfile.apath) or { panic('Error while embedding file: $err') } + g.embedded_data.write_string('static const unsigned char _v_embed_blob_$i[$fbytes.len] = {\n ') + for j := 0; j < fbytes.len; j++ { + b := fbytes[j].hex() + if j < fbytes.len - 1 { + g.embedded_data.write_string('0x$b,') + } else { + g.embedded_data.write_string('0x$b') + } + if 0 == ((j + 1) % 16) { + g.embedded_data.write_string('\n ') + } + } + g.embedded_data.writeln('\n};') + } + g.embedded_data.writeln('') + g.embedded_data.writeln('const v__embed_file__EmbedFileIndexEntry _v_embed_file_index[] = {') + for i, emfile in g.embedded_files { + g.embedded_data.writeln('\t{$i, { .str=(byteptr)("${cestring(emfile.rpath)}"), .len=${emfile.rpath.len - 1}, .is_lit=1 }, _v_embed_blob_$i},') + } + g.embedded_data.writeln('\t{-1, { .str=(byteptr)(""), .len=0, .is_lit=1 }, NULL}') + g.embedded_data.writeln('};') + // see vlib/v/embed_file/embed_file.v, find_index_entry_by_id/2 and find_index_entry_by_path/2 +} diff --git a/v_windows/v/vlib/v/gen/c/fn.v b/v_windows/v/vlib/v/gen/c/fn.v new file mode 100644 index 0000000..7838221 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/fn.v @@ -0,0 +1,1558 @@ +// Copyright (c) 2019-2021 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 strings +import v.ast +import v.util + +fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool { + mut is_used_by_main := true + if g.pref.skip_unused { + fkey := node.fkey() + is_used_by_main = g.table.used_fns[fkey] + $if trace_skip_unused_fns ? { + println('> is_used_by_main: $is_used_by_main | node.name: $node.name | fkey: $fkey | node.is_method: $node.is_method') + } + if !is_used_by_main { + $if trace_skip_unused_fns_in_c_code ? { + g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $fkey') + } + } + } else { + $if trace_skip_unused_fns_in_c_code ? { + g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $node.fkey()') + } + } + return is_used_by_main +} + +fn (mut g Gen) process_fn_decl(node ast.FnDecl) { + if !g.is_used_by_main(node) { + return + } + if node.should_be_skipped { + return + } + if g.is_builtin_mod && g.pref.gc_mode == .boehm_leak && node.name == 'malloc' { + g.definitions.write_string('#define _v_malloc GC_MALLOC\n') + return + } + g.gen_attrs(node.attrs) + mut skip := false + pos := g.out.len + should_bundle_module := util.should_bundle_module(node.mod) + if g.pref.build_mode == .build_module { + // if node.name.contains('parse_text') { + // println('!!! $node.name mod=$node.mod, built=$g.module_built') + // } + // TODO true for not just "builtin" + // TODO: clean this up + mod := if g.is_builtin_mod { 'builtin' } else { node.name.all_before_last('.') } + if (mod != g.module_built && node.mod != g.module_built.after('/')) || should_bundle_module { + // Skip functions that don't have to be generated for this module. + // println('skip bm $node.name mod=$node.mod module_built=$g.module_built') + skip = true + } + if g.is_builtin_mod && g.module_built == 'builtin' && node.mod == 'builtin' { + skip = false + } + if !skip && g.pref.is_verbose { + println('build module `$g.module_built` fn `$node.name`') + } + } + if g.pref.use_cache { + // We are using prebuilt modules, we do not need to generate + // their functions in main.c. + if node.mod != 'main' && node.mod != 'help' && !should_bundle_module && !g.pref.is_test + && node.generic_names.len == 0 { + skip = true + } + } + keep_fn_decl := g.fn_decl + unsafe { + g.fn_decl = &node + } + if node.is_main { + g.has_main = true + } + if node.name == 'backtrace' || node.name == 'backtrace_symbols' + || node.name == 'backtrace_symbols_fd' { + g.write('\n#ifndef __cplusplus\n') + } + g.gen_fn_decl(node, skip) + if node.name == 'backtrace' || node.name == 'backtrace_symbols' + || node.name == 'backtrace_symbols_fd' { + g.write('\n#endif\n') + } + g.fn_decl = keep_fn_decl + if skip { + g.out.go_back_to(pos) + } + if !g.pref.skip_unused { + if node.language != .c { + g.writeln('') + } + } +} + +fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { + // TODO For some reason, build fails with autofree with this line + // as it's only informative, comment it for now + // g.gen_attrs(it.attrs) + if node.language == .c { + // || node.no_body { + return + } + + tmp_defer_vars := g.defer_vars // must be here because of workflow + if !g.anon_fn { + g.defer_vars = []string{} + } else { + if node.defer_stmts.len > 0 { + g.defer_vars = []string{} + defer { + g.defer_vars = tmp_defer_vars + } + } + } + // Skip [if xxx] if xxx is not defined + /* + for attr in node.attrs { + if !attr.is_comptime_define { + continue + } + if attr.name !in g.pref.compile_defines_all { + // println('skipping [if]') + return + } + } + */ + + g.returned_var_name = '' + // + old_g_autofree := g.is_autofree + if node.is_manualfree { + g.is_autofree = false + } + defer { + g.is_autofree = old_g_autofree + } + // + // if g.fileis('vweb.v') { + // println('\ngen_fn_decl() $node.name $node.is_generic $g.cur_generic_type') + // } + if node.generic_names.len > 0 && g.table.cur_concrete_types.len == 0 { // need the cur_concrete_type check to avoid inf. recursion + // loop thru each generic type and generate a function + for concrete_types in g.table.fn_generic_types[node.name] { + if g.pref.is_verbose { + syms := concrete_types.map(g.table.get_type_symbol(it)) + the_type := syms.map(it.name).join(', ') + println('gen fn `$node.name` for type `$the_type`') + } + g.table.cur_concrete_types = concrete_types + g.gen_fn_decl(node, skip) + } + g.table.cur_concrete_types = [] + return + } + cur_fn_save := g.table.cur_fn + defer { + g.table.cur_fn = cur_fn_save + } + unsafe { + // TODO remove unsafe + g.table.cur_fn = node + } + fn_start_pos := g.out.len + is_closure := node.scope.has_inherited_vars() + mut cur_closure_ctx := '' + if is_closure { + cur_closure_ctx = closure_ctx_struct(node) + // declare the struct before its implementation + g.definitions.write_string(cur_closure_ctx) + g.definitions.writeln(';') + } + + g.write_v_source_line_info(node.pos) + msvc_attrs := g.write_fn_attrs(node.attrs) + // Live + is_livefn := node.attrs.contains('live') + is_livemain := g.pref.is_livemain && is_livefn + is_liveshared := g.pref.is_liveshared && is_livefn + is_livemode := g.pref.is_livemain || g.pref.is_liveshared + is_live_wrap := is_livefn && is_livemode + if is_livefn && !is_livemode { + eprintln('INFO: compile with `v -live $g.pref.path `, if you want to use the [live] function $node.name .') + } + // + mut name := node.name + if name in ['+', '-', '*', '/', '%', '<', '=='] { + name = util.replace_op(name) + } + if node.is_method { + unwrapped_rec_sym := g.table.get_type_symbol(g.unwrap_generic(node.receiver.typ)) + if unwrapped_rec_sym.kind == .placeholder { + return + } + name = g.cc_type(node.receiver.typ, false) + '_' + name + // name = g.table.get_type_symbol(node.receiver.typ).name + '_' + name + } + if node.language == .c { + name = util.no_dots(name) + } else { + name = c_name(name) + } + mut type_name := g.typ(node.return_type) + + name = g.generic_fn_name(g.table.cur_concrete_types, name, true) + + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main + && node.name != 'str' { + mut key := node.name + if node.is_method { + sym := g.table.get_type_symbol(node.receiver.typ) + key = sym.name + '.' + node.name + } + g.writeln('/* obf: $key */') + name = g.obf_table[key] or { + panic('cgen: fn_decl: obf name "$key" not found, this should never happen') + } + } + // if g.pref.show_cc && it.is_builtin { + // println(name) + // } + // type_name := g.ast.Type_to_str(it.return_type) + // Live functions are protected by a mutex, because otherwise they + // can be changed by the live reload thread, *while* they are + // running, with unpredictable results (usually just crashing). + // For this purpose, the actual body of the live function, + // is put under a non publicly accessible function, that is prefixed + // with 'impl_live_' . + if is_livemain { + g.hotcode_fn_names << name + } + mut impl_fn_name := name + if is_live_wrap { + impl_fn_name = 'impl_live_$name' + } + last_fn_c_name_save := g.last_fn_c_name + defer { + g.last_fn_c_name = last_fn_c_name_save + } + g.last_fn_c_name = impl_fn_name + // + if is_live_wrap { + if is_livemain { + g.definitions.write_string('$type_name (* $impl_fn_name)(') + g.write('$type_name no_impl_${name}(') + } + if is_liveshared { + g.definitions.write_string('$type_name ${impl_fn_name}(') + g.write('$type_name ${impl_fn_name}(') + } + } else { + if !(node.is_pub || g.pref.is_debug) { + // Private functions need to marked as static so that they are not exportable in the + // binaries + if g.pref.build_mode != .build_module && !g.pref.use_cache { + // if !(g.pref.build_mode == .build_module && g.is_builtin_mod) { + // If we are building vlib/builtin, we need all private functions like array_get + // to be public, so that all V programs can access them. + g.write('VV_LOCAL_SYMBOL ') + g.definitions.write_string('VV_LOCAL_SYMBOL ') + } + } + fn_header := if msvc_attrs.len > 0 { + '$type_name $msvc_attrs ${name}(' + } else { + '$type_name ${name}(' + } + g.definitions.write_string(fn_header) + g.write(fn_header) + } + arg_start_pos := g.out.len + fargs, fargtypes, heap_promoted := g.fn_args(node.params, node.scope) + if is_closure { + mut s := '$cur_closure_ctx *$c.closure_ctx' + if node.params.len > 0 { + s = ', ' + s + } else { + // remove generated `void` + g.out.cut_to(arg_start_pos) + } + g.definitions.write_string(s) + g.write(s) + g.nr_closures++ + } + arg_str := g.out.after(arg_start_pos) + if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin + && !g.is_test) || skip { + // Just a function header. Builtin function bodies are defined in builtin.o + g.definitions.writeln(');') // // NO BODY') + g.writeln(');') + return + } + g.definitions.writeln(');') + g.writeln(') {') + for i, is_promoted in heap_promoted { + if is_promoted { + g.writeln('${fargtypes[i]}* ${fargs[i]} = HEAP(${fargtypes[i]}, _v_toheap_${fargs[i]});') + } + } + for defer_stmt in node.defer_stmts { + g.writeln('bool ${g.defer_flag_var(defer_stmt)} = false;') + for var in defer_stmt.defer_vars { + if var.name in fargs || var.kind == .constant { + continue + } + if var.kind == .variable { + if var.name !in g.defer_vars { + g.defer_vars << var.name + mut deref := '' + if v := var.scope.find_var(var.name) { + if v.is_auto_heap { + deref = '*' + } + } + info := var.obj as ast.Var + g.writeln('${g.typ(info.typ)}$deref $var.name;') + } + } + } + } + if is_live_wrap { + // The live function just calls its implementation dual, while ensuring + // that the call is wrapped by the mutex lock & unlock calls. + // Adding the mutex lock/unlock inside the body of the implementation + // function is not reliable, because the implementation function can do + // an early exit, which will leave the mutex locked. + mut fn_args_list := []string{} + for ia, fa in fargs { + fn_args_list << '${fargtypes[ia]} $fa' + } + mut live_fncall := '${impl_fn_name}(' + fargs.join(', ') + ');' + mut live_fnreturn := '' + if type_name != 'void' { + live_fncall = '$type_name res = $live_fncall' + live_fnreturn = 'return res;' + } + g.definitions.writeln('$type_name ${name}(' + fn_args_list.join(', ') + ');') + g.hotcode_definitions.writeln('$type_name ${name}(' + fn_args_list.join(', ') + '){') + g.hotcode_definitions.writeln(' pthread_mutex_lock(&live_fn_mutex);') + g.hotcode_definitions.writeln(' $live_fncall') + g.hotcode_definitions.writeln(' pthread_mutex_unlock(&live_fn_mutex);') + g.hotcode_definitions.writeln(' $live_fnreturn') + g.hotcode_definitions.writeln('}') + } + // Profiling mode? Start counting at the beginning of the function (save current time). + if g.pref.is_prof && g.pref.build_mode != .build_module { + g.profile_fn(node) + } + // we could be in an anon fn so save outer fn defer stmts + prev_defer_stmts := g.defer_stmts + g.defer_stmts = [] + ctmp := g.tmp_count + g.tmp_count = 0 + defer { + g.tmp_count = ctmp + } + g.stmts(node.stmts) + if node.is_noreturn { + g.writeln('\twhile(1);') + } + // clear g.fn_mut_arg_names + + if !node.has_return { + g.write_defer_stmts_when_needed() + } + if node.is_anon { + g.defer_stmts = prev_defer_stmts + } else { + g.defer_stmts = [] + } + if node.return_type != ast.void_type && node.stmts.len > 0 && node.stmts.last() !is ast.Return + && !node.attrs.contains('_naked') { + default_expr := g.type_default(node.return_type) + // TODO: perf? + if default_expr == '{0}' { + // if node.return_type.idx() == 1 && node.return_type.has_flag(.optional) { + // // The default return for anonymous functions that return `?, + // // should have .ok = true set, otherwise calling them with + // // optfn() or { panic(err) } will cause a panic: + // g.writeln('\treturn (Option_void){0};') + // } else { + g.writeln('\treturn ($type_name)$default_expr;') + // } + } else { + g.writeln('\treturn $default_expr;') + } + } + g.writeln('}') + if g.pref.printfn_list.len > 0 && g.last_fn_c_name in g.pref.printfn_list { + println(g.out.after(fn_start_pos)) + } + for attr in node.attrs { + if attr.name == 'export' { + g.writeln('// export alias: $attr.arg -> $name') + export_alias := '$type_name ${attr.arg}($arg_str)' + g.definitions.writeln('VV_EXPORTED_SYMBOL $export_alias; // exported fn $node.name') + g.writeln('$export_alias {') + g.write('\treturn ${name}(') + g.write(fargs.join(', ')) + g.writeln(');') + g.writeln('}') + } + } +} + +const closure_ctx = '_V_closure_ctx' + +fn closure_ctx_struct(node ast.FnDecl) string { + return 'struct _V_${node.name}_Ctx' +} + +fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) { + g.gen_anon_fn_decl(mut node) + if !node.decl.scope.has_inherited_vars() { + g.write(node.decl.name) + return + } + // it may be possible to optimize `memdup` out if the closure never leaves current scope + ctx_var := g.new_tmp_var() + cur_line := g.go_before_stmt(0) + ctx_struct := closure_ctx_struct(node.decl) + g.writeln('$ctx_struct* $ctx_var = ($ctx_struct*) memdup(&($ctx_struct){') + g.indent++ + for var in node.inherited_vars { + g.writeln('.$var.name = $var.name,') + } + g.indent-- + g.writeln('}, sizeof($ctx_struct));') + g.empty_line = false + g.write(cur_line) + // TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data) + g.write('__closure_create($node.decl.name, ${node.decl.params.len + 1}, $ctx_var)') +} + +fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) { + if node.has_gen { + return + } + node.has_gen = true + mut builder := strings.new_builder(256) + if node.inherited_vars.len > 0 { + ctx_struct := closure_ctx_struct(node.decl) + builder.writeln('$ctx_struct {') + for var in node.inherited_vars { + styp := g.typ(var.typ) + builder.writeln('\t$styp $var.name;') + } + builder.writeln('};\n') + } + pos := g.out.len + was_anon_fn := g.anon_fn + g.anon_fn = true + g.process_fn_decl(node.decl) + g.anon_fn = was_anon_fn + builder.write_string(g.out.cut_to(pos)) + g.anon_fn_definitions << builder.str() +} + +fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string { + return '${g.last_fn_c_name}_defer_$stmt.idx_in_fn' +} + +fn (mut g Gen) write_defer_stmts_when_needed() { + // unlock all mutexes, in case we are in a lock statement. defers are not allowed in lock statements + g.unlock_locks() + if g.defer_stmts.len > 0 { + g.write_defer_stmts() + } + if g.defer_profile_code.len > 0 { + g.writeln('') + g.writeln('\t// defer_profile_code') + g.writeln(g.defer_profile_code) + g.writeln('') + } +} + +// fn decl args +fn (mut g Gen) fn_args(args []ast.Param, scope &ast.Scope) ([]string, []string, []bool) { + mut fargs := []string{} + mut fargtypes := []string{} + mut heap_promoted := []bool{} + if args.len == 0 { + // in C, `()` is untyped, unlike `(void)` + g.write('void') + } + for i, arg in args { + mut caname := if arg.name == '_' { g.new_tmp_declaration_name() } else { c_name(arg.name) } + typ := g.unwrap_generic(arg.typ) + arg_type_sym := g.table.get_type_symbol(typ) + mut arg_type_name := g.typ(typ) // util.no_dots(arg_type_sym.name) + if arg_type_sym.kind == .function { + info := arg_type_sym.info as ast.FnType + func := info.func + g.write('${g.typ(func.return_type)} (*$caname)(') + g.definitions.write_string('${g.typ(func.return_type)} (*$caname)(') + g.fn_args(func.params, voidptr(0)) + g.write(')') + g.definitions.write_string(')') + fargs << caname + fargtypes << arg_type_name + } else { + mut heap_prom := false + if scope != voidptr(0) { + if arg.name != '_' { + if v := scope.find_var(arg.name) { + if !v.is_stack_obj && v.is_auto_heap { + heap_prom = true + } + } + } + } + var_name_prefix := if heap_prom { '_v_toheap_' } else { '' } + const_prefix := if arg.typ.is_any_kind_of_pointer() && !arg.is_mut + && arg.name.starts_with('const_') { + 'const ' + } else { + '' + } + s := '$const_prefix$arg_type_name $var_name_prefix$caname' + g.write(s) + g.definitions.write_string(s) + fargs << caname + fargtypes << arg_type_name + heap_promoted << heap_prom + } + if i < args.len - 1 { + g.write(', ') + g.definitions.write_string(', ') + } + } + return fargs, fargtypes, heap_promoted +} + +fn (mut g Gen) call_expr(node ast.CallExpr) { + // g.write('/*call expr*/') + // NOTE: everything could be done this way + // see my comment in parser near anon_fn + if node.left is ast.AnonFn { + g.expr(node.left) + } + if node.left is ast.IndexExpr && node.name == '' { + g.is_fn_index_call = true + g.expr(node.left) + g.is_fn_index_call = false + } + if node.should_be_skipped { + return + } + g.inside_call = true + defer { + g.inside_call = false + } + gen_keep_alive := node.is_keep_alive && node.return_type != ast.void_type + && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] + gen_or := node.or_block.kind != .absent // && !g.is_autofree + is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result + cur_line := if is_gen_or_and_assign_rhs || gen_keep_alive { // && !g.is_autofree { + // `x := foo() or { ...}` + // cut everything that has been generated to prepend optional variable creation + line := g.go_before_stmt(0) + g.out.write_string(util.tabs(g.indent)) + // g.write('/*is_gen_or_and_assign_rhs*/') + line + } else { + '' + } + tmp_opt := if gen_or || gen_keep_alive { g.new_tmp_var() } else { '' } + if gen_or || gen_keep_alive { + mut ret_typ := node.return_type + if gen_or { + ret_typ = ret_typ.set_flag(.optional) + } + styp := g.typ(ret_typ) + g.write('$styp $tmp_opt = ') + } + if node.is_method && !node.is_field { + if node.name == 'writeln' && g.pref.experimental && node.args.len > 0 + && node.args[0].expr is ast.StringInterLiteral + && g.table.get_type_symbol(node.receiver_type).name == 'strings.Builder' { + g.string_inter_literal_sb_optimized(node) + } else { + g.method_call(node) + } + } else { + g.fn_call(node) + } + if gen_or { // && !g.autofree { + // if !g.is_autofree { + g.or_block(tmp_opt, node.or_block, node.return_type) + //} + if is_gen_or_and_assign_rhs { + unwrapped_typ := node.return_type.clear_flag(.optional) + unwrapped_styp := g.typ(unwrapped_typ) + if unwrapped_typ == ast.void_type { + g.write('\n $cur_line') + } else if g.table.get_type_symbol(node.return_type).kind == .multi_return { + g.write('\n $cur_line $tmp_opt /*U*/') + } else { + if !g.inside_const { + g.write('\n $cur_line (*($unwrapped_styp*)${tmp_opt}.data)') + } else { + g.write('\n $cur_line $tmp_opt') + } + } + } + } else if gen_keep_alive { + if node.return_type == ast.void_type { + g.write('\n $cur_line') + } else { + g.write('\n $cur_line $tmp_opt') + } + } + if node.is_noreturn { + if g.inside_ternary == 0 { + g.writeln(';') + g.write('VUNREACHABLE()') + } else { + $if msvc { + // MSVC has no support for the statement expressions used below + } $else { + g.write(', ({VUNREACHABLE();})') + } + } + } +} + +fn (mut g Gen) conversion_function_call(prefix string, postfix string, node ast.CallExpr) { + g.write('${prefix}( (') + g.expr(node.left) + dot := if node.left_type.is_ptr() { '->' } else { '.' } + g.write(')${dot}_typ )$postfix') +} + +fn (mut g Gen) method_call(node ast.CallExpr) { + // TODO: there are still due to unchecked exprs (opt/some fn arg) + if node.left_type == 0 { + g.checker_bug('CallExpr.left_type is 0 in method_call', node.pos) + } + if node.receiver_type == 0 { + g.checker_bug('CallExpr.receiver_type is 0 in method_call', node.pos) + } + mut unwrapped_rec_type := node.receiver_type + if g.table.cur_fn.generic_names.len > 0 { // in generic fn + unwrapped_rec_type = g.unwrap_generic(node.receiver_type) + } else { // in non-generic fn + sym := g.table.get_type_symbol(node.receiver_type) + match sym.info { + ast.Struct, ast.Interface, ast.SumType { + generic_names := sym.info.generic_types.map(g.table.get_type_symbol(it).name) + if utyp := g.table.resolve_generic_to_concrete(node.receiver_type, generic_names, + sym.info.concrete_types) + { + unwrapped_rec_type = utyp + } + } + else {} + } + } + mut typ_sym := g.table.get_type_symbol(unwrapped_rec_type) + // alias type that undefined this method (not include `str`) need to use parent type + if typ_sym.kind == .alias && node.name != 'str' && !typ_sym.has_method(node.name) { + unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type + typ_sym = g.table.get_type_symbol(unwrapped_rec_type) + } + rec_cc_type := g.cc_type(unwrapped_rec_type, false) + mut receiver_type_name := util.no_dots(rec_cc_type) + if typ_sym.kind == .interface_ && (typ_sym.info as ast.Interface).defines_method(node.name) { + // Speaker_name_table[s._interface_idx].speak(s._object) + $if debug_interface_method_call ? { + eprintln('>>> interface typ_sym.name: $typ_sym.name | receiver_type_name: $receiver_type_name') + } + g.write('${c_name(receiver_type_name)}_name_table[') + g.expr(node.left) + dot := if node.left_type.is_ptr() { '->' } else { '.' } + mname := c_name(node.name) + g.write('${dot}_typ]._method_${mname}(') + g.expr(node.left) + g.write('${dot}_object') + if node.args.len > 0 { + g.write(', ') + g.call_args(node) + } + g.write(')') + return + } + left_sym := g.table.get_type_symbol(node.left_type) + final_left_sym := g.table.get_final_type_symbol(node.left_type) + if left_sym.kind == .array { + match node.name { + 'filter' { + g.gen_array_filter(node) + return + } + 'sort' { + g.gen_array_sort(node) + return + } + 'insert' { + g.gen_array_insert(node) + return + } + 'map' { + g.gen_array_map(node) + return + } + 'prepend' { + g.gen_array_prepend(node) + return + } + 'contains' { + g.gen_array_contains(node) + return + } + 'index' { + g.gen_array_index(node) + return + } + 'wait' { + g.gen_array_wait(node) + return + } + 'any' { + g.gen_array_any(node) + return + } + 'all' { + g.gen_array_all(node) + return + } + else {} + } + } + + if left_sym.kind == .map && node.name == 'delete' { + left_info := left_sym.info as ast.Map + elem_type_str := g.typ(left_info.key_type) + g.write('map_delete(') + if node.left_type.is_ptr() { + g.expr(node.left) + } else { + g.write('&') + g.expr(node.left) + } + g.write(', &($elem_type_str[]){') + g.expr(node.args[0].expr) + g.write('})') + return + } + + if left_sym.kind in [.sum_type, .interface_] { + if node.name == 'type_name' { + if left_sym.kind == .sum_type { + g.conversion_function_call('charptr_vstring_literal( /* $left_sym.name */ v_typeof_sumtype_$typ_sym.cname', + ')', node) + return + } + if left_sym.kind == .interface_ { + g.conversion_function_call('charptr_vstring_literal( /* $left_sym.name */ v_typeof_interface_$typ_sym.cname', + ')', node) + return + } + } + if node.name == 'type_idx' { + if left_sym.kind == .sum_type { + g.conversion_function_call('/* $left_sym.name */ v_typeof_sumtype_idx_$typ_sym.cname', + '', node) + return + } + if left_sym.kind == .interface_ { + g.conversion_function_call('/* $left_sym.name */ v_typeof_interface_idx_$typ_sym.cname', + '', node) + return + } + } + } + + if node.name == 'str' { + mut rec_type := node.receiver_type + if rec_type.has_flag(.shared_f) { + rec_type = rec_type.clear_flag(.shared_f).set_nr_muls(0) + } + g.gen_str_for_type(rec_type) + } + mut has_cast := false + if left_sym.kind == .map && node.name in ['clone', 'move'] { + receiver_type_name = 'map' + } + if final_left_sym.kind == .array + && node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { + // `array_Xyz_clone` => `array_clone` + receiver_type_name = 'array' + } + if node.name in ['last', 'first', 'pop'] { + return_type_str := g.typ(node.return_type) + has_cast = true + g.write('(*($return_type_str*)') + } + } + mut name := util.no_dots('${receiver_type_name}_$node.name') + mut array_depth := -1 + mut noscan := '' + if left_sym.kind == .array { + needs_depth := node.name in ['clone', 'repeat'] + if needs_depth { + elem_type := (left_sym.info as ast.Array).elem_type + array_depth = g.get_array_depth(elem_type) + } + maybe_noscan := needs_depth + || node.name in ['pop', 'push', 'push_many', 'reverse', 'grow_cap', 'grow_len'] + if maybe_noscan { + info := left_sym.info as ast.Array + noscan = g.check_noscan(info.elem_type) + } + } else if left_sym.kind == .chan { + if node.name in ['close', 'try_pop', 'try_push'] { + name = 'sync__Channel_$node.name' + } + } else if left_sym.kind == .map { + if node.name == 'keys' { + name = 'map_keys' + } + } + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') + && node.name != 'str' { + sym := g.table.get_type_symbol(node.receiver_type) + key := sym.name + '.' + node.name + g.write('/* obf method call: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + // Check if expression is: arr[a..b].clone(), arr[a..].clone() + // if so, then instead of calling array_clone(&array_slice(...)) + // call array_clone_static(array_slice(...)) + mut is_range_slice := false + if node.receiver_type.is_ptr() && !node.left_type.is_ptr() { + if node.left is ast.IndexExpr { + idx := node.left.index + if idx is ast.RangeExpr { + // expr is arr[range].clone() + // use array_clone_static instead of array_clone + name = util.no_dots('${receiver_type_name}_${node.name}_static') + is_range_slice = true + } + } + } + name = g.generic_fn_name(node.concrete_types, name, false) + // TODO2 + // g.generate_tmp_autofree_arg_vars(node, name) + // + // if node.receiver_type != 0 { + // g.write('/*${g.typ(node.receiver_type)}*/') + // g.write('/*expr_type=${g.typ(node.left_type)} rec type=${g.typ(node.receiver_type)}*/') + // } + if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name == 'str' { + g.write('ptr_str(') + } else { + if left_sym.kind == .array { + if array_depth >= 0 { + name = name + '_to_depth' + } + g.write('$name${noscan}(') + } else { + g.write('${name}(') + } + } + if node.receiver_type.is_ptr() + && (!node.left_type.is_ptr() || node.left_type.has_flag(.variadic) + || node.from_embed_type != 0 + || (node.left_type.has_flag(.shared_f) && node.name != 'str')) { + // The receiver is a reference, but the caller provided a value + // Add `&` automatically. + // TODO same logic in call_args() + if !is_range_slice { + if !node.left.is_lvalue() { + g.write('ADDR($rec_cc_type, ') + has_cast = true + } else { + g.write('&') + } + } + } else if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name != 'str' + && node.from_embed_type == 0 { + if !node.left_type.has_flag(.shared_f) { + g.write('/*rec*/*') + } + } else if !is_range_slice && node.from_embed_type == 0 && node.name != 'str' { + diff := node.left_type.nr_muls() - node.receiver_type.nr_muls() + if diff < 0 { + // TODO + // g.write('&') + } else if diff > 0 { + g.write('/*diff=$diff*/') + g.write([]byte{len: diff, init: `*`}.bytestr()) + } + } + + // if node.left_type.idx() != node.receiver_type.idx() { + // println('${g.typ(node.left_type)} ${g.typ(node.receiver_type)}') + // } + + if g.is_autofree && node.free_receiver && !g.inside_lambda && !g.is_builtin_mod { + // The receiver expression needs to be freed, use the temp var. + fn_name := node.name.replace('.', '_') + arg_name := '_arg_expr_${fn_name}_0_$node.pos.pos' + g.write('/*af receiver arg*/' + arg_name) + } else { + if left_sym.kind == .array && node.left.is_auto_deref_var() + && node.name in ['first', 'last', 'repeat'] { + g.write('*') + } + if node.left is ast.MapInit { + g.write('(map[]){') + g.expr(node.left) + g.write('}[0]') + } else { + g.expr(node.left) + } + if node.from_embed_type != 0 { + embed_name := typ_sym.embed_name() + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + g.write(embed_name) + } + if node.left_type.has_flag(.shared_f) { + g.write('->val') + } + } + if has_cast { + g.write(')') + } + is_variadic := node.expected_arg_types.len > 0 + && node.expected_arg_types[node.expected_arg_types.len - 1].has_flag(.variadic) + if node.args.len > 0 || is_variadic { + g.write(', ') + } + // ///////// + /* + if name.contains('subkeys') { + println('call_args $name $node.arg_types.len') + for t in node.arg_types { + sym := g.table.get_type_symbol(t) + print('$sym.name ') + } + println('') +} + */ + // /////// + g.call_args(node) + if array_depth >= 0 { + g.write(', $array_depth') + } + g.write(')') +} + +fn (mut g Gen) fn_call(node ast.CallExpr) { + // call struct field with fn type + // TODO: test node.left instead + // left & left_type will be `x` and `x type` in `x.fieldfn()` + // will be `0` for `foo()` + mut is_interface_call := false + mut is_selector_call := false + if node.left_type != 0 { + left_sym := g.table.get_type_symbol(node.left_type) + if left_sym.kind == .interface_ { + is_interface_call = true + g.write('(*') + } + g.expr(node.left) + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + is_selector_call = true + } + mut name := node.name + is_print := name in ['print', 'println', 'eprint', 'eprintln', 'panic'] + print_method := name + is_json_encode := name == 'json.encode' + is_json_encode_pretty := name == 'json.encode_pretty' + is_json_decode := name == 'json.decode' + g.is_json_fn = is_json_encode || is_json_encode_pretty || is_json_decode + mut json_type_str := '' + mut json_obj := '' + if g.is_json_fn { + json_obj = g.new_tmp_var() + mut tmp2 := '' + cur_line := g.go_before_stmt(0) + if is_json_encode || is_json_encode_pretty { + g.gen_json_for_type(node.args[0].typ) + json_type_str = g.typ(node.args[0].typ) + // `json__encode` => `json__encode_User` + // encode_name := c_name(name) + '_' + util.no_dots(json_type_str) + encode_name := js_enc_name(json_type_str) + g.writeln('// json.encode') + g.write('cJSON* $json_obj = ${encode_name}(') + if node.args[0].typ.is_ptr() { + g.write('*') + } + g.call_args(node) + g.writeln(');') + tmp2 = g.new_tmp_var() + if is_json_encode { + g.writeln('string $tmp2 = json__json_print($json_obj);') + } else { + g.writeln('string $tmp2 = json__json_print_pretty($json_obj);') + } + } else { + ast_type := node.args[0].expr as ast.TypeNode + // `json.decode(User, s)` => json.decode_User(s) + typ := c_name(g.typ(ast_type.typ)) + fn_name := c_name(name) + '_' + typ + g.gen_json_for_type(ast_type.typ) + g.writeln('// json.decode') + g.write('cJSON* $json_obj = json__json_parse(') + // Skip the first argument in json.decode which is a type + // its name was already used to generate the function call + g.is_js_call = true + g.call_args(node) + g.is_js_call = false + g.writeln(');') + tmp2 = g.new_tmp_var() + g.writeln('Option_$typ $tmp2 = $fn_name ($json_obj);') + } + if !g.is_autofree { + g.write('cJSON_Delete($json_obj); //del') + } + g.write('\n$cur_line') + name = '' + json_obj = tmp2 + } + if node.language == .c { + // Skip "C." + name = util.no_dots(name[2..]) + } else { + name = c_name(name) + } + // Obfuscate only functions in the main module for now + if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { + key := node.name + g.write('/* obf call: $key */') + name = g.obf_table[key] or { + panic('cgen: obf name "$key" not found, this should never happen') + } + } + if !is_selector_call { + name = g.generic_fn_name(node.concrete_types, name, false) + } + // TODO2 + // cgen shouldn't modify ast nodes, this should be moved + // g.generate_tmp_autofree_arg_vars(node, name) + // Handle `print(x)` + mut print_auto_str := false + if is_print && node.args[0].typ != ast.string_type { // && !free_tmp_arg_vars { + mut typ := node.args[0].typ + if typ == 0 { + g.checker_bug('print arg.typ is 0', node.pos) + } + if typ != ast.string_type { + expr := node.args[0].expr + if g.is_autofree && !typ.has_flag(.optional) { + // Create a temporary variable so that the value can be freed + tmp := g.new_tmp_var() + // tmps << tmp + g.write('string $tmp = ') + g.gen_expr_to_string(expr, typ) + g.writeln('; ${c_name(print_method)}($tmp); string_free(&$tmp);') + } else { + g.write('${c_name(print_method)}(') + g.gen_expr_to_string(expr, typ) + g.write(')') + } + print_auto_str = true + } + } + if !print_auto_str { + if g.pref.is_debug && node.name == 'panic' { + paline, pafile, pamod, pafn := g.panic_debug_info(node.pos) + g.write('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), ') + g.call_args(node) + g.write(')') + } else { + // Simple function call + // if free_tmp_arg_vars { + // g.writeln(';') + // g.write(cur_line + ' /* <== af cur line*/') + // } + g.write(g.get_ternary_name(name)) + if is_interface_call { + g.write(')') + } + mut tmp_cnt_save := -1 + g.write('(') + if g.is_json_fn { + g.write(json_obj) + } else { + if node.is_keep_alive + && g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] { + cur_line := g.go_before_stmt(0) + tmp_cnt_save = g.keep_alive_call_pregen(node) + g.write(cur_line) + for i in 0 .. node.args.len { + if i > 0 { + g.write(', ') + } + g.write('__tmp_arg_${tmp_cnt_save + i}') + } + } else { + g.call_args(node) + } + } + g.write(')') + if tmp_cnt_save >= 0 { + g.writeln(';') + g.keep_alive_call_postgen(node, tmp_cnt_save) + } + } + } + g.is_json_fn = false +} + +fn (mut g Gen) autofree_call_pregen(node ast.CallExpr) { + // g.writeln('// autofree_call_pregen()') + // Create a temporary var before fn call for each argument in order to free it (only if it's a complex expression, + // like `foo(get_string())` or `foo(a + b)` + mut free_tmp_arg_vars := g.is_autofree && !g.is_builtin_mod && node.args.len > 0 + && !node.args[0].typ.has_flag(.optional) // TODO copy pasta checker.v + if !free_tmp_arg_vars { + return + } + if g.is_js_call { + return + } + if g.inside_const { + return + } + free_tmp_arg_vars = false // set the flag to true only if we have at least one arg to free + g.tmp_count2++ + mut scope := g.file.scope.innermost(node.pos.pos) + // prepend the receiver for now (TODO turn the receiver into a CallArg everywhere?) + mut args := [ast.CallArg{ + typ: node.receiver_type + expr: node.left + is_tmp_autofree: node.free_receiver + }] + args << node.args + // for i, arg in node.args { + for i, arg in args { + if !arg.is_tmp_autofree { + continue + } + if arg.expr is ast.CallExpr { + // Any argument can be an expression that has to be freed. Generate a tmp expression + // for each of those recursively. + g.autofree_call_pregen(arg.expr) + } + free_tmp_arg_vars = true + // t := g.new_tmp_var() + '_arg_expr_${name}_$i' + fn_name := node.name.replace('.', '_') // can't use name... + // t := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + t := '_arg_expr_${fn_name}_${i}_$node.pos.pos' + // g.called_fn_name = name + used := false // scope.known_var(t) + mut s := '$t = ' + if used { + // This means this tmp var name was already used (the same function was called and + // `_arg_fnname_1` was already generated). + // We do not need to declare this variable again, so just generate `t = ...` + // instead of `string t = ...`, and we need to mark this variable as unused, + // so that it's freed after the call. (Used tmp arg vars are not freed to avoid double frees). + if x := scope.find(t) { + match mut x { + ast.Var { x.is_used = false } + else {} + } + } + s = '$t = ' + } else { + scope.register(ast.Var{ + name: t + typ: ast.string_type + is_autofree_tmp: true + pos: node.pos + }) + s = 'string $t = ' + } + // g.expr(arg.expr) + s += g.expr_string(arg.expr) + // g.writeln(';// new af pre') + s += ';// new af2 pre' + g.strs_to_free0 << s + // This tmp arg var will be freed with the rest of the vars at the end of the scope. + } +} + +fn (mut g Gen) autofree_call_postgen(node_pos int) { + // if g.strs_to_free.len == 0 { + // return + // } + // g.writeln('\n/* strs_to_free3: $g.nr_vars_to_free */') + // if g.nr_vars_to_free <= 0 { + // return + // } + /* + for s in g.strs_to_free { + g.writeln('string_free(&$s);') + } + if !g.inside_or_block { + // we need to free the vars both inside the or block (in case of an error) and after it + // if we reset the array here, then the vars will not be freed after the block. + g.strs_to_free = [] + } + */ + if g.inside_vweb_tmpl { + return + } + // g.doing_autofree_tmp = true + // g.write('/* postgen */') + scope := g.file.scope.innermost(node_pos) + for _, obj in scope.objects { + match mut obj { + ast.Var { + // if var.typ == 0 { + // // TODO why 0? + // continue + // } + is_optional := obj.typ.has_flag(.optional) + if is_optional { + // TODO: free optionals + continue + } + if !obj.is_autofree_tmp { + continue + } + if obj.is_used { + // this means this tmp expr var has already been freed + continue + } + obj.is_used = true // TODO bug? sets all vars is_used to true + g.autofree_variable(obj) + // g.nr_vars_to_free-- + } + else {} + } + } + // g.write('/* postgen end */') + // g.doing_autofree_tmp = false +} + +fn (mut g Gen) call_args(node ast.CallExpr) { + args := if g.is_js_call { node.args[1..] } else { node.args } + expected_types := node.expected_arg_types + // only v variadic, C variadic args will be appeneded like normal args + is_variadic := expected_types.len > 0 && expected_types.last().has_flag(.variadic) + && node.language == .v + for i, arg in args { + if is_variadic && i == expected_types.len - 1 { + break + } + use_tmp_var_autofree := g.is_autofree && arg.typ == ast.string_type && arg.is_tmp_autofree + && !g.inside_const && !g.is_builtin_mod + // g.write('/* af=$arg.is_tmp_autofree */') + // some c fn definitions dont have args (cfns.v) or are not updated in checker + // when these are fixed we wont need this check + if i < expected_types.len { + if use_tmp_var_autofree { + if arg.is_tmp_autofree { // && !g.is_js_call { + // We saved expressions in temp variables so that they can be freed later. + // `foo(str + str2) => x := str + str2; foo(x); x.free()` + // g.write('_arg_expr_${g.called_fn_name}_$i') + // Use these variables here. + fn_name := node.name.replace('.', '_') + // name := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + name := '_arg_expr_${fn_name}_${i + 1}_$node.pos.pos' + g.write('/*af arg*/' + name) + } + } else { + if node.concrete_types.len > 0 && arg.expr.is_auto_deref_var() && !arg.is_mut + && !expected_types[i].is_ptr() { + g.write('*') + } + g.ref_or_deref_arg(arg, expected_types[i], node.language) + } + } else { + if use_tmp_var_autofree { + // TODO copypasta, move to an inline fn + fn_name := node.name.replace('.', '_') + // name := '_tt${g.tmp_count2}_arg_expr_${fn_name}_$i' + name := '_arg_expr_${fn_name}_${i + 1}_$node.pos.pos' + g.write('/*af arg2*/' + name) + } else { + g.expr(arg.expr) + } + } + if i < args.len - 1 || is_variadic { + g.write(', ') + } + } + arg_nr := expected_types.len - 1 + if is_variadic { + varg_type := expected_types.last() + variadic_count := args.len - arg_nr + arr_sym := g.table.get_type_symbol(varg_type) + mut arr_info := arr_sym.info as ast.Array + if varg_type.has_flag(.generic) { + if fn_def := g.table.find_fn(node.name) { + if utyp := g.table.resolve_generic_to_concrete(arr_info.elem_type, fn_def.generic_names, + node.concrete_types) + { + arr_info.elem_type = utyp + } + } else { + g.error('unable to find function $node.name', node.pos) + } + } + elem_type := g.typ(arr_info.elem_type) + if g.pref.translated && args.len == 1 { + // Handle `foo(c'str')` for `fn foo(args ...&u8)` + // TODOC2V handle this in a better place + // println(g.table.type_to_str(args[0].typ)) + g.expr(args[0].expr) + } else if args.len > 0 && args[args.len - 1].expr is ast.ArrayDecompose { + g.expr(args[args.len - 1].expr) + } else { + if variadic_count > 0 { + noscan := g.check_noscan(arr_info.elem_type) + g.write('new_array_from_c_array${noscan}($variadic_count, $variadic_count, sizeof($elem_type), _MOV(($elem_type[$variadic_count]){') + for j in arg_nr .. args.len { + g.ref_or_deref_arg(args[j], arr_info.elem_type, node.language) + if j < args.len - 1 { + g.write(', ') + } + } + g.write('}))') + } else { + g.write('__new_array(0, 0, sizeof($elem_type))') + } + } + } +} + +// similar to `autofree_call_pregen()` but only to to handle [keep_args_alive] for C functions +fn (mut g Gen) keep_alive_call_pregen(node ast.CallExpr) int { + g.empty_line = true + g.writeln('// keep_alive_call_pregen()') + // reserve the next tmp_vars for arguments + tmp_cnt_save := g.tmp_count + 1 + g.tmp_count += node.args.len + for i, arg in node.args { + // save all arguments in temp vars (not only pointers) to make sure the + // evaluation order is preserved + expected_type := node.expected_arg_types[i] + typ := g.table.get_type_symbol(expected_type).cname + g.write('$typ __tmp_arg_${tmp_cnt_save + i} = ') + // g.expr(arg.expr) + g.ref_or_deref_arg(arg, expected_type, node.language) + g.writeln(';') + } + g.empty_line = false + return tmp_cnt_save +} + +fn (mut g Gen) keep_alive_call_postgen(node ast.CallExpr, tmp_cnt_save int) { + g.writeln('// keep_alive_call_postgen()') + for i, expected_type in node.expected_arg_types { + if expected_type.is_ptr() || expected_type.is_pointer() { + g.writeln('GC_reachable_here(__tmp_arg_${tmp_cnt_save + i});') + } + } +} + +[inline] +fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang ast.Language) { + arg_is_ptr := expected_type.is_ptr() || expected_type.idx() in ast.pointer_type_idxs + expr_is_ptr := arg.typ.is_ptr() || arg.typ.idx() in ast.pointer_type_idxs + if expected_type == 0 { + g.checker_bug('ref_or_deref_arg expected_type is 0', arg.pos) + } + exp_sym := g.table.get_type_symbol(expected_type) + mut needs_closing := false + if arg.is_mut && !arg_is_ptr { + g.write('&/*mut*/') + } else if arg_is_ptr && !expr_is_ptr { + if arg.is_mut { + if exp_sym.kind == .array { + if (arg.expr is ast.Ident && (arg.expr as ast.Ident).kind == .variable) + || arg.expr is ast.SelectorExpr { + g.write('&/*arr*/') + g.expr(arg.expr) + } else { + // Special case for mutable arrays. We can't `&` function + // results, have to use `(array[]){ expr }[0]` hack. + g.write('&/*111*/(array[]){') + g.expr(arg.expr) + g.write('}[0]') + } + return + } + } + if !g.is_json_fn { + if arg.typ == 0 { + g.checker_bug('ref_or_deref_arg arg.typ is 0', arg.pos) + } + arg_typ_sym := g.table.get_type_symbol(arg.typ) + expected_deref_type := if expected_type.is_ptr() { + expected_type.deref() + } else { + expected_type + } + deref_sym := g.table.get_type_symbol(expected_deref_type) + if !((arg_typ_sym.kind == .function) + || deref_sym.kind in [.sum_type, .interface_]) && lang != .c { + if arg.expr.is_lvalue() { + g.write('(voidptr)&/*qq*/') + } else { + mut atype := expected_deref_type + if atype.has_flag(.generic) { + atype = g.unwrap_generic(atype) + } + if atype.has_flag(.generic) { + g.write('(voidptr)&/*qq*/') + } else { + needs_closing = true + g.write('ADDR(${g.typ(atype)}/*qq*/, ') + } + } + } + } + } else if arg.typ.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) { + if expected_type.is_ptr() { + g.write('&') + } + g.expr(arg.expr) + g.write('->val') + return + } + g.expr_with_cast(arg.expr, arg.typ, expected_type) + if needs_closing { + g.write(')') + } +} + +fn (mut g Gen) is_gui_app() bool { + $if windows { + if g.force_main_console { + return false + } + for cf in g.table.cflags { + if cf.value == 'gdi32' { + return true + } + } + } + return false +} + +fn (g &Gen) fileis(s string) bool { + return g.file.path.contains(s) +} + +fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string { + mut msvc_attrs := '' + for attr in attrs { + match attr.name { + 'inline' { + g.write('inline ') + } + 'noinline' { + // since these are supported by GCC, clang and MSVC, we can consider them officially supported. + g.write('__NOINLINE ') + } + 'noreturn' { + // a `[noreturn]` tag tells the compiler, that a function + // *DOES NOT RETURN* to its callsites. + // See: https://en.cppreference.com/w/c/language/_Noreturn + // Such functions should have no return type. They can be used + // in places where `panic(err)` or `exit(0)` can be used. + // panic/1 and exit/0 themselves will also be marked as + // `[noreturn]` soon. + // These functions should have busy `for{}` loops injected + // at their end, when they do not end by calling other fns + // marked by `[noreturn]`. + g.write('VNORETURN ') + } + 'irq_handler' { + g.write('__IRQHANDLER ') + } + '_cold' { + // GCC/clang attributes + // prefixed by _ to indicate they're for advanced users only and not really supported by V. + // source for descriptions: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes + // The cold attribute on functions is used to inform the compiler that the function is unlikely + // to be executed. The function is optimized for size rather than speed and on many targets it + // is placed into a special subsection of the text section so all cold functions appear close + // together, improving code locality of non-cold parts of program. + g.write('__attribute__((cold)) ') + } + '_constructor' { + // The constructor attribute causes the function to be called automatically before execution + // enters main (). + g.write('__attribute__((constructor)) ') + } + '_destructor' { + // The destructor attribute causes the function to be called automatically after main () + // completes or exit () is called. + g.write('__attribute__((destructor)) ') + } + '_flatten' { + // Generally, inlining into a function is limited. For a function marked with this attribute, + // every call inside this function is inlined, if possible. + g.write('__attribute__((flatten)) ') + } + '_hot' { + // The hot attribute on a function is used to inform the compiler that the function is a hot + // spot of the compiled program. + g.write('__attribute__((hot)) ') + } + '_malloc' { + // This tells the compiler that a function is malloc-like, i.e., that the pointer P returned by + // the function cannot alias any other pointer valid when the function returns, and moreover no + // pointers to valid objects occur in any storage addressed by P. + g.write('__attribute__((malloc)) ') + } + '_pure' { + // Calls to functions whose return value is not affected by changes to the observable state + // of the program and that have no observable effects on such state other than to return a + // value may lend themselves to optimizations such as common subexpression elimination. + // Declaring such functions with the const attribute allows GCC to avoid emitting some calls in + // repeated invocations of the function with the same argument values. + g.write('__attribute__((const)) ') + } + '_naked' { + g.write('__attribute__((naked)) ') + } + 'windows_stdcall' { + // windows attributes (msvc/mingw) + // prefixed by windows to indicate they're for advanced users only and not really supported by V. + msvc_attrs += '__stdcall ' + } + 'console' { + g.force_main_console = true + } + else { + // nothing but keep V happy + } + } + } + return msvc_attrs +} diff --git a/v_windows/v/vlib/v/gen/c/index.v b/v_windows/v/vlib/v/gen/c/index.v new file mode 100644 index 0000000..26dc455 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/index.v @@ -0,0 +1,452 @@ +// Copyright (c) 2019-2021 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.get_final_type_symbol(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 + 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.get_final_type_symbol(node.left_type) + if sym.kind == .string { + g.write('string_substr(') + g.expr(node.left) + } else if sym.kind == .array { + g.write('array_slice(') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.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) + g.write('array_slice(new_array_from_c_array${noscan}(') + g.write('$info.size') + g.write(', $info.size') + g.write(', sizeof(') + if node.left_type.is_ptr() { + g.write('(*') + } + g.expr(node.left) + if node.left_type.is_ptr() { + g.write(')') + } + g.write('[0]), ') + 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 node.left_type.is_ptr() { + g.write('(') + g.write('*') + g.expr(node.left) + g.write(')') + g.write('.len') + } else { + g.expr(node.left) + g.write('.len') + } + g.write(')') +} + +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 + elem_type_str := g.typ(info.elem_type) + elem_type := info.elem_type + elem_typ := g.table.get_type_symbol(elem_type) + // `vals[i].field = x` is an exception and requires `array_get`: + // `(*(Val*)array_get(vals, i)).field = x;` + is_selector := node.left is ast.SelectorExpr + if g.is_assign_lhs && !is_selector && node.is_setter { + is_direct_array_access := g.fn_decl != 0 && g.fn_decl.is_direct_arr + is_op_assign := g.assign_op != .assign && info.elem_type != ast.string_type + array_ptr_type_str := match elem_typ.kind { + .function { 'voidptr*' } + else { '$elem_type_str*' } + } + if is_direct_array_access { + g.write('(($array_ptr_type_str)') + } else if is_op_assign { + g.write('(*($array_ptr_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 { + if elem_typ.kind == .function { + g.write(', &(voidptr[]) { ') + } else { + 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 + array_ptr_type_str := match elem_typ.kind { + .function { 'voidptr*' } + else { '$elem_type_str*' } + } + // 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('$array_ptr_type_str $tmp_opt_ptr = ($array_ptr_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_typ.info is ast.FnType { + g.write('((') + g.write_fn_ptr_decl(&elem_typ.info, '') + g.write(')(*($array_ptr_type_str)/*ee elem_typ */array_get(') + } + if left_is_ptr && !node.left_type.has_flag(.shared_f) { + g.write('*') + } + } else if is_direct_array_access { + g.write('(($array_ptr_type_str)') + } else { + g.write('(*($array_ptr_type_str)/*ee elem_typ */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.get_type_symbol(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.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 + elem_type_str := g.typ(elem_type) + elem_typ := g.table.get_type_symbol(elem_type) + get_and_set_types := elem_typ.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) { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(', &($key_type_str[]){') + g.expr(node.index) + g.write('}') + if elem_typ.kind == .function { + g.write(', &(voidptr[]) { ') + } else { + 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 { + g.write('&') + } + g.expr(node.left) + 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_typ.info is ast.FnType { + g.write('((') + g.write_fn_ptr_decl(&elem_typ.info, '') + g.write(')(*(voidptr*)map_get(') + } + } else if elem_typ.kind == .function { + 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 if elem_typ.kind == .function { + 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') + } + } +} diff --git a/v_windows/v/vlib/v/gen/c/infix_expr.v b/v_windows/v/vlib/v/gen/c/infix_expr.v new file mode 100644 index 0000000..dc842c9 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/infix_expr.v @@ -0,0 +1,628 @@ +// Copyright (c) 2019-2021 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.token +import v.util + +fn (mut g Gen) infix_expr(node ast.InfixExpr) { + if node.auto_locked != '' { + g.writeln('sync__RwMutex_lock(&$node.auto_locked->mtx);') + } + match node.op { + .arrow { + g.infix_expr_arrow_op(node) + } + .eq, .ne { + g.infix_expr_eq_op(node) + } + .gt, .ge, .lt, .le { + g.infix_expr_cmp_op(node) + } + .key_in, .not_in { + g.infix_expr_in_op(node) + } + .key_is, .not_is { + g.infix_expr_is_op(node) + } + .plus, .minus, .mul, .div, .mod { + g.infix_expr_arithmetic_op(node) + } + .left_shift { + g.infix_expr_left_shift_op(node) + } + .and, .logical_or { + g.infix_expr_and_or_op(node) + } + else { + // `x & y == 0` => `(x & y) == 0` in C + need_par := node.op in [.amp, .pipe, .xor] + if need_par { + g.write('(') + } + g.gen_plain_infix_expr(node) + if need_par { + g.write(')') + } + } + } + if node.auto_locked != '' { + g.writeln(';') + g.write('sync__RwMutex_unlock(&$node.auto_locked->mtx)') + } +} + +// infix_expr_arrow_op generates C code for pushing into channels (chan <- val) +fn (mut g Gen) infix_expr_arrow_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + styp := left.sym.cname + elem_type := (left.sym.info as ast.Chan).elem_type + gen_or := node.or_block.kind != .absent + tmp_opt := if gen_or { g.new_tmp_var() } else { '' } + if gen_or { + elem_styp := g.typ(elem_type) + g.register_chan_push_optional_call(elem_styp, styp) + g.write('Option_void $tmp_opt = __Option_${styp}_pushval(') + } else { + g.write('__${styp}_pushval(') + } + g.expr(node.left) + g.write(', ') + g.expr(node.right) + g.write(')') + if gen_or { + g.or_block(tmp_opt, node.or_block, ast.void_type) + } +} + +// infix_expr_eq_op generates code for `==` and `!=` +fn (mut g Gen) infix_expr_eq_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '==') + if (left.typ.is_ptr() && right.typ.is_int()) || (right.typ.is_ptr() && left.typ.is_int()) { + g.gen_plain_infix_expr(node) + } else if (left.typ.idx() == ast.string_type_idx || (!has_operator_overloading + && left.unaliased.idx() == ast.string_type_idx)) && node.right is ast.StringLiteral + && (node.right as ast.StringLiteral).val == '' { + // `str == ''` -> `str.len == 0` optimization + g.write('(') + g.expr(node.left) + g.write(')') + arrow := if left.typ.is_ptr() { '->' } else { '.' } + g.write('${arrow}len $node.op 0') + } else if has_operator_overloading { + if node.op == .ne { + g.write('!') + } + g.write(g.typ(left.unaliased.set_nr_muls(0))) + g.write('__eq(') + g.write('*'.repeat(left.typ.nr_muls())) + g.expr(node.left) + g.write(', ') + g.write('*'.repeat(right.typ.nr_muls())) + g.expr(node.right) + g.write(')') + } else if left.typ.idx() == right.typ.idx() + && left.sym.kind in [.array, .array_fixed, .alias, .map, .struct_, .sum_type] { + match left.sym.kind { + .alias { + ptr_typ := g.gen_alias_equality_fn(left.typ) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_alias_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + .array { + ptr_typ := g.gen_array_equality_fn(left.unaliased.clear_flag(.shared_f)) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_arr_eq(') + if left.typ.is_ptr() && !left.typ.has_flag(.shared_f) { + g.write('*') + } + g.expr(node.left) + if left.typ.has_flag(.shared_f) { + if left.typ.is_ptr() { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(', ') + if right.typ.is_ptr() && !right.typ.has_flag(.shared_f) { + g.write('*') + } + g.expr(node.right) + if right.typ.has_flag(.shared_f) { + if right.typ.is_ptr() { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(')') + } + .array_fixed { + ptr_typ := g.gen_fixed_array_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_arr_eq(') + if left.typ.is_ptr() { + g.write('*') + } + if node.left is ast.ArrayInit { + s := g.typ(left.unaliased) + g.write('($s)') + } + g.expr(node.left) + g.write(', ') + if node.right is ast.ArrayInit { + s := g.typ(right.unaliased) + g.write('($s)') + } + g.expr(node.right) + g.write(')') + } + .map { + ptr_typ := g.gen_map_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_map_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + .struct_ { + ptr_typ := g.gen_struct_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_struct_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + .sum_type { + ptr_typ := g.gen_sumtype_equality_fn(left.unaliased) + if node.op == .ne { + g.write('!') + } + g.write('${ptr_typ}_sumtype_eq(') + if left.typ.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(', ') + if right.typ.is_ptr() { + g.write('*') + } + g.expr(node.right) + g.write(')') + } + else {} + } + } else if left.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && right.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + unsigned_type: left.unaliased + unsigned_expr: node.left + signed_type: right.unaliased + signed_expr: node.right + ) + } else if right.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && left.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + reverse: true + unsigned_type: right.unaliased + unsigned_expr: node.right + signed_type: left.unaliased + signed_expr: node.left + ) + } else { + g.gen_plain_infix_expr(node) + } +} + +// infix_expr_cmp_op generates code for `<`, `<=`, `>`, `>=` +// It handles operator overloading when necessary +fn (mut g Gen) infix_expr_cmp_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + has_operator_overloading := g.table.type_has_method(left.sym, '<') + if left.sym.kind == right.sym.kind && has_operator_overloading { + if node.op in [.le, .ge] { + g.write('!') + } + g.write(g.typ(left.typ.set_nr_muls(0))) + g.write('__lt') + if node.op in [.lt, .ge] { + g.write('(') + g.write('*'.repeat(left.typ.nr_muls())) + g.expr(node.left) + g.write(', ') + g.write('*'.repeat(right.typ.nr_muls())) + g.expr(node.right) + g.write(')') + } else { + g.write('(') + g.write('*'.repeat(right.typ.nr_muls())) + g.expr(node.right) + g.write(', ') + g.write('*'.repeat(left.typ.nr_muls())) + g.expr(node.left) + g.write(')') + } + } else if left.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && right.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + unsigned_type: left.unaliased + unsigned_expr: node.left + signed_type: right.unaliased + signed_expr: node.right + ) + } else if right.unaliased.idx() in [ast.u32_type_idx, ast.u64_type_idx] + && left.unaliased.is_signed() { + g.gen_safe_integer_infix_expr( + op: node.op + reverse: true + unsigned_type: right.unaliased + unsigned_expr: node.right + signed_type: left.unaliased + signed_expr: node.left + ) + } else { + g.gen_plain_infix_expr(node) + } +} + +// infix_expr_in_op generates code for `in` and `!in` +fn (mut g Gen) infix_expr_in_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + if node.op == .not_in { + g.write('!') + } + if right.unaliased_sym.kind == .array { + if mut node.right is ast.ArrayInit { + if node.right.exprs.len > 0 { + // `a in [1,2,3]` optimization => `a == 1 || a == 2 || a == 3` + // avoids an allocation + g.write('(') + g.infix_expr_in_optimization(node.left, node.right) + g.write(')') + return + } + } + fn_name := g.gen_array_contains_method(node.right_type) + g.write('(${fn_name}(') + if right.typ.is_ptr() && right.typ.share() != .shared_t { + g.write('*') + } + g.expr(node.right) + if right.typ.share() == .shared_t { + g.write('->val') + } + g.write(', ') + g.expr(node.left) + g.write('))') + return + } else if right.unaliased_sym.kind == .map { + g.write('_IN_MAP(') + if !left.typ.is_ptr() { + styp := g.typ(node.left_type) + g.write('ADDR($styp, ') + g.expr(node.left) + g.write(')') + } else { + g.expr(node.left) + } + g.write(', ') + if !right.typ.is_ptr() { + g.write('ADDR(map, ') + g.expr(node.right) + g.write(')') + } else { + g.expr(node.right) + } + g.write(')') + } else if right.unaliased_sym.kind == .string { + g.write('string_contains(') + g.expr(node.right) + g.write(', ') + g.expr(node.left) + g.write(')') + } +} + +// infix_expr_in_optimization optimizes `<var> in <array>` expressions, +// and transform them in a serie of equality comparison +// i.e. `a in [1,2,3]` => `a == 1 || a == 2 || a == 3` +fn (mut g Gen) infix_expr_in_optimization(left ast.Expr, right ast.ArrayInit) { + is_str := right.elem_type.idx() == ast.string_type_idx + elem_sym := g.table.get_type_symbol(right.elem_type) + is_array := elem_sym.kind == .array + for i, array_expr in right.exprs { + if is_str { + g.write('string__eq(') + } else if is_array { + ptr_typ := g.gen_array_equality_fn(right.elem_type) + g.write('${ptr_typ}_arr_eq(') + } + g.expr(left) + if is_str || is_array { + g.write(', ') + } else { + g.write(' == ') + } + g.expr(array_expr) + if is_str || is_array { + g.write(')') + } + if i < right.exprs.len - 1 { + g.write(' || ') + } + } +} + +// infix_expr_is_op generates code for `is` and `!is` +fn (mut g Gen) infix_expr_is_op(node ast.InfixExpr) { + sym := g.table.get_type_symbol(node.left_type) + right_sym := g.table.get_type_symbol(node.right_type) + if sym.kind == .interface_ && right_sym.kind == .interface_ { + g.gen_interface_is_op(node) + return + } + + cmp_op := if node.op == .key_is { '==' } else { '!=' } + g.write('(') + g.expr(node.left) + g.write(')') + if node.left_type.is_ptr() { + g.write('->') + } else { + g.write('.') + } + if sym.kind == .interface_ { + g.write('_typ $cmp_op ') + // `_Animal_Dog_index` + sub_type := match mut node.right { + ast.TypeNode { node.right.typ } + ast.None { g.table.type_idxs['None__'] } + else { ast.Type(0) } + } + sub_sym := g.table.get_type_symbol(sub_type) + g.write('_${c_name(sym.name)}_${c_name(sub_sym.name)}_index') + return + } else if sym.kind == .sum_type { + g.write('_typ $cmp_op ') + } + g.expr(node.right) +} + +fn (mut g Gen) gen_interface_is_op(node ast.InfixExpr) { + mut left_sym := g.table.get_type_symbol(node.left_type) + right_sym := g.table.get_type_symbol(node.right_type) + + mut info := left_sym.info as ast.Interface + + common_variants := info.conversions[node.right_type] or { + left_variants := g.table.iface_types[left_sym.name] + right_variants := g.table.iface_types[right_sym.name] + c := left_variants.filter(it in right_variants) + info.conversions[node.right_type] = c + c + } + left_sym.info = info + if common_variants.len == 0 { + g.write('false') + return + } + g.write('I_${left_sym.cname}_is_I_${right_sym.cname}(') + if node.left_type.is_ptr() { + g.write('*') + } + g.expr(node.left) + g.write(')') +} + +// infix_expr_arithmetic_op generates code for `+`, `-`, `*`, `/`, and `%` +// It handles operator overloading when necessary +fn (mut g Gen) infix_expr_arithmetic_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + method := g.table.type_find_method(left.sym, node.op.str()) or { + g.gen_plain_infix_expr(node) + return + } + left_styp := g.typ(left.typ.set_nr_muls(0)) + g.write(left_styp) + g.write('_') + g.write(util.replace_op(node.op.str())) + g.write('(') + g.op_arg(node.left, method.params[0].typ, left.typ) + g.write(', ') + g.op_arg(node.right, method.params[1].typ, right.typ) + g.write(')') +} + +// infix_expr_left_shift_op generates code for the `<<` operator +// This can either be a value pushed into an array or a bit shift +fn (mut g Gen) infix_expr_left_shift_op(node ast.InfixExpr) { + left := g.unwrap(node.left_type) + right := g.unwrap(node.right_type) + if left.unaliased_sym.kind == .array { + // arr << val + tmp_var := g.new_tmp_var() + array_info := left.unaliased_sym.info as ast.Array + noscan := g.check_noscan(array_info.elem_type) + //&& array_info.elem_type != g.unwrap_generic(node.right_type) + if right.unaliased_sym.kind == .array && array_info.elem_type != right.typ { + // push an array => PUSH_MANY, but not if pushing an array to 2d array (`[][]int << []int`) + g.write('_PUSH_MANY${noscan}(') + mut expected_push_many_atype := left.typ + if !expected_push_many_atype.is_ptr() { + // fn f(mut a []int) { a << [1,2,3] } -> type of `a` is `array_int*` -> no need for & + g.write('&') + } else { + expected_push_many_atype = expected_push_many_atype.deref() + } + g.expr(node.left) + g.write(', (') + g.expr_with_cast(node.right, node.right_type, left.unaliased) + styp := g.typ(expected_push_many_atype) + g.write('), $tmp_var, $styp)') + } else { + // push a single element + elem_type_str := g.typ(array_info.elem_type) + elem_sym := g.table.get_type_symbol(array_info.elem_type) + g.write('array_push${noscan}((array*)') + if !left.typ.is_ptr() { + g.write('&') + } + g.expr(node.left) + if elem_sym.kind == .function { + g.write(', _MOV((voidptr[]){ ') + } else { + g.write(', _MOV(($elem_type_str[]){ ') + } + // if g.autofree + needs_clone := array_info.elem_type.idx() == ast.string_type_idx && !g.is_builtin_mod + if needs_clone { + g.write('string_clone(') + } + if right.unaliased_sym.kind == .interface_ && node.right.is_auto_deref_var() { + g.write('*') + } + g.expr_with_cast(node.right, node.right_type, array_info.elem_type) + if needs_clone { + g.write(')') + } + g.write(' }))') + } + } else { + g.gen_plain_infix_expr(node) + } +} + +// infix_expr_and_or_op generates code for `&&` and `||` +fn (mut g Gen) infix_expr_and_or_op(node ast.InfixExpr) { + if node.right is ast.IfExpr { + // b := a && if true { a = false ...} else {...} + prev_inside_ternary := g.inside_ternary + g.inside_ternary = 0 + if g.need_tmp_var_in_if(node.right) { + tmp := g.new_tmp_var() + cur_line := g.go_before_stmt(0).trim_space() + g.empty_line = true + g.write('bool $tmp = (') + g.expr(node.left) + g.writeln(');') + g.stmt_path_pos << g.out.len + g.write('$cur_line $tmp $node.op.str() ') + g.infix_left_var_name = if node.op == .and { tmp } else { '!$tmp' } + g.expr(node.right) + g.infix_left_var_name = '' + g.inside_ternary = prev_inside_ternary + return + } + g.inside_ternary = prev_inside_ternary + } + g.gen_plain_infix_expr(node) +} + +// gen_plain_infix_expr generates basic code for infix expressions, +// without any overloading of any kind +// i.e. v`a + 1` => c`a + 1` +// It handles auto dereferencing of variables, as well as automatic casting +// (see Gen.expr_with_cast for more details) +fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) { + if node.left_type.is_ptr() && node.left.is_auto_deref_var() { + g.write('*') + } + g.expr(node.left) + g.write(' $node.op.str() ') + g.expr_with_cast(node.right, node.right_type, node.left_type) +} + +fn (mut g Gen) op_arg(expr ast.Expr, expected ast.Type, got ast.Type) { + mut needs_closing := false + mut nr_muls := got.nr_muls() + if expected.is_ptr() { + if nr_muls > 0 { + nr_muls-- + } else { + if expr.is_lvalue() { + g.write('&') + } else { + styp := g.typ(got.set_nr_muls(0)) + g.write('ADDR($styp, ') + needs_closing = true + } + } + } + g.write('*'.repeat(nr_muls)) + g.expr(expr) + if needs_closing { + g.write(')') + } +} + +struct GenSafeIntegerCfg { + op token.Kind + reverse bool + unsigned_type ast.Type + unsigned_expr ast.Expr + signed_type ast.Type + signed_expr ast.Expr +} + +// gen_safe_integer_infix_expr generates code for comparison of +// unsigned and signed integers +fn (mut g Gen) gen_safe_integer_infix_expr(cfg GenSafeIntegerCfg) { + bitsize := if cfg.unsigned_type.idx() == ast.u32_type_idx + && cfg.signed_type.idx() != ast.i64_type_idx { + 32 + } else { + 64 + } + op_idx := int(cfg.op) - int(token.Kind.eq) + op_str := if cfg.reverse { cmp_rev[op_idx] } else { cmp_str[op_idx] } + g.write('_us${bitsize}_${op_str}(') + g.expr(cfg.unsigned_expr) + g.write(',') + g.expr(cfg.signed_expr) + g.write(')') +} diff --git a/v_windows/v/vlib/v/gen/c/json.v b/v_windows/v/vlib/v/gen/c/json.v new file mode 100644 index 0000000..cfc480d --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/json.v @@ -0,0 +1,339 @@ +// Copyright (c) 2019-2021 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 +import strings + +// TODO replace with comptime code generation. +// TODO remove cJSON dependency. +// OLD: User decode_User(string js) { +// now it's +// User decode_User(cJSON* root) { +// User res; +// res.name = decode_string(js_get(root, "name")); +// res.profile = decode_Profile(js_get(root, "profile")); +// return res; +// } +// Codegen json_decode/encode funcs +fn (mut g Gen) gen_json_for_type(typ ast.Type) { + utyp := g.unwrap_generic(typ) + mut dec := strings.new_builder(100) + mut enc := strings.new_builder(100) + sym := g.table.get_type_symbol(utyp) + styp := g.typ(utyp) + if is_js_prim(sym.name) || sym.kind == .enum_ { + return + } + if sym.kind == .array { + // return + } + if sym.name in g.json_types { + return + } + g.json_types << sym.name + // println('gen_json_for_type($sym.name)') + // decode_TYPE funcs receive an actual cJSON* object to decode + // cJSON_Parse(str) call is added by the compiler + // Code gen decoder + dec_fn_name := js_dec_name(styp) + // Make sure that this optional type actually exists + g.register_optional(utyp) + dec_fn_dec := 'Option_$styp ${dec_fn_name}(cJSON* root)' + dec.writeln(' +$dec_fn_dec { + $styp res; + if (!root) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + // fprintf(stderr, "Error in decode() for $styp error_ptr=: %s\\n", error_ptr); + // printf("\\nbad js=%%s\\n", js.str); + return (Option_$styp){.state = 2,.err = _v_error(tos2((byteptr)error_ptr)),.data = {0}}; + } + } +') + g.json_forward_decls.writeln('$dec_fn_dec;') + // Code gen encoder + // encode_TYPE funcs receive an object to encode + enc_fn_name := js_enc_name(styp) + enc_fn_dec := 'cJSON* ${enc_fn_name}($styp val)' + g.json_forward_decls.writeln('$enc_fn_dec;\n') + enc.writeln(' +$enc_fn_dec { +\tcJSON *o;') + if sym.kind == .array { + // Handle arrays + value_type := g.table.value_type(utyp) + // If we have `[]Profile`, have to register a Profile en(de)coder first + g.gen_json_for_type(value_type) + dec.writeln(g.decode_array(value_type)) + enc.writeln(g.encode_array(value_type)) + // enc += g.encode_array(t) + } else if sym.kind == .map { + // Handle maps + m := sym.info as ast.Map + g.gen_json_for_type(m.key_type) + g.gen_json_for_type(m.value_type) + dec.writeln(g.decode_map(m.key_type, m.value_type)) + enc.writeln(g.encode_map(m.key_type, m.value_type)) + } else if sym.kind == .alias { + a := sym.info as ast.Alias + parent_typ := a.parent_type + psym := g.table.get_type_symbol(parent_typ) + if is_js_prim(g.typ(parent_typ)) { + g.gen_json_for_type(parent_typ) + return + } + enc.writeln('\to = cJSON_CreateObject();') + if psym.info !is ast.Struct { + verror('json: $sym.name is not struct') + } + g.gen_struct_enc_dec(psym.info, styp, mut enc, mut dec) + } else { + enc.writeln('\to = cJSON_CreateObject();') + // Structs. Range through fields + if sym.info !is ast.Struct { + verror('json: $sym.name is not struct') + } + g.gen_struct_enc_dec(sym.info, styp, mut enc, mut dec) + } + // cJSON_delete + // p.cgen.fns << '$dec return opt_ok(res); \n}' + dec.writeln('\tOption_$styp ret;') + dec.writeln('\topt_ok(&res, (Option*)&ret, sizeof(res));') + dec.writeln('\treturn ret;\n}') + enc.writeln('\treturn o;\n}') + g.definitions.writeln(dec.str()) + g.gowrappers.writeln(enc.str()) +} + +[inline] +fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc strings.Builder, mut dec strings.Builder) { + info := type_info as ast.Struct + for field in info.fields { + mut name := field.name + mut is_raw := false + mut is_skip := false + mut is_required := false + for attr in field.attrs { + match attr.name { + 'json' { + name = attr.arg + } + 'skip' { + is_skip = true + } + 'raw' { + is_raw = true + } + 'required' { + is_required = true + } + else {} + } + } + if is_skip { + continue + } + field_type := g.typ(field.typ) + field_sym := g.table.get_type_symbol(field.typ) + // First generate decoding + if is_raw { + dec.writeln('\tres.${c_name(field.name)} = tos5(cJSON_PrintUnformatted(' + + 'js_get(root, "$name")));') + } else { + // Now generate decoders for all field types in this struct + // need to do it here so that these functions are generated first + g.gen_json_for_type(field.typ) + dec_name := js_dec_name(field_type) + if is_js_prim(field_type) { + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = $dec_name (jsonroot_$tmp);') + } else if field_sym.kind == .enum_ { + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = json__decode_u64(jsonroot_$tmp);') + } else if field_sym.name == 'time.Time' { + // time struct requires special treatment + // it has to be decoded from a unix timestamp number + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = time__unix(json__decode_u64(jsonroot_$tmp));') + } else if field_sym.kind == .alias { + alias := field_sym.info as ast.Alias + parent_type := g.typ(alias.parent_type) + parent_dec_name := js_dec_name(parent_type) + if is_js_prim(parent_type) { + tmp := g.new_tmp_var() + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = $parent_dec_name (jsonroot_$tmp);') + } else { + g.gen_json_for_type(field.typ) + tmp := g.new_tmp_var() + gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + } + } else { + tmp := g.new_tmp_var() + gen_js_get_opt(dec_name, field_type, styp, tmp, name, mut dec, is_required) + dec.writeln('\tres.${c_name(field.name)} = *($field_type*) ${tmp}.data;') + } + } + // Encoding + mut enc_name := js_enc_name(field_type) + if !is_js_prim(field_type) { + if field_sym.kind == .alias { + ainfo := field_sym.info as ast.Alias + enc_name = js_enc_name(g.typ(ainfo.parent_type)) + } + } + if field_sym.kind == .enum_ { + enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));\n') + } else { + if field_sym.name == 'time.Time' { + // time struct requires special treatment + // it has to be encoded as a unix timestamp number + enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}._v_unix));') + } else { + enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));\n') + } + } + } +} + +fn gen_js_get(styp string, tmp string, name string, mut dec strings.Builder, is_required bool) { + dec.writeln('\tcJSON *jsonroot_$tmp = js_get(root,"$name");') + if is_required { + dec.writeln('\tif(jsonroot_$tmp == 0) {') + dec.writeln('\t\treturn (Option_$styp){ .state = 2, .err = _v_error(_SLIT("expected field \'$name\' is missing")), .data = {0} };') + dec.writeln('\t}') + } +} + +fn gen_js_get_opt(dec_name string, field_type string, styp string, tmp string, name string, mut dec strings.Builder, is_required bool) { + gen_js_get(styp, tmp, name, mut dec, is_required) + dec.writeln('\tOption_$field_type $tmp = $dec_name (jsonroot_$tmp);') + dec.writeln('\tif(${tmp}.state != 0) {') + dec.writeln('\t\treturn (Option_$styp){ .state = ${tmp}.state, .err = ${tmp}.err, .data = {0} };') + dec.writeln('\t}') +} + +fn js_enc_name(typ string) string { + suffix := if typ.ends_with('*') { typ.replace('*', '') } else { typ } + name := 'json__encode_$suffix' + return util.no_dots(name) +} + +fn js_dec_name(typ string) string { + name := 'json__decode_$typ' + return util.no_dots(name) +} + +fn is_js_prim(typ string) bool { + return typ in ['int', 'string', 'bool', 'f32', 'f64', 'i8', 'i16', 'i64', 'u16', 'u32', 'u64', + 'byte', + ] +} + +fn (mut g Gen) decode_array(value_type ast.Type) string { + styp := g.typ(value_type) + fn_name := js_dec_name(styp) + mut s := '' + if is_js_prim(styp) { + s = '$styp val = ${fn_name}((cJSON *)jsval); ' + } else { + s = ' + Option_$styp val2 = $fn_name ((cJSON *)jsval); + if(val2.state != 0) { + array_free(&res); + return *(Option_Array_$styp*)&val2; + } + $styp val = *($styp*)val2.data; +' + } + noscan := g.check_noscan(value_type) + return ' + if(root && !cJSON_IsArray(root) && !cJSON_IsNull(root)) { + return (Option_Array_$styp){.state = 2, .err = _v_error(string__plus(_SLIT("Json element is not an array: "), tos2((byteptr)cJSON_PrintUnformatted(root)))), .data = {0}}; + } + res = __new_array${noscan}(0, 0, sizeof($styp)); + const cJSON *jsval = NULL; + cJSON_ArrayForEach(jsval, root) + { + $s + array_push${noscan}((array*)&res, &val); + } +' +} + +fn (mut g Gen) encode_array(value_type ast.Type) string { + styp := g.typ(value_type) + fn_name := js_enc_name(styp) + return ' + o = cJSON_CreateArray(); + for (int i = 0; i < val.len; i++){ + cJSON_AddItemToArray(o, $fn_name ( (($styp*)val.data)[i] )); + } +' +} + +fn (mut g Gen) decode_map(key_type ast.Type, value_type ast.Type) string { + styp := g.typ(key_type) + styp_v := g.typ(value_type) + key_type_symbol := g.table.get_type_symbol(key_type) + hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_type_symbol) + fn_name_v := js_dec_name(styp_v) + mut s := '' + if is_js_prim(styp_v) { + s = '$styp_v val = $fn_name_v (js_get(root, jsval->string));' + } else { + s = ' + Option_$styp_v val2 = $fn_name_v (js_get(root, jsval->string)); + if(val2.state != 0) { + map_free(&res); + return *(Option_Map_${styp}_$styp_v*)&val2; + } + $styp_v val = *($styp_v*)val2.data; +' + } + return ' + if(!cJSON_IsObject(root) && !cJSON_IsNull(root)) { + return (Option_Map_${styp}_$styp_v){ .state = 2, .err = _v_error(string__plus(_SLIT("Json element is not an object: "), tos2((byteptr)cJSON_PrintUnformatted(root)))), .data = {0}}; + } + res = new_map(sizeof($styp), sizeof($styp_v), $hash_fn, $key_eq_fn, $clone_fn, $free_fn); + cJSON *jsval = NULL; + cJSON_ArrayForEach(jsval, root) + { + $s + string key = tos2((byteptr)jsval->string); + map_set(&res, &key, &val); + } +' +} + +fn (mut g Gen) encode_map(key_type ast.Type, value_type ast.Type) string { + styp := g.typ(key_type) + styp_v := g.typ(value_type) + fn_name_v := js_enc_name(styp_v) + zero := g.type_default(value_type) + keys_tmp := g.new_tmp_var() + mut key := 'string key = ' + if key_type.is_string() { + key += '(($styp*)${keys_tmp}.data)[i];' + } else { + // key += '${styp}_str((($styp*)${keys_tmp}.data)[i]);' + verror('json: encode only maps with string keys') + } + return ' + o = cJSON_CreateObject(); + Array_$styp $keys_tmp = map_keys(&val); + for (int i = 0; i < ${keys_tmp}.len; ++i) { + $key + cJSON_AddItemToObject(o, (char*) key.str, $fn_name_v ( *($styp_v*) map_get(&val, &key, &($styp_v[]) { $zero } ) ) ); + } + array_free(&$keys_tmp); +' +} diff --git a/v_windows/v/vlib/v/gen/c/live.v b/v_windows/v/vlib/v/gen/c/live.v new file mode 100644 index 0000000..69709f9 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/live.v @@ -0,0 +1,104 @@ +module c + +import v.pref +import v.util + +fn (mut g Gen) generate_hotcode_reloading_declarations() { + if g.pref.os == .windows { + if g.pref.is_livemain { + g.hotcode_definitions.writeln('HANDLE live_fn_mutex = 0;') + } + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('HANDLE live_fn_mutex;') + } + g.hotcode_definitions.writeln(' +void pthread_mutex_lock(HANDLE *m) { + WaitForSingleObject(*m, INFINITE); +} +void pthread_mutex_unlock(HANDLE *m) { + ReleaseMutex(*m); +} +') + } else { + if g.pref.is_livemain { + g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;') + } + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex;') + } + } +} + +fn (mut g Gen) generate_hotcode_reloader_code() { + if g.pref.is_liveshared { + g.hotcode_definitions.writeln('') + return + } + // Hot code reloading + if g.pref.is_livemain { + mut phd := '' + mut load_code := []string{} + if g.pref.os != .windows { + for so_fn in g.hotcode_fn_names { + load_code << 'impl_live_$so_fn = dlsym(live_lib, "impl_live_$so_fn");' + } + phd = c.posix_hotcode_definitions_1 + } else { + for so_fn in g.hotcode_fn_names { + load_code << 'impl_live_$so_fn = (void *)GetProcAddress(live_lib, "impl_live_$so_fn"); ' + } + phd = c.windows_hotcode_definitions_1 + } + g.hotcode_definitions.writeln(phd.replace('@LOAD_FNS@', load_code.join('\n'))) + } +} + +const ( + posix_hotcode_definitions_1 = ' +void v_bind_live_symbols(void* live_lib){ + @LOAD_FNS@ +} +' + windows_hotcode_definitions_1 = ' +void v_bind_live_symbols(void* live_lib){ + @LOAD_FNS@ +} +' +) + +fn (mut g Gen) generate_hotcode_reloading_main_caller() { + if !g.pref.is_livemain { + return + } + g.writeln('') + // We are in live code reload mode, so start the .so loader in the background + g.writeln('\t// live code initialization section:') + g.writeln('\t{') + g.writeln('\t\t// initialization of live function pointers') + for fname in g.hotcode_fn_names { + g.writeln('\t\timpl_live_$fname = 0;') + } + vexe := util.cescaped_path(pref.vexe_path()) + file := util.cescaped_path(g.pref.path) + msvc := if g.pref.ccompiler == 'msvc' { '-cc msvc' } else { '' } + so_debug_flag := if g.pref.is_debug { '-cg' } else { '' } + vopts := '$msvc $so_debug_flag -sharedlive -shared' + // + g.writeln('\t\t// start background reloading thread') + if g.pref.os == .windows { + g.writeln('\t\tlive_fn_mutex = CreateMutexA(0, 0, 0);') + } + g.writeln('\t\tv__live__LiveReloadInfo* live_info = v__live__executable__new_live_reload_info(') + g.writeln('\t\t\t\t\t tos2("$file"),') + g.writeln('\t\t\t\t\t tos2("$vexe"),') + g.writeln('\t\t\t\t\t tos2("$vopts"),') + g.writeln('\t\t\t\t\t &live_fn_mutex,') + g.writeln('\t\t\t\t\t v_bind_live_symbols') + g.writeln('\t\t);') + // g_live_info gives access to the LiveReloadInfo methods, + // to the custom user code, through calling v_live_info() + g.writeln('\t\t g_live_info = (void*)live_info;') + g.writeln('\t\tv__live__executable__start_reloader(live_info);') + g.writeln('\t}\t// end of live code initialization section') + g.writeln('') +} diff --git a/v_windows/v/vlib/v/gen/c/profile.v b/v_windows/v/vlib/v/gen/c/profile.v new file mode 100644 index 0000000..602c7b0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/profile.v @@ -0,0 +1,52 @@ +module c + +import v.ast + +pub struct ProfileCounterMeta { + fn_name string + vpc_name string + vpc_calls string +} + +fn (mut g Gen) profile_fn(fn_decl ast.FnDecl) { + if g.pref.profile_no_inline && fn_decl.attrs.contains('inline') { + g.defer_profile_code = '' + return + } + fn_name := fn_decl.name + if fn_name.starts_with('time.vpc_now') { + g.defer_profile_code = '' + } else { + measure_fn_name := if g.pref.os == .macos { 'time__vpc_now_darwin' } else { 'time__vpc_now' } + fn_profile_counter_name := 'vpc_$g.last_fn_c_name' + fn_profile_counter_name_calls := '${fn_profile_counter_name}_calls' + g.writeln('') + g.writeln('\tdouble _PROF_FN_START = ${measure_fn_name}(); $fn_profile_counter_name_calls++; // $fn_name') + g.writeln('') + g.defer_profile_code = '\t$fn_profile_counter_name += ${measure_fn_name}() - _PROF_FN_START;' + g.pcs_declarations.writeln('double $fn_profile_counter_name = 0.0; u64 $fn_profile_counter_name_calls = 0;') + g.pcs << ProfileCounterMeta{ + fn_name: g.last_fn_c_name + vpc_name: fn_profile_counter_name + vpc_calls: fn_profile_counter_name_calls + } + } +} + +pub fn (mut g Gen) gen_vprint_profile_stats() { + g.pcs_declarations.writeln('void vprint_profile_stats(){') + fstring := '"%14llu %14.3fms %14.0fns %s \\n"' + if g.pref.profile_file == '-' { + for pc_meta in g.pcs { + g.pcs_declarations.writeln('\tif ($pc_meta.vpc_calls) printf($fstring, $pc_meta.vpc_calls, $pc_meta.vpc_name/1000000.0, $pc_meta.vpc_name/$pc_meta.vpc_calls, "$pc_meta.fn_name" );') + } + } else { + g.pcs_declarations.writeln('\tFILE * fp;') + g.pcs_declarations.writeln('\tfp = fopen ("$g.pref.profile_file", "w+");') + for pc_meta in g.pcs { + g.pcs_declarations.writeln('\tif ($pc_meta.vpc_calls) fprintf(fp, $fstring, $pc_meta.vpc_calls, $pc_meta.vpc_name/1000000.0, $pc_meta.vpc_name/$pc_meta.vpc_calls, "$pc_meta.fn_name" );') + } + g.pcs_declarations.writeln('\tfclose(fp);') + } + g.pcs_declarations.writeln('}') +} diff --git a/v_windows/v/vlib/v/gen/c/sql.v b/v_windows/v/vlib/v/gen/c/sql.v new file mode 100644 index 0000000..001f68c --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/sql.v @@ -0,0 +1,836 @@ +// Copyright (c) 2019-2021 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 + +enum SqlExprSide { + left + right +} + +enum SqlType { + sqlite3 + mysql + psql + mssql + unknown +} + +fn (mut g Gen) sql_stmt(node ast.SqlStmt) { + conn := g.new_tmp_var() + g.writeln('') + g.writeln('// orm') + g.write('orm__Connection $conn = (orm__Connection){._') + mut fn_prefix := '' + typ := g.parse_db_type(node.db_expr) + match typ { + .sqlite3 { + fn_prefix = 'sqlite__DB' + } + .mysql { + fn_prefix = 'mysql__Connection' + } + .psql { + fn_prefix = 'pg__DB' + } + else { + verror('This database type `$typ` is not implemented yet in orm') // TODO add better error + } + } + g.write('$fn_prefix = &') + g.expr(node.db_expr) + g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') + for line in node.lines { + g.sql_stmt_line(line, conn) + } +} + +fn (mut g Gen) sql_stmt_line(nd ast.SqlStmtLine, expr string) { + mut node := nd + table_name := g.get_table_name(node.table_expr) + g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + res := g.new_tmp_var() + mut subs := false + mut dcheck := false + + if node.kind != .create { + mut fields := []ast.StructField{} + for f in node.fields { + mut skip := false + mut primary := false + for attr in f.attrs { + if attr.name == 'primary' { + primary = true + } + if attr.name == 'skip' { + skip = true + } + } + if !skip && !primary { + fields << f + } + } + node.fields = fields.clone() + unsafe { fields.free() } + } + + if node.kind == .create { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_create_table(node, expr, table_name) + subs = true + } else if node.kind == .drop { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.writeln('drop(${expr}._object, _SLIT("$table_name"));') + subs = true + } else if node.kind == .insert { + arr := g.new_tmp_var() + g.writeln('Array_orm__Primitive $arr = new_array_from_c_array(0, 0, sizeof(orm__Primitive), NULL);') + g.sql_insert(node, expr, table_name, arr, res, '', false, '') + dcheck = true + } else if node.kind == .update { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_update(node, expr, table_name) + } else if node.kind == .delete { + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.sql_delete(node, expr, table_name) + } + if !dcheck { + g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + } + if subs { + for _, sub in node.sub_structs { + g.sql_stmt_line(sub, expr) + } + } +} + +fn (mut g Gen) sql_create_table(node ast.SqlStmtLine, expr string, table_name string) { + g.write('create(${expr}._object, _SLIT("$table_name"), new_array_from_c_array($node.fields.len, $node.fields.len, sizeof(orm__TableField),') + if node.fields.len > 0 { + g.write(' _MOV((orm__TableField[$node.fields.len]){') + for field in node.fields { + sym := g.table.get_type_symbol(field.typ) + g.write('(orm__TableField){') + g.write('.name = _SLIT("$field.name"),') + mut typ := int(field.typ) + if sym.name == 'time.Time' { + typ = -2 + } + g.write('.typ = $typ,') + g.write('.is_arr = ${sym.kind == .array}, ') + g.write('.is_time = ${int(g.table.get_type_name(field.typ) == 'time__Time')},') + g.write('.default_val = (string){.str = (byteptr) "$field.default_val", .is_lit = 1},') + g.write('.attrs = new_array_from_c_array($field.attrs.len, $field.attrs.len, sizeof(StructAttribute),') + if field.attrs.len > 0 { + g.write(' _MOV((StructAttribute[$field.attrs.len]){') + for attr in field.attrs { + g.write('(StructAttribute){') + g.write('.name = _SLIT("$attr.name"),') + g.write('.has_arg = ${int(attr.has_arg)},') + g.write('.arg = _SLIT("$attr.arg"),') + g.write('.kind = ${int(attr.kind)},') + g.write('},') + } + g.write('})') + } else { + g.write('NULL') + } + g.write(')') + g.write('},') + } + g.write('})') + } else { + g.write('NULL') + } + g.writeln('));') +} + +fn (mut g Gen) sql_insert(node ast.SqlStmtLine, expr string, table_name string, last_ids_arr string, res string, pid string, is_array bool, fkey string) { + mut subs := []ast.SqlStmtLine{} + mut arrs := []ast.SqlStmtLine{} + mut fkeys := []string{} + mut field_names := []string{} + + for f in node.fields { + sym := g.table.get_type_symbol(f.typ) + if sym.kind == .struct_ && sym.name != 'time.Time' { + subs << node.sub_structs[int(f.typ)] + } else if sym.kind == .array { + mut f_key := '' + for attr in f.attrs { + if attr.name == 'fkey' && attr.has_arg && attr.kind == .string { + f_key = attr.arg + } + } + if f_key == '' { + verror('An field which holds an array, needs a fkey defined') + } + fkeys << f_key + info := sym.array_info() + if info.nr_dims == 1 { + arrs << node.sub_structs[int(info.elem_type)] + field_names << f.name + } else { + verror('V ORM only supports 1 dimensional arrays') + } + } + } + + fields := node.fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + + for sub in subs { + g.sql_stmt_line(sub, expr) + g.writeln('array_push(&$last_ids_arr, _MOV((orm__Primitive[]){orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object)}));') + } + + g.write('Option_void $res = orm__Connection_name_table[${expr}._typ]._method_') + g.write('insert(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') + + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + if fields.len > 0 { + g.write('_MOV((string[$fields.len]){') + for f in fields { + g.write('_SLIT("${g.get_field_name(f)}"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.data = new_array_from_c_array($fields.len, $fields.len, sizeof(orm__Primitive),') + if fields.len > 0 { + g.write(' _MOV((orm__Primitive[$fields.len]){') + mut structs := 0 + for f in fields { + if f.name == fkey { + g.write('$pid, ') + continue + } + mut sym := g.table.get_type_symbol(f.typ) + mut typ := sym.cname + if sym.kind == .struct_ && typ != 'time__Time' { + g.write('(*(orm__Primitive*) array_get($last_ids_arr, $structs)),') + structs++ + continue + } + if typ == 'time__Time' { + typ = 'time' + } + g.write('orm__${typ}_to_primitive(${node.object_var_name}.$f.name),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.writeln('});') + + g.writeln('if (${res}.state != 0 && ${res}.err._typ != _IError_None___index) { _v_panic(IError_str(${res}.err)); }') + if arrs.len > 0 { + mut id_name := g.new_tmp_var() + g.writeln('orm__Primitive $id_name = orm__Connection_name_table[${expr}._typ]._method_last_id(${expr}._object);') + for i, mut arr in arrs { + idx := g.new_tmp_var() + g.writeln('for (int $idx = 0; $idx < ${arr.object_var_name}.${field_names[i]}.len; $idx++) {') + last_ids := g.new_tmp_var() + res_ := g.new_tmp_var() + tmp_var := g.new_tmp_var() + ctyp := g.typ(arr.table_expr.typ) + g.writeln('$ctyp $tmp_var = (*($ctyp*)array_get(${arr.object_var_name}.${field_names[i]}, $idx));') + arr.object_var_name = tmp_var + mut fff := []ast.StructField{} + for f in arr.fields { + mut skip := false + mut primary := false + for attr in f.attrs { + if attr.name == 'primary' { + primary = true + } + if attr.name == 'skip' { + skip = true + } + } + if !skip && !primary { + fff << f + } + } + arr.fields = fff.clone() + unsafe { fff.free() } + g.sql_insert(arr, expr, g.get_table_name(arr.table_expr), last_ids, res_, + id_name, true, fkeys[i]) + g.writeln('}') + } + } +} + +fn (mut g Gen) sql_update(node ast.SqlStmtLine, expr string, table_name string) { + g.write('update(${expr}._object, _SLIT("$table_name"), (orm__QueryData){') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.fields = new_array_from_c_array($node.updated_columns.len, $node.updated_columns.len, sizeof(string),') + if node.updated_columns.len > 0 { + g.write(' _MOV((string[$node.updated_columns.len]){') + for field in node.updated_columns { + g.write('_SLIT("$field"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + g.write('.data = new_array_from_c_array($node.update_exprs.len, $node.update_exprs.len, sizeof(orm__Primitive),') + if node.update_exprs.len > 0 { + g.write(' _MOV((orm__Primitive[$node.update_exprs.len]){') + for e in node.update_exprs { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } + g.write('),},') + g.sql_gen_where_data(node.where_expr) + g.writeln(');') +} + +fn (mut g Gen) sql_delete(node ast.SqlStmtLine, expr string, table_name string) { + g.write('_v_delete(${expr}._object, _SLIT("$table_name"),') + g.sql_gen_where_data(node.where_expr) + g.writeln(');') +} + +fn (mut g Gen) sql_expr_to_orm_primitive(expr ast.Expr) { + match expr { + ast.InfixExpr { + g.sql_write_orm_primitive(g.table.find_type_idx('orm.InfixType'), expr) + } + ast.StringLiteral { + g.sql_write_orm_primitive(ast.string_type, expr) + } + ast.IntegerLiteral { + g.sql_write_orm_primitive(ast.int_type, expr) + } + ast.BoolLiteral { + g.sql_write_orm_primitive(ast.bool_type, expr) + } + ast.Ident { + info := expr.info as ast.IdentVar + g.sql_write_orm_primitive(info.typ, expr) + } + ast.SelectorExpr { + g.sql_write_orm_primitive(expr.typ, expr) + } + else { + eprintln(expr) + verror('Unknown expr') + } + } +} + +fn (mut g Gen) sql_write_orm_primitive(t ast.Type, expr ast.Expr) { + mut sym := g.table.get_type_symbol(t) + mut typ := sym.cname + if typ == 'orm__Primitive' { + g.expr(expr) + g.write(',') + return + } + if typ == 'time__Time' { + typ = 'time' + } + if typ == 'orm__InfixType' { + typ = 'infix' + } + g.write('orm__${typ}_to_primitive(') + if expr is ast.InfixExpr { + g.write('(orm__InfixType){') + g.write('.name = _SLIT("$expr.left"),') + mut kind := match expr.op { + .plus { + 'orm__MathOperationKind__add' + } + .minus { + 'orm__MathOperationKind__sub' + } + .div { + 'orm__MathOperationKind__div' + } + .mul { + 'orm__MathOperationKind__mul' + } + else { + '' + } + } + g.write('.operator = $kind,') + g.write('.right = ') + g.sql_expr_to_orm_primitive(expr.right) + g.write('}') + } else { + g.expr(expr) + } + g.write('),') +} + +fn (mut g Gen) sql_where_data(expr ast.Expr, mut fields []string, mut kinds []string, mut data []ast.Expr, mut is_and []bool) { + match expr { + ast.InfixExpr { + g.sql_side = .left + g.sql_where_data(expr.left, mut fields, mut kinds, mut data, mut is_and) + mut kind := match expr.op { + .ne { + 'orm__OperationKind__neq' + } + .eq { + 'orm__OperationKind__eq' + } + .lt { + 'orm__OperationKind__lt' + } + .gt { + 'orm__OperationKind__gt' + } + .ge { + 'orm__OperationKind__ge' + } + .le { + 'orm__OperationKind__le' + } + else { + '' + } + } + if kind == '' { + if expr.op == .logical_or { + is_and << false + } else if expr.op == .and { + is_and << true + } else { + kind = 'orm__OperationKind__eq' + } + } + if expr.left !is ast.InfixExpr && expr.right !is ast.InfixExpr { + kinds << kind + } + g.sql_side = .right + g.sql_where_data(expr.right, mut fields, mut kinds, mut data, mut is_and) + } + ast.Ident { + if g.sql_side == .left { + fields << g.get_field_name(g.get_struct_field(expr.name)) + } else { + data << expr + } + } + ast.StringLiteral { + data << expr + } + ast.IntegerLiteral { + data << expr + } + ast.SelectorExpr { + data << expr + } + ast.BoolLiteral { + data << expr + } + else {} + } +} + +fn (mut g Gen) sql_gen_where_data(where_expr ast.Expr) { + g.write('(orm__QueryData){') + mut fields := []string{} + mut kinds := []string{} + mut data := []ast.Expr{} + mut is_and := []bool{} + g.sql_where_data(where_expr, mut fields, mut kinds, mut data, mut is_and) + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.fields = new_array_from_c_array($fields.len, $fields.len, sizeof(string),') + if fields.len > 0 { + g.write(' _MOV((string[$fields.len]){') + for field in fields { + g.write('_SLIT("$field"),') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.data = new_array_from_c_array($data.len, $data.len, sizeof(orm__Primitive),') + if data.len > 0 { + g.write(' _MOV((orm__Primitive[$data.len]){') + for e in data { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } + g.write('),') + + g.write('.kinds = new_array_from_c_array($kinds.len, $kinds.len, sizeof(orm__OperationKind),') + if kinds.len > 0 { + g.write(' _MOV((orm__OperationKind[$kinds.len]){') + for k in kinds { + g.write('$k,') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + + g.write('.is_and = new_array_from_c_array($is_and.len, $is_and.len, sizeof(bool),') + if is_and.len > 0 { + g.write(' _MOV((bool[$is_and.len]){') + for b in is_and { + g.write('$b, ') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),}') +} + +fn (mut g Gen) sql_select_expr(node ast.SqlExpr) { + left := g.go_before_stmt(0) + conn := g.new_tmp_var() + g.writeln('') + g.writeln('// orm') + g.write('orm__Connection $conn = (orm__Connection){._') + mut fn_prefix := '' + typ := g.parse_db_type(node.db_expr) + match typ { + .sqlite3 { + fn_prefix = 'sqlite__DB' + } + .mysql { + fn_prefix = 'mysql__Connection' + } + .psql { + fn_prefix = 'pg__DB' + } + else { + verror('This database type `$typ` is not implemented yet in orm') // TODO add better error + } + } + + g.write('$fn_prefix = &') + g.expr(node.db_expr) + g.writeln(', ._typ = _orm__Connection_${fn_prefix}_index};') + g.sql_select(node, conn, left) +} + +fn (mut g Gen) sql_select(node ast.SqlExpr, expr string, left string) { + mut fields := []ast.StructField{} + mut prim := '' + for f in node.fields { + mut skip := false + for attr in f.attrs { + if attr.name == 'primary' { + prim = f.name + } + if attr.name == 'skip' { + skip = true + } + } + if !skip { + fields << f + } + } + + res := g.new_tmp_var() + table_name := g.get_table_name(node.table_expr) + g.sql_table_name = g.table.get_type_symbol(node.table_expr.typ).name + g.write('Option_Array_Array_orm__Primitive _o$res = orm__Connection_name_table[${expr}._typ]._method_select(${expr}._object, ') + g.write('(orm__SelectConfig){') + g.write('.table = _SLIT("$table_name"),') + g.write('.is_count = $node.is_count,') + g.write('.has_where = $node.has_where,') + g.write('.has_order = $node.has_order,') + if node.has_order { + g.write('.order = _SLIT("') + g.expr(node.order_expr) + g.write('"),') + if node.has_desc { + g.write('.order_type = orm__OrderType__desc,') + } else { + g.write('.order_type = orm__OrderType__asc,') + } + } + g.write('.has_limit = $node.has_limit,') + g.write('.has_offset = $node.has_offset,') + if prim != '' { + g.write('.primary = _SLIT("$prim"),') + } + select_fields := fields.filter(g.table.get_type_symbol(it.typ).kind != .array) + g.write('.fields = new_array_from_c_array($select_fields.len, $select_fields.len, sizeof(string),') + mut types := []int{} + if select_fields.len > 0 { + g.write(' _MOV((string[$select_fields.len]){') + for field in select_fields { + g.write('_SLIT("${g.get_field_name(field)}"),') + sym := g.table.get_type_symbol(field.typ) + if sym.name == 'time.Time' { + types << -2 + continue + } + if sym.kind == .struct_ { + types << int(ast.int_type) + continue + } + types << int(field.typ) + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),') + g.write('.types = new_array_from_c_array($types.len, $types.len, sizeof(int),') + if types.len > 0 { + g.write(' _MOV((int[$types.len]){') + for typ in types { + g.write('$typ,') + } + g.write('})') + } else { + g.write('NULL') + } + g.write('),},') + + mut exprs := []ast.Expr{} + if node.has_limit { + exprs << node.limit_expr + } + if node.has_offset { + exprs << node.offset_expr + } + g.write('(orm__QueryData) {') + g.write('.types = new_array_from_c_array(0, 0, sizeof(int), NULL),') + g.write('.kinds = new_array_from_c_array(0, 0, sizeof(orm__OperationKind), NULL),') + g.write('.is_and = new_array_from_c_array(0, 0, sizeof(bool), NULL),') + g.write('.data = new_array_from_c_array($exprs.len, $exprs.len, sizeof(orm__Primitive),') + if exprs.len > 0 { + g.write(' _MOV((orm__Primitive[$exprs.len]){') + for e in exprs { + g.sql_expr_to_orm_primitive(e) + } + g.write('})') + } else { + g.write('NULL') + } + g.write(')},') + + if node.has_where { + g.sql_gen_where_data(node.where_expr) + } else { + g.write('(orm__QueryData) {}') + } + g.writeln(');') + g.writeln('if (_o${res}.state != 0 && _o${res}.err._typ != _IError_None___index) { _v_panic(IError_str(_o${res}.err)); }') + g.writeln('Array_Array_orm__Primitive $res = (*(Array_Array_orm__Primitive*)_o${res}.data);') + + if node.is_count { + g.writeln('$left *((*(orm__Primitive*) array_get((*(Array_orm__Primitive*)array_get($res, 0)), 0))._int);') + } else { + tmp := g.new_tmp_var() + styp := g.typ(node.typ) + idx := g.new_tmp_var() + g.writeln('int $idx = 0;') + mut typ_str := '' + if node.is_array { + info := g.table.get_type_symbol(node.typ).array_info() + typ_str = g.typ(info.elem_type) + g.writeln('$styp ${tmp}_array = __new_array(0, ${res}.len, sizeof($typ_str));') + g.writeln('for (; $idx < ${res}.len; $idx++) {') + g.write('\t$typ_str $tmp = ($typ_str) {') + inf := g.table.get_type_symbol(info.elem_type).struct_info() + for i, field in inf.fields { + g.zero_struct_field(field) + if i != inf.fields.len - 1 { + g.write(', ') + } + } + g.writeln('};') + } else { + g.write('$styp $tmp = ($styp){') + info := g.table.get_type_symbol(node.typ).struct_info() + for i, field in info.fields { + g.zero_struct_field(field) + if i != info.fields.len - 1 { + g.write(', ') + } + } + g.writeln('};') + } + + g.writeln('if (${res}.len > 0) {') + for i, field in fields { + sel := '(*(orm__Primitive*) array_get((*(Array_orm__Primitive*) array_get($res, $idx)), $i))' + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ && sym.name != 'time.Time' { + mut sub := node.sub_structs[int(field.typ)] + mut where_expr := sub.where_expr as ast.InfixExpr + mut ident := where_expr.right as ast.Ident + name := sel + s := g.table.find_type_idx('orm.Primitive') + if s != 0 { + if ident.info is ast.IdentVar { + mut info := ident.info as ast.IdentVar + info.typ = s + ident.info = info + } + } + ident.name = name + where_expr.right = ident + sub.where_expr = where_expr + + g.sql_select(sub, expr, '${tmp}.$field.name = ') + } else if sym.kind == .array { + mut fkey := '' + for attr in field.attrs { + if attr.name == 'fkey' && attr.has_arg && attr.kind == .string { + fkey = attr.arg + } + } + if fkey == '' { + verror('An field which holds an array, needs a fkey defined') + } + info := sym.array_info() + arr_typ := info.elem_type + sub := node.sub_structs[int(arr_typ)] + mut where_expr := sub.where_expr as ast.InfixExpr + mut l := where_expr.left as ast.Ident + mut r := where_expr.right as ast.Ident + l.name = fkey + r.name = tmp + where_expr.left = l + where_expr.right = ast.SelectorExpr{ + pos: r.pos + field_name: prim + is_mut: false + expr: r + expr_type: (r.info as ast.IdentVar).typ + typ: ast.int_type + scope: 0 + } + mut arr := ast.SqlExpr{ + typ: field.typ + is_count: sub.is_count + db_expr: sub.db_expr + has_where: sub.has_where + has_offset: sub.has_offset + offset_expr: sub.offset_expr + has_order: sub.has_order + order_expr: sub.order_expr + has_desc: sub.has_desc + is_array: true + pos: sub.pos + has_limit: sub.has_limit + limit_expr: sub.limit_expr + table_expr: sub.table_expr + fields: sub.fields + where_expr: where_expr + } + + g.sql_select(arr, expr, '${tmp}.$field.name = ') + } else { + mut typ := sym.cname + g.writeln('${tmp}.$field.name = *(${sel}._$typ);') + } + } + g.writeln('}') + + if node.is_array { + g.writeln('array_push(&${tmp}_array, _MOV(($typ_str[]){ $tmp }));') + g.writeln('}') + } + + g.write('$left $tmp') + if node.is_array { + g.write('_array') + } + g.writeln(';') + } +} + +fn (mut g Gen) parse_db_type(expr ast.Expr) SqlType { + match expr { + ast.Ident { + if expr.info is ast.IdentVar { + return g.parse_db_from_type_string(g.table.get_type_name(expr.info.typ)) + } + } + ast.SelectorExpr { + return g.parse_db_from_type_string(g.table.get_type_name(expr.typ)) + } + else { + return .unknown + } + } + return .unknown +} + +fn (mut g Gen) parse_db_from_type_string(name string) SqlType { + match name { + 'sqlite.DB' { + return .sqlite3 + } + 'mysql.Connection' { + return .mysql + } + 'pg.DB' { + return .psql + } + 'mssql.Connection' { + return .mssql + } + else { + return .unknown + } + } +} + +fn (mut g Gen) get_table_name(table_expr ast.TypeNode) string { + info := g.table.get_type_symbol(table_expr.typ).struct_info() + mut tablename := util.strip_mod_name(g.table.get_type_symbol(table_expr.typ).name) + for attr in info.attrs { + if attr.kind == .string && attr.name == 'table' && attr.arg != '' { + tablename = attr.arg + break + } + } + return tablename +} + +fn (mut g Gen) get_struct_field(name string) ast.StructField { + info := g.table.get_type_symbol(g.table.type_idxs[g.sql_table_name]).struct_info() + mut f := ast.StructField{} + for field in info.fields { + if field.name == name { + f = field + } + } + return f +} + +fn (mut g Gen) get_field_name(field ast.StructField) string { + mut name := field.name + for attr in field.attrs { + if attr.kind == .string && attr.name == 'sql' && attr.arg != '' { + name = attr.arg + break + } + } + sym := g.table.get_type_symbol(field.typ) + if sym.kind == .struct_ && sym.name != 'time.Time' { + name = '${name}_id' + } + return name +} diff --git a/v_windows/v/vlib/v/gen/c/str.v b/v_windows/v/vlib/v/gen/c/str.v new file mode 100644 index 0000000..788da2c --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/str.v @@ -0,0 +1,145 @@ +// Copyright (c) 2019-2021 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) string_literal(node ast.StringLiteral) { + escaped_val := util.smart_quote(node.val, node.is_raw) + if node.language == .c { + g.write('"$escaped_val"') + } else { + g.write('_SLIT("$escaped_val")') + } +} + +// optimize string interpolation in string builders: +// `sb.writeln('a=$a')` => +// `sb.writeln('a='); sb.writeln(a.str())` +fn (mut g Gen) string_inter_literal_sb_optimized(call_expr ast.CallExpr) { + node := call_expr.args[0].expr as ast.StringInterLiteral + // sb_name := g.cur_call_expr.left + // g.go_before_stmt(0) + g.writeln('// sb inter opt') + is_nl := call_expr.name == 'writeln' + // println('optimize sb $call_expr.name') + for i, val in node.vals { + escaped_val := util.smart_quote(val, false) + // if val == '' { + // break + // continue + // } + g.write('strings__Builder_write_string(&') + g.expr(call_expr.left) + g.write(', _SLIT("') + g.write(escaped_val) + g.writeln('"));') + // + if i >= node.exprs.len { + break + } + // if node.expr_types.len <= i || node.exprs.len <= i { + // continue + // } + if is_nl && i == node.exprs.len - 1 { + g.write('strings__Builder_writeln(&') + } else { + g.write('strings__Builder_write_string(&') + } + g.expr(call_expr.left) + g.write(', ') + typ := node.expr_types[i] + g.write(g.typ(typ)) + g.write('_str(') + sym := g.table.get_type_symbol(typ) + if sym.kind != .function { + g.expr(node.exprs[i]) + } + g.writeln('));') + } + g.writeln('') + // println(node.vals) + return +} + +fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { + is_shared := etype.has_flag(.shared_f) + mut typ := etype + if is_shared { + typ = typ.clear_flag(.shared_f).set_nr_muls(0) + } + mut sym := g.table.get_type_symbol(typ) + // when type is alias, print the aliased value + if mut sym.info is ast.Alias { + parent_sym := g.table.get_type_symbol(sym.info.parent_type) + if parent_sym.has_method('str') { + typ = sym.info.parent_type + sym = parent_sym + } + } + sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() + if typ.has_flag(.variadic) { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + g.expr(expr) + g.write(')') + } else if typ == ast.string_type { + g.expr(expr) + } else if typ == ast.bool_type { + g.expr(expr) + g.write(' ? _SLIT("true") : _SLIT("false")') + } else if sym.kind == .none_ { + g.write('_SLIT("<none>")') + } else if sym.kind == .enum_ { + if expr !is ast.EnumVal { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + g.enum_expr(expr) + g.write(')') + } else { + g.write('_SLIT("') + g.enum_expr(expr) + g.write('")') + } + } else if sym_has_str_method + || sym.kind in [.array, .array_fixed, .map, .struct_, .multi_return, .sum_type, .interface_] { + is_ptr := typ.is_ptr() + is_var_mut := expr.is_auto_deref_var() + str_fn_name := g.gen_str_for_type(typ) + if is_ptr && !is_var_mut { + g.write('str_intp(1, _MOV((StrIntpData[]){{_SLIT("&"), $si_s_code ,{.d_s=') + } + g.write('${str_fn_name}(') + if str_method_expects_ptr && !is_ptr { + g.write('&') + } else if (!str_method_expects_ptr && is_ptr && !is_shared) || is_var_mut { + g.write('*') + } + if expr is ast.ArrayInit { + if expr.is_fixed { + s := g.typ(expr.typ) + g.write('($s)') + } + } + g.expr_with_cast(expr, typ, typ) + if is_shared { + g.write('->val') + } + g.write(')') + if is_ptr && !is_var_mut { + g.write('}}}))') + // g.write(')') + } + } else { + str_fn_name := g.gen_str_for_type(typ) + g.write('${str_fn_name}(') + if expr.is_auto_deref_var() { + g.write('*') + } + if sym.kind != .function { + g.expr_with_cast(expr, typ, typ) + } + g.write(')') + } +} diff --git a/v_windows/v/vlib/v/gen/c/str_intp.v b/v_windows/v/vlib/v/gen/c/str_intp.v new file mode 100644 index 0000000..7ace443 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/str_intp.v @@ -0,0 +1,206 @@ +/* +str_intp.v + +Copyright (c) 2019-2021 Dario Deledda. All rights reserved. +Use of this source code is governed by an MIT license +that can be found in the LICENSE file. + +This file contains string interpolation V functions +*/ +module c + +import v.ast +import v.util + +fn (mut g Gen) str_format(node ast.StringInterLiteral, i int) (u64, string) { + mut base := 0 // numeric base + mut upper_case := false // set upercase for the result string + mut typ := g.unwrap_generic(node.expr_types[i]) + sym := g.table.get_type_symbol(typ) + if sym.kind == .alias { + typ = (sym.info as ast.Alias).parent_type + } + mut remove_tail_zeros := false + fspec := node.fmts[i] + mut fmt_type := StrIntpType{} + + // upper cases + if (fspec - `A`) <= (`Z` - `A`) { + upper_case = true + } + + if fspec in [`s`, `S`] { + /* + if node.fwidths[i] == 0 { + fmt_type = .si_s + } else { + fmt_type = .si_s + } + */ + fmt_type = .si_s + } else if typ.is_float() { + if fspec in [`g`, `G`] { + match typ { + ast.f32_type { fmt_type = .si_g32 } + // ast.f64_type { fmt_type = .si_g64 } + else { fmt_type = .si_g64 } + } + remove_tail_zeros = true + } else if fspec in [`e`, `E`] { + match typ { + ast.f32_type { fmt_type = .si_e32 } + // ast.f64_type { fmt_type = .si_e64 } + else { fmt_type = .si_e64 } + } + } else if fspec in [`f`, `F`] { + match typ { + ast.f32_type { fmt_type = .si_f32 } + // ast.f64_type { fmt_type = .si_f64 } + else { fmt_type = .si_f64 } + } + } + } else if typ.is_pointer() { + if fspec in [`x`, `X`] { + base = 16 - 2 // our base start from 2 + } + if fspec in [`p`, `x`, `X`] { + fmt_type = .si_p + } else { + fmt_type = .si_vp + } + } else if typ.is_int() { + if fspec in [`x`, `X`] { + base = 16 - 2 // our base start from 2 + } + // if fspec in [`o`] { + if fspec == `o` { + base = 8 - 2 // our base start from 2 + } + if fspec == `c` { + fmt_type = .si_c + } else { + match typ { + ast.i8_type { fmt_type = .si_i8 } + ast.byte_type { fmt_type = .si_u8 } + ast.i16_type { fmt_type = .si_i16 } + ast.u16_type { fmt_type = .si_u16 } + ast.i64_type { fmt_type = .si_i64 } + ast.u64_type { fmt_type = .si_u64 } + ast.u32_type { fmt_type = .si_u32 } + else { fmt_type = .si_i32 } + } + } + } else { + // TODO: better check this case + fmt_type = .si_p + } + + /* + // pad filling 64bit format + mut pad_ch := u8(0) + if node.fills[i] { + pad_ch = u8(`0`) + } + res := get_str_intp_u64_format(fmt_type, node.fwidths[i], node.precisions[i], remove_tail_zeros, node.pluss[i], pad_ch, base, upper_case) + */ + + // pad filling 32bit format + mut pad_ch := 0 + if node.fills[i] { + pad_ch = 1 + } + res := get_str_intp_u32_format(fmt_type, node.fwidths[i], node.precisions[i], remove_tail_zeros, + node.pluss[i], byte(pad_ch), base, upper_case) + // + return res, fmt_type.str() +} + +fn (mut g Gen) str_val(node ast.StringInterLiteral, i int) { + expr := node.exprs[i] + + typ := g.unwrap_generic(node.expr_types[i]) + if typ == ast.string_type { + if g.inside_vweb_tmpl { + g.write('vweb__filter(') + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + g.write(')') + } else { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + } + } else if node.fmts[i] == `s` || typ.has_flag(.variadic) { + g.gen_expr_to_string(expr, typ) + } else if typ.is_number() || typ.is_pointer() || node.fmts[i] == `d` { + if typ.is_signed() && node.fmts[i] in [`x`, `X`, `o`] { + // convert to unsigned first befors C's integer propagation strikes + if typ == ast.i8_type { + g.write('(byte)(') + } else if typ == ast.i16_type { + g.write('(u16)(') + } else if typ == ast.int_type { + g.write('(u32)(') + } else { + g.write('(u64)(') + } + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + g.write(')') + } else { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + } + } else { + if expr.is_auto_deref_var() { + g.write('*') + } + g.expr(expr) + } +} + +fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { + // fn (mut g Gen) str_int2(node ast.StringInterLiteral) { + g.write(' str_intp($node.vals.len, ') + g.write('_MOV((StrIntpData[]){') + for i, val in node.vals { + escaped_val := util.smart_quote(val, false) + + if escaped_val.len > 0 { + g.write('{_SLIT("$escaped_val"), ') + } else { + g.write('{_SLIT0, ') + } + + if i >= node.exprs.len { + // last part of the string + g.write('0, { .d_c = 0 }}') + break + } + + ft_u64, ft_str := g.str_format(node, i) + g.write('0x$ft_u64.hex(), {.d_$ft_str = ') + + // for pointers we need a void* cast + if unsafe { ft_str.str[0] } == `p` { + g.write('(void*)(') + g.str_val(node, i) + g.write(')') + } else { + g.str_val(node, i) + } + + g.write('}}') + if i < (node.vals.len - 1) { + g.write(', ') + } + } + g.write('}))') +} diff --git a/v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have b/v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have new file mode 100644 index 0000000..300bd6a --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/addition.c.must_have @@ -0,0 +1 @@ +int zzz = 579
\ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/c/testdata/addition.out b/v_windows/v/vlib/v/gen/c/testdata/addition.out new file mode 100644 index 0000000..72b67e0 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/addition.out @@ -0,0 +1 @@ +579
\ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/c/testdata/addition.vv b/v_windows/v/vlib/v/gen/c/testdata/addition.vv new file mode 100644 index 0000000..ff95896 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/addition.vv @@ -0,0 +1,4 @@ +fn main() { + zzz := 123 + 456 + println(zzz) +}
\ No newline at end of file diff --git a/v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have b/v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have new file mode 100644 index 0000000..11a2cb8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/const_references.c.must_have @@ -0,0 +1,3 @@ +VV_LOCAL_SYMBOL int main__a_const_accepting_fn(int* x, const int* const_x); +VV_LOCAL_SYMBOL int main__a_const_accepting_fn(int* x, const int* const_x) { +main__a_const_accepting_fn(&a, &b) diff --git a/v_windows/v/vlib/v/gen/c/testdata/const_references.out b/v_windows/v/vlib/v/gen/c/testdata/const_references.out new file mode 100644 index 0000000..01e79c3 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/const_references.out @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/v_windows/v/vlib/v/gen/c/testdata/const_references.vv b/v_windows/v/vlib/v/gen/c/testdata/const_references.vv new file mode 100644 index 0000000..ae94cc7 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/testdata/const_references.vv @@ -0,0 +1,11 @@ +fn a_const_accepting_fn(x &int, const_x &int) int { + return *x + *const_x +} + +fn main() { + a := 1 + b := 2 + println(a) + println(b) + println(a_const_accepting_fn(&a, &b)) +} diff --git a/v_windows/v/vlib/v/gen/c/utils.v b/v_windows/v/vlib/v/gen/c/utils.v new file mode 100644 index 0000000..a4768c8 --- /dev/null +++ b/v_windows/v/vlib/v/gen/c/utils.v @@ -0,0 +1,49 @@ +// Copyright (c) 2019-2021 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 + +fn (mut g Gen) unwrap_generic(typ ast.Type) ast.Type { + if typ.has_flag(.generic) { + if t_typ := g.table.resolve_generic_to_concrete(typ, g.table.cur_fn.generic_names, + g.table.cur_concrete_types) + { + return t_typ + } + } + return typ +} + +struct Type { + // typ is the original type + typ ast.Type [required] + sym &ast.TypeSymbol [required] + // unaliased is `typ` once aliased have been resolved + // it may not contain informations such as flags and nr_muls + unaliased ast.Type [required] + unaliased_sym &ast.TypeSymbol [required] +} + +// unwrap returns the following variants of a type: +// * generics unwrapped +// * alias unwrapped +fn (mut g Gen) unwrap(typ ast.Type) Type { + no_generic := g.unwrap_generic(typ) + no_generic_sym := g.table.get_type_symbol(no_generic) + if no_generic_sym.kind != .alias { + return Type{ + typ: no_generic + sym: no_generic_sym + unaliased: no_generic + unaliased_sym: no_generic_sym + } + } + return Type{ + typ: no_generic + sym: no_generic_sym + unaliased: no_generic_sym.parent_idx + unaliased_sym: g.table.get_type_symbol(no_generic_sym.parent_idx) + } +} |