]> git.sur5r.net Git - cc65/blob - libsrc/common/_scanf.c
Fixed a bug
[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 indesc*   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 char             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
50 /* Flags */
51 static unsigned char    Positive;       /* Flag for positive value */
52 static unsigned char    NoAssign;       /* Supppress assigment */
53 static unsigned char    IsShort;        /* Short type */
54 static unsigned char    IsLong;         /* Long type */
55 static unsigned char    Invert;         /* Do we need to invert the charset? */
56 static unsigned char    CharSet[32];    /* 32 * 8 bits = 256 bits */
57 static const unsigned char Bits[8] = {
58     0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
59 };
60
61
62
63 /*****************************************************************************/
64 /*                              Character sets                               */
65 /*****************************************************************************/
66
67
68
69 static void AddCharToSet (unsigned char C)
70 /* Set the given bit in the character set */
71 {
72     asm ("ldy #%o", C);
73     asm ("lda (sp),y");
74     asm ("lsr a");
75     asm ("lsr a");
76     asm ("lsr a");
77     asm ("tax");
78     asm ("lda (sp),y");
79     asm ("and #$07");
80     asm ("tay");
81     asm ("lda %v,y", Bits);
82     asm ("ora %v,x", CharSet);
83     asm ("sta %v,x", CharSet);
84 }
85
86
87
88 static unsigned char IsCharInSet (unsigned char C)
89 /* Check if the given char is part of the character set */
90 {
91     asm ("ldy #%o", C);
92     asm ("lda (sp),y");
93     asm ("lsr a");
94     asm ("lsr a");
95     asm ("lsr a");
96     asm ("tax");
97     asm ("lda (sp),y");
98     asm ("and #$07");
99     asm ("tay");
100     asm ("lda %v,y", Bits);
101     asm ("and %v,x", CharSet);
102     asm ("ldx #$00");
103     return __AX__;
104 }
105
106
107
108 /*****************************************************************************/
109 /*                                   Code                                    */
110 /*****************************************************************************/
111
112
113
114 static void ReadChar (void)
115 /* Get an input character, count characters */
116 {
117     C = D->fin (D);
118     ++D->ccount;
119 }
120
121
122
123 static void SkipWhite (void)
124 /* Skip white space in the input and return the first non white character */
125 {
126     while (isspace (C)) {
127         ReadChar ();
128     }
129 }
130
131
132
133 static void ReadSign (void)
134 /* Read an optional sign and skip it. Store 1 in Positive if the value is
135  * positive, store 0 otherwise.
136  */
137 {
138     switch (C) {
139         case '-':
140             ReadChar ();
141             Positive = 0;
142         case '+':
143             ReadChar ();
144             /* FALLTHROUGH */
145         default:
146             Positive = 1;
147     }
148 }
149
150
151
152 static unsigned char HexVal (char C)
153 /* Convert a digit to a value */
154 {
155
156     if (isdigit (C)) {
157         return C - '0';
158     } else {
159         return C - toupper (C) + ('A' + 10);
160     }
161 }
162
163
164
165 static void ReadInt (unsigned char Base)
166 /* Read an integer and store it into IntVal */
167 {
168     /* Value must start with a digit */
169     if (!isdigit (C)) {
170         longjmp (JumpBuf, RC_NOCONV);
171     }
172
173     /* Read the value */
174     IntVal = 0;
175     while (isxdigit (C) && Width-- > 0) {
176         IntVal = IntVal * Base + HexVal (C);
177         ReadChar ();
178     }
179
180     /* One more conversion */
181     ++Conversions;
182 }
183
184
185
186 static void AssignInt (void)
187 /* Assign the integer value in Val to the next argument. The function makes
188  * several non portable assumptions to reduce code size:
189  *   - int and unsigned types have the same representation
190  *   - short and int have the same representation.
191  *   - all pointer types have the same representation.
192  */
193 {
194     if (!NoAssign) {
195         /* Get the next argument pointer */
196         void* P = va_arg (ap, void*);
197
198         /* Assign to the converted value */
199         if (IsLong) {
200             *(long*)P = IntVal;
201         } else {
202             *(int*)P = (int) IntVal;
203         }
204     }
205 }
206
207
208
209 int _scanf (struct indesc* D_, const char* format, va_list ap_)
210 /* This is the routine used to do the actual work. It is called from several
211  * types of wrappers to implement the actual ISO xxscanf functions.
212  */
213 {
214     char          F;            /* Character from format string */
215     unsigned char Result;       /* setjmp result */
216     char*         S;
217     unsigned char Base;         /* Integer base in %i */
218     unsigned char HaveWidth;    /* True if a width was given */
219     char          Start;        /* Start of range */
220
221     /* Place copies of the arguments into global variables. This is not very
222      * nice, but on a 6502 platform it gives better code, since the values
223      * do not have to be passed as parameters.
224      */
225     D   = D_;
226     ap  = ap_;
227
228     /* Initialize variables */
229     Conversions = 0;
230     D->ccount   = 0;
231
232     /* Set up the jump label. The get() routine will use this label when EOF
233      * is reached.
234      */
235     Result = setjmp (JumpBuf);
236     if (Result == RC_OK) {
237
238 Again:
239         /* Get the next input character */
240         ReadChar ();
241
242         /* Walk over the format string */
243         while (F = *format++) {
244
245             /* Check for a conversion */
246             if (F != '%' || *format == '%') {
247
248                 /* %% or any char other than % */
249                 if (F == '%') {
250                     ++format;
251                 }
252
253                 /* Check for a match */
254                 if (isspace (F)) {
255
256                     /* Special white space handling: Any whitespace matches
257                      * any amount of whitespace including none(!). So this
258                      * match will never fail.
259                      */
260                     SkipWhite ();
261                     continue;
262
263                 } else if (F != C) {
264
265                     /* A mismatch. We will stop scanning the input and return
266                      * the number of conversions.
267                      */
268                     return Conversions;
269
270                 } else {
271
272                     /* A match. Read the next input character and start over */
273                     goto Again;
274
275                 }
276
277             } else {
278
279                 /* A conversion. Skip the percent sign. */
280                 F = *format++;
281
282                 /* Initialize variables */
283                 NoAssign    = 0;
284                 IsShort     = 0;
285                 IsLong      = 0;
286                 Width       = UINT_MAX;
287                 HaveWidth   = 0;
288
289                 /* Check for flags. */
290                 while (1) {
291                     if (isdigit (F)) {
292                         HaveWidth = 1;
293                         Width     = 0;
294                         do {
295                             /* ### Non portable ### */
296                             Width = Width * 10 + (F & 0x0F);
297                             F = *format++;
298                         } while (isdigit (F));
299                     } else {
300                         switch (F) {
301                             case '*':   NoAssign = 1;   break;
302                             case 'h':   IsShort = 1;    break;
303                             case 'l':
304                             case 'L':   IsLong = 1;     break;
305                             default:    goto FlagsDone;
306                         }
307                         F = *format++;
308                     }
309                 }
310 FlagsDone:
311
312                 /* Check for the actual conversion character */
313                 switch (F) {
314
315                     case 'D':
316                         IsLong = 1;
317                     case 'd':
318                         /* Optionally signed decimal integer */
319                         SkipWhite ();
320                         ReadSign ();
321                         ReadInt (10);
322                         if (!Positive) {
323                             IntVal = -IntVal;
324                         }
325                         AssignInt ();
326                         break;
327
328                     case 'i':
329                         /* Optionally signed integer with a base */
330                         SkipWhite ();
331                         ReadSign ();
332                         if (C == '0') {
333                             ReadChar ();
334                             switch (C) {
335                                 case 'x':
336                                 case 'X':
337                                     Base = 16;
338                                     ReadChar();
339                                     break;
340                                 default:
341                                     Base = 8;
342                             }
343                         } else {
344                             Base = 10;
345                         }
346                         ReadInt (Base);
347                         if (!Positive) {
348                             IntVal = -IntVal;
349                         }
350                         AssignInt ();
351                         break;
352
353                     case 'o':
354                         /* Unsigned octal integer */
355                         SkipWhite ();
356                         ReadInt (8);
357                         AssignInt ();
358                         break;
359
360                     case 'u':
361                         /* Unsigned decimal integer */
362                         SkipWhite ();
363                         ReadInt (10);
364                         AssignInt ();
365                         break;
366
367                     case 'x':
368                     case 'X':
369                         /* Unsigned hexadecimal integer */
370                         SkipWhite ();
371                         ReadInt (16);
372                         AssignInt ();
373                         break;
374
375                     case 'E':
376                     case 'e':
377                     case 'f':
378                     case 'g':
379                         /* Optionally signed float */
380                         longjmp (JumpBuf, RC_NOCONV);
381                         break;
382
383                     case 's':
384                         /* Whitespace terminated string */
385                         SkipWhite ();
386                         if (!NoAssign) {
387                             S = va_arg (ap, char*);
388                         }
389                         while (!isspace (C) && Width--) {
390                             if (!NoAssign) {
391                                 *S++ = C;
392                             }
393                             ReadChar ();
394                         }
395                         /* Terminate the string just read */
396                         if (!NoAssign) {
397                             *S = '\0';
398                         }
399                         ++Conversions;
400                         break;
401
402                     case 'c':
403                         /* Fixed length string, NOT zero terminated */
404                         if (!HaveWidth) {
405                             /* No width given, default is 1 */
406                             Width = 1;
407                         }
408                         if (!NoAssign) {
409                             S = va_arg (ap, char*);
410                             while (Width--) {
411                                 *S++ = C;
412                                 ReadChar ();
413                             }
414                         } else {
415                             /* Just skip as many chars as given */
416                             while (Width--) {
417                                 ReadChar ();
418                             }
419                         }
420                         ++Conversions;
421                         break;
422
423                     case '[':
424                         /* String using characters from a set */
425                         Invert = 0;
426                         /* Clear the set */
427                         memset (CharSet, 0, sizeof (CharSet));
428                         F = *format++;
429                         if (F == '^') {
430                             Invert = 1;
431                             F = *format++;
432                         }
433                         if (F == ']') {
434                             AddCharToSet (']');
435                             F = *format++;
436                         }
437                         /* Read the characters that are part of the set */
438                         while (F != ']' && F != '\0') {
439                             if (*format == '-') {
440                                 /* A range. Get start and end, skip the '-' */
441                                 Start = F;
442                                 F = *++format;
443                                 ++format;
444                                 if (F == ']') {
445                                     /* '-' as last char means: include '-' */
446                                     AddCharToSet (Start);
447                                     AddCharToSet ('-');
448                                 } else if (F != '\0') {
449                                     /* Include all chars in the range */
450                                     while (1) {
451                                         AddCharToSet (Start);
452                                         if (Start == F) {
453                                             break;
454                                         }
455                                         ++Start;
456                                     }
457                                     /* Get next char after range */
458                                     F = *format++;
459                                 }
460                             } else {
461                                 /* Just a character */
462                                 AddCharToSet (F);
463                                 /* Get next char */
464                                 F = *format++;
465                             }
466                         }
467
468                         /* Invert the set if requested */
469                         if (Invert) {
470                             for (Start = 0; Start < sizeof (CharSet); ++Start) {
471                                 CharSet[Start] ^= 0xFF;
472                             }
473                         }
474
475                         /* We have the set in CharSet. Read characters and
476                          * store them into a string while they are part of
477                          * the set.
478                          */
479                         if (!NoAssign) {
480                             S = va_arg (ap, char*);
481                             while (IsCharInSet (C) && Width--) {
482                                 *S++ = C;
483                                 ReadChar ();
484                             }
485                             *S = '\0';
486                         } else {
487                             while (IsCharInSet (C) && Width--) {
488                                 ReadChar ();
489                             }
490                         }
491                         ++Conversions;
492                         break;
493
494                     case 'p':
495                         /* Pointer, format is 0xABCD */
496                         SkipWhite ();
497                         if (C != '0') {
498                             longjmp (JumpBuf, RC_NOCONV);
499                         }
500                         ReadChar ();
501                         if (C != 'x' && C != 'X') {
502                             longjmp (JumpBuf, RC_NOCONV);
503                         }
504                         ReadChar ();
505                         ReadInt (16);
506                         AssignInt ();
507                         break;
508
509                     case 'n':
510                         /* Store characters consumed so far */
511                         IntVal = D->ccount;
512                         AssignInt ();
513                         break;
514
515                     default:
516                         /* Invalid conversion */
517                         longjmp (JumpBuf, RC_NOCONV);
518                         break;
519
520                 }
521
522                 /* Skip the format char */
523                 goto Again;
524
525             }
526
527         }
528
529     } else if (Result == RC_EOF) {
530
531         /* Jump via JumpBuf means EOF on input */
532         if (D->ccount == 0) {
533             /* Special case: error */
534             return -1;
535         }
536
537     }
538
539     /* Return the number of conversions */
540     return Conversions;
541 }
542
543
544