]> git.sur5r.net Git - cc65/blob - libsrc/common/_scanf.c
Squeezed out a few bytes
[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
211         /* Get the next argument pointer */
212         __AX__ = (unsigned) va_arg (ap, void*);
213
214         /* Store the argument pointer into ptr1 */
215         asm ("sta ptr1");
216         asm ("stx ptr1+1");
217
218         /* Get the number of bytes-1 to copy */
219         asm ("ldy #3");
220         asm ("lda %v", IsLong);
221         asm ("bne L1");
222         asm ("ldy #1");
223
224         /* Assign the integer value */
225         asm ("L1: lda %v,y", IntVal);
226         asm ("sta (ptr1),y");
227         asm ("dey");
228         asm ("bpl L1");
229
230     }
231 }
232
233
234
235 int _scanf (struct indesc* D_, register const char* format, va_list ap_)
236 /* This is the routine used to do the actual work. It is called from several
237  * types of wrappers to implement the actual ISO xxscanf functions.
238  */
239 {
240     char          F;            /* Character from format string */
241     unsigned char Result;       /* setjmp result */
242     char*         S;
243     unsigned char Base;         /* Integer base in %i */
244     unsigned char HaveWidth;    /* True if a width was given */
245     char          Start;        /* Start of range */
246
247     /* Place copies of the arguments into global variables. This is not very
248      * nice, but on a 6502 platform it gives better code, since the values
249      * do not have to be passed as parameters.
250      */
251     D   = D_;
252     ap  = ap_;
253
254     /* Initialize variables */
255     Conversions = 0;
256     D->ccount   = 0;
257
258     /* Set up the jump label. The get() routine will use this label when EOF
259      * is reached.
260      */
261     Result = setjmp (JumpBuf);
262     if (Result == RC_OK) {
263
264 Again:
265         /* Get the next input character */
266         ReadChar ();
267
268         /* Walk over the format string */
269         while (F = *format++) {
270
271             /* Check for a conversion */
272             if (F != '%' || *format == '%') {
273
274                 /* %% or any char other than % */
275                 if (F == '%') {
276                     ++format;
277                 }
278
279                 /* Check for a match */
280                 if (isspace (F)) {
281
282                     /* Special white space handling: Any whitespace matches
283                      * any amount of whitespace including none(!). So this
284                      * match will never fail.
285                      */
286                     SkipWhite ();
287                     continue;
288
289                 } else if (F != C) {
290
291                     /* A mismatch. We will stop scanning the input and return
292                      * the number of conversions.
293                      */
294                     return Conversions;
295
296                 } else {
297
298                     /* A match. Read the next input character and start over */
299                     goto Again;
300
301                 }
302
303             } else {
304
305                 /* A conversion. Skip the percent sign. */
306                 F = *format++;
307
308                 /* Initialize variables */
309                 NoAssign    = 0;
310                 IsShort     = 0;
311                 IsLong      = 0;
312                 Width       = UINT_MAX;
313                 HaveWidth   = 0;
314
315                 /* Check for flags. */
316                 while (1) {
317                     if (isdigit (F)) {
318                         HaveWidth = 1;
319                         Width     = 0;
320                         do {
321                             /* ### Non portable ### */
322                             Width = Width * 10 + (F & 0x0F);
323                             F = *format++;
324                         } while (isdigit (F));
325                     } else {
326                         switch (F) {
327                             case '*':   NoAssign = 1;   break;
328                             case 'h':   IsShort = 1;    break;
329                             case 'l':
330                             case 'L':   IsLong = 1;     break;
331                             default:    goto FlagsDone;
332                         }
333                         F = *format++;
334                     }
335                 }
336 FlagsDone:
337
338                 /* Check for the actual conversion character */
339                 switch (F) {
340
341                     case 'D':
342                         IsLong = 1;
343                     case 'd':
344                         /* Optionally signed decimal integer */
345                         SkipWhite ();
346                         ReadSign ();
347                         ReadInt (10);
348                         if (!Positive) {
349                             IntVal = -IntVal;
350                         }
351                         AssignInt ();
352                         break;
353
354                     case 'i':
355                         /* Optionally signed integer with a base */
356                         SkipWhite ();
357                         ReadSign ();
358                         if (C == '0') {
359                             ReadChar ();
360                             switch (C) {
361                                 case 'x':
362                                 case 'X':
363                                     Base = 16;
364                                     ReadChar();
365                                     break;
366                                 default:
367                                     Base = 8;
368                             }
369                         } else {
370                             Base = 10;
371                         }
372                         ReadInt (Base);
373                         if (!Positive) {
374                             IntVal = -IntVal;
375                         }
376                         AssignInt ();
377                         break;
378
379                     case 'o':
380                         /* Unsigned octal integer */
381                         SkipWhite ();
382                         ReadInt (8);
383                         AssignInt ();
384                         break;
385
386                     case 'u':
387                         /* Unsigned decimal integer */
388                         SkipWhite ();
389                         ReadInt (10);
390                         AssignInt ();
391                         break;
392
393                     case 'x':
394                     case 'X':
395                         /* Unsigned hexadecimal integer */
396                         SkipWhite ();
397                         ReadInt (16);
398                         AssignInt ();
399                         break;
400
401                     case 'E':
402                     case 'e':
403                     case 'f':
404                     case 'g':
405                         /* Optionally signed float */
406                         longjmp (JumpBuf, RC_NOCONV);
407                         break;
408
409                     case 's':
410                         /* Whitespace terminated string */
411                         SkipWhite ();
412                         if (!NoAssign) {
413                             S = va_arg (ap, char*);
414                         }
415                         while (!isspace (C) && Width--) {
416                             if (!NoAssign) {
417                                 *S++ = C;
418                             }
419                             ReadChar ();
420                         }
421                         /* Terminate the string just read */
422                         if (!NoAssign) {
423                             *S = '\0';
424                         }
425                         ++Conversions;
426                         break;
427
428                     case 'c':
429                         /* Fixed length string, NOT zero terminated */
430                         if (!HaveWidth) {
431                             /* No width given, default is 1 */
432                             Width = 1;
433                         }
434                         if (!NoAssign) {
435                             S = va_arg (ap, char*);
436                             while (Width--) {
437                                 *S++ = C;
438                                 ReadChar ();
439                             }
440                         } else {
441                             /* Just skip as many chars as given */
442                             while (Width--) {
443                                 ReadChar ();
444                             }
445                         }
446                         ++Conversions;
447                         break;
448
449                     case '[':
450                         /* String using characters from a set */
451                         Invert = 0;
452                         /* Clear the set */
453                         memset (CharSet, 0, sizeof (CharSet));
454                         F = *format++;
455                         if (F == '^') {
456                             Invert = 1;
457                             F = *format++;
458                         }
459                         if (F == ']') {
460                             AddCharToSet (']');
461                             F = *format++;
462                         }
463                         /* Read the characters that are part of the set */
464                         while (F != ']' && F != '\0') {
465                             if (*format == '-') {
466                                 /* A range. Get start and end, skip the '-' */
467                                 Start = F;
468                                 F = *++format;
469                                 ++format;
470                                 if (F == ']') {
471                                     /* '-' as last char means: include '-' */
472                                     AddCharToSet (Start);
473                                     AddCharToSet ('-');
474                                 } else if (F != '\0') {
475                                     /* Include all chars in the range */
476                                     while (1) {
477                                         AddCharToSet (Start);
478                                         if (Start == F) {
479                                             break;
480                                         }
481                                         ++Start;
482                                     }
483                                     /* Get next char after range */
484                                     F = *format++;
485                                 }
486                             } else {
487                                 /* Just a character */
488                                 AddCharToSet (F);
489                                 /* Get next char */
490                                 F = *format++;
491                             }
492                         }
493
494                         /* Invert the set if requested */
495                         if (Invert) {
496                             InvertCharSet ();
497                         }
498
499                         /* We have the set in CharSet. Read characters and
500                          * store them into a string while they are part of
501                          * the set.
502                          */
503                         if (!NoAssign) {
504                             S = va_arg (ap, char*);
505                             while (IsCharInSet (C) && Width--) {
506                                 *S++ = C;
507                                 ReadChar ();
508                             }
509                             *S = '\0';
510                         } else {
511                             while (IsCharInSet (C) && Width--) {
512                                 ReadChar ();
513                             }
514                         }
515                         ++Conversions;
516                         break;
517
518                     case 'p':
519                         /* Pointer, format is 0xABCD */
520                         SkipWhite ();
521                         if (C != '0') {
522                             longjmp (JumpBuf, RC_NOCONV);
523                         }
524                         ReadChar ();
525                         if (C != 'x' && C != 'X') {
526                             longjmp (JumpBuf, RC_NOCONV);
527                         }
528                         ReadChar ();
529                         ReadInt (16);
530                         AssignInt ();
531                         break;
532
533                     case 'n':
534                         /* Store characters consumed so far */
535                         IntVal = D->ccount;
536                         AssignInt ();
537                         break;
538
539                     default:
540                         /* Invalid conversion */
541                         longjmp (JumpBuf, RC_NOCONV);
542                         break;
543
544                 }
545
546                 /* Skip the format char */
547                 goto Again;
548
549             }
550
551         }
552
553     } else if (Result == RC_EOF) {
554
555         /* Jump via JumpBuf means EOF on input */
556         if (D->ccount == 0) {
557             /* Special case: error */
558             return -1;
559         }
560
561     }
562
563     /* Return the number of conversions */
564     return Conversions;
565 }
566
567
568