]> git.sur5r.net Git - cc65/blob - libsrc/common/_scanf.c
Removed (pretty inconsistently used) tab chars from source code base.
[cc65] / libsrc / common / _scanf.c
1 /*
2  * _scanf.c
3  *
4  * (c) Copyright 2001-2005, Ullrich von Bassewitz <uz@cc65.org>
5  * 2005-01-24, Greg King <gngking@erols.com>
6  *
7  * This is the basic layer for all scanf-type functions.  It should be
8  * rewritten in assembly, at some time in the future.  So, some of the code
9  * is not as elegant as it could be.
10  */
11
12
13
14 #include <stddef.h>
15 #include <stdarg.h>
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <setjmp.h>
20 #include <limits.h>
21 #include <errno.h>
22
23 #include <ctype.h>
24 /* _scanf() can give EOF to these functions.  But, the macroes can't
25 ** understand it; so, they are removed.
26 */
27 #undef isspace
28 #undef isxdigit
29
30 #include "_scanf.h"
31
32 #pragma static-locals(on)
33
34
35
36 /*****************************************************************************/
37 /*                            SetJmp return codes                            */
38 /*****************************************************************************/
39
40
41
42 enum {
43     RC_OK,                              /* setjmp() call */
44     RC_NOCONV,                          /* No conversion possible */
45     RC_EOF                              /* EOF reached */
46 };
47
48
49
50 /*****************************************************************************/
51 /*                                   Data                                    */
52 /*****************************************************************************/
53
54
55
56 static const char*    format;           /* Copy of function argument */
57 static const struct scanfdata* D_;      /* Copy of function argument */
58 static va_list        ap;               /* Copy of function argument */
59 static jmp_buf        JumpBuf;          /* "Label" that is used for failures */
60 static char           F;                /* Character from format string */
61 static unsigned       CharCount;        /* Characters read so far */
62 static int            C;                /* Character from input */
63 static unsigned       Width;            /* Maximum field width */
64 static long           IntVal;           /* Converted int value */
65 static int            Assignments;      /* Number of assignments */
66 static unsigned char  IntBytes;         /* Number of bytes-1 for int conversions */
67
68 /* Flags */
69 static bool           Converted;        /* Some object was converted */
70 static bool           Positive;         /* Flag for positive value */
71 static bool           NoAssign;         /* Suppress assignment */
72 static bool           Invert;           /* Do we need to invert the charset? */
73 static unsigned char  CharSet[(1+UCHAR_MAX)/CHAR_BIT];
74 static const unsigned char Bits[CHAR_BIT] = {
75     0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
76 };
77
78 /* We need C to be 16 bits since we cannot check for EOF otherwise.
79  * Unfortunately, this causes the code to be quite larger, even if for most
80  * purposes, checking the low byte would be enough, since if C is EOF, the
81  * low byte will not match any useful character anyway (at least for the
82  * supported platforms - I know that this is not portable). So the following
83  * macro is used to access just the low byte of C.
84  */
85 #define CHAR(c)         (*((unsigned char*)&(c)))
86
87
88
89 /*****************************************************************************/
90 /*                              Character sets                               */
91 /*****************************************************************************/
92
93
94
95 /* We don't want the optimizer to ruin our "perfect" ;-)
96  * assembly code!
97  */
98 #pragma optimize (push, off)
99
100 static unsigned FindBit (void)
101 /* Locate the character's bit in the charset array.
102  * < .A - Argument character
103  * > .X - Offset of the byte in the character-set mask
104  * > .A - Bit-mask
105  */
106 {
107     asm ("pha");
108     asm ("lsr a");              /* Divide by CHAR_BIT */
109     asm ("lsr a");
110     asm ("lsr a");
111     asm ("tax");                /* Byte's offset */
112     asm ("pla");
113     asm ("and #%b", CHAR_BIT-1);
114     asm ("tay");                /* Bit's offset */
115     asm ("lda %v,y", Bits);
116     return (unsigned) __AX__;
117 }
118
119 #pragma optimize (pop)
120
121
122 static void __fastcall__ AddCharToSet (unsigned char /* C */)
123 /* Set the given bit in the character set */
124 {
125     FindBit();
126     asm ("ora %v,x", CharSet);
127     asm ("sta %v,x", CharSet);
128 }
129
130
131
132 #pragma optimize (push, off)
133
134 static unsigned char IsCharInSet (void)
135 /* Check if the char. is part of the character set. */
136 {
137     /* Get the character from C. */
138     asm ("lda #$00");
139     asm ("ldx %v+1", C);
140     asm ("bne L1");                     /* EOF never is in the set */
141     asm ("lda %v", C);
142     FindBit();
143     asm ("and %v,x", CharSet);
144     asm ("L1:");
145     asm ("ldx #$00");
146     return (unsigned char) __AX__;
147 }
148
149 #pragma optimize (pop)
150
151
152
153 static void InvertCharSet (void)
154 /* Invert the character set */
155 {
156     asm ("ldy #%b", sizeof (CharSet) - 1);
157     asm ("L1:");
158     asm ("lda %v,y", CharSet);
159     asm ("eor #$FF");
160     asm ("sta %v,y", CharSet);
161     asm ("dey");
162     asm ("bpl L1");
163 }
164
165
166
167 /*****************************************************************************/
168 /*                                   Code                                    */
169 /*****************************************************************************/
170
171
172
173 static void PushBack (void)
174 /* Push back the last (unused) character, provided it is not EOF. */
175 {
176     /* Get the character from C. */
177     /* Only the high-byte needs to be checked for EOF. */
178     asm ("ldx %v+1", C);
179     asm ("bne %g", Done);
180     asm ("lda %v", C);
181
182     /* Put unget()'s first argument on the stack. */
183     asm ("jsr pushax");
184
185     /* Copy D into the zero-page. */
186     (const struct scanfdata*) __AX__ = D_;
187     asm ("sta ptr1");
188     asm ("stx ptr1+1");
189
190     /* Copy the unget vector to jmpvec. */
191     asm ("ldy #%b", offsetof (struct scanfdata, unget));
192     asm ("lda (ptr1),y");
193     asm ("sta jmpvec+1");
194     asm ("iny");
195     asm ("lda (ptr1),y");
196     asm ("sta jmpvec+2");
197
198     /* Load D->data into __AX__. */
199     asm ("ldy #%b", offsetof (struct scanfdata, data) + 1);
200     asm ("lda (ptr1),y");
201     asm ("tax");
202     asm ("dey");
203     asm ("lda (ptr1),y");
204
205     /* Call the unget routine. */
206     asm ("jsr jmpvec");
207
208     /* Take back that character's count. */
209     asm ("lda %v", CharCount);
210     asm ("bne %g", Yank);
211     asm ("dec %v+1", CharCount);
212 Yank:
213     asm ("dec %v", CharCount);
214
215 Done:
216     ;
217 }
218
219
220
221 static void ReadChar (void)
222 /* Get an input character, count characters */
223 {
224     /* Move D to ptr1 */
225     asm ("lda %v", D_);
226     asm ("ldx %v+1", D_);
227     asm ("sta ptr1");
228     asm ("stx ptr1+1");
229
230     /* Copy the get vector to jmpvec */
231     asm ("ldy #%b", offsetof (struct scanfdata, get));
232     asm ("lda (ptr1),y");
233     asm ("sta jmpvec+1");
234     asm ("iny");
235     asm ("lda (ptr1),y");
236     asm ("sta jmpvec+2");
237
238     /* Load D->data into __AX__ */
239     asm ("ldy #%b", offsetof (struct scanfdata, data) + 1);
240     asm ("lda (ptr1),y");
241     asm ("tax");
242     asm ("dey");
243     asm ("lda (ptr1),y");
244
245     /* Call the get routine */
246     asm ("jsr jmpvec");
247
248     /* Assign the result to C */
249     asm ("sta %v", C);
250     asm ("stx %v+1", C);
251
252     /* If C is EOF, don't bump the character counter.
253      * Only the high-byte needs to be checked.
254      */
255     asm ("inx");
256     asm ("beq %g", Done);
257
258     /* Must bump CharCount. */
259     asm ("inc %v", CharCount);
260     asm ("bne %g", Done);
261     asm ("inc %v+1", CharCount);
262
263 Done:
264     ;
265 }
266
267
268
269 #pragma optimize (push, off)
270
271 static void __fastcall__ Error (unsigned char /* Code */)
272 /* Does a longjmp using the given code */
273 {
274     asm ("pha");
275     (char*) __AX__ = JumpBuf;
276     asm ("jsr pushax");
277     asm ("pla");
278     asm ("ldx #>0");
279     asm ("jmp %v", longjmp);
280 }
281
282 #pragma optimize (pop)
283
284
285
286 static void CheckEnd (void)
287 /* Stop a scan if it prematurely reaches the end of a string or a file. */
288 {
289     /* Only the high-byte needs to be checked for EOF. */
290     asm ("ldx %v+1", C);
291     asm ("beq %g", Done);
292
293         Error (RC_EOF);
294 Done:
295     ;
296 }
297
298
299
300 static void SkipWhite (void)
301 /* Skip white space in the input and return the first non white character */
302 {
303     while ((bool) isspace (C)) {
304         ReadChar ();
305     }
306 }
307
308
309
310 #pragma optimize (push, off)
311
312 static void ReadSign (void)
313 /* Read an optional sign and skip it. Store 1 in Positive if the value is
314  * positive, store 0 otherwise.
315  */
316 {
317     /* We can ignore the high byte of C here, since if it is EOF, the lower
318      * byte won't match anyway.
319      */
320     asm ("lda %v", C);
321     asm ("cmp #'-'");
322     asm ("bne %g", NotNeg);
323
324     /* Negative value */
325     asm ("sta %v", Converted);
326     asm ("jsr %v", ReadChar);
327     asm ("lda #$00");           /* Flag as negative */
328     asm ("beq %g", Store);
329
330     /* Positive value */
331 NotNeg:
332     asm ("cmp #'+'");
333     asm ("bne %g", Pos);
334     asm ("sta %v", Converted);
335     asm ("jsr %v", ReadChar);   /* Skip the + sign */
336 Pos:
337     asm ("lda #$01");           /* Flag as positive */
338 Store:
339     asm ("sta %v", Positive);
340 }
341
342 #pragma optimize (pop)
343
344
345
346 static unsigned char __fastcall__ HexVal (char C)
347 /* Convert a digit to a value */
348 {
349     return (bool) isdigit (C) ?
350         C - '0' :
351         (char) tolower ((int) C) - ('a' - 10);
352 }
353
354
355
356 static void __fastcall__ ReadInt (unsigned char Base)
357 /* Read an integer, and store it into IntVal. */
358 {
359     unsigned char Val, CharCount = 0;
360
361     /* Read the integer value */
362     IntVal = 0L;
363     while ((bool) isxdigit (C) && ++Width != 0
364            && (Val = HexVal ((char) C)) < Base) {
365         ++CharCount;
366         IntVal = IntVal * (long) Base + (long) Val;
367         ReadChar ();
368     }
369
370     /* If we didn't convert anything, it's a failure. */
371     if (CharCount == 0) {
372         Error (RC_NOCONV);
373     }
374
375     /* Another conversion */
376     Converted = true;
377 }
378
379
380
381 static void AssignInt (void)
382 /* Assign the integer value in Val to the next argument. The function makes
383  * several non-portable assumptions, to reduce code size:
384  *   - signed and unsigned types have the same representation.
385  *   - short and int have the same representation.
386  *   - all pointer types have the same representation.
387  */
388 {
389     if (NoAssign == false) {
390
391         /* Get the next argument pointer */
392         (void*) __AX__ = va_arg (ap, void*);
393
394         /* Put the argument pointer into the zero-page. */
395         asm ("sta ptr1");
396         asm ("stx ptr1+1");
397
398         /* Get the number of bytes-1 to copy */
399         asm ("ldy %v", IntBytes);
400
401         /* Assign the integer value */
402 Loop:   asm ("lda %v,y", IntVal);
403         asm ("sta (ptr1),y");
404         asm ("dey");
405         asm ("bpl %g", Loop);
406
407         /* Another assignment */
408         asm ("inc %v", Assignments);
409         asm ("bne %g", Done);
410         asm ("inc %v+1", Assignments);
411 Done:   ;
412     }
413 }
414
415
416
417 static void __fastcall__ ScanInt (unsigned char Base)
418 /* Scan an integer including white space, sign and optional base spec,
419  * and store it into IntVal.
420  */
421 {
422     /* Skip whitespace */
423     SkipWhite ();
424
425     /* Read an optional sign */
426     ReadSign ();
427
428     /* If Base is unknown (zero), figure it out */
429     if (Base == 0) {
430         if (CHAR (C) == '0') {
431             ReadChar ();
432             switch (CHAR (C)) {
433                 case 'x':
434                 case 'X':
435                     Base = 16;
436                     Converted = true;
437                     ReadChar ();
438                     break;
439                 default:
440                     Base = 8;
441
442                     /* Restart at the beginning of the number because it might
443                      * be only a single zero digit (which already was read).
444                      */
445                     PushBack ();
446                     C = '0';
447             }
448         } else {
449             Base = 10;
450         }
451     }
452
453     /* Read the integer value */
454     ReadInt (Base);
455
456     /* Apply the sign */
457     if (Positive == false) {
458         IntVal = -IntVal;
459     }
460
461     /* Assign the value to the next argument unless suppressed */
462     AssignInt ();
463 }
464
465
466
467 static char GetFormat (void)
468 /* Pick up the next character from the format string. */
469 {
470 /*  return (F = *format++); */
471     (const char*) __AX__ = format;
472     asm ("sta regsave");
473     asm ("stx regsave+1");
474     ++format;
475     asm ("ldy #0");
476     asm ("lda (regsave),y");
477     asm ("ldx #>0");
478     return (F = (char) __AX__);
479 }
480
481
482
483 int __fastcall__ _scanf (const struct scanfdata* D,
484                          const char* format_, va_list ap_)
485 /* This is the routine used to do the actual work. It is called from several
486  * types of wrappers to implement the actual ISO xxscanf functions.
487  */
488 {
489     register char* S;
490              bool  HaveWidth;   /* True if a width was given */
491              bool  Match;       /* True if a character-set has any matches */
492              char  Start;       /* Walks over a range */
493
494     /* Place copies of the arguments into global variables. This is not very
495      * nice, but on a 6502 platform it gives better code, since the values
496      * do not have to be passed as parameters.
497      */
498     D_     = D;
499     format = format_;
500     ap     = ap_;
501
502     /* Initialize variables */
503     Converted   = false;
504     Assignments = 0;
505     CharCount   = 0;
506
507     /* Set up the jump "label".  CheckEnd() will use that label when EOF
508      * is reached.  ReadInt() will use it when number-conversion fails.
509      */
510     if ((unsigned char) setjmp (JumpBuf) == RC_OK) {
511 Again:
512
513         /* Get the next input character */
514         ReadChar ();
515
516         /* Walk over the format string */
517         while (GetFormat ()) {
518
519             /* Check for a conversion */
520             if (F != '%') {
521
522                 /* Check for a match */
523                 if ((bool) isspace ((int) F)) {
524
525                     /* Special white space handling: Any whitespace in the
526                      * format string matches any amount of whitespace including
527                      * none(!). So this match will never fail.
528                      */
529                     SkipWhite ();
530                     continue;
531                 }
532
533 Percent:
534                 /* ### Note:  The opposite test (C == F)
535                 ** would be optimized into buggy code!
536                 */
537                 if (C != (int) F) {
538
539                     /* A mismatch -- we will stop scanning the input,
540                      * and return the number of assigned conversions.
541                      */
542                     goto NoConv;
543                 }
544
545                 /* A match -- get the next input character, and continue. */
546                 goto Again;
547
548             } else {
549
550                 /* A conversion. Skip the percent sign. */
551                 /* 0. Check for %% */
552                 if (GetFormat () == '%') {
553                     goto Percent;
554                 }
555
556                 /* 1. Assignment suppression */
557                 NoAssign = (F == '*');
558                 if (NoAssign) {
559                     GetFormat ();
560                 }
561
562                 /* 2. Maximum field width */
563                 Width     = UINT_MAX;
564                 HaveWidth = (bool) isdigit (F);
565                 if (HaveWidth) {
566                     Width = 0;
567                     do {
568                         /* ### Non portable ### */
569                         Width = Width * 10 + (F & 0x0F);
570                     } while ((bool) isdigit (GetFormat ()));
571                 }
572                 if (Width == 0) {
573                     /* Invalid specification */
574                     /* Note:  This method of leaving the function might seem
575                      * to be crude, but it optimizes very well because
576                      * the four exits can share this code.
577                      */
578                     _seterrno (EINVAL);
579                     Assignments = EOF;
580                     PushBack ();
581                     return Assignments;
582                 }
583                 /* Increment-and-test makes better code than test-and-decrement
584                  * does.  So, change the width into a form that can be used in
585                  * that way.
586                  */
587                 Width = ~Width;
588
589                 /* 3. Length modifier */
590                 IntBytes = sizeof(int) - 1;
591                 switch (F) {
592                     case 'h':
593                         if (*format == 'h') {
594                             IntBytes = sizeof(char) - 1;
595                             ++format;
596                         }
597                         GetFormat ();
598                         break;
599
600                     case 'l':
601                         if (*format == 'l') {
602                             /* Treat long long as long */
603                             ++format;
604                         }
605                         /* FALLTHROUGH */
606                     case 'j':   /* intmax_t */
607                         IntBytes = sizeof(long) - 1;
608                         /* FALLTHROUGH */
609
610                     case 'z':   /* size_t */
611                     case 't':   /* ptrdiff_t */
612                         /* Same size as int */
613
614                     case 'L':   /* long double - ignore this one */
615                         GetFormat ();
616                 }
617
618                 /* 4. Conversion specifier */
619                 switch (F) {
620                     /* 'd' and 'u' conversions are actually the same, since the
621                      * standard says that even the 'u' modifier allows an
622                      * optionally signed integer.
623                      */
624                     case 'd':   /* Optionally signed decimal integer */
625                     case 'u':
626                         ScanInt (10);
627                         break;
628
629                     case 'i':
630                         /* Optionally signed integer with a base */
631                         ScanInt (0);
632                         break;
633
634                     case 'o':
635                         /* Optionally signed octal integer */
636                         ScanInt (8);
637                         break;
638
639                     case 'x':
640                     case 'X':
641                         /* Optionally signed hexadecimal integer */
642                         ScanInt (16);
643                         break;
644
645                     case 's':
646                         /* Whitespace-terminated string */
647                         SkipWhite ();
648                         CheckEnd ();       /* Is it an input failure? */
649                         Converted = true;  /* No, conversion will succeed */
650                         if (NoAssign == false) {
651                             S = va_arg (ap, char*);
652                         }
653                         while (C != EOF
654                                && (bool) isspace (C) == false
655                                && ++Width) {
656                             if (NoAssign == false) {
657                                 *S++ = C;
658                             }
659                             ReadChar ();
660                         }
661                         /* Terminate the string just read */
662                         if (NoAssign == false) {
663                             *S = '\0';
664                             ++Assignments;
665                         }
666                         break;
667
668                     case 'c':
669                         /* Fixed-length string, NOT zero-terminated */
670                         if (HaveWidth == false) {
671                             /* No width given, default is 1 */
672                             Width = ~1u;
673                         }
674                         CheckEnd ();       /* Is it an input failure? */
675                         Converted = true;  /* No, at least 1 char. available */
676                         if (NoAssign == false) {
677                             S = va_arg (ap, char*);
678                             /* ## This loop is convenient for us, but it isn't
679                              * standard C.  The standard implies that a failure
680                              * shouldn't put anything into the array argument.
681                              */
682                             while (++Width) {
683                                 CheckEnd ();  /* Is it a matching failure? */
684                                 *S++ = C;
685                                 ReadChar ();
686                             }
687                             ++Assignments;
688                         } else {
689                             /* Just skip as many chars as given */
690                             while (++Width) {
691                                 CheckEnd ();  /* Is it a matching failure? */
692                                 ReadChar ();
693                             }
694                         }
695                         break;
696
697                     case '[':
698                         /* String using characters from a set */
699                         /* Clear the set */
700                         memset (CharSet, 0, sizeof (CharSet));
701                         /* Skip the left-bracket, and test for inversion. */
702                         Invert = (GetFormat () == '^');
703                         if (Invert) {
704                             GetFormat ();
705                         }
706                         if (F == ']') {
707                             /* Empty sets aren't allowed; so, a right-bracket
708                              * at the beginning must be a member of the set.
709                              */
710                             AddCharToSet (F);
711                             GetFormat ();
712                         }
713                         /* Read the characters that are part of the set */
714                         while (F != '\0' && F != ']') {
715                             if (*format == '-') {  /* Look ahead at next char. */
716                                 /* A range. Get start and end, skip the '-' */
717                                 Start = F;
718                                 ++format;
719                                 switch (GetFormat ()) {
720                                     case '\0':
721                                     case ']':
722                                         /* '-' as last char means:  include '-' */
723                                         AddCharToSet (Start);
724                                         AddCharToSet ('-');
725                                         break;
726                                     default:
727                                         /* Include all characters
728                                          * that are in the range.
729                                          */
730                                         while (1) {
731                                             AddCharToSet (Start);
732                                             if (Start == F) {
733                                                 break;
734                                             }
735                                             ++Start;
736                                         }
737                                         /* Get next char after range */
738                                         GetFormat ();
739                                 }
740                             } else {
741                                 /* Just a character */
742                                 AddCharToSet (F);
743                                 /* Get next char */
744                                 GetFormat ();
745                             }
746                         }
747                         /* Don't go beyond the end of the format string. */
748                         /* (Maybe, this should mean an invalid specification.) */
749                         if (F == '\0') {
750                             --format;
751                         }
752
753                         /* Invert the set if requested */
754                         if (Invert) {
755                             InvertCharSet ();
756                         }
757
758                         /* We have the set in CharSet. Read characters and
759                          * store them into a string while they are part of
760                          * the set.
761                          */
762                         Match = false;
763                         if (NoAssign == false) {
764                             S = va_arg (ap, char*);
765                         }
766                         while (IsCharInSet () && ++Width) {
767                             if (NoAssign == false) {
768                                 *S++ = C;
769                             }
770                             Match = Converted = true;
771                             ReadChar ();
772                         }
773                         /* At least one character must match the set. */
774                         if (Match == false) {
775                             goto NoConv;
776                         }
777                         if (NoAssign == false) {
778                             *S = '\0';
779                             ++Assignments;
780                         }
781                         break;
782
783                     case 'p':
784                         /* Pointer, general format is 0xABCD.
785                          * %hhp --> zero-page pointer
786                          * %hp --> near pointer
787                          * %lp --> far pointer
788                          */
789                         SkipWhite ();
790                         if (CHAR (C) != '0') {
791                             goto NoConv;
792                         }
793                         Converted = true;
794                         ReadChar ();
795                         switch (CHAR (C)) {
796                             case 'x':
797                             case 'X':
798                                 break;
799                             default:
800                                 goto NoConv;
801                         }
802                         ReadChar ();
803                         ReadInt (16);
804                         AssignInt ();
805                         break;
806
807                     case 'n':
808                         /* Store the number of characters consumed so far
809                          * (the read-ahead character hasn't been consumed).
810                          */
811                         IntVal = (long) (CharCount - (C == EOF ? 0u : 1u));
812                         AssignInt ();
813                         /* Don't count it. */
814                         if (NoAssign == false) {
815                             --Assignments;
816                         }
817                         break;
818
819                     case 'S':
820                     case 'C':
821                         /* Wide characters */
822
823                     case 'a':
824                     case 'A':
825                     case 'e':
826                     case 'E':
827                     case 'f':
828                     case 'F':
829                     case 'g':
830                     case 'G':
831                         /* Optionally signed float */
832
833                         /* Those 2 groups aren't implemented. */
834                         _seterrno (ENOSYS);
835                         Assignments = EOF;
836                         PushBack ();
837                         return Assignments;
838
839                     default:
840                         /* Invalid specification */
841                         _seterrno (EINVAL);
842                         Assignments = EOF;
843                         PushBack ();
844                         return Assignments;
845                 }
846             }
847         }
848     } else {
849 NoConv:
850
851         /* Coming here means a failure. If that happens at EOF, with no
852          * conversion attempts, then it is considered an error; otherwise,
853          * the number of assignments is returned (the default behaviour).
854          */
855         if (C == EOF && Converted == false) {
856             Assignments = EOF;  /* Special case:  error */
857         }
858     }
859
860     /* Put the read-ahead character back into the input stream. */
861     PushBack ();
862
863     /* Return the number of conversion-and-assignments. */
864     return Assignments;
865 }
866
867
868