v/vlib/strconv/format.v

860 lines
17 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 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 := 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 := []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++] = `+`
}
} 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.data, 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.data, r_i)
}
}
/*
Single format functions
*/
pub struct BF_param {
pad_ch byte = 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 // flag for print sign as prefix in padding
allign Align_text = .right // alignment of the string
rm_tail_zero bool // 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 := 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 := unsafe {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 unsafe {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 := byte(` `) // 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 {
v_sprintf_panic(p_index, pt.len)
d1 := unsafe {*(&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 {
v_sprintf_panic(p_index, pt.len)
res.write("0x")
res.write(ptr_str(unsafe {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` {
v_sprintf_panic(p_index, pt.len)
len := unsafe {*(&int(pt[p_index]))}
p_index++
v_sprintf_panic(p_index, pt.len)
mut s := unsafe {*(&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` {
v_sprintf_panic(p_index, pt.len)
x := unsafe {*(&i8(pt[p_index]))}
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
} else {
x := unsafe {*(&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` {
v_sprintf_panic(p_index, pt.len)
x := *(&i128(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u128(x) } else { u128(-x) }
} else {
v_sprintf_panic(p_index, pt.len)
x := *(&i64(pt[p_index]))
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
*/
v_sprintf_panic(p_index, pt.len)
x := unsafe {*(&i64(pt[p_index]))}
positive = if x >= 0 { true } else { false }
d1 = if positive { u64(x) } else { u64(-x) }
}
// default int
else {
v_sprintf_panic(p_index, pt.len)
x := unsafe {*(&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
v_sprintf_panic(p_index, pt.len)
match ch1 {
// h for 16 bit unsigned int
// hh fot 8 bit unsigned int
`h` {
if ch2 == `h` {
d1 = u64(unsafe {*(&byte(pt[p_index]))})
} else {
d1 = u64(unsafe {*(&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(unsafe {*(&u64(pt[p_index]))})
}
// default int
else {
d1 = u64(unsafe {*(&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`] {
v_sprintf_panic(p_index, pt.len)
mut s := ""
match ch1 {
// h for 16 bit int
// hh fot 8 bit int
`h` {
if ch2 == `h` {
x := unsafe {*(&i8(pt[p_index]))}
s = x.hex()
} else {
x := unsafe {*(&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 := unsafe {*(&i64(pt[p_index]))}
s = x.hex()
}
else {
x := unsafe {*(&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`] {
v_sprintf_panic(p_index, pt.len)
x := unsafe {*(&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`] {
v_sprintf_panic(p_index, pt.len)
x := unsafe {*(&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`] {
v_sprintf_panic(p_index, pt.len)
x := unsafe {*(&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` {
v_sprintf_panic(p_index, pt.len)
s1 := unsafe{*(&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++
}
if p_index != pt.len {
panic('${p_index} % conversion specifiers, but given ${pt.len} args')
}
return res.str()
}
[inline]
fn v_sprintf_panic( idx, len int) {
if idx >= len {
panic('${idx+1} % conversion specifiers, but given only ${len} args')
}
}
fn fabs(x f64) f64 {
if x < 0.0 {
return -x
}
return x
}