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