Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions include/sys/string_conv.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_SYS_STRING_CONV_H__
#define ZEPHYR_INCLUDE_SYS_STRING_CONV_H__

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief Convert string to long param.
*
* @note On failure the passed value reference will not be altered.
*
* @param str Input string
* @param val Converted value
*
* @return 0 on success.
* @return -EINVAL on invalid string input.
* @return -ERANGE if numeric string input is to large to convert.
*/
int string_conv_str2long(const char *str, long *val);

/**
* @brief Convert string to unsigned long param.
*
* @note On failure the passed value reference will not be altered.
*
* @param str Input string
* @param val Converted value
*
* @return 0 on success.
* @return -EINVAL on invalid string input.
* @return -ERANGE if numeric string input is to large to convert.
*/
int string_conv_str2ulong(const char *str, unsigned long *val);

/**
* @brief Convert string to double param.
*
* @note On failure the passed value reference will not be altered.
*
* @param str Input string
* @param val Converted value
*
* @return 0 on success.
* @return -EINVAL on invalid string input.
* @return -ERANGE if numeric string input is to large to convert.
*/
int string_conv_str2dbl(const char *str, double *val);

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_SYS_STRING_CONV_H__ */
1 change: 1 addition & 0 deletions lib/util/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: Apache-2.0

add_subdirectory_ifdef(CONFIG_FNMATCH fnmatch)
add_subdirectory(string_conv)
6 changes: 6 additions & 0 deletions lib/util/string_conv/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) 2022 Nordic Semiconductor
#
# SPDX-License-Identifier: Apache-2.0
# #

zephyr_sources(string_conv.c)
176 changes: 176 additions & 0 deletions lib/util/string_conv/string_conv.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

static size_t whitespace_trim(char *out, size_t len, const char *str)
{
if (len == 0) {
return 0;
}

const char *end;
size_t out_size;

while (str[0] == ' ') {
str++;
}

if (*str == 0) {
*out = 0;
return 1;
}

end = str + strlen(str) - 1;
while (end > str && (end[0] == ' ')) {
end--;
}
end++;

out_size = (end - str) + 1;

if (out_size > len) {
return 0;
}

memcpy(out, str, out_size - 1);
out[out_size - 1] = 0;

return out_size;
}

int string_conv_str2long(char *str, long *val)
{
char trimmed_buf[12] = { 0 };
size_t len = whitespace_trim(trimmed_buf, sizeof(trimmed_buf), str);

if (len < 2) {
return -EINVAL;
}

long temp_val;
int idx = 0;

if ((trimmed_buf[0] == '-') || (trimmed_buf[0] == '+')) {
idx++;
}

for (int i = idx; i < (len - 1); i++) {
if (!isdigit((int)trimmed_buf[i])) {
return -EINVAL;
}
}

errno = 0;
temp_val = strtol(trimmed_buf, NULL, 10);

if (errno == ERANGE) {
return -ERANGE;
}

*val = temp_val;
return 0;
}

int string_conv_str2ulong(char *str, unsigned long *val)
{
char trimmed_buf[12] = { 0 };
size_t len = whitespace_trim(trimmed_buf, sizeof(trimmed_buf), str);

if (len < 2) {
return -EINVAL;
}

unsigned long temp_val;
int idx = 0;

if (trimmed_buf[0] == '+') {
idx++;
}

for (int i = idx; i < (len - 1); i++) {
if (!isdigit((int)trimmed_buf[i])) {
return -EINVAL;
}
}

errno = 0;
temp_val = strtoul(trimmed_buf, NULL, 10);

if (errno == ERANGE) {
return -ERANGE;
}

*val = temp_val;
return 0;
}

int string_conv_str2dbl(const char *str, double *val)
{
char trimmed_buf[22] = { 0 };
long decimal;
unsigned long frac;
double frac_dbl;
int err = 0;

size_t len = whitespace_trim(trimmed_buf, sizeof(trimmed_buf), str);

if (len < 2) {
return -EINVAL;
}

int comma_idx = strcspn(trimmed_buf, ".");
int frac_len = strlen(trimmed_buf + comma_idx + 1);

/* Covers corner case "." input */
if (strlen(trimmed_buf) < 2 && trimmed_buf[comma_idx] != 0) {
Comment on lines +129 to +132
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why srlen(trimmed_buf) again? isnt line 122 already assigning the len with trimmed_buf len?

return -EINVAL;
}

trimmed_buf[comma_idx] = 0;

/* Avoid fractional overflow by losing one precision point */
if (frac_len > 9) {
trimmed_buf[comma_idx + 10] = 0;
frac_len = 9;
}

/* Avoid doing str2long if decimal part is empty */
if (trimmed_buf[0] == '\0') {
decimal = 0;
} else {
err = string_conv_str2long(trimmed_buf, &decimal);

if (err) {
return err;
}
}

/* Avoid doing str2ulong if fractional part is empty */
if ((trimmed_buf + comma_idx + 1)[0] == '\0') {
frac = 0;
} else {
err = string_conv_str2ulong(trimmed_buf + comma_idx + 1, &frac);

if (err) {
return err;
}
}

frac_dbl = (double)frac;

for (int i = 0; i < frac_len; i++) {
frac_dbl /= 10;
}

*val = (trimmed_buf[0] == '-') ? ((double)decimal - frac_dbl) :
((double)decimal + frac_dbl);

return err;
}
8 changes: 8 additions & 0 deletions tests/lib/string_conv/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(string_conv)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
1 change: 1 addition & 0 deletions tests/lib/string_conv/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_ZTEST=y
Loading