v/vlib/strconv/format.v

837 lines
16 KiB
V

/**********************************************************************
*
* printf/sprintf V implementation
*
* Copyright (c) 2020 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 the printf/sprintf functions
*
**********************************************************************/
module strconv
import strconv.ftoa
import strings
enum Char_parse_state {
start
norm_char
field_char
pad_ch
len_set_start
len_set_in
check_type
check_float
check_float_in
reset_params
}
enum Align_text {
right = 0
left
center
}
/******************************************************************************
*
* Float conversion utility
*
******************************************************************************/
const(
// rounding value
dec_round = [
f64(0.44),
0.044,
0.0044,
0.00044,
0.000044,
0.0000044,
0.00000044,
0.000000044,
0.0000000044,
0.00000000044,
0.000000000044,
0.0000000000044,
0.00000000000044,
0.000000000000044,
0.0000000000000044,
0.00000000000000044,
0.000000000000000044,
0.0000000000000000044,
0.00000000000000000044,
0.000000000000000000044,
]
)
// max float 1.797693134862315708145274237317043567981e+308
pub fn f64_to_str_lnd(f f64, dec_digit int) string {
// we add the rounding value
s := ftoa.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
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++
}
for c in s[i..] {
exp = exp * 10 + int(c-`0`)
}
// allocate exp+32 chars for the return string
//mut res := []byte{len:exp+32,init:`0`}
mut res := [`0`].repeat(exp+32) // TODO: Slow!! is there other possibilities to allocate this?
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++] = `+`
}
} else {
res[r_i++] = `-`
}
i = 0
if exp_sgn >= 0 {
for b[i] != 0 {
res[r_i++] = b[i]
i++
if i >= d_pos && exp >= 0 {
if exp == 0 {
dot_res_sp = r_i
res[r_i++] = `.`
}
exp--
}
}
for exp >= 0 {
res[r_i++] = `0`
exp--
}
//println("exp: $exp $r_i $dot_res_sp")
} else {
mut dot_p := true
for exp > 0 {
res[r_i++] = `0`
exp--
if dot_p {
dot_res_sp = r_i
res[r_i++] = `.`
dot_p = false
}
}
for b[i] != 0 {
res[r_i++] = b[i]
i++
}
}
//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)}]")
return tos(&res[0],r_i)
} else {
if dec_digit > 0 {
mut c := 0
res[r_i++] = `.`
for c < dec_digit {
res[r_i++] = `0`
c++
}
res[r_i] = 0
}
return tos(&res[0],r_i)
}
}
/******************************************************************************
*
* Single format functions
*
******************************************************************************/
struct BF_param {
pad_ch byte = ` ` // padding char
len0 int = -1 // default len for whole the number or string
len1 int = 6 // number of decimal digits, if needed
positive bool = true // mandatory: the sign of the number passed
sign_flag bool = false // flag for print sign as prefix in padding
allign Align_text = .right // alignment of the string
rm_tail_zero bool = false // remove the tail zeros from floats
}
pub fn format_str(s string, p BF_param) string {
dif := p.len0 - s.len
if dif <= 0 {
return s
}
mut res := strings.new_builder(s.len + dif)
if p.allign == .right {
for i1 :=0; i1 < dif; i1++ {
res.write_b(p.pad_ch)
}
}
res.write(s)
if p.allign == .left {
for i1 :=0; i1 < dif; i1++ {
res.write_b(p.pad_ch)
}
}
return res.str()
}
// max int64 9223372036854775807
pub fn format_dec(d u64, p BF_param) string {
mut s := ""
mut res := strings.new_builder(20)
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
}
s = d.str()
} else {
if p.positive {
if p.sign_flag {
s = "+" + d.str()
} else {
s = d.str()
}
} else {
s = "-" + d.str()
}
}
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(s)
if p.allign == .left {
for i1 :=0; i1 < dif; i1++ {
res.write_b(p.pad_ch)
}
}
return res.str()
}
pub fn format_fl(f f64, p BF_param) string {
mut s := ""
mut fs := f64_to_str_lnd(if f >= 0.0 {f} else {-f}, p.len1)
// error!!
if fs[0] == `[` {
return fs
}
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
}
s = fs
} else {
if p.positive {
if p.sign_flag {
s = "+" + fs
} else {
s = fs
}
} else {
s = "-" + fs
}
}
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(s)
if p.allign == .left {
for i1 :=0; i1 < dif; i1++ {
res.write_b(p.pad_ch)
}
}
return res.str()
}
pub fn format_es(f f64, p BF_param) string {
mut s := ""
mut fs := ftoa.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
}
s = fs
} else {
if p.positive {
if p.sign_flag {
s = "+" + fs
} else {
s = fs
}
} else {
s = "-" + fs
}
}
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(s)
if p.allign == .left {
for i1 :=0; i1 < dif; i1++ {
res.write_b(p.pad_ch)
}
}
return res.str()
}
pub fn remove_tail_zeros(s string) string {
mut i := 0
mut last_zero_start := -1
mut dot_pos := -1
mut in_decimal := false
mut prev_ch := byte(0)
for i < s.len {
ch := s.str[i]
if ch == `.` {
in_decimal = true
dot_pos = i
}
else if in_decimal {
if ch == `0` && prev_ch != `0` {
last_zero_start = i
} else if ch >= `1` && ch <= `9` {
last_zero_start = -1
} else if ch == `e` {
break
}
}
prev_ch = ch
i++
}
mut tmp := ""
if last_zero_start > 0 {
if last_zero_start == dot_pos+1 {
tmp = s[..dot_pos] + s [i..]
}else {
tmp = s[..last_zero_start] + s [i..]
}
} else {
tmp = s
}
if tmp.str[tmp.len-1] == `.` {
return tmp[..tmp.len-1]
}
return tmp
}
/******************************************************************************
*
* Main functions
*
******************************************************************************/
pub fn v_printf(str string, pt ... voidptr) {
print(v_sprintf(str, pt))
}
pub fn v_sprintf(str string, pt ... voidptr) string{
mut res := strings.new_builder(pt.len * 16)
mut i := 0 // main strign index
mut p_index := 0 // parameter index
mut sign := false // sign flag
mut allign := Align_text.right
mut len0 := -1 // forced length, if -1 free length
mut len1 := -1 // decimal part for floats
def_len1 := 6 // default value for len1
mut pad_ch := ` ` // pad char
mut th_separator := false // thousands separator flag
// prefix chars for Length field
mut ch1 := `0` // +1 char if present else `0`
mut ch2 := `0` // +2 char if present else `0`
mut status := Char_parse_state.norm_char
for i < str.len {
if status == .reset_params {
sign = false
allign = .right
len0 = -1
len1 = -1
pad_ch = ` `
th_separator = false
status = .norm_char
ch1 = `0`
ch2 = `0`
continue
}
ch := str[i]
if ch != `%` && status == .norm_char {
res.write_b(ch)
i++
continue
}
if ch == `%` && status == .norm_char {
status = .field_char
i++
continue
}
// single char, manage it here
if ch == `c` && status == .field_char {
d1 := *(&byte(pt[p_index]))
res.write_b(d1)
status = .reset_params
p_index++
i++
continue
}
// pointer, manage it here
if ch == `p` && status == .field_char {
res.write("0x"+ptr_str(pt[p_index]))
status = .reset_params
p_index++
i++
continue
}
if status == .field_char {
mut fc_ch1 := `0`
mut fc_ch2 := `0`
if (i + 1) < str.len {
fc_ch1 = str[i+1]
if (i + 2) < str.len {
fc_ch2 = str[i+2]
}
}
if ch == `+` {
sign = true
i++
continue
} else if ch == `-` {
allign = .left
i++
continue
} else if ch in [`0`,` `] {
if allign == .right {
pad_ch = ch
}
i++
continue
} else if ch == `'` {
th_separator = true
i++
continue
} else if ch == `.` && fc_ch1 >= `1` && fc_ch1 <= `9` {
status = .check_float
i++
continue
}
// manage "%.*s" precision field
else if ch == `.` && fc_ch1 == `*` && fc_ch2 == `s` {
len := *(&int(pt[p_index]))
p_index++
mut s := *(&string(pt[p_index]))
s = s[..len]
p_index++
res.write(s)
status = .reset_params
i += 3
continue
}
status = .len_set_start
continue
}
if status == .len_set_start {
if ch >= `1` && ch <= `9` {
len0 = int(ch - `0`)
status = .len_set_in
i++
continue
}
if ch == `.` {
status = .check_float
i++
continue
}
status = .check_type
continue
}
if status == .len_set_in {
if ch >= `0` && ch <= `9` {
len0 *= 10
len0 += int(ch - `0`)
i++
continue
}
if ch == `.` {
status = .check_float
i++
continue
}
status = .check_type
continue
}
if status == .check_float {
if ch >= `0` && ch <= `9` {
len1 = int(ch - `0`)
status = .check_float_in
i++
continue
}
status = .check_type
continue
}
if status == .check_float_in {
if ch >= `0` && ch <= `9` {
len1 *= 10
len1 += int(ch - `0`)
i++
continue
}
status = .check_type
continue
}
if status == .check_type {
if ch == `l` {
if ch1 == `0` {
ch1 = `l`
i++
continue
} else {
ch2 = `l`
i++
continue
}
}
else if ch == `h` {
if ch1 == `0` {
ch1 = `h`
i++
continue
} else {
ch2 = `h`
i++
continue
}
}
// signed integer
else if ch in [`d`,`i`] {
mut d1 := u64(0)
mut positive := true
//println("$ch1 $ch2")
match ch1 {
// h for 16 bit int
// hh fot 8 bit int
`h` {
if ch2 == `h` {
x := *(&i8(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
} else {
x := *(&i16(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
}
// l i64
// ll i64 for now
`l` {
// placeholder for future 128bit integer code
/*
if ch2 == `l` {
x := *(&i128(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u128(x) } else { u128(-x) }
} else {
x := *(&i64(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
*/
x := *(&i64(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
// defualt int
else {
x := *(&int(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
}
res.write(format_dec(d1,{pad_ch: pad_ch, len0: len0, len1: 0, positive: positive, sign_flag: sign, allign: allign}))
status = .reset_params
p_index++
i++
ch1 = `0`
ch2 = `0`
continue
}
// unsigned integer
else if ch == `u` {
mut d1 := u64(0)
positive := true
match ch1 {
// h for 16 bit unsigned int
// hh fot 8 bit unsigned int
`h` {
if ch2 == `h` {
d1 = u64(*(&byte(pt[p_index])))
} else {
d1 = u64(*(&u16(pt[p_index])))
}
}
// l u64
// ll u64 for now
`l` {
// placeholder for future 128bit integer code
/*
if ch2 == `l` {
d1 = u128(*(&u128(pt[p_index])))
} else {
d1 = u64(*(&u64(pt[p_index])))
}
*/
d1 = u64(*(&u64(pt[p_index])))
}
// defualt int
else {
d1 = u64(*(&u32(pt[p_index])))
}
}
res.write(format_dec(d1,{pad_ch: pad_ch, len0: len0, len1: 0, positive: positive, sign_flag: sign, allign: allign}))
status = .reset_params
p_index++
i++
continue
}
// hex
else if ch in [`x`, `X`] {
mut s := ""
match ch1 {
// h for 16 bit int
// hh fot 8 bit int
`h` {
if ch2 == `h` {
x := *(&i8(pt[p_index]))
s = x.hex()
} else {
x := *(&i16(pt[p_index]))
s = x.hex()
}
}
// l i64
// ll i64 for now
`l` {
// placeholder for future 128bit integer code
/*
if ch2 == `l` {
x := *(&i128(pt[p_index]))
s = x.hex()
} else {
x := *(&i64(pt[p_index]))
s = x.hex()
}
*/
x := *(&i64(pt[p_index]))
s = x.hex()
}
else {
x := *(&int(pt[p_index]))
s = x.hex()
}
}
if ch == `X` {
s = s.to_upper()
}
res.write(format_str(s,{pad_ch: pad_ch, len0: len0, len1: 0, positive: true, sign_flag: false, allign: allign}))
status = .reset_params
p_index++
i++
continue
}
// float and double
if ch in [`f`, `F`] {
x := *(&f64(pt[p_index]))
mut positive := x >= f64(0.0)
len1 = if len1 >= 0 { len1 } else { def_len1 }
s := format_fl(f64(x), {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign})
res.write(if ch == `F` {s.to_upper()} else {s})
status = .reset_params
p_index++
i++
continue
}
else if ch in [`e`, `E`] {
x := *(&f64(pt[p_index]))
mut positive := x >= f64(0.0)
len1 = if len1 >= 0 { len1 } else { def_len1 }
s := format_es(f64(x), {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign})
res.write(if ch == `E` {s.to_upper()} else {s})
status = .reset_params
p_index++
i++
continue
}
else if ch in [`g`, `G`] {
x := *(&f64(pt[p_index]))
mut positive := x >= f64(0.0)
mut s := ""
tx := fabs(x)
if tx < 999_999.0 && tx >= 0.00001 {
//println("Here g format_fl [$tx]")
len1 = if len1 >= 0 { len1+1 } else { def_len1 }
s = format_fl(x, {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign, rm_tail_zero: true})
} else {
len1 = if len1 >= 0 { len1+1 } else { def_len1 }
s = format_es(x, {pad_ch: pad_ch, len0: len0, len1: len1, positive: positive, sign_flag: sign, allign: allign, rm_tail_zero: true})
}
res.write(if ch == `G` {s.to_upper()} else {s})
status = .reset_params
p_index++
i++
continue
}
// string
else if ch == `s` {
s1 := *(&string(pt[p_index]))
pad_ch = ` `
res.write(format_str(s1, {pad_ch: pad_ch, len0: len0, len1: 0, positive: true, sign_flag: false, allign: allign}))
status = .reset_params
p_index++
i++
continue
}
}
status = .reset_params
p_index++
i++
}
return res.str()
}
fn fabs(x f64) f64 {
if x < 0.0 {
return -x
}
return x
}