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