/******************************************************************************
Welcome to GDB Online.
GDB online is an online compiler and debugger tool for C, C++, Python, PHP, Ruby,
C#, VB, Perl, Swift, Prolog, Javascript, Pascal, HTML, CSS, JS
Code, Compile, Run and Debug online from anywhere in world.
*******************************************************************************/
#include <stdio.h>
//-------------------------------------------//
// //
// Includes //
// //
//-------------------------------------------//
#include <ctype.h> //< isdigit
#include <errno.h> //< For strtol_s
#include <stdarg.h> //< va_list, va_start, va_arg, va_end
#include <stdbool.h> //< bool
#include <stdint.h> //< *int*_t
#include <stdio.h> //< TODO: DELERE
#include <stdlib.h> //< strtol
#include <string.h> //< Strsep
#include <limits.h> //< LONG_MAX
//-------------------------------------------//
// //
// Interface //
// //
//-------------------------------------------//
int16_t
strsepf(char mutStr[], char const* fmt, ...);
int16_t
vstrsepf(char mutStr[], char const* fmt, va_list arg);
long
strtol_s(const char* buff, uint8_t base, int8_t* err);
//-------------------------------------------//
// //
// Implementation //
// //
//-------------------------------------------//
/*
* %d -> The format of the number is the same as expected by strtol() with the value 10 for the base
* argument %s ->
*/
int16_t
strsepf(char mutStr[], char const* fmt, ...)
{
int16_t rc;
va_list arg;
va_start(arg, fmt);
rc = vstrsepf(mutStr, fmt, arg);
va_end(arg);
return rc;
}
/*
* `vstrsepf` is string parsing utility function born from
* the idea of using `strsep` with a `sscanf` interface.
*
* It is designed to be a safer `scanf` for embedded system application.
*
* Some key design choices:
* - Will destroy its input string (by adding '\0').
* - No dynamic memory allocation
* - No string copy, only pointer address are changed.
* - No floating point support.
*
* ARGUMENTS:
* @param: mutStr - mutable input string.
* @param: fmt - format string.
* @param: arg - Aguments lists (va_list)
*
* FORMAT SPECIFIER:
*
* | Specifier | Descriptions |
* |-------------|-----------------------------------------------------------------|
* | %i, %d, %u, | Any number of digits* |
* | %o | Any number of octal digits (0-7)* |
* | %x | Any number of hexadecimal digits (0-9, a-f, A-F* |
* | %% | A % followed by another % matches a single %. |
* | %s | A string with any character in it. A terminating null character |
* | | is automatically added at the end of the stored sequence.# |
*
* FORMAT OPTIONAL SPECIFIER:
*
* | O Specifier | Descriptions |
* |-------------|-----------------------------------------------------------------|
* | * | An optional starting asterisk indicates that the data is to be |
* | | read from the stream but ignored. |
* | width | Specifies the maximum number of characters to be read in the |
* | | current reading operation. |
*
*
*/
int16_t
vstrsepf(char mutStr[], char const* fmt, va_list arg)
{
if (mutStr == NULL || fmt == NULL) {
return -1;
}
#define SUPPORTED_SPECIFIER "dibouxs%"
int count = 0;
while (*mutStr && *fmt) {
// printf("fmt = %c, s=%c\n", *fmt, *mutStr);
if (*fmt == '%') {
fmt++;
// A format specifier follows this prototype: [=%[*][width][modifiers]type=]
char specifierType = '\0';
bool noAssignFlag = false;
size_t width = 0;
for (; *fmt; fmt++) {
if (strchr(SUPPORTED_SPECIFIER, *fmt)) {
specifierType = *fmt;
fmt++;
break; //< Specifier type is always the last element of a specifier string
}
if (*fmt == '*') {
noAssignFlag = true;
} else if (*fmt >= '1' && *fmt <= '9') {
char const* tc;
for (tc = fmt; isdigit(*fmt); fmt++)
;
width = strtol(tc, (char**)&fmt, 10);
fmt--;
}
}
if (specifierType == '%') {
mutStr++; //< `%%` is just the `%` character.
continue;
}
char* token;
const bool continueUntilTheEnd = (*fmt == '\0');
if (continueUntilTheEnd) {
token = mutStr;
} else {
char termination[] = { *fmt, '\0' };
token = strsep(&mutStr, termination);
if (token == NULL) {
return -1; //< no token found
}
fmt++;
}
if (noAssignFlag) {
continue; //< ignore it.
}
if (width > 0) {
if (width < strlen(token)) {
return -1;
}
}
int8_t strtolErr = 0;
switch (specifierType) {
/*********************************
* Scan string
*********************************/
case 's': {
char** ptr = va_arg(arg, char**);
if (ptr == NULL) {
return -1;
}
*ptr = token;
count++;
} break;
/*********************************
* Scan int
*********************************/
case 'd':
/* FALLTHROUGH */
case 'i':
/* FALLTHROUGH */
case 'u': {
uint8_t base = 10;
uint32_t* ptr = va_arg(arg, uint32_t*);
if (ptr == NULL) {
return -1;
}
*ptr = strtol_s(token, base, &strtolErr);
if (strtolErr < 0) {
return -1;
}
count++;
} break;
case 'x': {
uint8_t base = 16;
uint32_t* ptr = va_arg(arg, uint32_t*);
if (ptr == NULL) {
return -1;
}
*ptr = strtol_s(token, base, &strtolErr);
if (strtolErr < 0) {
return -1;
}
count++;
} break;
case 'o': {
uint8_t base = 8;
uint32_t* ptr = va_arg(arg, uint32_t*);
if (ptr == NULL) {
return -1;
}
*ptr = strtol_s(token, base, &strtolErr);
if (strtolErr < 0) {
return -1;
}
count++;
} break;
case 'b': {
uint8_t base = 2;
uint32_t* ptr = va_arg(arg, uint32_t*);
if (ptr == NULL) {
return -1;
}
*ptr = strtol_s(token, base, &strtolErr);
if (strtolErr < 0) {
return -1;
}
count++;
} break;
/*********************************
* Scan default
*********************************/
default: {
return -1;
}
}
} else { /* !(*fmt == '%') */
if (*fmt != *mutStr) {
return -1;
} else {
fmt++;
mutStr++;
}
} // END if (*fmt == '%')
} // END while (*mutStr && *fmt)
return count;
}
long
strtol_s(const char* buff, uint8_t base, int8_t* err)
{
char* end;
errno = 0;
const long sl = strtol(buff, &end, base);
if (end == buff) {
*err = -1; //< not a decimal number
} else if ('\0' != *end) {
*err = -2; //< extra characters at end of input
} else if ((LONG_MIN == sl || LONG_MAX == sl) && ERANGE == errno) {
*err = -3; //< out of range of type long
} else if (sl > INT_MAX) {
*err = -4; //< greater than INT_MAX
} else if (sl < INT_MIN) {
*err = -5; //< less than INT_MIN
} else {
*err = 0; //< ok
}
return sl;
}
int main()
{
char test[] = "192.168.0.13";
char const* const format = "%3d.%3d.%3d.%3d";
uint32_t answer0 = 0;
uint32_t answer1 = 0;
uint32_t answer2 = 0;
uint32_t answer3 = 0;
int16_t n = strsepf(test, format, &answer0, &answer1, &answer2, &answer3);
printf("Parsed %d.\n", n);
return 0;
}