ChibiOS/os/hal/lib/streams/chscanf.c

796 lines
21 KiB
C

/*
ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
This file was contributed by Alex Lewontin.
*/
/**
* @file chscanf.c
* @brief Mini scanf-like functionality.
*
* @addtogroup HAL_CHSCANF
* @details Mini scanf-like functionality.
* @{
*/
#include <ctype.h>
#include "hal.h"
#include "chscanf.h"
#include "memstreams.h"
static long sym_to_val(char sym, int base)
{
sym = tolower(sym);
if (sym <= '7' && sym >= '0') {
return sym - '0';
}
switch (base) {
case 16:
if (sym <= 'f' && sym >= 'a') {
return (sym - 'a' + 0xa);
}
/* fallthrough */
case 10:
if (sym == '8') {
return 8;
}
if (sym == '9') {
return 9;
}
/* fallthrough */
default:
return -1;
}
}
#if CHSCANF_USE_FLOAT
/* Custom mixed-type power function. The internal promotion of the result to a double
allows for a greater dynamic range than integral types. This function is mostly for
simplicity, to allow us to do floating point math without either requiring any
libc linkages, or actually having to write floating point algorithms ourselves */
static inline double ch_mpow(double x, unsigned long y)
{
double res = 1;
do {
if (y & 1) {
res *= x;
}
x *= x;
} while (y >>= 1);
return res;
}
#endif
/**
* @brief System formatted input function.
* @details This function implements a minimal @p vscanf()-like functionality
* with input on a @p BaseSequentialStream.
* The general parameters format is: %[*][width][l|L]p
* The following parameter types (p) are supported:
* - <b>x</b> hexadecimal integer.
* - <b>X</b> hexadecimal long.
* - <b>o</b> octal integer.
* - <b>O</b> octal long.
* - <b>d</b> decimal signed integer.
* - <b>D</b> decimal signed long.
* - <b>u</b> decimal unsigned integer.
* - <b>U</b> decimal unsigned long.
* - <b>c</b> character.
* - <b>s</b> string.
* .
*
* @param[in] chp pointer to a @p BufferedStream implementing object
* @param[in] fmt formatting string
* @param[in] ap list of parameters
* @return The number parameters in ap that have been successfully
* filled. This does not conform to the standard in that if
* a failure (either matching or input) occurs before any
* parameters are assigned, the function will return 0.
*
* @api
*/
int chvscanf(BaseBufferedStream *chp, const char *fmt, va_list ap)
{
char f;
msg_t c;
int width, base, i;
int n = 0;
void* buf;
bool is_long, is_signed, is_positive;
long vall, digit;
#if CHSCANF_USE_FLOAT
long exp;
double valf;
char exp_char;
int exp_base;
bool exp_is_positive, initial_digit;
char* match;
int fixed_point;
#endif
/* Peek the first character of the format string. If it is null,
we don't even need to take any input, just return 0 */
f = *fmt++;
if (f == 0) {
return n;
}
/* Otherwise, get the first character from the input stream before we loop for the first time
(no peek function for the stream means an extra character is taken out every iteration of the
loop, so each loop iteration uses the value of c from the last one. However, the first iteration
has no value to work with, so we initialize it here) */
c = streamGet(chp);
while (c != STM_RESET && f != 0) {
/* There are 3 options for f:
- whitespace (take and discard as much contiguous whitespace as possible)
- a non-whitespace, non-control sequence character (must 1:1 match)
- a %, which indicates the beginning of a control sequence
*/
if (isspace(f)) {
while (isspace(c)) {
c = streamGet(chp);
}
f = *fmt++;
continue;
}
if (f != '%') {
if (f != c) {
break;
} else {
c = streamGet(chp);
f = *fmt++;
continue;
}
}
/* So we have a formatting token... probably */
f = *fmt++;
/* Special case: a %% is equivalent to a '%' literal */
if (f == '%') {
if (f != c) {
break;
} else {
c = streamGet(chp);
f = *fmt++;
continue;
}
}
if (f == '*') {
buf = NULL;
f = *fmt++;
} else {
buf = va_arg(ap, void*);
}
/* Parse the optional width specifier */
width = 0;
while (isdigit(f)) {
width = (width * 10) + (f - '0');
f = *fmt++;
}
if (!width) {
width = -1;
}
/* Parse the optional length specifier */
if (f == 'l' || f == 'L') {
is_long = true;
f = *fmt++;
} else {
is_long = isupper(f);
}
is_positive = true;
is_signed = true;
base = 10;
switch (f) {
case 'c':
/* Not supporting wchar_t, is_long is just ignored */
if (width == 0) {
width = 1;
}
for (i = 0; i < width; ++i) {
if (buf) {
((char*)buf)[i] = c;
}
c = streamGet(chp);
if (c == STM_RESET) {
return n;
}
}
++n;
f = *fmt++;
continue;
case 's':
/* S specifier discards leading whitespace */
while (isspace(c)) {
c = streamGet(chp);
if (c == STM_RESET) {
return n;
}
}
/* Not supporting wchar_t, is_long is just ignored */
if (width == 0) {
width = -1;
}
for (i = 0; i < width; ++i) {
if (isspace(c)) {
if (buf) {
((char*)buf)[i] = 0;
}
break;
}
if (buf) {
((char*)buf)[i] = c;
}
c = streamGet(chp);
if (c == STM_RESET) {
return n;
}
}
if (width != -1) {
if (buf) {
((char*)buf)[width] = 0;
}
}
++n;
f = *fmt++;
continue;
#if CHSCANF_USE_FLOAT
case 'f':
valf = -1;
exp_char = 'e';
exp_base = 10;
fixed_point = 0;
initial_digit = false;
while (isspace(c)) {
c = streamGet(chp);
}
if (c == '+') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (--width == 0) {
return n;
}
is_positive = false;
c = streamGet(chp);
}
/* Special cases: a float can be INF(INITY) or NAN. As a note about this behavior:
this consumes "the longest sequence of input characters which does not exceed any
specified field width and which is, or is a prefix of, a matching input sequence" (from
the C99 standard). Therefore, if a '%f' format token gets the input 'INFINITxyx',
it will consume the 'INFINIT', leaving 'xyz' in the stream. Similarly, if it gets
'NAxyz', it will consume the 'NA', leaving 'xyz' in the stream.
Given that it seems a little odd to accept a short version and a long version, but not
a version in between that contains the short version but isn't long enough to be the
long version, This implementation is fairly permissive, and will accept anything from
'INF' to 'INFINITY', case insensative, (e.g. 'INF', 'INfiN', 'INFit', or 'infinity')
as a valid token meaning INF. It will not, however, accept less than 'INF' or 'NAN' as
a valid token (so the above example 'NAxyz' would consume the 'NA', but not recognize it
as signifying NaN)
*/
if (tolower(c) == 'n') {
c = streamGet(chp);
match = "an";
while (*match != 0) {
if (*match != tolower(c)) {
streamUnget(chp, c);
return n;
}
if (--width == 0) {
streamUnget(chp, c);
return n;
}
++match;
c = streamGet(chp);
}
valf = NAN;
goto float_common;
}
if (tolower(c) == 'i') {
c = streamGet(chp);
match = "nf";
while (*match != 0) {
if (*match != tolower(c)) {
streamUnget(chp, c);
return n;
}
++match;
c = streamGet(chp);
if (--width == 0) {
streamUnget(chp, c);
return n;
}
}
valf = INFINITY;
match = "inity";
while (*match != 0) {
if (*match != tolower(c)) {
break;
}
++match;
if (--width == 0) {
break;
}
c = streamGet(chp);
}
goto float_common;
}
if (c == '0') {
c = streamGet(chp);
if (--width == 0) {
valf = 0;
goto float_common;
}
if (c == 'x' || c == 'X') {
base = 16;
exp_char = 'p';
exp_base = 2;
c = streamGet(chp);
if (--width == 0) {
streamUnget(chp, c);
return n;
}
} else {
valf = 0;
}
}
if (sym_to_val(c, base) != -1) {
valf = 0;
}
while (width--) {
digit = sym_to_val(c, base);
if (digit == -1) {
break;
}
valf = (valf * base) + (double)digit;
c = streamGet(chp);
}
if (c == '.') {
c = streamGet(chp);
while (width--) {
digit = sym_to_val(c, base);
if (digit == -1) {
break;
}
if (valf == -1) {
valf = 0;
}
valf = (valf * base) + (double)digit;
++fixed_point;
c = streamGet(chp);
}
}
if (valf == -1.0) {
streamUnget(chp, c);
return n;
}
valf = valf / ch_mpow(base, fixed_point);
if (tolower(c) == exp_char) {
if (width-- == 0) {
return n;
}
c = streamGet(chp);
exp_is_positive = true;
exp = 0;
if (c == '+') {
if (width-- == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (width-- == 0) {
return n;
}
exp_is_positive = false;
c = streamGet(chp);
}
/*
"When parsing an incomplete floating-point value that ends in the exponent with no digits,
such as parsing "100er" with the conversion specifier %f, the sequence "100e" (the longest
prefix of a possibly valid floating-point number) is consumed, resulting in a matching
error (the consumed sequence cannot be converted to a floating-point number), with "r"
remaining." (https://en.cppreference.com/w/c/io/fscanf)
*/
digit = sym_to_val(c, 10);
if (digit == -1) {
streamUnget(chp, c);
return n;
}
while (width--) {
/* Even if the significand was hex, the exponent is decimal */
digit = sym_to_val(c, 10);
if (digit == -1) {
break;
}
exp = (exp * 10) + digit;
c = streamGet(chp);
}
if (exp_is_positive) {
valf = valf * (double)ch_mpow(exp_base, exp);
} else {
valf = valf / (double)ch_mpow(exp_base, exp);
}
}
float_common:
if (!is_positive) {
valf = -1 * valf;
}
if (buf) {
if (is_long) {
*(double*)buf = valf;
} else {
*(float*)buf = valf;
}
}
++n;
f = *fmt++;
continue;
#endif
case 'i':
case 'I':
/* I specifier discards leading whitespace */
while (isspace(c)) {
c = streamGet(chp);
}
/* The char might be +, might be -, might be 0, or might be something else */
if (c == '+') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (--width == 0) {
return n;
}
is_positive = false;
c = streamGet(chp);
}
if (c == '0') {
if (--width == 0) {
return ++n;
}
c = streamGet(chp);
if (c == 'x' || c == 'X') {
base = 16;
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else {
base = 8;
}
}
break;
case 'd':
case 'D':
while (isspace(c)) {
c = streamGet(chp);
}
if (c == '+') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (--width == 0) {
return n;
}
is_positive = false;
c = streamGet(chp);
}
break;
case 'X':
case 'x':
case 'P':
case 'p':
is_signed = false;
base = 16;
while (isspace(c)) {
c = streamGet(chp);
}
if (c == '+') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (--width == 0) {
return n;
}
is_positive = false;
c = streamGet(chp);
}
if (c == '0') {
if (--width == 0) {
return ++n;
}
c = streamGet(chp);
if (c == 'x' || c == 'X') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
}
}
break;
case 'U':
case 'u':
is_signed = false;
while (isspace(c)) {
c = streamGet(chp);
}
if (c == '+') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (--width == 0) {
return n;
}
is_positive = false;
c = streamGet(chp);
}
break;
case 'O':
case 'o':
is_signed = false;
base = 8;
while (isspace(c)) {
c = streamGet(chp);
}
if (c == '+') {
if (--width == 0) {
return n;
}
c = streamGet(chp);
} else if (c == '-') {
if (--width == 0) {
return n;
}
is_positive = false;
c = streamGet(chp);
}
break;
default:
streamUnget(chp, c);
return n;
}
vall = 0UL;
/* If we don't have at least one additional eligible character, it's a matching failure */
if (sym_to_val(c, base) == -1) {
break;
}
while (width--) {
digit = sym_to_val(c, base);
if (digit == -1) {
break;
}
vall = (vall * base) + digit;
c = streamGet(chp);
}
if (!is_positive) {
vall = -1 * vall;
}
if (buf) {
if (is_long && is_signed) {
*((signed long*)buf) = vall;
} else if (is_long && !is_signed) {
*((unsigned long*)buf) = vall;
} else if (!is_long && is_signed) {
*((signed int*)buf) = vall;
} else if (!is_long && !is_signed) {
*((unsigned int*)buf) = vall;
}
}
f = *fmt++;
++n;
}
streamUnget(chp, c);
return n;
}
/**
* @brief System formatted input function.
* @details This function implements a minimal @p scanf() like functionality
* with input from a @p BufferedStream.
* The general parameters format is: %[*][width][l|L]p
* The following parameter types (p) are supported:
* - <b>x</b> hexadecimal integer.
* - <b>X</b> hexadecimal long.
* - <b>o</b> octal integer.
* - <b>O</b> octal long.
* - <b>d</b> decimal signed integer.
* - <b>D</b> decimal signed long.
* - <b>u</b> decimal unsigned integer.
* - <b>U</b> decimal unsigned long.
* - <b>c</b> character.
* - <b>s</b> string.
* .
*
* @param[in] chp pointer to a @p BufferedStream implementing object
* @param[in] fmt formatting string
* @return The number parameters in ap that have been successfully
* filled. This does not conform to the standard in that if
* a failure (either matching or input) occurs before any
* parameters are assigned, the function will return 0.
*
* @api
*/
int chscanf(BaseBufferedStream *chp, const char *fmt, ...)
{
va_list ap;
int retval;
va_start(ap, fmt);
retval = chvscanf(chp, fmt, ap);
va_end(ap);
return retval;
}
/**
* @brief System formatted input function.
* @details This function implements a minimal @p snscanf()-like functionality.
* The general parameters format is: %[*][width][l|L]p
* The following parameter types (p) are supported:
* - <b>x</b> hexadecimal integer.
* - <b>X</b> hexadecimal long.
* - <b>o</b> octal integer.
* - <b>O</b> octal long.
* - <b>d</b> decimal signed integer.
* - <b>D</b> decimal signed long.
* - <b>u</b> decimal unsigned integer.
* - <b>U</b> decimal unsigned long.
* - <b>c</b> character.
* - <b>s</b> string.
* .
*
* @param[in] str pointer to a buffer
* @param[in] size size of the buffer
* @param[in] fmt formatting string
* @return The number parameters in ap that have been successfully
* filled. This does not conform to the standard in that if
* a failure (either matching or input) occurs before any
* parameters are assigned, the function will return 0.
*
* @api
*/
int chsnscanf(char *str, size_t size, const char *fmt, ...)
{
va_list ap;
int retval;
/* Performing the scan operation.*/
va_start(ap, fmt);
retval = chvsnscanf(str, size, fmt, ap);
va_end(ap);
/* Return number of receiving arguments successfully assigned.*/
return retval;
}
/**
* @brief System formatted input function.
* @details This function implements a minimal @p vsnscanf()-like functionality.
* The general parameters format is: %[*][width][l|L]p
* The following parameter types (p) are supported:
* - <b>x</b> hexadecimal integer.
* - <b>X</b> hexadecimal long.
* - <b>o</b> octal integer.
* - <b>O</b> octal long.
* - <b>d</b> decimal signed integer.
* - <b>D</b> decimal signed long.
* - <b>u</b> decimal unsigned integer.
* - <b>U</b> decimal unsigned long.
* - <b>c</b> character.
* - <b>s</b> string.
* .
*
* @param[in] str pointer to a buffer
* @param[in] size size of the buffer
* @param[in] fmt formatting string
* @param[in] ap list of parameters
* @return The number parameters in ap that have been successfully
* filled. This does not conform to the standard in that if
* a failure (either matching or input) occurs before any
* parameters are assigned, the function will return 0.
*
* @api
*/
int chvsnscanf(char *str, size_t size, const char *fmt, va_list ap)
{
MemoryStream ms;
size_t size_wo_nul;
if (size > 0)
size_wo_nul = size - 1;
else
size_wo_nul = 0;
/* Memory stream object to be used as a string writer, reserving one
byte for the final zero. */
msObjectInit(&ms, (uint8_t*)str, size_wo_nul, 0);
/* Performing the scan operation using the common code and
return number of receiving arguments successfully assigned. */
return chvscanf((BaseBufferedStream *)&ms, fmt, ap);
}
/** @} */