// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license that can be found in the LICENSE file. module c import v.ast import v.util 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) { $if trace_autostr ? { eprintln('> gen_str_default: $sym.name | $styp | str_fn_name') } 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.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('}') } struct StrType { styp string mut: typ ast.Type } fn (mut g Gen) get_str_fn(typ ast.Type) string { $if trace_autostr ? { eprintln('> get_str_fn: $typ.debug()') } mut unwrapped := g.unwrap_generic(typ).set_nr_muls(0).clear_flag(.variadic) if g.pref.nofloat { if typ == ast.f32_type { unwrapped = ast.u32_type } else if typ == ast.f64_type { unwrapped = ast.u64_type } } if typ.has_flag(.optional) { unwrapped.set_flag(.optional) } styp := g.typ(unwrapped) mut sym := g.table.sym(unwrapped) 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.sym(sym.info.parent_type) str_fn_name = styp_to_str_fn_name(sym.name) } } if sym.has_method_with_generic_parent('str') && mut sym.info is ast.Struct { str_fn_name = g.generic_fn_name(sym.info.concrete_types, str_fn_name, false) } g.str_types << StrType{ typ: unwrapped styp: styp } return str_fn_name } fn (mut g Gen) final_gen_str(typ StrType) { if typ in g.generated_str_fns { return } $if trace_autostr ? { eprintln('> final_gen_str: $typ') } g.generated_str_fns << typ sym := g.table.sym(typ.typ) if sym.has_method_with_generic_parent('str') && !typ.typ.has_flag(.optional) { return } styp := typ.styp str_fn_name := styp_to_str_fn_name(styp) if typ.typ.has_flag(.optional) { g.gen_str_for_option(typ.typ, styp, str_fn_name) return } 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`') } } } fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) { $if trace_autostr ? { eprintln('> gen_str_for_option: $typ.debug() | $styp | $str_fn_name') } parent_type := typ.clear_flag(.optional) sym := g.table.sym(parent_type) sym_has_str_method, _, _ := sym.str_method_info() parent_str_fn_name := g.get_str_fn(parent_type) g.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.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.get_str_fn(info.parent_type) $if trace_autostr ? { eprintln('> gen_str_for_alias: $parent_str_fn_name | $styp | $str_fn_name') } mut clean_type_v_type_name := util.strip_main_name(styp.replace('__', '.')) g.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.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) { $if trace_autostr ? { eprintln('> gen_str_for_multi_return: $info.types | $styp | $str_fn_name') } g.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.sym(typ) is_arg_ptr := typ.is_ptr() sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() arg_str_fn_name := g.get_str_fn(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) { $if trace_autostr ? { eprintln('> gen_str_for_enum: $info | $styp | $str_fn_name') } s := util.no_dots(styp) g.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) { $if trace_autostr ? { eprintln('> gen_str_for_interface: $info.types | $styp | $str_fn_name') } // _str() functions should have a single argument, the indenting ones take 2: g.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.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.sym(typ) mut func_name := g.get_str_fn(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) { $if trace_autostr ? { eprintln('> gen_str_for_union_sum_type: $info.variants | $styp | $str_fn_name') } // _str() functions should have a single argument, the indenting ones take 2: g.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.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 := '' if info.is_anon { variant_names := info.variants.map(util.strip_main_name(g.table.sym(it).name)) clean_sum_type_v_type_name = '(${variant_names.join(' | ')})' } else { 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.get_str_fn(typ) sym := g.table.sym(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) { $if trace_autostr ? { eprintln('> gen_str_for_fn_type: $info.func.name | $styp | $str_fn_name') } g.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) { $if trace_autostr ? { eprintln('> gen_str_for_chan: $info.elem_type.debug() | $styp | $str_fn_name') } elem_type_name := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.elem_type))) g.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) { $if trace_autostr ? { eprintln('> gen_str_for_thread: $info.return_type.debug() | $styp | $str_fn_name') } ret_type_name := util.strip_main_name(g.table.get_type_name(info.return_type)) g.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' } // deref_kind returns deref, deref_label 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) { $if trace_autostr ? { eprintln('> gen_str_for_array: $info.elem_type.debug() | $styp | $str_fn_name') } mut typ := info.elem_type mut sym := g.table.sym(info.elem_type) if mut sym.info is ast.Alias { typ = sym.info.parent_type sym = g.table.sym(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.get_str_fn(typ) if sym.kind == .byte { elem_str_fn_name = elem_str_fn_name + '_escaped' } g.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.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 { if is_elem_ptr { 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 { 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. // Note: 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) { $if trace_autostr ? { eprintln('> gen_str_for_array_fixed: $info.elem_type.debug() | $styp | $str_fn_name') } mut typ := info.elem_type mut sym := g.table.sym(info.elem_type) if mut sym.info is ast.Alias { typ = sym.info.parent_type sym = g.table.sym(typ) } is_elem_ptr := typ.is_ptr() sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() elem_str_fn_name := g.get_str_fn(typ) g.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.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) { $if trace_autostr ? { eprintln('> gen_str_for_map: $info.key_type.debug() -> $info.value_type.debug() | $styp | $str_fn_name') } mut key_typ := info.key_type mut key_sym := g.table.sym(key_typ) if mut key_sym.info is ast.Alias { key_typ = key_sym.info.parent_type key_sym = g.table.sym(key_typ) } key_styp := g.typ(key_typ) key_str_fn_name := key_styp.replace('*', '') + '_str' if !key_sym.has_method('str') { g.get_str_fn(key_typ) } mut val_typ := info.value_type mut val_sym := g.table.sym(val_typ) if mut val_sym.info is ast.Alias { val_typ = val_sym.info.parent_type val_sym = g.table.sym(val_typ) } val_styp := g.typ(val_typ) elem_str_fn_name := val_styp.replace('*', '') + '_str' if !val_sym.has_method('str') { g.get_str_fn(val_typ) } g.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.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_fmt(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.sym(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) { $if trace_autostr ? { eprintln('> gen_str_for_struct: $info.parent_type.debug() | $styp | $str_fn_name') } // _str() functions should have a single argument, the indenting ones take 2: g.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.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);') mut fn_body_surrounder := util.new_surrounder(info.fields.len) mut fn_body := strings.new_builder(info.fields.len * 256) defer { fn_body_surrounder.builder_write_befores(mut fn_builder) fn_builder << fn_body fn_body_surrounder.builder_write_afters(mut fn_builder) fn_builder.writeln('\tstring_free(&indents);') fn_builder.writeln('\treturn res;') fn_builder.writeln('}') } fn_body.writeln('\tstring res = str_intp( ${info.fields.len * 4 + 3}, _MOV((StrIntpData[]){') fn_body.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_fmt(g.unwrap_generic(field.typ)) // manage prefix and quote symbol for the filed mut quote_str := '' mut prefix := '' sym := g.table.sym(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_body.write_string('\t\t{_SLIT0, $c.si_s_code, {.d_s=indents}}, {_SLIT(" $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ') } else { fn_body.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 sym_has_str_method, str_method_expects_ptr, _ := sym.str_method_info() sftyp := g.typ(field.typ) mut field_styp := sftyp.replace('*', '') field_styp_fn_name := if sym_has_str_method { '${field_styp}_str' } else { g.get_str_fn(field.typ) } // manage the fact hat with float we use always the g representation if sym.kind !in [.f32, .f64] { fn_body.write_string('{_SLIT("$quote_str"), ${int(base_fmt)}, {.${data_str(base_fmt)}=') } else { g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex() fn_body.write_string('{_SLIT("$quote_str"), $g_fmt, {.${data_str(base_fmt)}=') } mut funcprefix := '' mut func, mut caller_should_free := struct_auto_str_func(sym, field.typ, field_styp_fn_name, field.name, sym_has_str_method, str_method_expects_ptr) if field.typ in ast.cptr_types { func = '(voidptr) it.$field.name' caller_should_free = false } else if field.typ.is_ptr() { // reference types can be "nil" funcprefix += 'isnil(it.${c_name(field.name)})' funcprefix += ' ? _SLIT("nil") : ' // struct, floats and ints have a special case through the _str function if sym.kind !in [.struct_, .alias] && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() { funcprefix += '*' } } // handle circular ref type of struct to the struct itself if styp == field_styp { fn_body.write_string('${funcprefix}_SLIT("")') } else { // manage C charptr if field.typ in ast.charptr_types { fn_body.write_string('tos2((byteptr)$func)') } else { if field.typ.is_ptr() && sym.kind == .struct_ { funcprefix += '(indent_count > 25) ? _SLIT("") : ' } // eprintln('>>> caller_should_free: ${caller_should_free:6s} | funcprefix: $funcprefix | func: $func') if caller_should_free { tmpvar := g.new_tmp_var() fn_body_surrounder.add('\tstring $tmpvar = $funcprefix$func;', '\tstring_free(&$tmpvar);') fn_body.write_string(tmpvar) } else { fn_body.write_string(funcprefix) fn_body.write_string(func) } } } fn_body.writeln('}}, {_SLIT("$quote_str"), 0, {.d_c=0}},') } fn_body.writeln('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("}"), 0, {.d_c=0}},') fn_body.writeln('\t}));') } fn struct_auto_str_func(sym &ast.TypeSymbol, field_type ast.Type, fn_name string, field_name string, has_custom_str bool, expects_ptr bool) (string, bool) { $if trace_autostr ? { eprintln('> struct_auto_str_func: $sym.name | field_type.debug() | $fn_name | $field_name | $has_custom_str | $expects_ptr') } deref, _ := deref_kind(expects_ptr, field_type.is_ptr(), field_type) if sym.kind == .enum_ { return '${fn_name}(${deref}it.${c_name(field_name)})', true } else if should_use_indent_func(sym.kind) { obj := 'it.${c_name(field_name)}' if has_custom_str { return '${fn_name}($deref$obj)', true } return 'indent_${fn_name}($deref$obj, indent_count + 1)', true } else if sym.kind in [.array, .array_fixed, .map, .sum_type] { if has_custom_str { return '${fn_name}(${deref}it.${c_name(field_name)})', true } return 'indent_${fn_name}(${deref}it.${c_name(field_name)}, indent_count + 1)', true } else if sym.kind == .function { return '${fn_name}()', true } else { if sym.kind == .chan { return '${fn_name}(${deref}it.${c_name(field_name)})', true } mut method_str := 'it.${c_name(field_name)}' mut caller_should_free := false 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 }} }))', true } else if sym.kind == .f64 { return 'str_intp(1, _MOV((StrIntpData[]){ {_SLIT0, $si_g64_code, {.d_f64 = *$method_str }} }))', true } 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 }}}))', true } fmt_type := StrIntpType.si_i32 return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *$method_str }}}))', true } return method_str, caller_should_free } }