ChibiOS/HAL 9.0.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
36static 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 */
67static 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 */
111int 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 */
695int 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 */
734int 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 */
776int 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/** @} */
Mini scanf-like functionality.
int chscanf(BaseBufferedStream *chp, const char *fmt,...)
System formatted input function.
Definition chscanf.c:695
int chsnscanf(char *str, size_t size, const char *fmt,...)
System formatted input function.
Definition chscanf.c:734
int chvscanf(BaseBufferedStream *chp, const char *fmt, va_list ap)
System formatted input function.
Definition chscanf.c:111
static long sym_to_val(char sym, int base)
Definition chscanf.c:36
int chvsnscanf(char *str, size_t size, const char *fmt, va_list ap)
System formatted input function.
Definition chscanf.c:776
void msObjectInit(MemoryStream *msp, uint8_t *buffer, size_t size, size_t eos)
Memory stream object initialization.
Definition memstreams.c:116
#define STM_RESET
Definition hal_streams.h:51
#define streamGet(ip)
Sequential Stream blocking byte read.
#define streamUnget(ip, b)
Buffered Stream unget.
int32_t msg_t
Type of a message.
Definition osal.h:159
HAL subsystem header.
Memory streams structures and macros.
Buffered stream class.
Memory stream object.
Definition memstreams.h:70