v / vlib / strconv
Raw file | 811 loc (770 sloc) | 15.5 KB | Latest commit hash 017ace6ea
1/*=============================================================================
2Copyright (c) 2019-2023 Dario Deledda. All rights reserved.
3Use of this source code is governed by an MIT license
4that can be found in the LICENSE file.
5
6This file contains string interpolation V functions
7=============================================================================*/
8module strconv
9
10import strings
11
12enum Char_parse_state {
13 start
14 norm_char
15 field_char
16 pad_ch
17 len_set_start
18 len_set_in
19 check_type
20 check_float
21 check_float_in
22 reset_params
23}
24
25// v_printf prints a sprintf-like formated `string` to the terminal.
26// The format string `str` can be constructed at runtime.
27// Note, that this function is unsafe.
28// In most cases, you are better off using V's string interpolation,
29// when your format string is known at compile time.
30[unsafe]
31pub fn v_printf(str string, pt ...voidptr) {
32 print(unsafe { v_sprintf(str, ...pt) })
33}
34
35// v_sprintf returns a sprintf-like formated `string`.
36// The format string `str` can be constructed at runtime.
37// Note, that this function is unsafe.
38// In most cases, you are better off using V's string interpolation,
39// when your format string is known at compile time.
40// Example:
41// ```v
42// x := 3.141516
43// assert strconv.v_sprintf('aaa %G', x) == 'aaa 3.141516'
44// ```
45[direct_array_access; manualfree; unsafe]
46pub fn v_sprintf(str string, pt ...voidptr) string {
47 mut res := strings.new_builder(pt.len * 16)
48 defer {
49 unsafe { res.free() }
50 }
51
52 mut i := 0 // main string index
53 mut p_index := 0 // parameter index
54 mut sign := false // sign flag
55 mut allign := Align_text.right
56 mut len0 := -1 // forced length, if -1 free length
57 mut len1 := -1 // decimal part for floats
58 def_len1 := 6 // default value for len1
59 mut pad_ch := u8(` `) // pad char
60
61 // prefix chars for Length field
62 mut ch1 := `0` // +1 char if present else `0`
63 mut ch2 := `0` // +2 char if present else `0`
64
65 mut status := Char_parse_state.norm_char
66 for i < str.len {
67 if status == .reset_params {
68 sign = false
69 allign = .right
70 len0 = -1
71 len1 = -1
72 pad_ch = ` `
73 status = .norm_char
74 ch1 = `0`
75 ch2 = `0`
76 continue
77 }
78
79 ch := str[i]
80 if ch != `%` && status == .norm_char {
81 res.write_u8(ch)
82 i++
83 continue
84 }
85 if ch == `%` && status == .field_char {
86 status = .norm_char
87 res.write_u8(ch)
88 i++
89 continue
90 }
91 if ch == `%` && status == .norm_char {
92 status = .field_char
93 i++
94 continue
95 }
96
97 // single char, manage it here
98 if ch == `c` && status == .field_char {
99 v_sprintf_panic(p_index, pt.len)
100 d1 := unsafe { *(&u8(pt[p_index])) }
101 res.write_u8(d1)
102 status = .reset_params
103 p_index++
104 i++
105 continue
106 }
107
108 // pointer, manage it here
109 if ch == `p` && status == .field_char {
110 v_sprintf_panic(p_index, pt.len)
111 res.write_string('0x')
112 res.write_string(ptr_str(unsafe { pt[p_index] }))
113 status = .reset_params
114 p_index++
115 i++
116 continue
117 }
118
119 if status == .field_char {
120 mut fc_ch1 := `0`
121 mut fc_ch2 := `0`
122 if (i + 1) < str.len {
123 fc_ch1 = str[i + 1]
124 if (i + 2) < str.len {
125 fc_ch2 = str[i + 2]
126 }
127 }
128 if ch == `+` {
129 sign = true
130 i++
131 continue
132 } else if ch == `-` {
133 allign = .left
134 i++
135 continue
136 } else if ch in [`0`, ` `] {
137 if allign == .right {
138 pad_ch = ch
139 }
140 i++
141 continue
142 } else if ch == `'` {
143 i++
144 continue
145 } else if ch == `.` && fc_ch1 >= `1` && fc_ch1 <= `9` {
146 status = .check_float
147 i++
148 continue
149 }
150 // manage "%.*s" precision field
151 else if ch == `.` && fc_ch1 == `*` && fc_ch2 == `s` {
152 v_sprintf_panic(p_index, pt.len)
153 len := unsafe { *(&int(pt[p_index])) }
154 p_index++
155 v_sprintf_panic(p_index, pt.len)
156 mut s := unsafe { *(&string(pt[p_index])) }
157 s = s[..len]
158 p_index++
159 res.write_string(s)
160 status = .reset_params
161 i += 3
162 continue
163 }
164 status = .len_set_start
165 continue
166 }
167
168 if status == .len_set_start {
169 if ch >= `1` && ch <= `9` {
170 len0 = int(ch - `0`)
171 status = .len_set_in
172 i++
173 continue
174 }
175 if ch == `.` {
176 status = .check_float
177 i++
178 continue
179 }
180 status = .check_type
181 continue
182 }
183
184 if status == .len_set_in {
185 if ch >= `0` && ch <= `9` {
186 len0 *= 10
187 len0 += int(ch - `0`)
188 i++
189 continue
190 }
191 if ch == `.` {
192 status = .check_float
193 i++
194 continue
195 }
196 status = .check_type
197 continue
198 }
199
200 if status == .check_float {
201 if ch >= `0` && ch <= `9` {
202 len1 = int(ch - `0`)
203 status = .check_float_in
204 i++
205 continue
206 }
207 status = .check_type
208 continue
209 }
210
211 if status == .check_float_in {
212 if ch >= `0` && ch <= `9` {
213 len1 *= 10
214 len1 += int(ch - `0`)
215 i++
216 continue
217 }
218 status = .check_type
219 continue
220 }
221
222 if status == .check_type {
223 if ch == `l` {
224 if ch1 == `0` {
225 ch1 = `l`
226 i++
227 continue
228 } else {
229 ch2 = `l`
230 i++
231 continue
232 }
233 } else if ch == `h` {
234 if ch1 == `0` {
235 ch1 = `h`
236 i++
237 continue
238 } else {
239 ch2 = `h`
240 i++
241 continue
242 }
243 }
244 // signed integer
245 else if ch in [`d`, `i`] {
246 mut d1 := u64(0)
247 mut positive := true
248
249 // println("$ch1 $ch2")
250 match ch1 {
251 // h for 16 bit int
252 // hh fot 8 bit int
253 `h` {
254 if ch2 == `h` {
255 v_sprintf_panic(p_index, pt.len)
256 x := unsafe { *(&i8(pt[p_index])) }
257 positive = if x >= 0 { true } else { false }
258 d1 = if positive { u64(x) } else { u64(-x) }
259 } else {
260 x := unsafe { *(&i16(pt[p_index])) }
261 positive = if x >= 0 { true } else { false }
262 d1 = if positive { u64(x) } else { u64(-x) }
263 }
264 }
265 // l i64
266 // ll i64 for now
267 `l` {
268 // placeholder for future 128bit integer code
269 /*
270 if ch2 == `l` {
271 v_sprintf_panic(p_index, pt.len)
272 x := *(&i128(pt[p_index]))
273 positive = if x >= 0 { true } else { false }
274 d1 = if positive { u128(x) } else { u128(-x) }
275 } else {
276 v_sprintf_panic(p_index, pt.len)
277 x := *(&i64(pt[p_index]))
278 positive = if x >= 0 { true } else { false }
279 d1 = if positive { u64(x) } else { u64(-x) }
280 }
281 */
282 v_sprintf_panic(p_index, pt.len)
283 x := unsafe { *(&i64(pt[p_index])) }
284 positive = if x >= 0 { true } else { false }
285 d1 = if positive { u64(x) } else { u64(-x) }
286 }
287 // default int
288 else {
289 v_sprintf_panic(p_index, pt.len)
290 x := unsafe { *(&int(pt[p_index])) }
291 positive = if x >= 0 { true } else { false }
292 d1 = if positive { u64(x) } else { u64(-x) }
293 }
294 }
295 tmp := format_dec_old(d1,
296 pad_ch: pad_ch
297 len0: len0
298 len1: 0
299 positive: positive
300 sign_flag: sign
301 allign: allign
302 )
303 res.write_string(tmp)
304 unsafe { tmp.free() }
305 status = .reset_params
306 p_index++
307 i++
308 ch1 = `0`
309 ch2 = `0`
310 continue
311 }
312 // unsigned integer
313 else if ch == `u` {
314 mut d1 := u64(0)
315 positive := true
316 v_sprintf_panic(p_index, pt.len)
317 match ch1 {
318 // h for 16 bit unsigned int
319 // hh fot 8 bit unsigned int
320 `h` {
321 if ch2 == `h` {
322 d1 = u64(unsafe { *(&u8(pt[p_index])) })
323 } else {
324 d1 = u64(unsafe { *(&u16(pt[p_index])) })
325 }
326 }
327 // l u64
328 // ll u64 for now
329 `l` {
330 // placeholder for future 128bit integer code
331 /*
332 if ch2 == `l` {
333 d1 = u128(*(&u128(pt[p_index])))
334 } else {
335 d1 = u64(*(&u64(pt[p_index])))
336 }
337 */
338 d1 = u64(unsafe { *(&u64(pt[p_index])) })
339 }
340 // default int
341 else {
342 d1 = u64(unsafe { *(&u32(pt[p_index])) })
343 }
344 }
345
346 tmp := format_dec_old(d1,
347 pad_ch: pad_ch
348 len0: len0
349 len1: 0
350 positive: positive
351 sign_flag: sign
352 allign: allign
353 )
354 res.write_string(tmp)
355 unsafe { tmp.free() }
356 status = .reset_params
357 p_index++
358 i++
359 continue
360 }
361 // hex
362 else if ch in [`x`, `X`] {
363 v_sprintf_panic(p_index, pt.len)
364 mut s := ''
365 match ch1 {
366 // h for 16 bit int
367 // hh fot 8 bit int
368 `h` {
369 if ch2 == `h` {
370 x := unsafe { *(&i8(pt[p_index])) }
371 s = x.hex()
372 } else {
373 x := unsafe { *(&i16(pt[p_index])) }
374 s = x.hex()
375 }
376 }
377 // l i64
378 // ll i64 for now
379 `l` {
380 // placeholder for future 128bit integer code
381 /*
382 if ch2 == `l` {
383 x := *(&i128(pt[p_index]))
384 s = x.hex()
385 } else {
386 x := *(&i64(pt[p_index]))
387 s = x.hex()
388 }
389 */
390 x := unsafe { *(&i64(pt[p_index])) }
391 s = x.hex()
392 }
393 else {
394 x := unsafe { *(&int(pt[p_index])) }
395 s = x.hex()
396 }
397 }
398
399 if ch == `X` {
400 tmp := s
401 s = s.to_upper()
402 unsafe { tmp.free() }
403 }
404
405 tmp := format_str(s,
406 pad_ch: pad_ch
407 len0: len0
408 len1: 0
409 positive: true
410 sign_flag: false
411 allign: allign
412 )
413 res.write_string(tmp)
414 unsafe { tmp.free() }
415 unsafe { s.free() }
416 status = .reset_params
417 p_index++
418 i++
419 continue
420 }
421
422 // float and double
423 if ch in [`f`, `F`] {
424 $if !nofloat ? {
425 v_sprintf_panic(p_index, pt.len)
426 x := unsafe { *(&f64(pt[p_index])) }
427 positive := x >= f64(0.0)
428 len1 = if len1 >= 0 { len1 } else { def_len1 }
429 s := format_fl_old(f64(x),
430 pad_ch: pad_ch
431 len0: len0
432 len1: len1
433 positive: positive
434 sign_flag: sign
435 allign: allign
436 )
437 if ch == `F` {
438 tmp := s.to_upper()
439 res.write_string(tmp)
440 unsafe { tmp.free() }
441 } else {
442 res.write_string(s)
443 }
444 unsafe { s.free() }
445 }
446 status = .reset_params
447 p_index++
448 i++
449 continue
450 } else if ch in [`e`, `E`] {
451 $if !nofloat ? {
452 v_sprintf_panic(p_index, pt.len)
453 x := unsafe { *(&f64(pt[p_index])) }
454 positive := x >= f64(0.0)
455 len1 = if len1 >= 0 { len1 } else { def_len1 }
456 s := format_es_old(f64(x),
457 pad_ch: pad_ch
458 len0: len0
459 len1: len1
460 positive: positive
461 sign_flag: sign
462 allign: allign
463 )
464 if ch == `E` {
465 tmp := s.to_upper()
466 res.write_string(tmp)
467 unsafe { tmp.free() }
468 } else {
469 res.write_string(s)
470 }
471 unsafe { s.free() }
472 }
473 status = .reset_params
474 p_index++
475 i++
476 continue
477 } else if ch in [`g`, `G`] {
478 $if !nofloat ? {
479 v_sprintf_panic(p_index, pt.len)
480 x := unsafe { *(&f64(pt[p_index])) }
481 positive := x >= f64(0.0)
482 mut s := ''
483 tx := fabs(x)
484 if tx < 999_999.0 && tx >= 0.00001 {
485 // println("Here g format_fl [$tx]")
486 len1 = if len1 >= 0 { len1 + 1 } else { def_len1 }
487 tmp := s
488 s = format_fl_old(x,
489 pad_ch: pad_ch
490 len0: len0
491 len1: len1
492 positive: positive
493 sign_flag: sign
494 allign: allign
495 rm_tail_zero: true
496 )
497 unsafe { tmp.free() }
498 } else {
499 len1 = if len1 >= 0 { len1 + 1 } else { def_len1 }
500 tmp := s
501 s = format_es_old(x,
502 pad_ch: pad_ch
503 len0: len0
504 len1: len1
505 positive: positive
506 sign_flag: sign
507 allign: allign
508 rm_tail_zero: true
509 )
510 unsafe { tmp.free() }
511 }
512 if ch == `G` {
513 tmp := s.to_upper()
514 res.write_string(tmp)
515 unsafe { tmp.free() }
516 } else {
517 res.write_string(s)
518 }
519 unsafe { s.free() }
520 }
521 status = .reset_params
522 p_index++
523 i++
524 continue
525 }
526 // string
527 else if ch == `s` {
528 v_sprintf_panic(p_index, pt.len)
529 s1 := unsafe { *(&string(pt[p_index])) }
530 pad_ch = ` `
531 tmp := format_str(s1,
532 pad_ch: pad_ch
533 len0: len0
534 len1: 0
535 positive: true
536 sign_flag: false
537 allign: allign
538 )
539 res.write_string(tmp)
540 unsafe { tmp.free() }
541 status = .reset_params
542 p_index++
543 i++
544 continue
545 }
546 }
547
548 status = .reset_params
549 p_index++
550 i++
551 }
552
553 if p_index != pt.len {
554 panic('${p_index} % conversion specifiers, but given ${pt.len} args')
555 }
556
557 return res.str()
558}
559
560[inline]
561fn v_sprintf_panic(idx int, len int) {
562 if idx >= len {
563 panic('${idx + 1} % conversion specifiers, but given only ${len} args')
564 }
565}
566
567fn fabs(x f64) f64 {
568 if x < 0.0 {
569 return -x
570 }
571 return x
572}
573
574// strings.Builder version of format_fl
575[direct_array_access; manualfree]
576pub fn format_fl_old(f f64, p BF_param) string {
577 unsafe {
578 mut s := ''
579 // mut fs := "1.2343"
580 mut fs := f64_to_str_lnd1(if f >= 0.0 { f } else { -f }, p.len1)
581 // println("Dario")
582 // println(fs)
583
584 // error!!
585 if fs[0] == `[` {
586 s.free()
587 return fs
588 }
589
590 if p.rm_tail_zero {
591 tmp := fs
592 fs = remove_tail_zeros_old(fs)
593 tmp.free()
594 }
595 mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len })
596 defer {
597 res.free()
598 }
599
600 mut sign_len_diff := 0
601 if p.pad_ch == `0` {
602 if p.positive {
603 if p.sign_flag {
604 res.write_u8(`+`)
605 sign_len_diff = -1
606 }
607 } else {
608 res.write_u8(`-`)
609 sign_len_diff = -1
610 }
611 tmp := s
612 s = fs.clone()
613 tmp.free()
614 } else {
615 if p.positive {
616 if p.sign_flag {
617 tmp := s
618 s = '+' + fs
619 tmp.free()
620 } else {
621 tmp := s
622 s = fs.clone()
623 tmp.free()
624 }
625 } else {
626 tmp := s
627 s = '-' + fs
628 tmp.free()
629 }
630 }
631
632 dif := p.len0 - s.len + sign_len_diff
633
634 if p.allign == .right {
635 for i1 := 0; i1 < dif; i1++ {
636 res.write_u8(p.pad_ch)
637 }
638 }
639 res.write_string(s)
640 if p.allign == .left {
641 for i1 := 0; i1 < dif; i1++ {
642 res.write_u8(p.pad_ch)
643 }
644 }
645
646 s.free()
647 fs.free()
648 return res.str()
649 }
650}
651
652[manualfree]
653fn format_es_old(f f64, p BF_param) string {
654 unsafe {
655 mut s := ''
656 mut fs := f64_to_str_pad(if f > 0 { f } else { -f }, p.len1)
657 if p.rm_tail_zero {
658 tmp := fs
659 fs = remove_tail_zeros_old(fs)
660 tmp.free()
661 }
662 mut res := strings.new_builder(if p.len0 > fs.len { p.len0 } else { fs.len })
663 defer {
664 res.free()
665 fs.free()
666 s.free()
667 }
668
669 mut sign_len_diff := 0
670 if p.pad_ch == `0` {
671 if p.positive {
672 if p.sign_flag {
673 res.write_u8(`+`)
674 sign_len_diff = -1
675 }
676 } else {
677 res.write_u8(`-`)
678 sign_len_diff = -1
679 }
680 tmp := s
681 s = fs.clone()
682 tmp.free()
683 } else {
684 if p.positive {
685 if p.sign_flag {
686 tmp := s
687 s = '+' + fs
688 tmp.free()
689 } else {
690 tmp := s
691 s = fs.clone()
692 tmp.free()
693 }
694 } else {
695 tmp := s
696 s = '-' + fs
697 tmp.free()
698 }
699 }
700
701 dif := p.len0 - s.len + sign_len_diff
702 if p.allign == .right {
703 for i1 := 0; i1 < dif; i1++ {
704 res.write_u8(p.pad_ch)
705 }
706 }
707 res.write_string(s)
708 if p.allign == .left {
709 for i1 := 0; i1 < dif; i1++ {
710 res.write_u8(p.pad_ch)
711 }
712 }
713 return res.str()
714 }
715}
716
717fn remove_tail_zeros_old(s string) string {
718 mut i := 0
719 mut last_zero_start := -1
720 mut dot_pos := -1
721 mut in_decimal := false
722 mut prev_ch := u8(0)
723 for i < s.len {
724 ch := unsafe { s.str[i] }
725 if ch == `.` {
726 in_decimal = true
727 dot_pos = i
728 } else if in_decimal {
729 if ch == `0` && prev_ch != `0` {
730 last_zero_start = i
731 } else if ch >= `1` && ch <= `9` {
732 last_zero_start = -1
733 } else if ch == `e` {
734 break
735 }
736 }
737 prev_ch = ch
738 i++
739 }
740
741 mut tmp := ''
742 if last_zero_start > 0 {
743 if last_zero_start == dot_pos + 1 {
744 tmp = s[..dot_pos] + s[i..]
745 } else {
746 tmp = s[..last_zero_start] + s[i..]
747 }
748 } else {
749 tmp = s.clone()
750 }
751 if unsafe { tmp.str[tmp.len - 1] } == `.` {
752 return tmp[..tmp.len - 1]
753 }
754 return tmp
755}
756
757// max int64 9223372036854775807
758[manualfree]
759pub fn format_dec_old(d u64, p BF_param) string {
760 mut s := ''
761 mut res := strings.new_builder(20)
762 defer {
763 unsafe { res.free() }
764 unsafe { s.free() }
765 }
766 mut sign_len_diff := 0
767 if p.pad_ch == `0` {
768 if p.positive {
769 if p.sign_flag {
770 res.write_u8(`+`)
771 sign_len_diff = -1
772 }
773 } else {
774 res.write_u8(`-`)
775 sign_len_diff = -1
776 }
777 tmp := s
778 s = d.str()
779 unsafe { tmp.free() }
780 } else {
781 if p.positive {
782 if p.sign_flag {
783 tmp := s
784 s = '+' + d.str()
785 unsafe { tmp.free() }
786 } else {
787 tmp := s
788 s = d.str()
789 unsafe { tmp.free() }
790 }
791 } else {
792 tmp := s
793 s = '-' + d.str()
794 unsafe { tmp.free() }
795 }
796 }
797 dif := p.len0 - s.len + sign_len_diff
798
799 if p.allign == .right {
800 for i1 := 0; i1 < dif; i1++ {
801 res.write_u8(p.pad_ch)
802 }
803 }
804 res.write_string(s)
805 if p.allign == .left {
806 for i1 := 0; i1 < dif; i1++ {
807 res.write_u8(p.pad_ch)
808 }
809 }
810 return res.str()
811}