From c5e8ad19dfd6b57396b7781d423195305ce8c6b4 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 16 Oct 2024 14:18:10 +0300 Subject: [PATCH] builtin: implement a `s.hex()` method, allowing shortening `s.bytes().hex()` chains, and reducing the intermediate allocations; add tests for it (#22540) --- vlib/builtin/array.v | 23 +++------------ vlib/builtin/string.v | 28 +++++++++++++++++++ vlib/builtin/string_test.v | 9 ++++++ vlib/encoding/base58/base58_usage_test.v | 6 ++-- vlib/os/file_test.v | 2 -- vlib/os/os_test.c.v | 2 +- vlib/v/compiler_errors_test.v | 4 +-- vlib/v/gen/js/tests/interp.v | 4 +-- .../cstrings_test.c.v | 3 +- .../string_interpolation_test.v | 4 +-- vlib/v/tests/fns/go_call_fn_return_test.v | 4 +-- 11 files changed, 54 insertions(+), 35 deletions(-) diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 056b8300f..6fd5c596b 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -952,27 +952,12 @@ pub fn (a []string) str() string { return res } -// hex returns a string with the hexadecimal representation -// of the byte elements of the array. +// hex returns a string with the hexadecimal representation of the byte elements of the array `b`. pub fn (b []u8) hex() string { - mut hex := unsafe { malloc_noscan(u64(b.len) * 2 + 1) } - mut dst_i := 0 - for i in b { - n0 := i >> 4 - unsafe { - hex[dst_i] = if n0 < 10 { n0 + `0` } else { n0 + u8(87) } - dst_i++ - } - n1 := i & 0xF - unsafe { - hex[dst_i] = if n1 < 10 { n1 + `0` } else { n1 + u8(87) } - dst_i++ - } - } - unsafe { - hex[dst_i] = 0 - return tos(hex, dst_i) + if b.len == 0 { + return '' } + return unsafe { data_to_hex_string(&u8(b.data), b.len) } } // copy copies the `src` byte array elements to the `dst` byte array. diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index c2bf76508..dd090b94b 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -2857,3 +2857,31 @@ pub fn (s string) wrap(config WrapConfig) string { } return sb.str() } + +// hex returns a string with the hexadecimal representation of the bytes of the string `s` +pub fn (s string) hex() string { + if s == '' { + return '' + } + return unsafe { data_to_hex_string(&u8(s.str), s.len) } +} + +@[unsafe] +fn data_to_hex_string(data &u8, len int) string { + mut hex := unsafe { malloc_noscan(u64(len) * 2 + 1) } + mut dst := 0 + for c in 0 .. len { + b := data[c] + n0 := b >> 4 + n1 := b & 0xF + unsafe { + hex[dst] = if n0 < 10 { n0 + `0` } else { n0 + `W` } + hex[dst + 1] = if n1 < 10 { n1 + `0` } else { n1 + `W` } + } + dst += 2 + } + unsafe { + hex[dst] = 0 + return tos(hex, dst) + } +} diff --git a/vlib/builtin/string_test.v b/vlib/builtin/string_test.v index a0c28f07e..cacce108a 100644 --- a/vlib/builtin/string_test.v +++ b/vlib/builtin/string_test.v @@ -1577,3 +1577,12 @@ fn test_string_wrap() { assert 'The V programming language'.wrap(width: 20, end: '|') == 'The V programming|language' assert 'Hello, my name is Carl and I am a delivery'.wrap(width: 20) == 'Hello, my name is\nCarl and I am a\ndelivery' } + +fn test_hex() { + assert 'Hello World!'.hex() == '48656c6c6f20576f726c6421' + assert 'VLANG'.hex() == '564c414e47' + assert 'VLANG'.hex() == 'VLANG'.bytes().hex() + for c in u8(0) .. 255 { + assert c.ascii_str().hex() == [c].hex() + } +} diff --git a/vlib/encoding/base58/base58_usage_test.v b/vlib/encoding/base58/base58_usage_test.v index d7765967a..1f69789c0 100644 --- a/vlib/encoding/base58/base58_usage_test.v +++ b/vlib/encoding/base58/base58_usage_test.v @@ -9,7 +9,7 @@ fn test_encode() { '\x00\x00hello world': '11StV1DL6CwTryKyV' } { output := base58.encode(input) - println('> input: `${input}` | ${input.bytes().hex()} | => output: `${output}`') + println('> input: `${input}` | ${input.hex()} | => output: `${output}`') assert output == expected } } @@ -36,7 +36,7 @@ fn test_decode() { '3vQB7B6MrGQZaxCuFg4oh': hex.decode('68656c6c6f20776f726c64bc62d4b8')!.bytestr() } { input := base58.decode(output)! - println('> output: `${output}` | decoded input: `${input}` | bytes: ${input.bytes().hex()}') - assert input.bytes().hex() == expected.bytes().hex() + println('> output: `${output}` | decoded input: `${input}` | bytes: ${input.hex()}') + assert input.hex() == expected.hex() } } diff --git a/vlib/os/file_test.v b/vlib/os/file_test.v index e5868b507..8240b9230 100644 --- a/vlib/os/file_test.v +++ b/vlib/os/file_test.v @@ -509,8 +509,6 @@ fn test_write_lines() { os.write_lines(wline2_file, lines)! c1 := os.read_file(wline1_file)! c2 := os.read_file(wline2_file)! - // dump(c1.bytes().hex()) - // dump(c2.bytes().hex()) assert c1 == c2 assert c1.split_into_lines() == some_lines_content.split_into_lines() } diff --git a/vlib/os/os_test.c.v b/vlib/os/os_test.c.v index 45cdf0aca..f9009ade9 100644 --- a/vlib/os/os_test.c.v +++ b/vlib/os/os_test.c.v @@ -872,7 +872,7 @@ fn test_execute() { os.rm(print0script) or {} } result := os.execute('${os.quoted_path(@VEXE)} run ${os.quoted_path(print0script)}') - hexresult := result.output.bytes().hex() + hexresult := result.output.hex() // println('exit_code: $result.exit_code') // println('output: |$result.output|') // println('output.len: $result.output.len') diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index dab0bfcdd..654ef4e5f 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -419,8 +419,8 @@ fn diff_content(expected string, found string) { println('expected bytes:\n${chunka(expected.bytes(), 25)}') println(' found bytes:\n${chunka(found.bytes(), 25)}') println('============') - println(' expected hex:\n${chunks(expected.bytes().hex(), 80)}') - println(' found hex:\n${chunks(found.bytes().hex(), 80)}') + println(' expected hex:\n${chunks(expected.hex(), 80)}') + println(' found hex:\n${chunks(found.hex(), 80)}') } println('============\n') } diff --git a/vlib/v/gen/js/tests/interp.v b/vlib/v/gen/js/tests/interp.v index bca83c181..e58ff3a59 100644 --- a/vlib/v/gen/js/tests/interp.v +++ b/vlib/v/gen/js/tests/interp.v @@ -168,8 +168,8 @@ fn string_interpolation_with_negative_format_width_should_compile_and_run_withou i := 3 input := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' println('---------------------------------------------------------------------------------------------') - println('+60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():60} | $input') - println('-60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():-60} | $input') + println('+60 ${i:10} | input.len: ${input.len:10} | ${input.hex():60} | $input') + println('-60 ${i:10} | input.len: ${input.len:10} | ${input.hex():-60} | $input') println('---------------------------------------------------------------------------------------------') println(true) } diff --git a/vlib/v/tests/builtin_strings_and_interpolation/cstrings_test.c.v b/vlib/v/tests/builtin_strings_and_interpolation/cstrings_test.c.v index 6cc9121eb..f81589d8c 100644 --- a/vlib/v/tests/builtin_strings_and_interpolation/cstrings_test.c.v +++ b/vlib/v/tests/builtin_strings_and_interpolation/cstrings_test.c.v @@ -9,6 +9,5 @@ fn test_cstring() { fn test_cstring_with_zeros() { rawbytes := &char(c'\x00username\x00password') s := unsafe { rawbytes.vstring_with_len(18) } - h := s.bytes().hex() - assert h == '00757365726e616d650070617373776f7264' + assert s.hex() == '00757365726e616d650070617373776f7264' } diff --git a/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_test.v b/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_test.v index 1d2df7bf3..4fdea216a 100644 --- a/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_test.v +++ b/vlib/v/tests/builtin_strings_and_interpolation/string_interpolation_test.v @@ -165,8 +165,8 @@ fn test_string_interpolation_with_negative_format_width_should_compile_and_run_w i := 3 input := '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' eprintln('---------------------------------------------------------------------------------------------') - eprintln('+60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():60} | ${input}') - eprintln('-60 ${i:10} | input.len: ${input.len:10} | ${input.bytes().hex():-60} | ${input}') + eprintln('+60 ${i:10} | input.len: ${input.len:10} | ${input.hex():60} | ${input}') + eprintln('-60 ${i:10} | input.len: ${input.len:10} | ${input.hex():-60} | ${input}') eprintln('---------------------------------------------------------------------------------------------') assert true } diff --git a/vlib/v/tests/fns/go_call_fn_return_test.v b/vlib/v/tests/fns/go_call_fn_return_test.v index c5f30e735..e4d7370b6 100644 --- a/vlib/v/tests/fns/go_call_fn_return_test.v +++ b/vlib/v/tests/fns/go_call_fn_return_test.v @@ -1,8 +1,8 @@ const text = 'Hello world' fn test_go_call_fn_return() { - hex_go := spawn text.bytes().hex() - hex := text.bytes().hex() + hex_go := spawn text.hex() + hex := text.hex() assert hex == '48656c6c6f20776f726c64' assert hex_go.wait() == hex -- 2.39.5