From d8d05e01062a2c974b8652c78a58f988fe051709 Mon Sep 17 00:00:00 2001 From: penguindark <57967770+penguindark@users.noreply.github.com> Date: Mon, 24 May 2021 04:20:45 +0200 Subject: [PATCH] all: new string interpolation in pure V (#10181) --- cmd/tools/vtest-cleancode.v | 1 + examples/sokol/06_obj_viewer/show_obj.v | 2 +- vlib/builtin/float.v | 50 ++ vlib/builtin/string_interpolation.v | 674 +++++++++++++++++++++++ vlib/math/complex/complex.v | 4 +- vlib/strconv/atof.v | 10 +- vlib/strconv/f32_str.v | 2 +- vlib/strconv/format.v | 29 +- vlib/strconv/format_mem.v | 497 +++++++++++++++++ vlib/strconv/number_to_base.v | 76 ++- vlib/strconv/structs.v | 10 +- vlib/strconv/utilities.v | 182 +++++- vlib/strconv/vprintf.v | 164 ------ vlib/v/ast/str.v | 3 +- vlib/v/checker/check_types.v | 6 +- vlib/v/gen/c/auto_str_array.v | 170 ++++++ vlib/v/gen/c/auto_str_map.v | 90 +++ vlib/v/gen/c/auto_str_methods.v | 566 ++++++------------- vlib/v/gen/c/auto_str_struct.v | 387 +++++++++++++ vlib/v/gen/c/cgen.v | 14 +- vlib/v/gen/c/cheaders.v | 6 + vlib/v/gen/c/comptime.v | 6 +- vlib/v/gen/c/json.v | 4 +- vlib/v/gen/c/sql.v | 36 +- vlib/v/gen/c/str.v | 8 +- vlib/v/gen/c/str_intp.v | 239 ++++++++ vlib/v/markused/markused.v | 2 + vlib/v/tests/string_interpolation_test.v | 26 +- 28 files changed, 2624 insertions(+), 640 deletions(-) create mode 100644 vlib/builtin/string_interpolation.v create mode 100644 vlib/strconv/format_mem.v create mode 100644 vlib/v/gen/c/auto_str_array.v create mode 100644 vlib/v/gen/c/auto_str_map.v create mode 100644 vlib/v/gen/c/auto_str_struct.v create mode 100644 vlib/v/gen/c/str_intp.v diff --git a/cmd/tools/vtest-cleancode.v b/cmd/tools/vtest-cleancode.v index 58208e8193..4e1225e330 100644 --- a/cmd/tools/vtest-cleancode.v +++ b/cmd/tools/vtest-cleancode.v @@ -24,6 +24,7 @@ const ( 'examples/sokol/03_march_tracing_glsl/rt_glsl.v', 'examples/sokol/04_multi_shader_glsl/rt_glsl.v', 'examples/sokol/05_instancing_glsl/rt_glsl.v', + 'examples/sokol/06_obj_viewer/show_obj.v', 'vlib/gg/m4/graphic.v', 'vlib/gg/m4/m4_test.v', 'vlib/gg/m4/matrix.v', diff --git a/examples/sokol/06_obj_viewer/show_obj.v b/examples/sokol/06_obj_viewer/show_obj.v index dbf95ac7bf..18f0258a60 100644 --- a/examples/sokol/06_obj_viewer/show_obj.v +++ b/examples/sokol/06_obj_viewer/show_obj.v @@ -336,4 +336,4 @@ fn main() { app.ticks = time.ticks() app.gg.run() -} +} \ No newline at end of file diff --git a/vlib/builtin/float.v b/vlib/builtin/float.v index 8d64478a05..99fe8b8c09 100644 --- a/vlib/builtin/float.v +++ b/vlib/builtin/float.v @@ -12,6 +12,17 @@ import strconv // str return a `f64` as `string` in suitable notation. [inline] pub fn (x f64) str() string { + unsafe { + f := strconv.Float64u{ + f: x + } + if f.u == strconv.double_minus_zero { + return '-0' + } + if f.u == strconv.double_plus_zero { + return '0' + } + } abs_x := f64_abs(x) if abs_x >= 0.0001 && abs_x < 1.0e6 { return strconv.f64_to_str_l(x) @@ -20,6 +31,20 @@ pub fn (x f64) str() string { } } +// strg return a `f64` as `string` in "g" printf format +[inline] +pub fn (x f64) strg() string { + if x == 0 { + return '0' + } + abs_x := f64_abs(x) + if abs_x >= 0.0001 && abs_x < 1.0e6 { + return strconv.f64_to_str_l_no_dot(x) + } else { + return strconv.ftoa_64(x) + } +} + // str returns the value of the `float_literal` as a `string`. [inline] pub fn (d float_literal) str() string { @@ -53,6 +78,17 @@ pub fn (x f64) strlong() string { // str returns a `f32` as `string` in suitable notation. [inline] pub fn (x f32) str() string { + unsafe { + f := strconv.Float32u{ + f: x + } + if f.u == strconv.single_minus_zero { + return '-0' + } + if f.u == strconv.single_plus_zero { + return '0' + } + } abs_x := f32_abs(x) if abs_x >= 0.0001 && abs_x < 1.0e6 { return strconv.f32_to_str_l(x) @@ -61,6 +97,20 @@ pub fn (x f32) str() string { } } +// strg return a `f32` as `string` in "g" printf format +[inline] +pub fn (x f32) strg() string { + if x == 0 { + return '0' + } + abs_x := f32_abs(x) + if abs_x >= 0.0001 && abs_x < 1.0e6 { + return strconv.f32_to_str_l_no_dot(x) + } else { + return strconv.ftoa_32(x) + } +} + // strsci returns the `f32` as a `string` in scientific notation with `digit_num` deciamals displayed, max 8 digits. // Example: assert f32(1.234).strsci(3) == '1.234e+00' [inline] diff --git a/vlib/builtin/string_interpolation.v b/vlib/builtin/string_interpolation.v new file mode 100644 index 0000000000..208a5fa883 --- /dev/null +++ b/vlib/builtin/string_interpolation.v @@ -0,0 +1,674 @@ +/*============================================================================= +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 builtin + +import strconv +import strings + +//============================================================================= +// Enum format types max 0x1F => 32 types +//============================================================================= +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 (x StrIntpType) str() 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 (x StrIntpType) data_str() 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" } + } +} +*/ + +//============================================================================= +// Union data +//============================================================================= +pub union StrIntpMem { +pub mut: + d_c u32 + d_u8 byte + d_i8 i8 + d_u16 u16 + d_i16 i16 + d_u32 u32 + d_i32 int + d_u64 u64 + d_i64 i64 + d_f32 f32 + d_f64 f64 + d_s string + d_p voidptr + d_vp voidptr +} + +fn fabs64(x f64) f64 { + if x < 0 { + return -x + } + return x +} + +fn fabs32(x f32) f32 { + if x < 0 { + return -x + } + return x +} + +fn abs64(x i64) u64 { + if x < 0 { + return u64(-x) + } + return u64(x) +} + +//========================================= +// +// u32/u64 bit compact format +// +//___ 32 24 16 8 +//___ | | | | +//_3333333333222222222211111111110000000000 +//_9876543210987654321098765432109876543210 +//_nPPPPPPPPBBBBWWWWWWWWWWTDDDDDDDSUAA===== +// = data type 5 bit max 32 data type +// A allign 2 bit Note: for now only 1 used! +// U uppercase 1 bit 0 do nothing, 1 do to_upper() +// S sign 1 bit show the sign if positive +// D decimals 7 bit number of decimals digit to show +// T tail zeros 1 bit 1 remove tail zeros, 0 do nothing +// W Width 10 bit number of char for padding and indentation +// B num base 4 bit start from 2, 0 for base 10 +// P pad char 1/8 bit padding char (in u32 format reduced to 1 bit as flag for `0` padding) +// -------------- +// TOTAL: 39/32 bit +//========================================= + +// convert from data format to compact u64 +pub fn get_str_intp_u64_format(fmt_type StrIntpType, in_width int, in_precision int, in_tail_zeros bool, in_sign bool, in_pad_ch u8, in_base int, in_upper_case bool) u64 { + width := if in_width != 0 { abs64(in_width) } else { u64(0) } + allign := if in_width > 0 { u64(1 << 5) } else { u64(0) } // two bit 0 .left 1 .rigth, for now we use only one + upper_case := if in_upper_case { u64(1 << 7) } else { u64(0) } + sign := if in_sign { u64(1 << 8) } else { u64(0) } + precision := if in_precision != 987698 { + (u64(in_precision & 0x7F) << 9) + } else { + u64(0x7F) << 9 + } + tail_zeros := if in_tail_zeros { u32(1) << 16 } else { u32(0) } + base := u64((in_base & 0xf) << 27) + res := u64((u64(fmt_type) & 0x1F) | allign | upper_case | sign | precision | tail_zeros | (u64(width & 0x3FF) << 17) | base | (u64(in_pad_ch) << 31)) + return res +} + +// convert from data format to compact u32 +pub fn get_str_intp_u32_format(fmt_type StrIntpType, in_width int, in_precision int, in_tail_zeros bool, in_sign bool, in_pad_ch u8, in_base int, in_upper_case bool) u32 { + width := if in_width != 0 { abs64(in_width) } else { u32(0) } + allign := if in_width > 0 { u32(1 << 5) } else { u32(0) } // two bit 0 .left 1 .rigth, for now we use only one + upper_case := if in_upper_case { u32(1 << 7) } else { u32(0) } + sign := if in_sign { u32(1 << 8) } else { u32(0) } + precision := if in_precision != 987698 { + (u32(in_precision & 0x7F) << 9) + } else { + u32(0x7F) << 9 + } + tail_zeros := if in_tail_zeros { u32(1) << 16 } else { u32(0) } + base := u32((in_base & 0xf) << 27) + res := u32((u32(fmt_type) & 0x1F) | allign | upper_case | sign | precision | tail_zeros | (u32(width & 0x3FF) << 17) | base | (u32(in_pad_ch & 1) << 31)) + return res +} + +// convert from struct to formated string +[manualfree] +fn (data StrIntpData) get_fmt_format(mut sb strings.Builder) { + x := data.fmt + typ := StrIntpType(x & 0x1F) + allign := int((x >> 5) & 0x01) + upper_case := if ((x >> 7) & 0x01) > 0 { true } else { false } + sign := int((x >> 8) & 0x01) + precision := int((x >> 9) & 0x7F) + tail_zeros := if ((x >> 16) & 0x01) > 0 { true } else { false } + width := int(i16((x >> 17) & 0x3FF)) + mut base := int(x >> 27) & 0xF + fmt_pad_ch := byte((x >> 31) & 0xFF) + + // no string interpolation is needed, return empty string + if typ == .si_no_str { + return + } + + // if width > 0 { println("${x.hex()} Type: ${x & 0x7F} Width: ${width} Precision: ${precision} allign:${allign}") } + + // manage base if any + if base > 0 { + base += 2 // we start from 2, 0 == base 10 + } + + // mange pad char, for now only 0 allowed + mut pad_ch := byte(` `) + if fmt_pad_ch > 0 { + // pad_ch = fmt_pad_ch + pad_ch = `0` + } + + len0_set := if width > 0 { width } else { -1 } + len1_set := if precision == 0x7F { -1 } else { precision } + sign_set := if sign == 1 { true } else { false } + + mut bf := strconv.BF_param{ + pad_ch: pad_ch // padding char + len0: len0_set // default len for whole the number or string + len1: len1_set // number of decimal digits, if needed + positive: true // mandatory: the sign of the number passed + sign_flag: sign_set // flag for print sign as prefix in padding + allign: .left // alignment of the string + rm_tail_zero: tail_zeros // false // remove the tail zeros from floats + } + + // allign + if fmt_pad_ch == 0 { + match allign { + 0 { bf.allign = .left } + 1 { bf.allign = .right } + // 2 { bf.allign = .center } + else { bf.allign = .left } + } + } else { + bf.allign = .right + } + + unsafe { + // strings + if typ == .si_s { + mut s := '' + if upper_case { + s = data.d.d_s.to_upper() + } else { + s = data.d.d_s.clone() + } + if width == 0 { + sb.write_string(s) + } else { + strconv.format_str_sb(s, bf, mut sb) + } + s.free() + return + } + + // signed int + if typ in [.si_i8, .si_i16, .si_i32, .si_i64] { + mut d := data.d.d_i64 + if typ == .si_i8 { + d = i64(data.d.d_i8) + } else if typ == .si_i16 { + d = i64(data.d.d_i16) + } else if typ == .si_i32 { + d = i64(data.d.d_i32) + } + + if base == 0 { + if width == 0 { + d_str := d.str() + sb.write_string(d_str) + d_str.free() + return + } + if d < 0 { + bf.positive = false + } + strconv.format_dec_sb(abs64(d), bf, mut sb) + } else { + mut hx := strconv.format_int(d, base) + if upper_case { + tmp := hx + hx = hx.to_upper() + tmp.free() + } + if width == 0 { + sb.write_string(hx) + } else { + strconv.format_str_sb(hx, bf, mut sb) + } + hx.free() + } + return + } + + // unsigned int and pointers + if typ in [.si_u8, .si_u16, .si_u32, .si_u64] { + mut d := data.d.d_u64 + if typ == .si_u8 { + d = u64(data.d.d_u8) + } else if typ == .si_u16 { + d = u64(data.d.d_u16) + } else if typ == .si_u32 { + d = u64(data.d.d_u32) + } + if base == 0 { + if width == 0 { + d_str := d.str() + sb.write_string(d_str) + d_str.free() + return + } + strconv.format_dec_sb(d, bf, mut sb) + } else { + mut hx := strconv.format_uint(d, base) + if upper_case { + tmp := hx + hx = hx.to_upper() + tmp.free() + } + if width == 0 { + sb.write_string(hx) + } else { + strconv.format_str_sb(hx, bf, mut sb) + } + hx.free() + } + return + } + + // pointers + if typ == .si_p { + mut d := data.d.d_u64 + base = 16 // TODO: **** decide the behaviour of this flag! **** + if base == 0 { + if width == 0 { + d_str := d.str() + sb.write_string(d_str) + d_str.free() + return + } + strconv.format_dec_sb(d, bf, mut sb) + } else { + mut hx := strconv.format_uint(d, base) + if upper_case { + tmp := hx + hx = hx.to_upper() + tmp.free() + } + if width == 0 { + sb.write_string(hx) + } else { + strconv.format_str_sb(hx, bf, mut sb) + } + hx.free() + } + return + } + + // default settings for floats + mut use_default_str := false + if width == 0 && precision == 0x7F { + bf.len1 = 3 + use_default_str = true + } + if bf.len1 < 0 { + bf.len1 = 3 + } + + match typ { + // floating point + .si_f32 { + // println("HERE: f32") + if use_default_str { + mut f := data.d.d_f32.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + // println("HERE: f32 format") + // println(data.d.d_f32) + if data.d.d_f32 < 0 { + bf.positive = false + } + mut f := strconv.format_fl(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_f64 { + // println("HERE: f64") + if use_default_str { + mut f := data.d.d_f64.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f64 < 0 { + bf.positive = false + } + f_union := strconv.Float64u{ + f: data.d.d_f64 + } + if f_union.u == strconv.double_minus_zero { + bf.positive = false + } + + mut f := strconv.format_fl(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_g32 { + // println("HERE: g32") + if use_default_str { + mut f := data.d.d_f32.strg() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f32 < 0 { + bf.positive = false + } + d := fabs32(data.d.d_f32) + if d < 999_999.0 && d >= 0.00001 { + mut f := strconv.format_fl(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + return + } + mut f := strconv.format_es(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_g64 { + // println("HERE: g64") + if use_default_str { + mut f := data.d.d_f64.strg() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f64 < 0 { + bf.positive = false + } + d := fabs64(data.d.d_f64) + if d < 999_999.0 && d >= 0.00001 { + mut f := strconv.format_fl(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + return + } + mut f := strconv.format_es(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_e32 { + // println("HERE: e32") + bf.len1 = 6 + if use_default_str { + mut f := data.d.d_f32.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f32 < 0 { + bf.positive = false + } + mut f := strconv.format_es(data.d.d_f32, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + .si_e64 { + // println("HERE: e64") + bf.len1 = 6 + if use_default_str { + mut f := data.d.d_f64.str() + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } else { + if data.d.d_f64 < 0 { + bf.positive = false + } + mut f := strconv.format_es(data.d.d_f64, bf) + if upper_case { + tmp := f + f = f.to_upper() + tmp.free() + } + sb.write_string(f) + f.free() + } + } + // runes + .si_c { + sb.write_string(utf32_to_str(data.d.d_c)) + } + // v pointers + .si_vp { + sb.write_string(u64(data.d.d_vp).hex()) + } + else { + sb.write_string('***ERROR!***') + } + } + } +} + +//==================================================================================== + +// storing struct used by cgen +pub struct StrIntpCgenData { +pub: + str string + fmt string + d string +} + +// NOTE: LOW LEVEL struct +// storing struct passed to V in the C code +pub struct StrIntpData { +pub: + str string + // fmt u64 // expanded version for future use, 64 bit + fmt u32 + d StrIntpMem +} + +// interpolation function +[manualfree] +pub fn str_intp(data_len int, in_data voidptr) string { + mut res := strings.new_builder(256) + unsafe { + mut i := 0 + for i < data_len { + data := &StrIntpData(&byte(in_data) + (int(sizeof(StrIntpData)) * i)) + // avoid empty strings + if data.str.len != 0 { + res.write_string(data.str) + } + // skip empty data + if data.fmt != 0 { + data.get_fmt_format(mut &res) + } + i++ + } + } + ret := res.str() + unsafe { res.free() } + return ret +} + +//==================================================================================== +// Utility for the compiler "auto_str_methods.v" +//==================================================================================== + +// substitute old _STR calls + +pub 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' + si_g32_code = '0xfe0e' + si_g64_code = '0xfe0f' +) + +// replace _STR("\'%.*s\\000\'", 2, in_str) +[inline] +pub fn str_intp_sq(in_str string) string { + return 'str_intp(2, _MOV((StrIntpData[]){{_SLIT("\'"), $si_s_code, {.d_s = $in_str}},{_SLIT("\'"), 0, {.d_c = 0 }}}))' +} + +// replace _STR("\`%.*s\\000\`", 2, in_str) +[inline] +pub fn str_intp_rune(in_str string) string { + return 'str_intp(2, _MOV((StrIntpData[]){{_SLIT("\`"), $si_s_code, {.d_s = $in_str}},{_SLIT("\`"), 0, {.d_c = 0 }}}))' +} + +[inline] +pub fn str_intp_g32(in_str string) string { + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT(""), $si_g32_code, {.d_f32 = $in_str }}}))' +} + +[inline] +pub fn str_intp_g64(in_str string) string { + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT(""), $si_g64_code, {.d_f64 = $in_str }}}))' +} + +// replace %% with the in_str +pub fn str_intp_sub(base_str string, in_str string) string { + index := base_str.index('%%') or { + eprintln('No strin interpolation %% parameteres') + exit(1) + } + // return base_str[..index] + in_str + base_str[index+2..] + if index + 2 < base_str.len { + return 'str_intp(2, _MOV((StrIntpData[]){{_SLIT("${base_str[..index]}"), $si_s_code, {.d_s = $in_str }},{_SLIT("${base_str[ + index + 2..]}"), 0, {.d_c = 0}}}))' + } + return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT("${base_str[..index]}"), $si_s_code, {.d_s = $in_str }}}))' +} diff --git a/vlib/math/complex/complex.v b/vlib/math/complex/complex.v index f5917f1698..9fd7cc8cb9 100644 --- a/vlib/math/complex/complex.v +++ b/vlib/math/complex/complex.v @@ -18,8 +18,8 @@ pub fn complex(re f64, im f64) Complex { // To String method pub fn (c Complex) str() string { - mut out := '${c.re:f}' - out += if c.im >= 0 { '+${c.im:f}' } else { '${c.im:f}' } + mut out := '${c.re:.6f}' + out += if c.im >= 0 { '+${c.im:.6f}' } else { '${c.im:.6f}' } out += 'i' return out } diff --git a/vlib/strconv/atof.v b/vlib/strconv/atof.v index a8179f81d8..e641adbcf5 100644 --- a/vlib/strconv/atof.v +++ b/vlib/strconv/atof.v @@ -82,7 +82,14 @@ fn sub96(s2 u32, s1 u32, s0 u32, d2 u32, d1 u32, d0 u32) (u32, u32, u32) { Constants */ -const ( +pub const ( + // + // f32 constants + // + single_plus_zero = u32(0x0000_0000) + single_minus_zero = u32(0x8000_0000) + single_plus_infinity = u32(0x7F80_0000) + single_minus_infinity = u32(0xFF80_0000) // // f64 constants // @@ -407,6 +414,7 @@ Public functions // atof64 return a f64 from a string doing a parsing operation pub fn atof64(s string) f64 { + mut pn := PrepNumber{} mut res_parsing := 0 mut res := Float64u{} diff --git a/vlib/strconv/f32_str.v b/vlib/strconv/f32_str.v index 290887d2fa..ad629741f3 100644 --- a/vlib/strconv/f32_str.v +++ b/vlib/strconv/f32_str.v @@ -164,7 +164,7 @@ fn f32_to_decimal_exact_int(i_mant u32, exp u32) (Dec32,bool) { return d, true } -pub fn f32_to_decimal(mant u32, exp u32) Dec32 { +fn f32_to_decimal(mant u32, exp u32) Dec32 { mut e2 := 0 mut m2 := u32(0) if exp == 0 { diff --git a/vlib/strconv/format.v b/vlib/strconv/format.v index 7608c78ce3..876a92eab7 100644 --- a/vlib/strconv/format.v +++ b/vlib/strconv/format.v @@ -25,6 +25,33 @@ pub enum Align_text { Float conversion utility */ +const( + // rounding value + dec_round = [ + f64(0.5), + 0.05, + 0.005, + 0.0005, + 0.00005, + 0.000005, + 0.0000005, + 0.00000005, + 0.000000005, + 0.0000000005, + 0.00000000005, + 0.000000000005, + 0.0000000000005, + 0.00000000000005, + 0.000000000000005, + 0.0000000000000005, + 0.00000000000000005, + 0.000000000000000005, + 0.0000000000000000005, + 0.00000000000000000005, + ] +) + +/* const( // rounding value dec_round = [ @@ -50,7 +77,7 @@ const( 0.000000000000000000044, ] ) - +*/ // max float 1.797693134862315708145274237317043567981e+308 /* diff --git a/vlib/strconv/format_mem.v b/vlib/strconv/format_mem.v new file mode 100644 index 0000000000..6948cc70cf --- /dev/null +++ b/vlib/strconv/format_mem.v @@ -0,0 +1,497 @@ +/*============================================================================= +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 strconv +import strings + +// strings.Builder version of format_str +pub fn format_str_sb(s string, p BF_param, mut sb strings.Builder) { + if p.len0 <= 0 { + sb.write_string(s) + return + } + dif := p.len0 - utf8_str_visible_length(s) + if dif <= 0 { + sb.write_string(s) + return + } + + if p.allign == .right { + for i1 :=0; i1 < dif; i1++ { + sb.write_b(p.pad_ch) + } + } + sb.write_string(s) + if p.allign == .left { + for i1 :=0; i1 < dif; i1++ { + sb.write_b(p.pad_ch) + } + } +} + +const ( + // digit pairs in reverse order + digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999' +) + +// format_dec_sb format a u64 +[direct_array_access] +pub fn format_dec_sb(d u64, p BF_param, mut res strings.Builder) { + mut n_char := dec_digits(d) + sign_len := if !p.positive || p.sign_flag { 1 } else { 0 } + number_len := sign_len + n_char + dif := p.len0 - number_len + mut sign_written := false + + if p.allign == .right { + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_written = true + } + } else { + res.write_b(`-`) + sign_written = true + } + } + // write the pad chars + for i1 :=0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + + if !sign_written { + // no pad char, write the sign before the number + if p.positive { + if p.sign_flag { + res.write_b(`+`) + } + } else { + res.write_b(`-`) + } + } + + /* + // Legacy version + // max u64 18446744073709551615 => 20 byte + mut buf := [32]byte{} + mut i := 20 + mut d1 := d + for i >= (21 - n_char) { + buf[i] = byte(d1 % 10) + `0` + d1 = d1 / 10 + i-- + } + i++ + */ + + //=========================================== + // Speed version + // max u64 18446744073709551615 => 20 byte + mut buf := [32]byte{} + mut i := 20 + mut n := d + mut d_i := u64(0) + if n > 0 { + for n > 0 { + n1 := n / 100 + // calculate the digit_pairs start index + d_i = (n - (n1 * 100)) << 1 + n = n1 + unsafe{ buf[i] = digit_pairs.str[d_i] } + i-- + d_i++ + unsafe{ buf[i] = digit_pairs.str[d_i] } + i-- + } + i++ + // remove head zero + if d_i < 20 { + i++ + } + unsafe{ res.write_ptr(&buf[i],n_char) } + + } else { + // we have a zero no need of more code! + res.write_b(`0`) + } + //=========================================== + + if p.allign == .left { + for i1 :=0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + return +} + + + +[manualfree] +[direct_array_access] +pub fn f64_to_str_lnd1(f f64, dec_digit int) string { + unsafe{ + // we add the rounding value + s := f64_to_str(f + dec_round[dec_digit], 18) + // check for +inf -inf Nan + if s.len > 2 && (s[0] == `n` || s[1] == `i`) { + return s + } + + m_sgn_flag := false + mut sgn := 1 + mut b := [26]byte{} + mut d_pos := 1 + mut i := 0 + mut i1 := 0 + mut exp := 0 + mut exp_sgn := 1 + + mut dot_res_sp := -1 + + // get sign and deciaml parts + for c in s { + if c == `-` { + sgn = -1 + i++ + } else if c == `+` { + sgn = 1 + i++ + } + else if c >= `0` && c <= `9` { + b[i1] = c + i1++ + i++ + } else if c == `.` { + if sgn > 0 { + d_pos = i + } else { + d_pos = i-1 + } + i++ + } else if c == `e` { + i++ + break + } else { + s.free() + return "[Float conversion error!!]" + } + } + b[i1] = 0 + + // get exponent + if s[i] == `-` { + exp_sgn = -1 + i++ + } else if s[i] == `+` { + exp_sgn = 1 + i++ + } + + mut c := i + for c < s.len { + exp = exp * 10 + int(s[c] - `0`) + c++ + } + + // allocate exp+32 chars for the return string + //mut res := []byte{len:exp+32,init:`0`} + mut res := []byte{len: exp+32, init: 0} + mut r_i := 0 // result string buffer index + + //println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") + + // s no more needed + s.free() + + if sgn == 1 { + if m_sgn_flag { + res[r_i] = `+` + r_i++ + } + } else { + res[r_i] = `-` + r_i++ + } + + i = 0 + if exp_sgn >= 0 { + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + if i >= d_pos && exp >= 0 { + if exp == 0 { + dot_res_sp = r_i + res[r_i] = `.` + r_i++ + } + exp-- + } + } + for exp >= 0 { + res[r_i] = `0` + r_i++ + exp-- + } + //println("exp: $exp $r_i $dot_res_sp") + } else { + mut dot_p := true + for exp > 0 { + res[r_i] = `0` + r_i++ + exp-- + if dot_p { + dot_res_sp = r_i + res[r_i] = `.` + r_i++ + dot_p = false + } + } + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + } + } + + // no more digits needed, stop here + if dec_digit <= 0 { + tmp_res := tos(res.data, dot_res_sp).clone() + res.free() + return tmp_res + } + + //println("r_i-d_pos: ${r_i - d_pos}") + if dot_res_sp >= 0 { + if (r_i - dot_res_sp) > dec_digit { + r_i = dot_res_sp + dec_digit + 1 + } + res[r_i] = 0 + //println("result: [${tos(&res[0],r_i)}]") + tmp_res := tos(res.data, r_i).clone() + res.free() + return tmp_res + } else { + if dec_digit > 0 { + mut c1 := 0 + res[r_i] = `.` + r_i++ + for c1 < dec_digit { + res[r_i] = `0` + r_i++ + c1++ + } + res[r_i] = 0 + } + tmp_res := tos(res.data, r_i).clone() + res.free() + return tmp_res + } + + } +} + +// strings.Builder version of format_fl +[manualfree] +pub fn format_fl(f f64, p BF_param) string { + unsafe{ + mut s := "" + //mut fs := "1.2343" + mut fs := f64_to_str_lnd1(if f >= 0.0 {f} else {-f}, p.len1) + //println("Dario") + //println(fs) + + // error!! + if fs[0] == `[` { + s.free() + return fs + } + + if p.rm_tail_zero { + tmp := fs + fs = remove_tail_zeros(fs) + tmp.free() + } + mut res := strings.new_builder( if p.len0 > fs.len { p.len0 } else { fs.len }) + + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + tmp := s + s = fs.clone() + tmp.free() + } else { + if p.positive { + if p.sign_flag { + tmp := s + s = "+" + fs + tmp.free() + } else { + tmp := s + s = fs.clone() + tmp.free() + } + } else { + tmp := s + s = "-" + fs + tmp.free() + } + } + + dif := p.len0 - s.len + sign_len_diff + + if p.allign == .right { + for i1 :=0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 :=0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + + + s.free() + fs.free() + tmp_res := res.str() + res.free() + return tmp_res + } +} + +[manualfree] +pub fn format_es(f f64, p BF_param) string { + unsafe{ + mut s := "" + mut fs := f64_to_str_pad(if f> 0 {f} else {-f},p.len1) + if p.rm_tail_zero { + fs = remove_tail_zeros(fs) + } + mut res := strings.new_builder( if p.len0 > fs.len { p.len0 } else { fs.len }) + + mut sign_len_diff := 0 + if p.pad_ch == `0` { + if p.positive { + if p.sign_flag { + res.write_b(`+`) + sign_len_diff = -1 + } + } else { + res.write_b(`-`) + sign_len_diff = -1 + } + tmp := s + s = fs.clone() + tmp.free() + } else { + if p.positive { + if p.sign_flag { + tmp := s + s = "+" + fs + tmp.free() + } else { + tmp := s + s = fs.clone() + tmp.free() + } + } else { + tmp := s + s = "-" + fs + tmp.free() + } + } + + dif := p.len0 - s.len + sign_len_diff + if p.allign == .right { + for i1 :=0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + res.write_string(s) + if p.allign == .left { + for i1 :=0; i1 < dif; i1++ { + res.write_b(p.pad_ch) + } + } + s.free() + fs.free() + tmp_res := res.str() + res.free() + return tmp_res + } +} + +[direct_array_access] +pub fn remove_tail_zeros(s string) string { + unsafe{ + mut buf := malloc(s.len + 1) + mut i_d := 0 + mut i_s := 0 + + // skip spaces + for i_s < s.len && s[i_s] !in [`-`,`+`] && s[i_s] >= `9` && s[i_s] <= `0`{ + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + // sign + if i_s < s.len && s[i_s] in [`-`,`+`] { + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + + // integer part + for i_s < s.len && s[i_s] >= `0` && s[i_s] <= `9` { + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + + // check decimals + if i_s < s.len && s[i_s] == `.` { + mut i_s1 := i_s + 1 + mut sum := 0 + for i_s1 < s.len && s[i_s1] >= `0` && s[i_s1] <= `9` { + sum += s[i_s1] - byte(`0`) + i_s1++ + } + // decimal part must be copied + if sum > 0 { + for c_i in i_s .. i_s1 { + buf[i_d] = s[c_i] + i_d++ + } + } + i_s = i_s1 + } + + if s[i_s] != `.` { + // check exponent + for i_s < s.len { + buf[i_d] = s[i_s] + i_s++ + i_d++ + } + } + + buf[i_d] = 0 + return tos(buf, i_d+1) + } +} diff --git a/vlib/strconv/number_to_base.v b/vlib/strconv/number_to_base.v index 3284df0f70..8881d08689 100644 --- a/vlib/strconv/number_to_base.v +++ b/vlib/strconv/number_to_base.v @@ -4,42 +4,58 @@ const base_digits = '0123456789abcdefghijklmnopqrstuvwxyz' // format_int returns the string representation of the number n in base `radix` // for digit values > 10, this function uses the small latin leters a-z. +[manualfree] pub fn format_int(n i64, radix int) string { - if radix < 2 || radix > 36 { - panic('invalid radix: $radix . It should be => 2 and <= 36') + unsafe{ + if radix < 2 || radix > 36 { + panic('invalid radix: $radix . It should be => 2 and <= 36') + } + if n == 0 { + return '0' + } + mut n_copy := n + mut sign := '' + if n < 0 { + sign = '-' + n_copy = -n_copy + } + mut res := '' + for n_copy != 0 { + tmp_0 := res + tmp_1 := base_digits[n_copy % radix].ascii_str() + res = tmp_1 + res + tmp_0.free() + tmp_1.free() + //res = base_digits[n_copy % radix].ascii_str() + res + n_copy /= radix + } + return '$sign$res' } - if n == 0 { - return '0' - } - mut n_copy := n - mut sign := '' - if n < 0 { - sign = '-' - n_copy = -n_copy - } - mut res := '' - for n_copy != 0 { - res = base_digits[n_copy % radix].ascii_str() + res - n_copy /= radix - } - return '$sign$res' } // format_uint returns the string representation of the number n in base `radix` // for digit values > 10, this function uses the small latin leters a-z. +[manualfree] pub fn format_uint(n u64, radix int) string { - if radix < 2 || radix > 36 { - panic('invalid radix: $radix . It should be => 2 and <= 36') + unsafe{ + if radix < 2 || radix > 36 { + panic('invalid radix: $radix . It should be => 2 and <= 36') + } + if n == 0 { + return '0' + } + mut n_copy := n + mut res := '' + uradix := u64(radix) + for n_copy != 0 { + tmp_0 := res + tmp_1 := base_digits[n_copy % uradix].ascii_str() + res = tmp_1 + res + tmp_0.free() + tmp_1.free() + //res = base_digits[n_copy % uradix].ascii_str() + res + n_copy /= uradix + } + return res } - if n == 0 { - return '0' - } - mut n_copy := n - mut res := '' - uradix := u64(radix) - for n_copy != 0 { - res = base_digits[n_copy % uradix].ascii_str() + res - n_copy /= uradix - } - return res } diff --git a/vlib/strconv/structs.v b/vlib/strconv/structs.v index ebc45712e2..1f7abbf2ec 100644 --- a/vlib/strconv/structs.v +++ b/vlib/strconv/structs.v @@ -42,8 +42,14 @@ mut: u u64 } -union Float64u { -mut: +pub union Float64u { +pub mut: f f64 u u64 } + +pub union Float32u { +pub mut: + f f32 + u u32 +} \ No newline at end of file diff --git a/vlib/strconv/utilities.v b/vlib/strconv/utilities.v index c0aa43940d..c87863c376 100644 --- a/vlib/strconv/utilities.v +++ b/vlib/strconv/utilities.v @@ -81,7 +81,9 @@ fn get_string_special(neg bool, expZero bool, mantZero bool) string { 32 bit functions */ -fn decimal_len_32(u u32) int { +// decimal_len_32 return the number of decimal digits of the input +[deprecated] +pub fn decimal_len_32(u u32) int { // Function precondition: u is not a 10-digit number. // (9 digits are sufficient for round-tripping.) // This benchmarked faster than the log2 approach used for u64. @@ -172,7 +174,9 @@ fn pow5_bits(e int) int { 64 bit functions */ -fn decimal_len_64(u u64) int { +[deprecated] +// decimal_len_64 return the number of decimal digits of the input +pub fn decimal_len_64(u u64) int { // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 log2 := 64 - bits.leading_zeros_64(u) - 1 t := (log2 + 1) * 1233 >> 12 @@ -227,15 +231,44 @@ f64 to string with string format */ +// TODO: Investigate precision issues // f32_to_str_l return a string with the f32 converted in a string in decimal notation -pub fn f32_to_str_l(f f64) string { - return f64_to_str_l(f32(f)) +[manualfree] +pub fn f32_to_str_l(f f32) string { + s := f32_to_str(f,6) + res := fxx_to_str_l_parse(s) + unsafe{s.free()} + return res } -// f64_to_str_l return a string with the f64 converted in a string in decimal notation +[manualfree] +pub fn f32_to_str_l_no_dot(f f32) string { + s := f32_to_str(f,6) + res := fxx_to_str_l_parse_no_dot(s) + unsafe{s.free()} + return res +} + +[manualfree] pub fn f64_to_str_l(f f64) string { s := f64_to_str(f,18) + res := fxx_to_str_l_parse(s) + unsafe{s.free()} + return res +} +[manualfree] +pub fn f64_to_str_l_no_dot(f f64) string { + s := f64_to_str(f,18) + res := fxx_to_str_l_parse_no_dot(s) + unsafe{s.free()} + return res +} + + +// f64_to_str_l return a string with the f64 converted in a string in decimal notation +[manualfree] +pub fn fxx_to_str_l_parse(s string) string { // check for +inf -inf Nan if s.len > 2 && (s[0] == `n` || s[1] == `i`) { return s @@ -287,8 +320,11 @@ pub fn f64_to_str_l(f f64) string { exp_sgn = 1 i++ } - for c in s[i..] { - exp = exp * 10 + int(c-`0`) + + mut c := i + for c < s.len { + exp = exp * 10 + int(s[c]-`0`) + c++ } // allocate exp+32 chars for the return string @@ -296,7 +332,7 @@ pub fn f64_to_str_l(f f64) string { mut r_i := 0 // result string buffer index //println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") - + if sgn == 1 { if m_sgn_flag { res[r_i] = `+` @@ -344,6 +380,136 @@ pub fn f64_to_str_l(f f64) string { i++ } } +/* + // remove the dot form the numbers like 2. + if r_i > 1 && res[r_i-1] == `.` { + r_i-- + } +*/ + res[r_i] = 0 + return unsafe { tos(res.data,r_i) } +} + +// f64_to_str_l return a string with the f64 converted in a string in decimal notation +[manualfree] +pub fn fxx_to_str_l_parse_no_dot(s string) string { + // check for +inf -inf Nan + if s.len > 2 && (s[0] == `n` || s[1] == `i`) { + return s + } + + m_sgn_flag := false + mut sgn := 1 + mut b := [26]byte{} + mut d_pos := 1 + mut i := 0 + mut i1 := 0 + mut exp := 0 + mut exp_sgn := 1 + + // get sign and decimal parts + for c in s { + if c == `-` { + sgn = -1 + i++ + } else if c == `+` { + sgn = 1 + i++ + } + else if c >= `0` && c <= `9` { + b[i1] = c + i1++ + i++ + } else if c == `.` { + if sgn > 0 { + d_pos = i + } else { + d_pos = i-1 + } + i++ + } else if c == `e` { + i++ + break + } else { + return "Float conversion error!!" + } + } + b[i1] = 0 + + // get exponent + if s[i] == `-` { + exp_sgn = -1 + i++ + } else if s[i] == `+` { + exp_sgn = 1 + i++ + } + + mut c := i + for c < s.len { + exp = exp * 10 + int(s[c]-`0`) + c++ + } + + // allocate exp+32 chars for the return string + mut res := []byte{len: exp+32, init: 0} + mut r_i := 0 // result string buffer index + + //println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") + + if sgn == 1 { + if m_sgn_flag { + res[r_i] = `+` + r_i++ + } + } else { + res[r_i] = `-` + r_i++ + } + + i = 0 + if exp_sgn >= 0 { + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + if i >= d_pos && exp >= 0 { + if exp == 0 { + res[r_i] = `.` + r_i++ + } + exp-- + } + } + for exp >= 0 { + res[r_i] = `0` + r_i++ + exp-- + } + } else { + mut dot_p := true + for exp > 0 { + res[r_i] = `0` + r_i++ + exp-- + if dot_p { + res[r_i] = `.` + r_i++ + dot_p = false + } + } + for b[i] != 0 { + res[r_i] = b[i] + r_i++ + i++ + } + } + + // remove the dot form the numbers like 2. + if r_i > 1 && res[r_i-1] == `.` { + r_i-- + } + res[r_i] = 0 return unsafe { tos(res.data,r_i) } } diff --git a/vlib/strconv/vprintf.v b/vlib/strconv/vprintf.v index 35ab0f74de..6123fe2454 100644 --- a/vlib/strconv/vprintf.v +++ b/vlib/strconv/vprintf.v @@ -455,170 +455,6 @@ fn fabs(x f64) f64 { return x } -[manualfree] -pub fn f64_to_str_lnd1(f f64, dec_digit int) string { - unsafe{ - // we add the rounding value - s := f64_to_str(f + dec_round[dec_digit], 18) - // check for +inf -inf Nan - if s.len > 2 && (s[0] == `n` || s[1] == `i`) { - return s - } - - m_sgn_flag := false - mut sgn := 1 - mut b := [26]byte{} - mut d_pos := 1 - mut i := 0 - mut i1 := 0 - mut exp := 0 - mut exp_sgn := 1 - - mut dot_res_sp := -1 - - // get sign and deciaml parts - for c in s { - if c == `-` { - sgn = -1 - i++ - } else if c == `+` { - sgn = 1 - i++ - } - else if c >= `0` && c <= `9` { - b[i1] = c - i1++ - i++ - } else if c == `.` { - if sgn > 0 { - d_pos = i - } else { - d_pos = i-1 - } - i++ - } else if c == `e` { - i++ - break - } else { - s.free() - return "[Float conversion error!!]" - } - } - b[i1] = 0 - - // get exponent - if s[i] == `-` { - exp_sgn = -1 - i++ - } else if s[i] == `+` { - exp_sgn = 1 - i++ - } - - mut c := i - for c < s.len { - exp = exp * 10 + int(s[c] - `0`) - c++ - } - - // allocate exp+32 chars for the return string - //mut res := []byte{len:exp+32,init:`0`} - mut res := []byte{len: exp+32, init: 0} - mut r_i := 0 // result string buffer index - - //println("s:${sgn} b:${b[0]} es:${exp_sgn} exp:${exp}") - - // s no more needed - s.free() - - if sgn == 1 { - if m_sgn_flag { - res[r_i] = `+` - r_i++ - } - } else { - res[r_i] = `-` - r_i++ - } - - i = 0 - if exp_sgn >= 0 { - for b[i] != 0 { - res[r_i] = b[i] - r_i++ - i++ - if i >= d_pos && exp >= 0 { - if exp == 0 { - dot_res_sp = r_i - res[r_i] = `.` - r_i++ - } - exp-- - } - } - for exp >= 0 { - res[r_i] = `0` - r_i++ - exp-- - } - //println("exp: $exp $r_i $dot_res_sp") - } else { - mut dot_p := true - for exp > 0 { - res[r_i] = `0` - r_i++ - exp-- - if dot_p { - dot_res_sp = r_i - res[r_i] = `.` - r_i++ - dot_p = false - } - } - for b[i] != 0 { - res[r_i] = b[i] - r_i++ - i++ - } - } - - // no more digits needed, stop here - if dec_digit <= 0 { - tmp_res := tos(res.data, dot_res_sp).clone() - res.free() - return tmp_res - } - - //println("r_i-d_pos: ${r_i - d_pos}") - if dot_res_sp >= 0 { - if (r_i - dot_res_sp) > dec_digit { - r_i = dot_res_sp + dec_digit + 1 - } - res[r_i] = 0 - //println("result: [${tos(&res[0],r_i)}]") - tmp_res := tos(res.data, r_i).clone() - res.free() - return tmp_res - } else { - if dec_digit > 0 { - mut c1 := 0 - res[r_i] = `.` - r_i++ - for c1 < dec_digit { - res[r_i] = `0` - r_i++ - c1++ - } - res[r_i] = 0 - } - tmp_res := tos(res.data, r_i).clone() - res.free() - return tmp_res - } - - } -} - // strings.Builder version of format_fl [manualfree] pub fn format_fl_old(f f64, p BF_param) string { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index dfcfa7aff1..0ecb6ac3ce 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -493,7 +493,8 @@ pub fn (node Stmt) str() string { } fn field_to_string(f ConstField) string { - return '${f.name.trim_prefix(f.mod + '.')} = $f.expr' + x := f.name.trim_prefix(f.mod + '.') + return '$x = $f.expr' } pub fn (e CompForKind) str() string { diff --git a/vlib/v/checker/check_types.v b/vlib/v/checker/check_types.v index 43c565aea1..6ed4293302 100644 --- a/vlib/v/checker/check_types.v +++ b/vlib/v/checker/check_types.v @@ -443,7 +443,9 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Typ typ := c.table.unalias_num_type(ftyp) mut fmt := node.fmts[i] // analyze and validate format specifier - if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `p`, `_`] { + if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `S`, `p`, + `_`, + ] { c.error('unknown format specifier `${fmt:c}`', node.fmt_poss[i]) } if fmt == `_` { // set default representation for type if none has been given @@ -469,7 +471,7 @@ pub fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Typ || (typ.is_int_literal() && fmt !in [`d`, `c`, `x`, `X`, `o`, `u`, `x`, `X`, `o`]) || (typ.is_float() && fmt !in [`E`, `F`, `G`, `e`, `f`, `g`]) || (typ.is_pointer() && fmt !in [`p`, `x`, `X`]) - || (typ.is_string() && fmt != `s`) + || (typ.is_string() && fmt !in [`s`, `S`]) || (typ.idx() in [ast.i64_type_idx, ast.f64_type_idx] && fmt == `c`) { c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`', node.fmt_poss[i]) diff --git a/vlib/v/gen/c/auto_str_array.v b/vlib/v/gen/c/auto_str_array.v new file mode 100644 index 0000000000..6cda13f1b4 --- /dev/null +++ b/vlib/v/gen/c/auto_str_array.v @@ -0,0 +1,170 @@ +module c + +// Copyright (c) 2019-2021 Alexander Medvednikov. 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. +import v.ast + +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 := '' + if sym_has_str_method { + elem_str_fn_name = if is_elem_ptr { + field_styp.replace('*', '') + '_str' + } else { + field_styp + '_str' + } + if sym.kind == .byte { + elem_str_fn_name = elem_str_fn_name + '_escaped' + } + } else { + elem_str_fn_name = styp_to_str_fn_name(field_styp) + } + if !sym_has_str_method { + 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(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')};') + } + + // g.auto_str_funcs.writeln('\t\tstring x = _STR("%g", 1, 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("\`"), $si_s_code, {.d_s = ${elem_str_fn_name}(it) }}, {_SLIT("\`"), 0, {.d_c = 0 }}}));\n') + + // g.auto_str_funcs.writeln('\t\tstring x = _STR("`%.*s\\000`", 2, ${elem_str_fn_name}(it));') + } else if sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstring x = str_intp(2, _MOV((StrIntpData[]){{_SLIT("\'"), $si_s_code, {.d_s = it }}, {_SLIT("\'"), 0, {.d_c = 0 }}}));\n') + + // g.auto_str_funcs.writeln('\t\tstring x = _STR("\'%.*s\\000\'", 2, it);') + } 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) + } + 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 := '' + if sym_has_str_method { + elem_str_fn_name = if is_elem_ptr { + field_styp.replace('*', '') + '_str' + } else { + field_styp + '_str' + } + } else { + elem_str_fn_name = styp_to_str_fn_name(field_styp) + } + if !sym.has_method('str') { + 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("["));') + if sym.kind == .function { + g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();') + } else { + deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) + g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; ++i) {') + 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]')} );') + } + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("%g", 1, a[i]));') + } else if sym.kind == .string { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, ${str_intp_sq('a[i]')});') + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, 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);') + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("`%.*s\\000`", 2, ${elem_str_fn_name}(a[i])));') + } 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('}') +} diff --git a/vlib/v/gen/c/auto_str_map.v b/vlib/v/gen/c/auto_str_map.v new file mode 100644 index 0000000000..25935b8a80 --- /dev/null +++ b/vlib/v/gen/c/auto_str_map.v @@ -0,0 +1,90 @@ +module c + +// Copyright (c) 2019-2021 Alexander Medvednikov. 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. +import v.ast + +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')});') + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, 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);') + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("`%.*s\\000`", 2, ${key_str_fn_name}(key)));') + } 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);') + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, *($val_styp*)DenseArray_value(&m.key_values, i)));') + } else if should_use_indent_func(val_sym.kind) && !val_sym.has_method('str') { + g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(*($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)});') + } + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("%g", 1, *($val_styp*)DenseArray_value(&m.key_values, i)));') + } 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);') + // g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("`%.*s\\000`", 2, ${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i))));') + } 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('}') +} diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index 06cae0665c..c93605434a 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -5,6 +5,82 @@ module c import v.ast import v.util +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_] } @@ -40,41 +116,6 @@ fn (mut g Gen) gen_str_default(sym ast.TypeSymbol, styp string, str_fn_name stri g.auto_str_funcs.writeln('}') } -fn (g &Gen) type_to_fmt(typ ast.Type) string { - if typ == ast.byte_type_idx { - return '%hhx\\000' - } - if typ == ast.char_type_idx { - return '%c\\000' - } - if typ == ast.voidptr_type_idx || typ in ast.byteptr_types { - return '%p\\000' - } - if typ in ast.charptr_types { - return '%C\\000' // a C string - } - sym := g.table.get_type_symbol(typ) - if typ.is_ptr() && (typ.is_int_valptr() || typ.is_float_valptr()) { - return '%.*s\\000' - } else if sym.kind in [.struct_, .array, .array_fixed, .map, .bool, .enum_, .interface_, - .sum_type, .function, .alias] { - return '%.*s\\000' - } else if sym.kind == .string { - return "'%.*s\\000'" - } else if sym.kind in [.f32, .f64] { - return '%g\\000' // g removes trailing zeros unlike %f - } else if sym.kind == .int { - return '%d\\000' - } else if sym.kind == .u32 { - return '%u\\000' - } else if sym.kind == .u64 { - return '%llu\\000' - } else if sym.kind == .i64 { - return '%lld\\000' - } - return '%d\\000' -} - 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)) @@ -158,16 +199,24 @@ fn (mut g Gen) gen_str_for_option(typ ast.Type, styp string, str_fn_name string) g.auto_str_funcs.writeln('\tstring res;') g.auto_str_funcs.writeln('\tif (it.state == 0) {') if sym.kind == .string { - g.auto_str_funcs.writeln('\t\tres = _STR("\'%.*s\\000\'", 2, ${parent_str_fn_name}(*($sym.cname*)it.data));') + tmp_res := '${parent_str_fn_name}(*($sym.cname*)it.data)' + g.auto_str_funcs.writeln('\t\tres = ${str_intp_sq(tmp_res)};') + // g.auto_str_funcs.writeln('\t\tres = _STR("\'%.*s\\000\'", 2, ${parent_str_fn_name}(*($sym.cname*)it.data));') } 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 {') - g.auto_str_funcs.writeln('\t\tres = _STR("error: %.*s\\000", 2, IError_str(it.err));') + + 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\tres = _STR("error: %.*s\\000", 2, IError_str(it.err));') + g.auto_str_funcs.writeln('\t}') - g.auto_str_funcs.writeln('\treturn _STR("Option(%.*s\\000)", 2, res);') + + g.auto_str_funcs.writeln('\treturn ${str_intp_sub('Option(%%)', 'res')};') + // g.auto_str_funcs.writeln('\treturn _STR("Option(%.*s\\000)", 2, res);') g.auto_str_funcs.writeln('}') } @@ -187,223 +236,15 @@ fn (mut g Gen) gen_str_for_alias(info ast.Alias, styp string, str_fn_name string g.auto_str_funcs.writeln('\tfor (int i = 0; i < indent_count; ++i) {') g.auto_str_funcs.writeln('\t\tindents = string_add(indents, _SLIT(" "));') g.auto_str_funcs.writeln('\t}') - g.auto_str_funcs.writeln('\treturn _STR("%.*s\\000${clean_type_v_type_name}(%.*s\\000)", 3, indents, ${parent_str_fn_name}(it));') - g.auto_str_funcs.writeln('}') -} -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 := '' - if sym_has_str_method { - elem_str_fn_name = if is_elem_ptr { - field_styp.replace('*', '') + '_str' - } else { - field_styp + '_str' - } - if sym.kind == .byte { - elem_str_fn_name = elem_str_fn_name + '_escaped' - } - } else { - elem_str_fn_name = styp_to_str_fn_name(field_styp) - } - if !sym_has_str_method { - 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(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] { - g.auto_str_funcs.writeln('\t\tstring x = _STR("%g", 1, it);') - } else if sym.kind == .rune { - g.auto_str_funcs.writeln('\t\tstring x = _STR("`%.*s\\000`", 2, ${elem_str_fn_name}(it));') - } else if sym.kind == .string { - g.auto_str_funcs.writeln('\t\tstring x = _STR("\'%.*s\\000\'", 2, it);') - } 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('}') -} + g.auto_str_funcs.writeln('\treturn 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 = ${parent_str_fn_name}(it) }}, + {_SLIT(")"), 0, {.d_c = 0 }} + }));\n') -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) - } - 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 := '' - if sym_has_str_method { - elem_str_fn_name = if is_elem_ptr { - field_styp.replace('*', '') + '_str' - } else { - field_styp + '_str' - } - } else { - elem_str_fn_name = styp_to_str_fn_name(field_styp) - } - if !sym.has_method('str') { - 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("["));') - if sym.kind == .function { - g.auto_str_funcs.writeln('\t\tstring x = ${elem_str_fn_name}();') - } else { - deref, deref_label := deref_kind(str_method_expects_ptr, is_elem_ptr, typ) - g.auto_str_funcs.writeln('\tfor (int i = 0; i < $info.size; ++i) {') - 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] { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("%g", 1, a[i]));') - } else if sym.kind == .string { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, a[i]));') - } else if sym.kind == .rune { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("`%.*s\\000`", 2, ${elem_str_fn_name}( $deref a[i])));') - } 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('}') -} + // g.auto_str_funcs.writeln('\treturn _STR("%.*s\\000${clean_type_v_type_name}(%.*s\\000)", 3, indents, ${parent_str_fn_name}(it));') -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("\'%.*s\\000\'", 2, key));') - } else if key_sym.kind == .rune { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("`%.*s\\000`", 2, ${key_str_fn_name}(key)));') - } 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 { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, *($val_styp*)DenseArray_value(&m.key_values, i)));') - } else if should_use_indent_func(val_sym.kind) && !val_sym.has_method('str') { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, indent_${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i), indent_count));') - } else if val_sym.kind in [.f32, .f64] { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("%g", 1, *($val_styp*)DenseArray_value(&m.key_values, i)));') - } else if val_sym.kind == .rune { - g.auto_str_funcs.writeln('\t\tstrings__Builder_write_string(&sb, _STR("`%.*s\\000`", 2, ${elem_str_fn_name}(*($val_styp*)DenseArray_value(&m.key_values, i))));') - } 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('}') } @@ -436,9 +277,18 @@ fn (mut g Gen) gen_str_for_multi_return(info ast.MultiReturn, styp string, str_f if should_use_indent_func(sym.kind) && !sym_has_str_method { g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}(a.arg$i));') } else if sym.kind in [.f32, .f64] { - g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _STR("%g", 1, a.arg$i));') + if sym.kind == .f32 { + tmp_val := str_intp_g32('a.arg$i') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, $tmp_val);') + } else { + tmp_val := str_intp_g64('a.arg$i') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, $tmp_val);') + } + // g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _STR("%g", 1, a.arg$i));') } else if sym.kind == .string { - g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, a.arg$i));') + tmp_str := str_intp_sq('a.arg$i') + g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, $tmp_str);') + // g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, _STR("\'%.*s\\000\'", 2, a.arg$i));') } else if sym.kind == .function { g.auto_str_funcs.writeln('\tstrings__Builder_write_string(&sb, ${arg_str_fn_name}());') } else { @@ -457,136 +307,6 @@ fn (mut g Gen) gen_str_for_multi_return(info ast.MultiReturn, styp string, str_f g.auto_str_funcs.writeln('}') } -fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string) { - // TODO: short it if possible - // generates all definitions of substructs - mut fnames2strfunc := map{ - '': '' - } - for field in info.fields { - sym := g.table.get_type_symbol(field.typ) - if !sym.has_method('str') { - mut typ := field.typ - if typ.is_ptr() { - typ = typ.deref() - } - field_styp := g.typ(typ) - field_fn_name := g.gen_str_for_type(field.typ) - fnames2strfunc[field_styp] = field_fn_name - } - } - // _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') - g.auto_str_funcs.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 - g.auto_str_funcs.writeln('\tstring indents = _SLIT("");') - g.auto_str_funcs.writeln('\tfor (int i = 0; i < indent_count; ++i) {') - g.auto_str_funcs.writeln('\t\tindents = string_add(indents, _SLIT(" "));') - g.auto_str_funcs.writeln('\t}') - if info.fields.len == 0 { - g.auto_str_funcs.write_string('\treturn _SLIT("$clean_struct_v_type_name{}");') - } else { - g.auto_str_funcs.write_string('\treturn _STR("$clean_struct_v_type_name{\\n"') - for field in info.fields { - mut fmt := if field.typ.is_ptr() { '&' } else { '' } - fmt += g.type_to_fmt(field.typ) - g.auto_str_funcs.writeln('\t\t"%.*s\\000 $field.name: $fmt\\n"') - } - g.auto_str_funcs.write_string('\t\t"%.*s\\000}", ${2 * (info.fields.len + 1)}') - if info.fields.len > 0 { - g.auto_str_funcs.write_string(',\n\t\t') - for i, field in info.fields { - sym := g.table.get_type_symbol(field.typ) - 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 { - fnames2strfunc[field_styp] - } - g.auto_str_funcs.write_string('indents, ') - mut func := struct_auto_str_func(sym, field.typ, field_styp_fn_name, field.name) - // reference types can be "nil" - if field.typ.is_ptr() && !(field.typ in ast.charptr_types - || field.typ in ast.byteptr_types - || field.typ == ast.voidptr_type_idx) { - g.auto_str_funcs.write_string('isnil(it.${c_name(field.name)})') - g.auto_str_funcs.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() { - g.auto_str_funcs.write_string('*') - } - } - // handle circular ref type of struct to the struct itself - if styp == field_styp { - g.auto_str_funcs.write_string('_SLIT("")') - } else { - g.auto_str_funcs.write_string(func) - } - - if i < info.fields.len - 1 { - g.auto_str_funcs.write_string(',\n\t\t') - } - } - } - g.auto_str_funcs.writeln(',') - g.auto_str_funcs.writeln('\t\tindents);') - } - g.auto_str_funcs.writeln('}') -} - -fn struct_auto_str_func(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 { - 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 castet to a string - fmt := if sym.kind in [.f32, .f64] { - '%g\\000' - } else if sym.kind == .u64 { - '%lld\\000' - } else { - '%d\\000' - } - method_str = '_STR("$fmt", 2, *$method_str)' - } - return method_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') @@ -653,6 +373,38 @@ fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, str_fn_nam 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 }} + }))' + g.auto_str_funcs.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + g.auto_str_funcs.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 }} + }))' + g.auto_str_funcs.write_string('\tif (x._typ == _${styp}_${subtype.cname}_index)') + g.auto_str_funcs.write_string(' return $res;\n') + } + + //------------------------------------------ + /* deref := if sym_has_str_method && str_method_expects_ptr { ' ' } else { '*' } value_fmt := if typ == ast.string_type { "'%.*s\\000'" } else { '%.*s\\000' } @@ -662,7 +414,9 @@ fn (mut g Gen) gen_str_for_interface(info ast.Interface, styp string, str_fn_nam if should_use_indent_func(subtype.kind) && !sym_has_str_method { g.auto_str_funcs.write_string(', indent_count') } - g.auto_str_funcs.writeln('));') + g.auto_str_funcs.writeln('));\n') + */ + //------------------------------------------ } g.auto_str_funcs.writeln('\treturn _SLIT("unknown interface value");') g.auto_str_funcs.writeln('}') @@ -690,10 +444,6 @@ fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_ clean_sum_type_v_type_name = util.strip_main_name(clean_sum_type_v_type_name) g.auto_str_funcs.writeln('\tswitch(x._typ) {') for typ in info.variants { - mut value_fmt := '%.*s\\000' - if typ == ast.string_type { - value_fmt = "'$value_fmt'" - } typ_str := g.typ(typ) mut func_name := if typ_str in gen_fn_names { gen_fn_names[typ_str] @@ -706,11 +456,46 @@ fn (mut g Gen) gen_str_for_union_sum_type(info ast.SumType, styp string, str_fn_ 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 }} + }))' + g.auto_str_funcs.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 }} + }))' + g.auto_str_funcs.write_string('\t\tcase $typ: return $res;') + } + + //------------------------------------------ + /* + mut value_fmt := '%.*s\\000' + if typ == ast.string_type { + value_fmt = "'$value_fmt'" + } g.auto_str_funcs.write_string('\t\tcase $typ: return _STR("${clean_sum_type_v_type_name}($value_fmt)", 2, ${func_name}(${deref}($typ_str*)x._$sym.cname') if should_use_indent_func(sym.kind) && !sym_has_str_method { g.auto_str_funcs.write_string(', indent_count') } g.auto_str_funcs.writeln('));') + */ + //------------------------------------------ } g.auto_str_funcs.writeln('\t\tdefault: return _SLIT("unknown sum type value");') g.auto_str_funcs.writeln('\t}') @@ -730,7 +515,8 @@ fn (mut g Gen) fn_decl_str(info ast.FnType) string { } fn_str += ')' if info.func.return_type != ast.void_type { - fn_str += ' ${util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type)))}' + x := util.strip_main_name(g.table.get_type_name(g.unwrap_generic(info.func.return_type))) + fn_str += ' $x' } return fn_str } diff --git a/vlib/v/gen/c/auto_str_struct.v b/vlib/v/gen/c/auto_str_struct.v new file mode 100644 index 0000000000..0fe68bce93 --- /dev/null +++ b/vlib/v/gen/c/auto_str_struct.v @@ -0,0 +1,387 @@ +module c + +// Copyright (c) 2019-2021 Alexander Medvednikov. 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. +import v.ast +import v.util + +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 == ast.voidptr_type_idx || typ in ast.byteptr_types { + return .si_p + } + if typ in ast.charptr_types { + return .si_s + // return '%C\\000' // a C string + } + 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] { + 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) { + // g.gen_str_for_struct1(info, styp, str_fn_name) + + // TODO: short it if possible + // generates all definitions of substructs + mut fnames2strfunc := map{ + '': '' + } + for field in info.fields { + sym := g.table.get_type_symbol(field.typ) + if !sym.has_method('str') { + mut typ := field.typ + if typ.is_ptr() { + typ = typ.deref() + } + field_styp := g.typ(typ) + field_fn_name := g.gen_str_for_type(field.typ) + fnames2strfunc[field_styp] = field_fn_name + } + } + // _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') + g.auto_str_funcs.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 + g.auto_str_funcs.writeln('\tstring indents = _SLIT("");') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < indent_count; ++i) {') + g.auto_str_funcs.writeln('\t\tindents = string_add(indents, _SLIT(" "));') + g.auto_str_funcs.writeln('\t}') + if info.fields.len == 0 { + g.auto_str_funcs.write_string('\treturn _SLIT("$clean_struct_v_type_name{}");') + } else { + g.auto_str_funcs.write_string('\treturn str_intp( ${info.fields.len * 4 + 3}, _MOV((StrIntpData[]){\n') + g.auto_str_funcs.write_string('\t\t{_SLIT("$clean_struct_v_type_name{\\n"), 0, {.d_c=0}},\n') + + for i, field in info.fields { + mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' } + base_fmt := g.type_to_fmt1(field.typ) + + // manage prefix and quote symbol for the filed + mut quote_str := '' + mut prefix := '' + sym := g.table.get_type_symbol(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 { + g.auto_str_funcs.write_string('\t\t{_SLIT0, $si_s_code, {.d_s=indents}}, {_SLIT(" $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ') + } else { + g.auto_str_funcs.write_string('\t\t{_SLIT("\\n"), $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 { + fnames2strfunc[field_styp] + } + + // manage the fact hat with float we use always the g representation + if sym.kind !in [.f32, .f64] { + g.auto_str_funcs.write_string('{_SLIT("$quote_str"), ${int(base_fmt)}, {.${data_str(base_fmt)}=') + } else { + g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex() + g.auto_str_funcs.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) + + // manage reference types can be "nil" + if field.typ.is_ptr() && !(field.typ in ast.charptr_types + || field.typ in ast.byteptr_types + || field.typ == ast.voidptr_type_idx) { + g.auto_str_funcs.write_string('isnil(it.${c_name(field.name)})') + g.auto_str_funcs.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() { + g.auto_str_funcs.write_string('*') + } + } + // handle circular ref type of struct to the struct itself + if styp == field_styp { + g.auto_str_funcs.write_string('_SLIT("")') + } else { + // manage C charptr + if field.typ in ast.charptr_types { + g.auto_str_funcs.write_string('tos2((byteptr)$func)') + } else { + g.auto_str_funcs.write_string(func) + } + } + + g.auto_str_funcs.write_string('}}, {_SLIT("$quote_str"), 0, {.d_c=0}},\n') + } + g.auto_str_funcs.write_string('\t\t{_SLIT("\\n"), $si_s_code, {.d_s=indents}}, {_SLIT("}"), 0, {.d_c=0}},\n') + g.auto_str_funcs.write_string('\t}));\n') + } + g.auto_str_funcs.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 { + 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 castet 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 + } +} + +//============================================================================= +// OLD CODE +//============================================================================= +/* +fn (g &Gen) type_to_fmt(typ ast.Type) string { + if typ == ast.byte_type_idx { + return '%hhx\\000' + } + if typ == ast.char_type_idx { + return '%c\\000' + } + if typ == ast.voidptr_type_idx || typ in ast.byteptr_types { + return '%p\\000' + } + if typ in ast.charptr_types { + return '%C\\000' // a C string + } + sym := g.table.get_type_symbol(typ) + if typ.is_ptr() && (typ.is_int_valptr() || typ.is_float_valptr()) { + return '%.*s\\000' + } else if sym.kind in [.struct_, .array, .array_fixed, .map, .bool, .enum_, .interface_, + .sum_type, .function, .alias] { + return '%.*s\\000' + } else if sym.kind == .string { + return "'%.*s\\000'" + } else if sym.kind in [.f32, .f64] { + return '%g\\000' // g removes trailing zeros unlike %f + } else if sym.kind == .int { + return '%d\\000' + } else if sym.kind == .u32 { + return '%u\\000' + } else if sym.kind == .u64 { + return '%llu\\000' + } else if sym.kind == .i64 { + return '%lld\\000' + } + return '%d\\000' +} + +fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name string) { + + //g.gen_str_for_struct1(info, styp, str_fn_name) + + // TODO: short it if possible + // generates all definitions of substructs + mut fnames2strfunc := map{ + '': '' + } + for field in info.fields { + sym := g.table.get_type_symbol(field.typ) + if !sym.has_method('str') { + mut typ := field.typ + if typ.is_ptr() { + typ = typ.deref() + } + field_styp := g.typ(typ) + field_fn_name := g.gen_str_for_type(field.typ) + fnames2strfunc[field_styp] = field_fn_name + } + } + // _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') + g.auto_str_funcs.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 + g.auto_str_funcs.writeln('\tstring indents = _SLIT("");') + g.auto_str_funcs.writeln('\tfor (int i = 0; i < indent_count; ++i) {') + g.auto_str_funcs.writeln('\t\tindents = string_add(indents, _SLIT(" "));') + g.auto_str_funcs.writeln('\t}') + if info.fields.len == 0 { + g.auto_str_funcs.write_string('\treturn _SLIT("$clean_struct_v_type_name{}");') + } else { + g.auto_str_funcs.write_string('\treturn _STR("$clean_struct_v_type_name{\\n"') + for field in info.fields { + mut fmt := if field.typ.is_ptr() { '&' } else { '' } + fmt += g.type_to_fmt(field.typ) + g.auto_str_funcs.writeln('\t\t"%.*s\\000 $field.name: $fmt\\n"') + } + g.auto_str_funcs.write_string('\t\t"%.*s\\000}", ${2 * (info.fields.len + 1)}') + if info.fields.len > 0 { + g.auto_str_funcs.write_string(',\n\t\t') + for i, field in info.fields { + sym := g.table.get_type_symbol(field.typ) + 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 { + fnames2strfunc[field_styp] + } + g.auto_str_funcs.write_string('indents, ') + mut func := struct_auto_str_func(sym, field.typ, field_styp_fn_name, field.name) + // reference types can be "nil" + if field.typ.is_ptr() && !(field.typ in ast.charptr_types + || field.typ in ast.byteptr_types + || field.typ == ast.voidptr_type_idx) { + g.auto_str_funcs.write_string('isnil(it.${c_name(field.name)})') + g.auto_str_funcs.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() { + g.auto_str_funcs.write_string('*') + } + } + // handle circular ref type of struct to the struct itself + if styp == field_styp { + g.auto_str_funcs.write_string('_SLIT("")') + } else { + g.auto_str_funcs.write_string(func) + } + + if i < info.fields.len - 1 { + g.auto_str_funcs.write_string(',\n\t\t') + } + } + } + g.auto_str_funcs.writeln(',') + g.auto_str_funcs.writeln('\t\tindents);') + } + g.auto_str_funcs.writeln('}') +} + +fn struct_auto_str_func(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 { + 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 castet to a string + fmt := if sym.kind in [.f32, .f64] { + '%g\\000' + } else if sym.kind == .u64 { + '%lld\\000' + } else { + '%d\\000' + } + method_str = '_STR("$fmt", 2, *$method_str)' + } + return method_str + } +} +*/ diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 5921517bfa..4a35f6b807 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -408,8 +408,8 @@ pub fn (mut g Gen) init() { } else { g.cheaders.writeln(c_headers) } - g.definitions.writeln('string _STR(const char*, int, ...);') - g.definitions.writeln('string _STR_TMP(const char*, ...);') + // g.definitions.writeln('string _STR(const char*, int, ...);') + // g.definitions.writeln('string _STR_TMP(const char*, ...);') } if g.pref.os == .ios { g.cheaders.writeln('#define __TARGET_IOS__ 1') @@ -4425,7 +4425,7 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { g.expr(channels[i]) g.write(')') } - g.writeln('}));') + 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 { @@ -4438,7 +4438,7 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { g.write('sync__Direction_pop') } } - g.writeln('}));') + 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 { @@ -4453,7 +4453,7 @@ fn (mut g Gen) select_expr(node ast.SelectExpr) { g.write(tmp_objs[i]) } } - g.writeln('}));') + 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 { @@ -6370,7 +6370,7 @@ fn (mut g Gen) as_cast(node ast.AsCast) { 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")}}));' + 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 @@ -6379,7 +6379,7 @@ fn (g Gen) as_cast_name_table() string { for key, value in g.as_cast_type_names { name_ast.writeln('\t\t, (VCastTypeIndexName){.tindex = $key, .tname = _SLIT("$value")}') } - name_ast.writeln('\t}));') + name_ast.writeln('\t}));\n') return name_ast.str() } diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index 7a1a1271d7..6746e67b87 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -47,6 +47,8 @@ static inline void __sort_ptr(uintptr_t a[], bool b[], int l) } } ' + c_str_fn_defs = '' // NO _STR() test + /* c_str_fn_defs = ' void _STR_PRINT_ARG(const char *fmt, char** refbufp, int *nbytes, int *memsize, int guess, ...) { va_list args; @@ -168,6 +170,7 @@ string _STR_TMP(const char *fmt, ...) { } // endof _STR_TMP ' + */ c_common_macros = ' #define EMPTY_VARG_INITIALIZATION 0 #define EMPTY_STRUCT_DECLARATION @@ -450,8 +453,11 @@ static inline uint64_t wy2u0k(uint64_t r, uint64_t k){ _wymum(&r,&k); return k; ' c_helper_macros = '//============================== HELPER C MACROS =============================*/ //#define tos4(s, slen) ((string){.str=(s), .len=(slen)}) +// _SLIT0 is used as NULL string for literal arguments // `"" s` is used to enforce a string literal argument +#define _SLIT0 (string){.len=0} #define _SLIT(s) ((string){.str=(byteptr)("" s), .len=(sizeof(s)-1), .is_lit=1}) +//#define _SLIT(s) ((string){.str=(byteptr)("" s), .len=(sizeof(s)-1), .is_lit=1}) // take the address of an rvalue #define ADDR(type, expr) (&((type[]){expr}[0])) // copy something to the heap diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 461822661e..5cb1965e8f 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -418,7 +418,7 @@ fn (mut g Gen) comp_for(node ast.CompFor) { 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(', ') + '}));') + attrs.join(', ') + '}));\n') } if method.params.len < 2 { // 0 or 1 (the receiver) args @@ -435,7 +435,7 @@ fn (mut g Gen) comp_for(node ast.CompFor) { } g.comptime_var_type_map['${node.val_var}.args[$j].typ'] = typ } - g.writeln('}));') + g.writeln('}));\n') } mut sig := 'anon_fn_' // skip the first (receiver) arg @@ -492,7 +492,7 @@ fn (mut g Gen) comp_for(node ast.CompFor) { 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(', ') + '}));') + attrs.join(', ') + '}));\n') } // field_sym := g.table.get_type_symbol(field.typ) // g.writeln('\t${node.val_var}.typ = _SLIT("$field_sym.name");') diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index b6a36680c1..cbdd41fed9 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -176,14 +176,14 @@ fn (mut g Gen) gen_struct_enc_dec(type_info ast.TypeInfo, styp string, mut enc s } } if field_sym.kind == .enum_ { - enc.writeln('\tcJSON_AddItemToObject(o, "$name", json__encode_u64(val.${c_name(field.name)}));') + 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)}));') + enc.writeln('\tcJSON_AddItemToObject(o, "$name", ${enc_name}(val.${c_name(field.name)}));\n') } } } diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index 5e383b566a..fd9b73b363 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -368,7 +368,7 @@ fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_ } } if node.is_array { - g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]){ $tmp }));') + g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]){ $tmp }));\n') } g.writeln('}') g.writeln('sqlite3_finalize($g.sql_stmt_name);') @@ -678,7 +678,7 @@ fn (mut g Gen) mysql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sq } } if node.is_array { - g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]) { $tmp }));') + g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]) { $tmp }));\n') g.writeln('\t $fields = mysql_fetch_row($res);') g.writeln('}') } @@ -699,7 +699,9 @@ fn (mut g Gen) mysql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') g.expr(db_expr) g.writeln(', _SLIT("$create_string"));') - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') } fn (mut g Gen) mysql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { @@ -710,7 +712,9 @@ fn (mut g Gen) mysql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.E g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') g.expr(db_expr) g.writeln(', _SLIT("$drop_string"));') - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') } fn (mut g Gen) mysql_bind(val string, typ ast.Type) { @@ -857,7 +861,10 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { res := g.new_tmp_var() g.writeln('Option_pg__Row $res = pg__DB_exec_one($db_name, _SLIT("SELECT LASTVAL();"));') - g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT0, $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln($tmp_str); }') + g.sql_buf = strings.new_builder(100) g.sql_bind('string_int((*(string*)array_get((*(pg__Row*)${res}.data).vals, 0)))', '', ast.int_type, typ) @@ -897,7 +904,10 @@ fn (mut g Gen) psql_stmt(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { if arr_stmt.len > 0 { res := g.new_tmp_var() g.writeln('Option_pg__Row $res = pg__DB_exec_one($db_name, _SLIT("SELECT LASTVAL();"));') - g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT0, $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln($tmp_str); }') + id_name := g.new_tmp_var() g.writeln('int $id_name = string_int((*(string*)array_get((*(pg__Row*)${res}.data).vals, 0)));') g.sql_arr_stmt(arr_stmt, arr_fkeys, arr_field_name, id_name, db_expr) @@ -928,7 +938,9 @@ fn (mut g Gen) psql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sql res := g.new_tmp_var() g.writeln('Option_Array_pg__Row $res = pg__DB_exec($db_name, $g.sql_stmt_name);') - g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln($tmp_str); }') rows := g.new_tmp_var() @@ -1025,7 +1037,7 @@ fn (mut g Gen) psql_select_expr(node ast.SqlExpr, sub bool, line string, typ Sql } } if node.is_array { - g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]) { $tmp }));') + g.writeln('\t array_push((array*)&${tmp}_array, _MOV(($elem_type_str[]) { $tmp }));\n') g.writeln('}') } g.writeln('string_free(&$g.sql_stmt_name);') @@ -1044,7 +1056,9 @@ fn (mut g Gen) psql_create_table(node ast.SqlStmtLine, typ SqlType, db_expr ast. g.write('Option_Array_pg__Row $tmp = pg__DB_exec(') g.expr(db_expr) g.writeln(', _SLIT("$create_string"));') - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') } fn (mut g Gen) psql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Expr) { @@ -1056,7 +1070,9 @@ fn (mut g Gen) psql_drop_table(node ast.SqlStmtLine, typ SqlType, db_expr ast.Ex g.write('Option_Array_pg__Row $tmp = pg__DB_exec(') g.expr(db_expr) g.writeln(', _SLIT("$drop_string"));') - g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') + + tmp_str := 'str_intp(1, _MOV((StrIntpData[]){_SLIT("Something went wrong: "), $si_s_code ,{.d_s=IError_str(err)}}))' + g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln($tmp_str); }') } fn (mut g Gen) psql_get_table_type(typ ast.Type) string { diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index 0a8b0976e5..7d646800b3 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -74,6 +74,7 @@ fn (mut g Gen) string_inter_literal_sb_optimized(call_expr ast.CallExpr) { return } +/* fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { g.write('_STR("') // Build the string with % @@ -205,6 +206,7 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { } g.write(')') } +*/ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { is_shared := etype.has_flag(.shared_f) @@ -255,7 +257,8 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { 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("&%.*s\\000", 2, ') + g.write('str_intp(1, _MOV((StrIntpData[]){_SLIT("&"), $si_s_code ,{.d_s=') + // g.write('_STR("&%.*s\\000", 2, ') } g.write('${str_fn_name}(') if str_method_expects_ptr && !is_ptr { @@ -275,7 +278,8 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { } g.write(')') if is_ptr && !is_var_mut { - g.write(')') + g.write('}}))') + // g.write(')') } } else { str_fn_name := g.gen_str_for_type(typ) diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v new file mode 100644 index 0000000000..d8b720ba80 --- /dev/null +++ b/vlib/v/gen/c/str_intp.v @@ -0,0 +1,239 @@ +/* +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 +import strings + +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], pad_ch, base, upper_case) + // + return res, fmt_type.str() +} + +fn (mut g Gen) str_val(node ast.StringInterLiteral, i int) string { + tmp_out := g.out + g.out = strings.new_builder(8) + + 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) + } + + /* + if node.fmts[i] in [`s`, `S`] && node.fwidths[i] != 0 { + g.write(', ${node.fwidths[i]}') + } + if i < node.exprs.len - 1 { + g.write(', ') + } + */ + + tmp_res := g.out.str() + g.out = tmp_out + + return tmp_res +} + +fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { + // fn (mut g Gen) str_int2(node ast.StringInterLiteral) { + mut data := []StrIntpCgenData{} + for i, val in node.vals { + // mut escaped_val := val.replace_each(['%', '%%']) + // escaped_val = util.smart_quote(escaped_val, false) + escaped_val := util.smart_quote(val, false) + + if i >= node.exprs.len { + // last part of the string without data, manage it with .no_str + data << StrIntpCgenData{ + str: escaped_val + fmt: '0' // no_str + d: '{ .d_c = 0 }' + } + break + } + + ft_u64, ft_str := g.str_format(node, i) + ft_data := g.str_val(node, i) + + // for pointers we need a void* cast + if unsafe { ft_str.str[0] } == `p` { + data << StrIntpCgenData{ + str: escaped_val + fmt: '0x' + ft_u64.hex() + d: '{.d_$ft_str = (void*)(${ft_data.trim(' ,')})}' + } + continue + } + data << StrIntpCgenData{ + str: escaped_val + fmt: '0x' + ft_u64.hex() + d: '{.d_$ft_str = ${ft_data.trim(' ,')}}' + } + } + + // write struct + g.write(' str_intp($data.len, ') + g.write('_MOV((StrIntpData[]){') + for i, item in data { + if item.str.len > 0 { + g.write('{_SLIT("$item.str"), $item.fmt, $item.d}') + } else { + g.write('{_SLIT0, $item.fmt, $item.d}') + } + + if i < (data.len - 1) { + g.write(', ') + } + } + g.write('})) ') +} diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index de8baa1164..11f82df8c2 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -16,6 +16,8 @@ pub fn mark_used(mut table ast.Table, pref &pref.Preferences, ast_files []&ast.F mut all_fn_root_names := [ 'main.main', '__new_array', + 'str_intp', + 'format_sb', '__new_array_with_default', '__new_array_with_array_default', 'v_realloc' /* needed for _STR */, diff --git a/vlib/v/tests/string_interpolation_test.v b/vlib/v/tests/string_interpolation_test.v index fc399d8e58..88946f8570 100644 --- a/vlib/v/tests/string_interpolation_test.v +++ b/vlib/v/tests/string_interpolation_test.v @@ -86,19 +86,19 @@ fn test_interpolation_string_prefix_expr() { } fn test_inttypes_string_interpolation() { - c := i8(-103) - uc := byte(217) - uc2 := byte(13) - s := i16(-23456) - us := u16(54321) - i := -1622999040 - ui := u32(3421958087) + c := i8(-103) // -0x67 + uc := byte(217) // 0xD9 + uc2 := byte(13) // 0x0D + s := i16(-23456) // -0x5BA0 + us := u16(54321) // 0xD431 + i := -1622999040 // -0x60BD 0000 + ui := u32(3421958087) // 0xCBF6 EFC7 vp := voidptr(ui) mut bp := byteptr(0) $if x64 { - bp = byteptr(15541149836) + bp = byteptr(15541149836) // 0x3 9E53 208C } $else { - bp = byteptr(3541149836) + bp = byteptr(3541149836) // 0xD311 A88C } l := i64(-7694555558525237396) ul := u64(17234006112912956370) @@ -108,10 +108,10 @@ fn test_inttypes_string_interpolation() { assert '>${s:11}:${us:-13}<' == '> -23456:54321 <' assert '0x${ul:-19x}:${l:22d}' == '0xef2b7d4001165bd2 : -7694555558525237396' assert '${c:5}${uc:-7}x' == ' -103217 x' - assert '${c:x}:${uc:x}:${uc2:02X}' == '99:d9:0D' - assert '${s:X}:${us:x}:${u16(uc):04x}' == 'A460:d431:00d9' - assert '${i:x}:${ui:X}:${int(s):x}' == '9f430000:CBF6EFC7:ffffa460' - assert '${l:x}:${ul:X}' == '9537727cad98876c:EF2B7D4001165BD2' + assert '${c:x}:${uc:x}:${uc2:02X}' == '-67:d9:0D' + assert '${s:X}:${us:x}:${u16(uc):04x}' == '-5BA0:d431:00d9' + assert '${i:x}:${ui:X}:${int(s):x}' == '-60bd0000:CBF6EFC7:-5ba0' + assert '${l:x}:${ul:X}' == '-6ac88d8352677894:EF2B7D4001165BD2' // default pointer format is platform dependent, so try a few eprintln("platform pointer format: '${vp:p}:$bp'") $if x64 {