ChibiOS/HAL  7.2.0
chscanf.c
Go to the documentation of this file.
1 /*
2  ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio
3 
4  Licensed under the Apache License, Version 2.0 (the "License");
5  you may not use this file except in compliance with the License.
6  You may obtain a copy of the License at
7 
8  http://www.apache.org/licenses/LICENSE-2.0
9 
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15 */
16 
17 /*
18  This file was contributed by Alex Lewontin.
19  */
20 
21 /**
22  * @file chscanf.c
23  * @brief Mini scanf-like functionality.
24  *
25  * @addtogroup HAL_CHSCANF
26  * @details Mini scanf-like functionality.
27  * @{
28  */
29 
30 #include <ctype.h>
31 
32 #include "hal.h"
33 #include "chscanf.h"
34 #include "memstreams.h"
35 
36 static long sym_to_val(char sym, int base)
37 {
38  sym = tolower(sym);
39  if (sym <= '7' && sym >= '0') {
40  return sym - '0';
41  }
42  switch (base) {
43  case 16:
44  if (sym <= 'f' && sym >= 'a') {
45  return (sym - 'a' + 0xa);
46  }
47  /* fallthrough */
48  case 10:
49  if (sym == '8') {
50  return 8;
51  }
52  if (sym == '9') {
53  return 9;
54  }
55  /* fallthrough */
56  default:
57  return -1;
58  }
59 }
60 
61 #if CHSCANF_USE_FLOAT
62 
63 /* Custom mixed-type power function. The internal promotion of the result to a double
64  allows for a greater dynamic range than integral types. This function is mostly for
65  simplicity, to allow us to do floating point math without either requiring any
66  libc linkages, or actually having to write floating point algorithms ourselves */
67 static inline double ch_mpow(double x, unsigned long y)
68 {
69  double res = 1;
70 
71  do {
72  if (y & 1) {
73  res *= x;
74  }
75  x *= x;
76  } while (y >>= 1);
77 
78  return res;
79 }
80 
81 #endif
82 
83 /**
84  * @brief System formatted input function.
85  * @details This function implements a minimal @p vscanf()-like functionality
86  * with input on a @p BaseSequentialStream.
87  * The general parameters format is: %[*][width][l|L]p
88  * The following parameter types (p) are supported:
89  * - <b>x</b> hexadecimal integer.
90  * - <b>X</b> hexadecimal long.
91  * - <b>o</b> octal integer.
92  * - <b>O</b> octal long.
93  * - <b>d</b> decimal signed integer.
94  * - <b>D</b> decimal signed long.
95  * - <b>u</b> decimal unsigned integer.
96  * - <b>U</b> decimal unsigned long.
97  * - <b>c</b> character.
98  * - <b>s</b> string.
99  * .
100  *
101  * @param[in] chp pointer to a @p BufferedStream implementing object
102  * @param[in] fmt formatting string
103  * @param[in] ap list of parameters
104  * @return The number parameters in ap that have been successfully
105  * filled. This does not conform to the standard in that if
106  * a failure (either matching or input) occurs before any
107  * parameters are assigned, the function will return 0.
108  *
109  * @api
110  */
111 int chvscanf(BaseBufferedStream *chp, const char *fmt, va_list ap)
112 {
113  char f;
114  msg_t c;
115  int width, base, i;
116  int n = 0;
117  void* buf;
118  bool is_long, is_signed, is_positive;
119  long vall, digit;
120 #if CHSCANF_USE_FLOAT
121  long exp;
122  double valf;
123  char exp_char;
124  int exp_base;
125  bool exp_is_positive, initial_digit;
126  char* match;
127  int fixed_point;
128 #endif
129 
130  /* Peek the first character of the format string. If it is null,
131  we don't even need to take any input, just return 0 */
132  f = *fmt++;
133  if (f == 0) {
134  return n;
135  }
136 
137  /* Otherwise, get the first character from the input stream before we loop for the first time
138  (no peek function for the stream means an extra character is taken out every iteration of the
139  loop, so each loop iteration uses the value of c from the last one. However, the first iteration
140  has no value to work with, so we initialize it here) */
141  c = streamGet(chp);
142 
143  while (c != STM_RESET && f != 0) {
144 
145  /* There are 3 options for f:
146  - whitespace (take and discard as much contiguous whitespace as possible)
147  - a non-whitespace, non-control sequence character (must 1:1 match)
148  - a %, which indicates the beginning of a control sequence
149  */
150 
151  if (isspace(f)) {
152  while (isspace(c)) {
153  c = streamGet(chp);
154  }
155  f = *fmt++;
156  continue;
157  }
158 
159  if (f != '%') {
160  if (f != c) {
161  break;
162  } else {
163  c = streamGet(chp);
164  f = *fmt++;
165  continue;
166  }
167  }
168 
169  /* So we have a formatting token... probably */
170  f = *fmt++;
171  /* Special case: a %% is equivalent to a '%' literal */
172  if (f == '%') {
173  if (f != c) {
174  break;
175  } else {
176  c = streamGet(chp);
177  f = *fmt++;
178  continue;
179  }
180  }
181 
182  if (f == '*') {
183  buf = NULL;
184  f = *fmt++;
185  } else {
186  buf = va_arg(ap, void*);
187  }
188 
189  /* Parse the optional width specifier */
190  width = 0;
191  while (isdigit(f)) {
192  width = (width * 10) + (f - '0');
193  f = *fmt++;
194  }
195 
196  if (!width) {
197  width = -1;
198  }
199 
200  /* Parse the optional length specifier */
201  if (f == 'l' || f == 'L') {
202  is_long = true;
203  f = *fmt++;
204  } else {
205  is_long = isupper(f);
206  }
207 
208  is_positive = true;
209  is_signed = true;
210  base = 10;
211 
212  switch (f) {
213 
214  case 'c':
215  /* Not supporting wchar_t, is_long is just ignored */
216  if (width == 0) {
217  width = 1;
218  }
219  for (i = 0; i < width; ++i) {
220  if (buf) {
221  ((char*)buf)[i] = c;
222  }
223  c = streamGet(chp);
224  if (c == STM_RESET) {
225  return n;
226  }
227  }
228  ++n;
229  f = *fmt++;
230  continue;
231 
232  case 's':
233  /* S specifier discards leading whitespace */
234  while (isspace(c)) {
235  c = streamGet(chp);
236  if (c == STM_RESET) {
237  return n;
238  }
239  }
240  /* Not supporting wchar_t, is_long is just ignored */
241  if (width == 0) {
242  width = -1;
243  }
244  for (i = 0; i < width; ++i) {
245 
246  if (isspace(c)) {
247  if (buf) {
248  ((char*)buf)[i] = 0;
249  }
250 
251  break;
252  }
253 
254  if (buf) {
255  ((char*)buf)[i] = c;
256  }
257  c = streamGet(chp);
258  if (c == STM_RESET) {
259  return n;
260  }
261  }
262 
263  if (width != -1) {
264  if (buf) {
265  ((char*)buf)[width] = 0;
266  }
267  }
268  ++n;
269  f = *fmt++;
270  continue;
271 
272 #if CHSCANF_USE_FLOAT
273  case 'f':
274  valf = -1;
275  exp_char = 'e';
276  exp_base = 10;
277  fixed_point = 0;
278  initial_digit = false;
279  while (isspace(c)) {
280  c = streamGet(chp);
281  }
282 
283  if (c == '+') {
284  if (--width == 0) {
285  return n;
286  }
287  c = streamGet(chp);
288 
289  } else if (c == '-') {
290  if (--width == 0) {
291  return n;
292  }
293  is_positive = false;
294  c = streamGet(chp);
295  }
296 
297  /* Special cases: a float can be INF(INITY) or NAN. As a note about this behavior:
298  this consumes "the longest sequence of input characters which does not exceed any
299  specified field width and which is, or is a prefix of, a matching input sequence" (from
300  the C99 standard). Therefore, if a '%f' format token gets the input 'INFINITxyx',
301  it will consume the 'INFINIT', leaving 'xyz' in the stream. Similarly, if it gets
302  'NAxyz', it will consume the 'NA', leaving 'xyz' in the stream.
303 
304  Given that it seems a little odd to accept a short version and a long version, but not
305  a version in between that contains the short version but isn't long enough to be the
306  long version, This implementation is fairly permissive, and will accept anything from
307  'INF' to 'INFINITY', case insensative, (e.g. 'INF', 'INfiN', 'INFit', or 'infinity')
308  as a valid token meaning INF. It will not, however, accept less than 'INF' or 'NAN' as
309  a valid token (so the above example 'NAxyz' would consume the 'NA', but not recognize it
310  as signifying NaN)
311  */
312 
313  if (tolower(c) == 'n') {
314  c = streamGet(chp);
315 
316  match = "an";
317  while (*match != 0) {
318  if (*match != tolower(c)) {
319  streamUnget(chp, c);
320  return n;
321  }
322  if (--width == 0) {
323  streamUnget(chp, c);
324  return n;
325  }
326  ++match;
327  c = streamGet(chp);
328  }
329 
330  valf = NAN;
331  goto float_common;
332  }
333 
334  if (tolower(c) == 'i') {
335  c = streamGet(chp);
336 
337  match = "nf";
338  while (*match != 0) {
339  if (*match != tolower(c)) {
340  streamUnget(chp, c);
341  return n;
342  }
343  ++match;
344  c = streamGet(chp);
345  if (--width == 0) {
346  streamUnget(chp, c);
347  return n;
348  }
349  }
350 
351  valf = INFINITY;
352 
353  match = "inity";
354  while (*match != 0) {
355  if (*match != tolower(c)) {
356  break;
357  }
358  ++match;
359  if (--width == 0) {
360  break;
361  }
362  c = streamGet(chp);
363  }
364 
365  goto float_common;
366  }
367 
368  if (c == '0') {
369  c = streamGet(chp);
370  if (--width == 0) {
371  valf = 0;
372  goto float_common;
373  }
374 
375  if (c == 'x' || c == 'X') {
376  base = 16;
377  exp_char = 'p';
378  exp_base = 2;
379  c = streamGet(chp);
380  if (--width == 0) {
381  streamUnget(chp, c);
382  return n;
383  }
384  } else {
385  valf = 0;
386  }
387  }
388 
389  if (sym_to_val(c, base) != -1) {
390  valf = 0;
391  }
392 
393  while (width--) {
394  digit = sym_to_val(c, base);
395  if (digit == -1) {
396  break;
397  }
398  valf = (valf * base) + (double)digit;
399  c = streamGet(chp);
400  }
401 
402  if (c == '.') {
403  c = streamGet(chp);
404 
405  while (width--) {
406  digit = sym_to_val(c, base);
407  if (digit == -1) {
408  break;
409  }
410  if (valf == -1) {
411  valf = 0;
412  }
413  valf = (valf * base) + (double)digit;
414  ++fixed_point;
415  c = streamGet(chp);
416  }
417  }
418 
419  if (valf == -1.0) {
420  streamUnget(chp, c);
421  return n;
422  }
423 
424  valf = valf / ch_mpow(base, fixed_point);
425 
426  if (tolower(c) == exp_char) {
427  if (width-- == 0) {
428  return n;
429  }
430  c = streamGet(chp);
431  exp_is_positive = true;
432  exp = 0;
433 
434  if (c == '+') {
435  if (width-- == 0) {
436  return n;
437  }
438  c = streamGet(chp);
439 
440  } else if (c == '-') {
441  if (width-- == 0) {
442  return n;
443  }
444  exp_is_positive = false;
445  c = streamGet(chp);
446  }
447  /*
448  "When parsing an incomplete floating-point value that ends in the exponent with no digits,
449  such as parsing "100er" with the conversion specifier %f, the sequence "100e" (the longest
450  prefix of a possibly valid floating-point number) is consumed, resulting in a matching
451  error (the consumed sequence cannot be converted to a floating-point number), with "r"
452  remaining." (https://en.cppreference.com/w/c/io/fscanf)
453  */
454  digit = sym_to_val(c, 10);
455  if (digit == -1) {
456  streamUnget(chp, c);
457  return n;
458  }
459  while (width--) {
460  /* Even if the significand was hex, the exponent is decimal */
461  digit = sym_to_val(c, 10);
462  if (digit == -1) {
463  break;
464  }
465  exp = (exp * 10) + digit;
466  c = streamGet(chp);
467  }
468  if (exp_is_positive) {
469  valf = valf * (double)ch_mpow(exp_base, exp);
470  } else {
471  valf = valf / (double)ch_mpow(exp_base, exp);
472  }
473  }
474 
475  float_common:
476  if (!is_positive) {
477  valf = -1 * valf;
478  }
479 
480  if (buf) {
481  if (is_long) {
482  *(double*)buf = valf;
483  } else {
484  *(float*)buf = valf;
485  }
486  }
487 
488  ++n;
489  f = *fmt++;
490  continue;
491 #endif
492 
493  case 'i':
494  case 'I':
495  /* I specifier discards leading whitespace */
496  while (isspace(c)) {
497  c = streamGet(chp);
498  }
499  /* The char might be +, might be -, might be 0, or might be something else */
500  if (c == '+') {
501  if (--width == 0) {
502  return n;
503  }
504  c = streamGet(chp);
505  } else if (c == '-') {
506  if (--width == 0) {
507  return n;
508  }
509  is_positive = false;
510  c = streamGet(chp);
511  }
512 
513  if (c == '0') {
514  if (--width == 0) {
515  return ++n;
516  }
517  c = streamGet(chp);
518  if (c == 'x' || c == 'X') {
519  base = 16;
520  if (--width == 0) {
521  return n;
522  }
523  c = streamGet(chp);
524 
525  } else {
526  base = 8;
527  }
528  }
529  break;
530 
531  case 'd':
532  case 'D':
533  while (isspace(c)) {
534  c = streamGet(chp);
535  }
536  if (c == '+') {
537  if (--width == 0) {
538  return n;
539  }
540  c = streamGet(chp);
541 
542  } else if (c == '-') {
543  if (--width == 0) {
544  return n;
545  }
546  is_positive = false;
547  c = streamGet(chp);
548  }
549  break;
550  case 'X':
551  case 'x':
552  case 'P':
553  case 'p':
554  is_signed = false;
555  base = 16;
556  while (isspace(c)) {
557  c = streamGet(chp);
558  }
559  if (c == '+') {
560  if (--width == 0) {
561  return n;
562  }
563  c = streamGet(chp);
564  } else if (c == '-') {
565  if (--width == 0) {
566  return n;
567  }
568  is_positive = false;
569  c = streamGet(chp);
570  }
571  if (c == '0') {
572  if (--width == 0) {
573  return ++n;
574  }
575  c = streamGet(chp);
576  if (c == 'x' || c == 'X') {
577  if (--width == 0) {
578  return n;
579  }
580  c = streamGet(chp);
581  }
582  }
583  break;
584  case 'U':
585  case 'u':
586  is_signed = false;
587  while (isspace(c)) {
588  c = streamGet(chp);
589  }
590  if (c == '+') {
591  if (--width == 0) {
592  return n;
593  }
594  c = streamGet(chp);
595  } else if (c == '-') {
596  if (--width == 0) {
597  return n;
598  }
599  is_positive = false;
600  c = streamGet(chp);
601  }
602  break;
603  case 'O':
604  case 'o':
605  is_signed = false;
606  base = 8;
607  while (isspace(c)) {
608  c = streamGet(chp);
609  }
610 
611  if (c == '+') {
612  if (--width == 0) {
613  return n;
614  }
615  c = streamGet(chp);
616  } else if (c == '-') {
617  if (--width == 0) {
618  return n;
619  }
620  is_positive = false;
621  c = streamGet(chp);
622  }
623 
624  break;
625  default:
626  streamUnget(chp, c);
627  return n;
628  }
629 
630  vall = 0UL;
631 
632  /* If we don't have at least one additional eligible character, it's a matching failure */
633  if (sym_to_val(c, base) == -1) {
634  break;
635  }
636 
637  while (width--) {
638  digit = sym_to_val(c, base);
639  if (digit == -1) {
640  break;
641  }
642  vall = (vall * base) + digit;
643  c = streamGet(chp);
644  }
645 
646  if (!is_positive) {
647  vall = -1 * vall;
648  }
649 
650  if (buf) {
651  if (is_long && is_signed) {
652  *((signed long*)buf) = vall;
653  } else if (is_long && !is_signed) {
654  *((unsigned long*)buf) = vall;
655  } else if (!is_long && is_signed) {
656  *((signed int*)buf) = vall;
657  } else if (!is_long && !is_signed) {
658  *((unsigned int*)buf) = vall;
659  }
660  }
661  f = *fmt++;
662  ++n;
663  }
664  streamUnget(chp, c);
665  return n;
666 }
667 
668 /**
669  * @brief System formatted input function.
670  * @details This function implements a minimal @p scanf() like functionality
671  * with input from a @p BufferedStream.
672  * The general parameters format is: %[*][width][l|L]p
673  * The following parameter types (p) are supported:
674  * - <b>x</b> hexadecimal integer.
675  * - <b>X</b> hexadecimal long.
676  * - <b>o</b> octal integer.
677  * - <b>O</b> octal long.
678  * - <b>d</b> decimal signed integer.
679  * - <b>D</b> decimal signed long.
680  * - <b>u</b> decimal unsigned integer.
681  * - <b>U</b> decimal unsigned long.
682  * - <b>c</b> character.
683  * - <b>s</b> string.
684  * .
685  *
686  * @param[in] chp pointer to a @p BufferedStream implementing object
687  * @param[in] fmt formatting string
688  * @return The number parameters in ap that have been successfully
689  * filled. This does not conform to the standard in that if
690  * a failure (either matching or input) occurs before any
691  * parameters are assigned, the function will return 0.
692  *
693  * @api
694  */
695 int chscanf(BaseBufferedStream *chp, const char *fmt, ...)
696 {
697  va_list ap;
698  int retval;
699 
700  va_start(ap, fmt);
701  retval = chvscanf(chp, fmt, ap);
702  va_end(ap);
703 
704  return retval;
705 }
706 
707 /**
708  * @brief System formatted input function.
709  * @details This function implements a minimal @p snscanf()-like functionality.
710  * The general parameters format is: %[*][width][l|L]p
711  * The following parameter types (p) are supported:
712  * - <b>x</b> hexadecimal integer.
713  * - <b>X</b> hexadecimal long.
714  * - <b>o</b> octal integer.
715  * - <b>O</b> octal long.
716  * - <b>d</b> decimal signed integer.
717  * - <b>D</b> decimal signed long.
718  * - <b>u</b> decimal unsigned integer.
719  * - <b>U</b> decimal unsigned long.
720  * - <b>c</b> character.
721  * - <b>s</b> string.
722  * .
723  *
724  * @param[in] str pointer to a buffer
725  * @param[in] size size of the buffer
726  * @param[in] fmt formatting string
727  * @return The number parameters in ap that have been successfully
728  * filled. This does not conform to the standard in that if
729  * a failure (either matching or input) occurs before any
730  * parameters are assigned, the function will return 0.
731  *
732  * @api
733  */
734 int chsnscanf(char *str, size_t size, const char *fmt, ...)
735 {
736  va_list ap;
737  int retval;
738 
739  /* Performing the scan operation.*/
740  va_start(ap, fmt);
741  retval = chvsnscanf(str, size, fmt, ap);
742  va_end(ap);
743 
744  /* Return number of receiving arguments successfully assigned.*/
745  return retval;
746 }
747 
748 /**
749  * @brief System formatted input function.
750  * @details This function implements a minimal @p vsnscanf()-like functionality.
751  * The general parameters format is: %[*][width][l|L]p
752  * The following parameter types (p) are supported:
753  * - <b>x</b> hexadecimal integer.
754  * - <b>X</b> hexadecimal long.
755  * - <b>o</b> octal integer.
756  * - <b>O</b> octal long.
757  * - <b>d</b> decimal signed integer.
758  * - <b>D</b> decimal signed long.
759  * - <b>u</b> decimal unsigned integer.
760  * - <b>U</b> decimal unsigned long.
761  * - <b>c</b> character.
762  * - <b>s</b> string.
763  * .
764  *
765  * @param[in] str pointer to a buffer
766  * @param[in] size size of the buffer
767  * @param[in] fmt formatting string
768  * @param[in] ap list of parameters
769  * @return The number parameters in ap that have been successfully
770  * filled. This does not conform to the standard in that if
771  * a failure (either matching or input) occurs before any
772  * parameters are assigned, the function will return 0.
773  *
774  * @api
775  */
776 int chvsnscanf(char *str, size_t size, const char *fmt, va_list ap)
777 {
778  MemoryStream ms;
779  size_t size_wo_nul;
780 
781  if (size > 0)
782  size_wo_nul = size - 1;
783  else
784  size_wo_nul = 0;
785 
786  /* Memory stream object to be used as a string writer, reserving one
787  byte for the final zero. */
788  msObjectInit(&ms, (uint8_t*)str, size_wo_nul, 0);
789 
790  /* Performing the scan operation using the common code and
791  return number of receiving arguments successfully assigned. */
792  return chvscanf((BaseBufferedStream *)&ms, fmt, ap);
793 }
794 
795 /** @} */
msObjectInit
void msObjectInit(MemoryStream *msp, uint8_t *buffer, size_t size, size_t eos)
Memory stream object initialization.
Definition: memstreams.c:116
streamUnget
#define streamUnget(ip, b)
Buffered Stream unget.
Definition: hal_streams.h:213
chvscanf
int chvscanf(BaseBufferedStream *chp, const char *fmt, va_list ap)
System formatted input function.
Definition: chscanf.c:111
hal.h
HAL subsystem header.
BaseBufferedStream
Buffered stream class.
Definition: hal_streams.h:186
MemoryStream
Memory stream object.
Definition: memstreams.h:70
chsnscanf
int chsnscanf(char *str, size_t size, const char *fmt,...)
System formatted input function.
Definition: chscanf.c:734
msg_t
int32_t msg_t
Type of a message.
Definition: osal.h:159
streamGet
#define streamGet(ip)
Sequential Stream blocking byte read.
Definition: hal_streams.h:151
chvsnscanf
int chvsnscanf(char *str, size_t size, const char *fmt, va_list ap)
System formatted input function.
Definition: chscanf.c:776
chscanf
int chscanf(BaseBufferedStream *chp, const char *fmt,...)
System formatted input function.
Definition: chscanf.c:695
memstreams.h
Memory streams structures and macros.
chscanf.h
Mini scanf-like functionality.