]> git.sur5r.net Git - cc65/blob - libsrc/common/_scanf.c
Rewrote _scanf. It does need some tests and improvements, but it's a more
[cc65] / libsrc / common / _scanf.c
1 /*
2  * _scanf.c
3  *
4  * (C) Copyright 2001-2002 Ullrich von Bassewitz (uz@cc65.org)
5  *
6  * This is the basic layer for all scanf type functions. It should get
7  * rewritten in assembler at some time in the future, so most of the code
8  * is not as elegant as it could be.
9  */
10
11
12
13 #include <stdio.h>
14 #include <stdarg.h>
15 #include <string.h>
16 #include <setjmp.h>
17 #include <ctype.h>
18 #include <limits.h>
19
20 #include "_scanf.h"
21
22
23
24 /*****************************************************************************/
25 /*                            SetJmp return codes                            */
26 /*****************************************************************************/
27
28
29
30 #define RC_OK           0               /* Regular call */
31 #define RC_EOF          1               /* EOF reached */
32 #define RC_NOCONV       2               /* No conversion possible */
33
34
35
36 /*****************************************************************************/
37 /*                                   Data                                    */
38 /*****************************************************************************/
39
40
41
42 static struct scanfdata*  D;            /* Copy of function argument */
43 static va_list            ap;           /* Copy of function argument */
44 static jmp_buf            JumpBuf;      /* Label that is used in case of EOF */
45 static int                C;            /* Character from input */
46 static unsigned           Width;        /* Maximum field width */
47 static long               IntVal;       /* Converted int value */
48 static unsigned           Conversions;  /* Number of conversions */
49 static unsigned char      IntBytes;     /* Number of bytes-1 for int conversions */
50
51 /* Flags */
52 static unsigned char      Positive;     /* Flag for positive value */
53 static unsigned char      NoAssign;     /* Supppress assigment */
54 static unsigned char      Invert;       /* Do we need to invert the charset? */
55 static unsigned char      CharSet[32];  /* 32 * 8 bits = 256 bits */
56 static const unsigned char Bits[8] = {
57     0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
58 };
59
60
61
62 /*****************************************************************************/
63 /*                              Character sets                               */
64 /*****************************************************************************/
65
66
67
68 static void AddCharToSet (unsigned char C)
69 /* Set the given bit in the character set */
70 {
71     asm ("ldy #%o", C);
72     asm ("lda (sp),y");
73     asm ("lsr a");
74     asm ("lsr a");
75     asm ("lsr a");
76     asm ("tax");
77     asm ("lda (sp),y");
78     asm ("and #$07");
79     asm ("tay");
80     asm ("lda %v,y", Bits);
81     asm ("ora %v,x", CharSet);
82     asm ("sta %v,x", CharSet);
83 }
84
85
86
87 static unsigned char IsCharInSet (unsigned char C)
88 /* Check if the given char is part of the character set */
89 {
90     asm ("ldy #%o", C);
91     asm ("lda (sp),y");
92     asm ("lsr a");
93     asm ("lsr a");
94     asm ("lsr a");
95     asm ("tax");
96     asm ("lda (sp),y");
97     asm ("and #$07");
98     asm ("tay");
99     asm ("lda %v,y", Bits);
100     asm ("and %v,x", CharSet);
101     asm ("ldx #$00");
102     return __AX__;
103 }
104
105
106
107 static void InvertCharSet (void)
108 /* Invert the character set */
109 {
110     asm ("ldy #%b", sizeof (CharSet) - 1);
111     asm ("L1:");
112     asm ("lda %v,y", CharSet);
113     asm ("eor #$FF");
114     asm ("sta %v,y", CharSet);
115     asm ("dey");
116     asm ("bpl L1");
117 }
118
119
120
121 /*****************************************************************************/
122 /*                                   Code                                    */
123 /*****************************************************************************/
124
125
126
127 static void ReadChar (void)
128 /* Get an input character, count characters */
129 {
130     C = D->get (D->data);
131     if (C != EOF) {
132         ++D->ccount;
133     }
134 }
135
136
137
138 static void ReadCharWithCheck (void)
139 /* Get an input char, use longjmp in case of EOF */
140 {
141     ReadChar ();
142     if (C == EOF) {
143         longjmp (JumpBuf, RC_EOF);
144     }
145 }
146
147
148
149 static void SkipWhite (void)
150 /* Skip white space in the input and return the first non white character */
151 {
152     while (isspace (C)) {
153         ReadChar ();
154     }
155 }
156
157
158
159 static void ReadSign (void)
160 /* Read an optional sign and skip it. Store 1 in Positive if the value is
161  * positive, store 0 otherwise.
162  */
163 {
164     switch (C) {
165         case '-':
166             ReadChar ();
167             Positive = 0;
168             break;
169         case '+':
170             ReadChar ();
171             /* FALLTHROUGH */
172         default:
173             Positive = 1;
174     }
175 }
176
177
178
179 static unsigned char HexVal (char C)
180 /* Convert a digit to a value */
181 {
182
183     if (isdigit (C)) {
184         return C - '0';
185     } else {
186         return toupper (C) - ('A' - 10);
187     }
188 }
189
190
191
192 static void AssignInt (void)
193 /* Assign the integer value in Val to the next argument. The function makes
194  * several non portable assumptions to reduce code size:
195  *   - int and unsigned types have the same representation
196  *   - short and int have the same representation.
197  *   - all pointer types have the same representation.
198  */
199 {
200     if (!NoAssign) {
201
202         /* Get the next argument pointer */
203         __AX__ = (unsigned) va_arg (ap, void*);
204
205         /* Store the argument pointer into ptr1 */
206         asm ("sta ptr1");
207         asm ("stx ptr1+1");
208
209         /* Get the number of bytes-1 to copy */
210         asm ("ldy %v", IntBytes);
211
212         /* Assign the integer value */
213         asm ("L1: lda %v,y", IntVal);
214         asm ("sta (ptr1),y");
215         asm ("dey");
216         asm ("bpl L1");
217
218     }
219 }
220
221
222
223 static unsigned char ReadInt (unsigned char Base)
224 /* Read an integer and store it into IntVal. Returns the number of chars
225  * converted. Does NOT bump Conversions.
226  */
227 {
228     unsigned char Val;
229     unsigned char CharCount = 0;
230
231     /* Read the integer value */
232     IntVal = 0;
233     while (isxdigit (C) && Width-- > 0 && (Val = HexVal (C)) < Base) {
234         ++CharCount;
235         IntVal = IntVal * Base + Val;
236         ReadChar ();
237     }
238
239     /* If we didn't convert anything, it's an error */
240     if (CharCount == 0) {
241         longjmp (JumpBuf, RC_NOCONV);
242     }
243
244     /* Return the number of characters converted */
245     return CharCount;
246 }
247
248
249
250 static void ScanInt (unsigned char Base)
251 /* Scan an integer including white space, sign and optional base spec,
252  * and store it into IntVal.
253  */
254 {
255     /* Skip whitespace */
256     SkipWhite ();
257
258     /* Read an optional sign */
259     ReadSign ();
260
261     /* If Base is unknown (zero), figure it out */
262     if (Base == 0) {
263         if (C == '0') {
264             ReadChar ();
265             switch (C) {
266                 case 'x':
267                 case 'X':
268                     Base = 16;
269                     ReadChar ();
270                     break;
271                 default:
272                     Base = 8;
273             }
274         } else {
275             Base = 10;
276         }
277     }
278
279     /* Read the integer value */
280     ReadInt (Base);
281
282     /* Apply the sign */
283     if (!Positive) {
284         IntVal = -IntVal;
285     }
286
287     /* Assign the value to the next argument unless suppressed */
288     AssignInt ();
289
290     /* One more conversion */
291     ++Conversions;
292 }
293
294
295
296 int _scanf (struct scanfdata* D_, register const char* format, va_list ap_)
297 /* This is the routine used to do the actual work. It is called from several
298  * types of wrappers to implement the actual ISO xxscanf functions.
299  */
300 {
301     char          F;            /* Character from format string */
302     unsigned char Result;       /* setjmp result */
303     char*         S;
304     unsigned char HaveWidth;    /* True if a width was given */
305     char          Start;        /* Start of range */
306
307     /* Place copies of the arguments into global variables. This is not very
308      * nice, but on a 6502 platform it gives better code, since the values
309      * do not have to be passed as parameters.
310      */
311     D   = D_;
312     ap  = ap_;
313
314     /* Initialize variables */
315     Conversions = 0;
316     D->ccount   = 0;
317
318     /* Set up the jump label. The get() routine will use this label when EOF
319      * is reached.
320      */
321     Result = setjmp (JumpBuf);
322     if (Result == RC_OK) {
323
324 Again:
325         /* Get the next input character */
326         ReadChar ();
327
328         /* Walk over the format string */
329         while (F = *format++) {
330
331             /* Check for a conversion */
332             if (F != '%' || *format == '%') {
333
334                 /* %% or any char other than % */
335                 if (F == '%') {
336                     ++format;
337                 }
338
339                 /* Check for a match */
340                 if (isspace (F)) {
341
342                     /* Special white space handling: Any whitespace in the
343                      * format string matches any amount of whitespace including
344                      * none(!). So this match will never fail.
345                      */
346                     SkipWhite ();
347                     continue;
348
349                 } else if (F == C) {
350
351                     /* A match. Read the next input character and start over */
352                     goto Again;
353
354                 } else {
355
356                     /* A mismatch. We will stop scanning the input and return
357                      * the number of conversions.
358                      */
359                     return Conversions;
360
361                 }
362
363             } else {
364
365                 /* A conversion. Skip the percent sign. */
366                 F = *format++;
367
368                 /* 1. Assignment suppression */
369                 if (F == '*') {
370                     F = *format++;
371                     NoAssign = 1;
372                 } else {
373                     NoAssign = 0;
374                 }
375
376                 /* 2. Maximum field width */
377                 Width     = UINT_MAX;
378                 HaveWidth = 0;
379                 if (isdigit (F)) {
380                     HaveWidth = 1;
381                     Width     = 0;
382                     do {
383                         /* ### Non portable ### */
384                         Width = Width * 10 + (F & 0x0F);
385                         F = *format++;
386                     } while (isdigit (F));
387                 }
388
389                 /* 3. Length modifier */
390                 IntBytes = sizeof(int) - 1;
391                 switch (F) {
392                     case 'h':
393                         if (*format == 'h') {
394                             IntBytes = sizeof(char) - 1;
395                             ++format;
396                         }
397                         F = *format++;
398                         break;
399
400                     case 'l':
401                         if (*format == 'l') {
402                             /* Treat long long as long */
403                             ++format;
404                         }
405                         /* FALLTHROUGH */
406                     case 'j':   /* intmax_t */
407                         IntBytes = sizeof(long) - 1;
408                         F = *format++;
409                         break;
410
411                     case 'z':   /* size_t */
412                     case 't':   /* ptrdiff_t */
413                     case 'L':   /* long double - ignore this one */
414                         F = *format++;
415                         break;
416                 }
417
418                 /* 4. Conversion specifier */
419                 switch (F) {
420
421                     /* 'd' and 'u' conversions are actually the same, since the
422                      * standard says that evene the 'u' modifier allows an
423                      * optionally signed integer.
424                      */
425                     case 'd':   /* Optionally signed decimal integer */
426                     case 'u':
427                         ScanInt (10);
428                         break;
429
430                     case 'i':
431                         /* Optionally signed integer with a base */
432                         ScanInt (0);
433                         break;
434
435                     case 'o':
436                         /* Optionally signed octal integer */
437                         ScanInt (8);
438                         break;
439
440                     case 'x':
441                     case 'X':
442                         /* Optionally signed hexadecimal integer */
443                         ScanInt (16);
444                         break;
445
446                     case 'E':
447                     case 'e':
448                     case 'f':
449                     case 'g':
450                         /* Optionally signed float */
451                         longjmp (JumpBuf, RC_NOCONV);
452                         break;
453
454                     case 's':
455                         /* Whitespace terminated string */
456                         SkipWhite ();
457                         if (!NoAssign) {
458                             S = va_arg (ap, char*);
459                         }
460                         while (!isspace (C) && Width--) {
461                             if (!NoAssign) {
462                                 *S++ = C;
463                             }
464                             ReadChar ();
465                         }
466                         /* Terminate the string just read */
467                         if (!NoAssign) {
468                             *S = '\0';
469                         }
470                         ++Conversions;
471                         break;
472
473                     case 'c':
474                         /* Fixed length string, NOT zero terminated */
475                         if (!HaveWidth) {
476                             /* No width given, default is 1 */
477                             Width = 1;
478                         }
479                         if (!NoAssign) {
480                             S = va_arg (ap, char*);
481                             while (Width--) {
482                                 *S++ = C;
483                                 ReadCharWithCheck ();
484                             }
485                         } else {
486                             /* Just skip as many chars as given */
487                             while (Width--) {
488                                 ReadCharWithCheck ();
489                             }
490                         }
491                         ++Conversions;
492                         break;
493
494                     case '[':
495                         /* String using characters from a set */
496                         Invert = 0;
497                         /* Clear the set */
498                         memset (CharSet, 0, sizeof (CharSet));
499                         F = *format++;
500                         if (F == '^') {
501                             Invert = 1;
502                             F = *format++;
503                         }
504                         if (F == ']') {
505                             AddCharToSet (']');
506                             F = *format++;
507                         }
508                         /* Read the characters that are part of the set */
509                         while (F != ']' && F != '\0') {
510                             if (*format == '-') {
511                                 /* A range. Get start and end, skip the '-' */
512                                 Start = F;
513                                 F = *++format;
514                                 ++format;
515                                 if (F == ']') {
516                                     /* '-' as last char means: include '-' */
517                                     AddCharToSet (Start);
518                                     AddCharToSet ('-');
519                                 } else if (F != '\0') {
520                                     /* Include all chars in the range */
521                                     while (1) {
522                                         AddCharToSet (Start);
523                                         if (Start == F) {
524                                             break;
525                                         }
526                                         ++Start;
527                                     }
528                                     /* Get next char after range */
529                                     F = *format++;
530                                 }
531                             } else {
532                                 /* Just a character */
533                                 AddCharToSet (F);
534                                 /* Get next char */
535                                 F = *format++;
536                             }
537                         }
538
539                         /* Invert the set if requested */
540                         if (Invert) {
541                             InvertCharSet ();
542                         }
543
544                         /* We have the set in CharSet. Read characters and
545                          * store them into a string while they are part of
546                          * the set.
547                          */
548                         if (!NoAssign) {
549                             S = va_arg (ap, char*);
550                             while (IsCharInSet (C) && Width--) {
551                                 *S++ = C;
552                                 ReadChar ();
553                             }
554                             *S = '\0';
555                         } else {
556                             while (IsCharInSet (C) && Width--) {
557                                 ReadChar ();
558                             }
559                         }
560                         ++Conversions;
561                         break;
562
563                     case 'p':
564                         /* Pointer, format is 0xABCD */
565                         SkipWhite ();
566                         if (C != '0') {
567                             longjmp (JumpBuf, RC_NOCONV);
568                         }
569                         ReadChar ();
570                         if (C != 'x' && C != 'X') {
571                             longjmp (JumpBuf, RC_NOCONV);
572                         }
573                         ReadChar ();
574                         if (ReadInt (16) != 4) { /* 4 chars expected */
575                             longjmp (JumpBuf, RC_NOCONV);
576                         }
577                         AssignInt ();
578                         ++Conversions;
579                         break;
580
581                     case 'n':
582                         /* Store characters consumed so far */
583                         IntVal = D->ccount;
584                         AssignInt ();
585                         break;
586
587                     default:
588                         /* Invalid conversion */
589                         longjmp (JumpBuf, RC_NOCONV);
590                         break;
591
592                 }
593             }
594         }
595
596         /* Push back the last unused character, provided it is not EOF */
597         if (C != EOF) {
598             D->unget (C, D->data);
599         }
600
601     } else {
602
603         /* Jump via JumpBuf means an error. If this happens at EOF with no
604          * conversions, it is considered an error, otherwise the number
605          * of conversions is returned (the default behaviour).
606          */
607         if (C == EOF && D->ccount == 0) {
608             /* Special case: error */
609             Conversions = EOF;
610         }
611
612     }
613
614     /* Return the number of conversions */
615     return Conversions;
616 }
617
618
619