Skip to content

Commit 10f3c9f

Browse files
authored
builtin: add string.replace_char and string.normalize_tabs (#15239)
1 parent 60094d9 commit 10f3c9f

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

vlib/builtin/string.v

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,75 @@ pub fn (s string) replace_each(vals []string) string {
495495
}
496496
}
497497

498+
// replace_char replaces all occurences of the character `rep` multiple occurences of the character passed in `with` with respect to `repeat`.
499+
// Example: assert '\tHello!'.replace_char(`\t`,` `,8) == ' Hello!'
500+
[direct_array_access]
501+
pub fn (s string) replace_char(rep u8, with u8, repeat int) string {
502+
$if !no_bounds_checking ? {
503+
if repeat <= 0 {
504+
panic('string.replace_char(): tab length too short')
505+
}
506+
}
507+
if s.len == 0 {
508+
return s.clone()
509+
}
510+
// TODO Allocating ints is expensive. Should be a stack array
511+
// - string.replace()
512+
mut idxs := []int{cap: s.len}
513+
defer {
514+
unsafe { idxs.free() }
515+
}
516+
// No need to do a contains(), it already traverses the entire string
517+
for i, ch in s {
518+
if ch == rep { // Found char? Mark its location
519+
idxs << i
520+
}
521+
}
522+
if idxs.len == 0 {
523+
return s.clone()
524+
}
525+
// Now we know the number of replacements we need to do and we can calc the len of the new string
526+
new_len := s.len + idxs.len * (repeat - 1)
527+
mut b := unsafe { malloc_noscan(new_len + 1) } // add space for the null byte at the end
528+
// Fill the new string
529+
mut b_i := 0
530+
mut s_idx := 0
531+
for rep_pos in idxs {
532+
for i in s_idx .. rep_pos { // copy everything up to piece being replaced
533+
unsafe {
534+
b[b_i] = s[i]
535+
}
536+
b_i++
537+
}
538+
s_idx = rep_pos + 1 // move string index past replacement
539+
for _ in 0 .. repeat { // copy replacement piece
540+
unsafe {
541+
b[b_i] = with
542+
}
543+
b_i++
544+
}
545+
}
546+
if s_idx < s.len { // if any original after last replacement, copy it
547+
for i in s_idx .. s.len {
548+
unsafe {
549+
b[b_i] = s[i]
550+
}
551+
b_i++
552+
}
553+
}
554+
unsafe {
555+
b[new_len] = 0
556+
return tos(b, new_len)
557+
}
558+
}
559+
560+
// normalize_tabs replaces all tab characters with `tab_len` amount of spaces
561+
// Example: assert '\t\tpop rax\t; pop rax'.normalize_tabs(2) == ' pop rax ; pop rax'
562+
[inline]
563+
pub fn (s string) normalize_tabs(tab_len int) string {
564+
return s.replace_char(`\t`, ` `, tab_len)
565+
}
566+
498567
// bool returns `true` if the string equals the word "true" it will return `false` otherwise.
499568
pub fn (s string) bool() bool {
500569
return s == 'true' || s == 't' // TODO t for pg, remove

vlib/builtin/string_test.v

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,18 @@ fn test_replace_each() {
380380
assert s2.replace_each(['hello_world', 'aaa', 'hello', 'bbb']) == 'aaa bbb'
381381
}
382382

383+
fn test_replace_char() {
384+
assert 'azert'.replace_char(`z`, `s`, 2) == 'assert'
385+
assert '\rHello!\r'.replace_char(`\r`, `\n`, 1) == '\nHello!\n'
386+
assert 'Hello!'.replace_char(`l`, `e`, 4) == 'Heeeeeeeeeo!'
387+
assert '1141'.replace_char(`1`, `8`, 2) == '8888488'
388+
}
389+
390+
fn test_normalize_tabs() {
391+
assert '\t\tHello!'.normalize_tabs(4) == ' Hello!'
392+
assert '\t\tHello!\t; greeting'.normalize_tabs(1) == ' Hello! ; greeting'
393+
}
394+
383395
fn test_itoa() {
384396
num := 777
385397
assert num.str() == '777'

0 commit comments

Comments
 (0)