From 10f3c9f1279aff58f5eb1d354df32a960c1b6324 Mon Sep 17 00:00:00 2001 From: l-m <66291634+l1mey112@users.noreply.github.com> Date: Thu, 28 Jul 2022 05:04:39 +1000 Subject: [PATCH] builtin: add string.replace_char and string.normalize_tabs (#15239) --- vlib/builtin/string.v | 69 ++++++++++++++++++++++++++++++++++++++ vlib/builtin/string_test.v | 12 +++++++ 2 files changed, 81 insertions(+) diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index 4990522d30..14a0b399ff 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -495,6 +495,75 @@ pub fn (s string) replace_each(vals []string) string { } } +// replace_char replaces all occurences of the character `rep` multiple occurences of the character passed in `with` with respect to `repeat`. +// Example: assert '\tHello!'.replace_char(`\t`,` `,8) == ' Hello!' +[direct_array_access] +pub fn (s string) replace_char(rep u8, with u8, repeat int) string { + $if !no_bounds_checking ? { + if repeat <= 0 { + panic('string.replace_char(): tab length too short') + } + } + if s.len == 0 { + return s.clone() + } + // TODO Allocating ints is expensive. Should be a stack array + // - string.replace() + mut idxs := []int{cap: s.len} + defer { + unsafe { idxs.free() } + } + // No need to do a contains(), it already traverses the entire string + for i, ch in s { + if ch == rep { // Found char? Mark its location + idxs << i + } + } + if idxs.len == 0 { + return s.clone() + } + // Now we know the number of replacements we need to do and we can calc the len of the new string + new_len := s.len + idxs.len * (repeat - 1) + mut b := unsafe { malloc_noscan(new_len + 1) } // add space for the null byte at the end + // Fill the new string + mut b_i := 0 + mut s_idx := 0 + for rep_pos in idxs { + for i in s_idx .. rep_pos { // copy everything up to piece being replaced + unsafe { + b[b_i] = s[i] + } + b_i++ + } + s_idx = rep_pos + 1 // move string index past replacement + for _ in 0 .. repeat { // copy replacement piece + unsafe { + b[b_i] = with + } + b_i++ + } + } + if s_idx < s.len { // if any original after last replacement, copy it + for i in s_idx .. s.len { + unsafe { + b[b_i] = s[i] + } + b_i++ + } + } + unsafe { + b[new_len] = 0 + return tos(b, new_len) + } +} + +// normalize_tabs replaces all tab characters with `tab_len` amount of spaces +// Example: assert '\t\tpop rax\t; pop rax'.normalize_tabs(2) == ' pop rax ; pop rax' +[inline] +pub fn (s string) normalize_tabs(tab_len int) string { + return s.replace_char(`\t`, ` `, tab_len) +} + // bool returns `true` if the string equals the word "true" it will return `false` otherwise. pub fn (s string) bool() bool { return s == 'true' || s == 't' // TODO t for pg, remove diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index 2a39c189db..68e3d64b59 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -380,6 +380,18 @@ fn test_replace_each() { assert s2.replace_each(['hello_world', 'aaa', 'hello', 'bbb']) == 'aaa bbb' } +fn test_replace_char() { + assert 'azert'.replace_char(`z`, `s`, 2) == 'assert' + assert '\rHello!\r'.replace_char(`\r`, `\n`, 1) == '\nHello!\n' + assert 'Hello!'.replace_char(`l`, `e`, 4) == 'Heeeeeeeeeo!' + assert '1141'.replace_char(`1`, `8`, 2) == '8888488' +} + +fn test_normalize_tabs() { + assert '\t\tHello!'.normalize_tabs(4) == ' Hello!' + assert '\t\tHello!\t; greeting'.normalize_tabs(1) == ' Hello! ; greeting' +} + fn test_itoa() { num := 777 assert num.str() == '777'