]> pd.if.org Git - pdclib/blob - draft.c
Skeleton implementation for now.
[pdclib] / draft.c
1 #include <stdarg.h>
2 #include <stddef.h>
3 #include <stdint.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6
7 /* These can be removed once integrated into PDCLIB make procedure */
8 #undef TEST
9 #include </home/solar/src/pdclib/functions/_PDCLIB/digits.c>
10 #include </home/solar/src/pdclib/functions/_PDCLIB/Xdigits.c>
11
12 /* Using an integer's bits as flags for both the conversion flags and length
13    modifiers.
14 */
15 #define E_minus    1<<0
16 #define E_plus     1<<1
17 #define E_alt      1<<2
18 #define E_space    1<<3
19 #define E_zero     1<<4
20 #define E_done     1<<5
21 #define E_char     1<<6
22 #define E_short    1<<7
23 #define E_long     1<<8
24 #define E_llong    1<<9
25 #define E_intmax   1<<10
26 #define E_size     1<<11
27 #define E_ptrdiff  1<<12
28 #define E_intptr   1<<13
29 #define E_double   1<<14
30 #define E_lower    1<<15
31 #define E_unsigned 1<<16
32
33 struct status_t
34 {
35     int           base;  /* base to which the value shall be converted       */
36     int_fast32_t  flags; /* flags and length modifiers                       */
37     size_t        n;     /* maximum number of characters to be written       */
38     size_t        i;     /* number of characters already written             */
39     size_t        this;  /* number of output chars in the current conversion */
40     char *        s;     /* target buffer                                    */
41     size_t        width; /* width of current field                           */
42     size_t        prec;  /* precision of current field                       */
43     FILE *        stream;/* for to-stream output                             */
44     va_list       ap;    /* the argument stack passed to the printf function */
45 };
46
47 const char * parse_out( const char * spec, struct status_t * status );
48 inline void test( size_t n, const char * expect, ... );
49 int _PDCLIB_vsnprintf( char * buffer, size_t n, const char * format, va_list ap );
50 int _PDCLIB_snprintf( char * s, size_t n, const char * format, ... );
51
52 /* The following only for testing. */
53 #include <limits.h>
54 #include <string.h>
55
56 int main( void )
57 {
58     test( SIZE_MAX, "%hhd", CHAR_MIN );
59     test( SIZE_MAX, "%hhd", CHAR_MAX );
60     test( SIZE_MAX, "%hhd", 0 );
61     test( SIZE_MAX, "%hd", SHRT_MIN );
62     test( SIZE_MAX, "%hd", SHRT_MAX );
63     test( SIZE_MAX, "%hd", 0 );
64     test( SIZE_MAX, "%d", INT_MIN );
65     test( SIZE_MAX, "%d", INT_MAX );
66     test( SIZE_MAX, "%d", 0 );
67     test( SIZE_MAX, "%ld", LONG_MIN );
68     test( SIZE_MAX, "%ld", LONG_MAX );
69     test( SIZE_MAX, "%ld", 0l );
70     test( SIZE_MAX, "%lld", LLONG_MIN );
71     test( SIZE_MAX, "%lld", LLONG_MAX );
72     test( SIZE_MAX, "%lld", 0ll );
73     test( SIZE_MAX, "%hhu", UCHAR_MAX );
74     test( SIZE_MAX, "%hhu", (unsigned char)-1 );
75     test( SIZE_MAX, "%hu", USHRT_MAX );
76     test( SIZE_MAX, "%hu", (unsigned short)-1 );
77     test( SIZE_MAX, "%u", UINT_MAX );
78     test( SIZE_MAX, "%u", -1u );
79     test( SIZE_MAX, "%lu", ULONG_MAX );
80     test( SIZE_MAX, "%lu", -1ul );
81     test( SIZE_MAX, "%llu", ULLONG_MAX );
82     test( SIZE_MAX, "%llu", -1ull );
83     test( SIZE_MAX, "%X", UINT_MAX );
84     test( SIZE_MAX, "%#X", -1u );
85     test( SIZE_MAX, "%x", UINT_MAX );
86     test( SIZE_MAX, "%#x", -1u );
87     test( SIZE_MAX, "%o", UINT_MAX );
88     test( SIZE_MAX, "%#o", -1u );
89     test( SIZE_MAX, "%.0#o", 0 );
90     test( SIZE_MAX, "%+d", INT_MIN );
91     test( SIZE_MAX, "%+d", INT_MAX );
92     test( SIZE_MAX, "%+d", 0 );
93     test( SIZE_MAX, "%+u", UINT_MAX );
94     test( SIZE_MAX, "%+u", -1u );
95     test( SIZE_MAX, "% d", INT_MIN );
96     test( SIZE_MAX, "% d", INT_MAX );
97     test( SIZE_MAX, "% d", 0 );
98     test( SIZE_MAX, "% u", UINT_MAX );
99     test( SIZE_MAX, "% u", -1u );
100     test( SIZE_MAX, "%9d", INT_MIN );
101     test( SIZE_MAX, "%9d", INT_MAX );
102     test( SIZE_MAX, "%10d", INT_MIN );
103     test( SIZE_MAX, "%10d", INT_MAX );
104     test( SIZE_MAX, "%11d", INT_MIN );
105     test( SIZE_MAX, "%11d", INT_MAX );
106     test( SIZE_MAX, "%12d", INT_MIN );
107     test( SIZE_MAX, "%12d", INT_MAX );
108     test( SIZE_MAX, "%-9d", INT_MIN );
109     test( SIZE_MAX, "%-9d", INT_MAX );
110     test( SIZE_MAX, "%-10d", INT_MIN );
111     test( SIZE_MAX, "%-10d", INT_MAX );
112     test( SIZE_MAX, "%-11d", INT_MIN );
113     test( SIZE_MAX, "%-11d", INT_MAX );
114     test( SIZE_MAX, "%-12d", INT_MIN );
115     test( SIZE_MAX, "%-12d", INT_MAX );
116     test( SIZE_MAX, "%09d", INT_MIN );
117     test( SIZE_MAX, "%09d", INT_MAX );
118     test( SIZE_MAX, "%010d", INT_MIN );
119     test( SIZE_MAX, "%010d", INT_MAX );
120     test( SIZE_MAX, "%011d", INT_MIN );
121     test( SIZE_MAX, "%011d", INT_MAX );
122     test( SIZE_MAX, "%012d", INT_MIN );
123     test( SIZE_MAX, "%012d", INT_MAX );
124     test( SIZE_MAX, "%-09d", INT_MIN );
125     test( SIZE_MAX, "%-09d", INT_MAX );
126     test( SIZE_MAX, "%-010d", INT_MIN );
127     test( SIZE_MAX, "%-010d", INT_MAX );
128     test( SIZE_MAX, "%-011d", INT_MIN );
129     test( SIZE_MAX, "%-011d", INT_MAX );
130     test( SIZE_MAX, "%-012d", INT_MIN );
131     test( SIZE_MAX, "%-012d", INT_MAX );
132     test( 8, "%9d", INT_MAX );
133     test( 8, "%9d", INT_MIN );
134     test( 9, "%9d", INT_MAX );
135     test( 9, "%9d", INT_MIN );
136     test( 10, "%9d", INT_MAX );
137     test( 10, "%9d", INT_MIN );
138     test( 9, "%10d", INT_MAX );
139     test( 9, "%10d", INT_MIN );
140     test( 10, "%10d", INT_MAX );
141     test( 10, "%10d", INT_MIN );
142     test( 11, "%10d", INT_MAX );
143     test( 11, "%10d", INT_MIN );
144     test( 10, "%11d", INT_MAX );
145     test( 10, "%11d", INT_MIN );
146     test( 11, "%11d", INT_MAX );
147     test( 11, "%11d", INT_MIN );
148     test( 12, "%11d", INT_MAX );
149     test( 12, "%11d", INT_MIN );
150     test( 11, "%12d", INT_MAX );
151     test( 11, "%12d", INT_MIN );
152     test( 12, "%12d", INT_MAX );
153     test( 12, "%12d", INT_MIN );
154     test( 13, "%12d", INT_MAX );
155     test( 13, "%12d", INT_MIN );
156     test( SIZE_MAX, "%030.20d", INT_MAX );
157     test( SIZE_MAX, "%.6x", UINT_MAX );
158     test( SIZE_MAX, "%#6.3x", UINT_MAX );
159     test( SIZE_MAX, "%#3.6x", UINT_MAX );
160     test( SIZE_MAX, "%.6d", INT_MIN );
161     test( SIZE_MAX, "%6.3d", INT_MIN );
162     test( SIZE_MAX, "%3.6d", INT_MIN );
163     test( SIZE_MAX, "%#0.6x", UINT_MAX );
164     test( SIZE_MAX, "%#06.3x", UINT_MAX );
165     test( SIZE_MAX, "%#03.6x", UINT_MAX );
166     test( SIZE_MAX, "%#0.6d", INT_MAX );
167     test( SIZE_MAX, "%#06.3d", INT_MAX );
168     test( SIZE_MAX, "%#03.6d", INT_MAX );
169     test( SIZE_MAX, "%#+.6d", INT_MAX );
170     test( SIZE_MAX, "%#+6.3d", INT_MAX );
171     test( SIZE_MAX, "%#+3.6d", INT_MAX );
172     test( SIZE_MAX, "%+0.6d", INT_MAX );
173     test( SIZE_MAX, "%+06.3d", INT_MAX );
174     test( SIZE_MAX, "%+03.6d", INT_MAX );
175     test( SIZE_MAX, "- %d", INT_MAX );
176     test( SIZE_MAX, "- %d %% %d", INT_MAX, INT_MIN );
177     test( SIZE_MAX, "%c", 'x' );
178     test( SIZE_MAX, "%s", "abcdef" );
179     test( SIZE_MAX, "%p", 0xdeadbeef );
180     {
181         char buffer[50];
182         int val1, val2, val3, val4;
183         snprintf( buffer, SIZE_MAX, "123456%n789%n", &val1, &val2 );
184         _PDCLIB_snprintf( buffer, SIZE_MAX, "123456%n789%n", &val3, &val4 );
185         if ( ( val1 != val3 ) || ( val2 != val4 ) )
186         {
187             printf( "Output %d/%d\nExpect %d/%d\n\n", val1, val2, val3, val4 );
188         }
189     }
190     return 0;
191 }
192
193 /* This macro delivers a given character to either a memory buffer or a stream,
194    depending on the contents of 'status' (struct status_t).
195    x - the character to be delivered
196    i - pointer to number of characters already delivered in this call
197    n - pointer to maximum number of characters to be delivered in this call
198    s - the buffer into which the character shall be delivered
199 */
200 #define DELIVER( x ) do { if ( status->i < status->n ) { if ( status->stream != NULL ) putc( x, status->stream ); else status->s[status->i] = x; } ++(status->i); } while ( 0 )
201
202 /* This function recursively converts a given integer value to a given base
203    into a character string. Persistent information - like the number of digits
204    parsed so far - is recorded in a struct status_t, which allows to avoid
205    overwriting snprintf() limits, and enables the function to do the necessary
206    padding / prefixing of the character string eventually printed.
207 */
208 static void int2base( intmax_t value, struct status_t * status )
209 {
210     /* Registering the character being printed at the end of the function here
211        already so it will be taken into account when the deepestmost recursion
212        does the prefix / padding stuff.
213     */
214     ++(status->this);
215     if ( ( value / status->base ) != 0 )
216     {
217         /* More digits to be done - recurse deeper */
218         int2base( value / status->base, status );
219     }
220     else
221     {
222         /* We reached the last digit, the deepest point of our recursion, and
223            only now know how long the number to be printed actually is. Now we
224            have to do the sign, prefix, width, and precision padding stuff
225            before printing the numbers while we resurface from the recursion.
226         */
227         /* At worst, we need two prefix characters (hex prefix). */
228         char preface[3] = "\0";
229         size_t preidx = 0;
230         if ( ( status->flags & E_alt ) && ( status->base == 16 || status->base == 8 ) )
231         {
232             /* Octal / hexadecimal prefix for "%#" conversions */
233             preface[ preidx++ ] = '0';
234             if ( status->base == 16 )
235             {
236                 preface[ preidx++ ] = ( status->flags & E_lower ) ? 'x' : 'X';
237             }
238         }
239         if ( value < 0 )
240         {
241             /* Negative sign for negative values - at all times. */
242             preface[ preidx++ ] = '-';
243         }
244         else if ( ! ( status->flags & E_unsigned ) )
245         {
246             /* plus sign / extra space are only for unsigned conversions */
247             if ( status->flags & E_plus )
248             {
249                 preface[ preidx++ ] = '+';
250             }
251             else if ( status->flags & E_space )
252             {
253                 preface[ preidx++ ] = ' ';
254             }
255         }
256         {
257         size_t prec_pads = ( status->prec > status->this ) ? ( status->prec - status->this ) : 0;
258         if ( ! ( status->flags & ( E_minus | E_zero ) ) )
259         {
260             /* Space padding is only done if no zero padding or left alignment
261                is requested. Leave space for any prefixes determined above.
262             */
263             /* The number of characters to be printed, plus prefixes if any. */
264             /* This line contained probably the most stupid, time-wasting bug
265                I've ever perpetrated. Greetings to Samface, DevL, and all
266                sceners at Breakpoint 2006.
267             */
268             size_t characters = preidx + ( ( status->this > status->prec ) ? status->this : status->prec );
269             if ( status->width > characters )
270             {
271                 for ( int i = 0; i < status->width - characters; ++i )
272                 {
273                     DELIVER( ' ' );
274                     ++(status->this);
275                 }
276             }
277         }
278         /* Now we did the padding, do the prefixes (if any). */
279         preidx = 0;
280         while ( preface[ preidx ] != '\0' )
281         {
282             DELIVER( preface[ preidx++ ] );
283             ++(status->this);
284         }
285         if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )
286         {
287             /* If field is not left aligned, and zero padding is requested, do
288                so.
289             */
290             while ( status->this < status->width )
291             {
292                 DELIVER( '0' );
293                 ++(status->this);
294             }
295         }
296         /* Do the precision padding if necessary. */
297         for ( int i = 0; i < prec_pads; ++i )
298         {
299             DELIVER( '0' );
300         }
301         }
302     }
303     /* Recursion tail - print the current digit. */
304     {
305     int digit = value % status->base;
306     if ( digit < 0 )
307     {
308         digit *= -1;
309     }
310     if ( status->flags & E_lower )
311     {
312         /* Lowercase letters. Same array used for strto...(). */
313         DELIVER( _PDCLIB_digits[ digit ] );
314     }
315     else
316     {
317         /* Uppercase letters. Array only used here, only 0-F. */
318         DELIVER( _PDCLIB_Xdigits[ digit ] );
319     }
320     }
321 }
322
323 /* This function is to be called with spec pointing to the leading '%' of a
324    printf() conversion specifier, with ap being 
325 */
326 const char * parse_out( const char * spec, struct status_t * status )
327 {
328     const char * orig_spec = spec;
329     if ( *(++spec) == '%' )
330     {
331         DELIVER( *spec );
332         return ++spec;
333     }
334     /* Initializing status structure */
335     status->flags = 0;
336     status->base  = 0;
337     status->this  = 0;
338     status->width = 0;
339     status->prec  = 0;
340
341     /* First come 0..n flags */
342     do
343     {
344         switch ( *spec )
345         {
346             case '-':
347                 status->flags |= E_minus;
348                 ++spec;
349                 break;
350             case '+':
351                 status->flags |= E_plus;
352                 ++spec;
353                 break;
354             case '#':
355                 status->flags |= E_alt;
356                 ++spec;
357                 break;
358             case ' ':
359                 status->flags |= E_space;
360                 ++spec;
361                 break;
362             case '0':
363                 status->flags |= E_zero;
364                 ++spec;
365                 break;
366             default:
367                 status->flags |= E_done;
368                 break;
369         }
370     } while ( ! ( status->flags & E_done ) );
371
372     /* Optional field width */
373     if ( *spec == '*' )
374     {
375         /* Retrieve width value from argument stack */
376         if ( ( status->width = va_arg( status->ap, int ) ) < 0 )
377         {
378             /* Negative value is '-' flag plus absolute value */
379             status->flags |= E_minus;
380             status->width *= -1;
381         }
382         ++spec;
383     }
384     else
385     {
386         /* If a width is given, strtol() will return its value. If not given,
387            strtol() will return zero. In both cases, endptr will point to the
388            rest of the conversion specifier - just what we need.
389         */
390         status->width = (int)strtol( spec, (char**)&spec, 10 );
391     }
392
393     /* Optional precision */
394     if ( *spec == '.' )
395     {
396         ++spec;
397         if ( *spec == '*' )
398         {
399             /* Retrieve precision value from argument stack. A negative value
400                is as if no precision is given - as precision is initalized to
401                EOF (negative), there is no need for testing for negative here.
402             */
403             status->prec = va_arg( status->ap, int );
404         }
405         else
406         {
407             char * endptr;
408             status->prec = (int)strtol( spec, &endptr, 10 );
409             if ( spec == endptr )
410             {
411                 /* Decimal point but no number - bad conversion specifier. */
412                 return orig_spec;
413             }
414             spec = endptr;
415         }
416         /* Having a precision cancels out any zero flag. */
417         status->flags ^= E_zero;
418     }
419
420     /* Optional length modifier
421        We step one character ahead in any case, and step back only if we find
422        there has been no length modifier (or step ahead another character if it
423        has been "hh" or "ll").
424     */
425     switch ( *(spec++) )
426     {
427         case 'h':
428             if ( *spec == 'h' )
429             {
430                 status->flags |= E_char;
431                 ++spec;
432             }
433             else
434             {
435                 status->flags |= E_short;
436             }
437             break;
438         case 'l':
439             if ( *spec == 'l' )
440             {
441                 status->flags |= E_llong;
442                 ++spec;
443             }
444             else
445             {
446                 status->flags |= E_long;
447             }
448             break;
449         case 'j':
450             status->flags |= E_intmax;
451             break;
452         case 'z':
453             status->flags |= E_size;
454             break;
455         case 't':
456             status->flags |= E_ptrdiff;
457             break;
458         case 'L':
459             status->flags |= E_double;
460             break;
461         default:
462             --spec;
463             break;
464     }
465
466     /* Conversion specifier */
467     switch ( *spec )
468     {
469         case 'd':
470             /* FALLTHROUGH */
471         case 'i':
472             status->base = 10;
473             break;
474         case 'o':
475             status->base = 8;
476             status->flags |= E_unsigned;
477             break;
478         case 'u':
479             status->base = 10;
480             status->flags |= E_unsigned;
481             break;
482         case 'x':
483             status->base = 16;
484             status->flags |= ( E_lower | E_unsigned );
485             break;
486         case 'X':
487             status->base = 16;
488             status->flags |= E_unsigned;
489             break;
490         case 'f':
491         case 'F':
492         case 'e':
493         case 'E':
494         case 'g':
495         case 'G':
496             break;
497         case 'a':
498         case 'A':
499             break;
500         case 'c':
501             /* TODO: Flags, wide chars. */
502             DELIVER( va_arg( status->ap, int ) );
503             return ++spec;
504         case 's':
505             /* TODO: Flags, wide chars. */
506             {
507                 char * s = va_arg( status->ap, char * );
508                 while ( *s != '\0' )
509                 {
510                     DELIVER( *(s++) );
511                 }
512                 return ++spec;
513             }
514         case 'p':
515             /* TODO: E_long -> E_intptr */
516             status->base = 16;
517             status->flags |= ( E_lower | E_unsigned | E_alt | E_long );
518             break;
519         case 'n':
520            {
521                int * val = va_arg( status->ap, int * );
522                *val = status->i;
523                return ++spec;
524            }
525         default:
526             /* No conversion specifier. Bad conversion. */
527             return orig_spec;
528     }
529
530     /* Do the actual output based on our findings */
531     if ( status->base != 0 )
532     {
533         /* Integer conversions */
534         /* TODO: Check for invalid flag combinations. */
535         if ( status->flags & E_unsigned )
536         {
537             uintmax_t value;
538             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_size ) )
539             {
540                 case E_char:
541                     value = (uintmax_t)(unsigned char)va_arg( status->ap, int );
542                     break;
543                 case E_short:
544                     value = (uintmax_t)(unsigned short)va_arg( status->ap, int );
545                     break;
546                 case 0:
547                     value = (uintmax_t)va_arg( status->ap, unsigned int );
548                     break;
549                 case E_long:
550                     value = (uintmax_t)va_arg( status->ap, unsigned long );
551                     break;
552                 case E_llong:
553                     value = (uintmax_t)va_arg( status->ap, unsigned long long );
554                     break;
555                 case E_size:
556                     value = (uintmax_t)va_arg( status->ap, size_t );
557                     break;
558             }
559             ++(status->this);
560             if ( ( value / status->base ) != 0 )
561             {
562                 int2base( (intmax_t)(value / status->base), status );
563             }
564             int digit = value % status->base;
565             if ( digit < 0 )
566             {
567                 digit *= -1;
568             }
569             if ( status->flags & E_lower )
570             {
571                 DELIVER( _PDCLIB_digits[ digit ] );
572             }
573             else
574             {
575                 DELIVER( _PDCLIB_Xdigits[ digit ] );
576             }
577         }
578         else
579         {
580             switch ( status->flags & ( E_char | E_short | E_long | E_llong | E_intmax ) )
581             {
582                 case E_char:
583                     int2base( (intmax_t)(char)va_arg( status->ap, int ), status );
584                     break;
585                 case E_short:
586                     int2base( (intmax_t)(short)va_arg( status->ap, int ), status );
587                     break;
588                 case 0:
589                     int2base( (intmax_t)va_arg( status->ap, int ), status );
590                     break;
591                 case E_long:
592                     int2base( (intmax_t)va_arg( status->ap, long ), status );
593                     break;
594                 case E_llong:
595                     int2base( (intmax_t)va_arg( status->ap, long long ), status );
596                     break;
597                 case E_ptrdiff:
598                     int2base( (intmax_t)va_arg( status->ap, ptrdiff_t ), status );
599                     break;
600                 case E_intmax:
601                     int2base( va_arg( status->ap, intmax_t ), status );
602                     break;
603             }
604         }
605         if ( status->flags & E_minus )
606         {
607             while ( status->this < status->width )
608             {
609                 DELIVER( ' ' );
610                 ++(status->this);
611             }
612         }
613         if ( status->i >= status->n )
614         {
615             status->s[status->n - 1] = '\0';
616         }
617     }
618     return ++spec;
619 }
620
621 inline void test( size_t n, const char * expect, ... )
622 {
623     char * buffer1 = malloc( 50 );
624     char * buffer2 = malloc( 50 );
625     int myrc;
626     int rc;
627     va_list ap;
628     va_start( ap, expect );
629     myrc = _PDCLIB_vsnprintf( buffer1, n, expect, ap );
630     rc = vsnprintf( buffer2, n, expect, ap );
631     if ( ( strcmp( buffer1, buffer2 ) != 0 ) || ( myrc != rc ) )
632     {
633         printf( "Output '%s', RC %d\nExpect '%s', RC %d\n\n", buffer1, myrc, buffer2, rc );
634     }
635     free( buffer1 );
636     free( buffer2 );
637 }
638
639 int _PDCLIB_vsnprintf( char * buffer, size_t n, const char * format, va_list ap )
640 {
641     struct status_t status = { 0, 0, n, 0, 0, buffer, 0, 0, NULL, ap };
642     while ( *format != '\0' )
643     {
644         const char * rc;
645         if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status ) ) == format ) )
646         {
647             /* No conversion specifier, print verbatim */
648             buffer[ status.i++ ] = *(format++);
649         }
650         else
651         {
652             /* Continue parsing after conversion specifier */
653             format = rc;
654         }
655     }
656     buffer[ status.i ] = '\0';
657     return status.i;
658 }
659
660 int _PDCLIB_snprintf( char * s, size_t n, const char * format, ... )
661 {
662     va_list ap;
663     va_start( ap, format );
664     return _PDCLIB_vsnprintf( s, n, format, ap );
665 }
666
667 #if 0
668 int _PDCLIB_fprintf( FILE * stream, const char * format, va_list ap )
669 {
670     struct status_t status = { 0, 0, SIZE_MAX, 0, 0, NULL, 0, 0, stream, ap };
671     while ( *format != '\0' )
672     {
673         const char * rc;
674         if ( ( *format != '%' ) || ( ( rc = parse_out( format, &status, ap ) ) == format ) )
675         {
676             /* No conversion specifier, print verbatim */
677             putc( *(format++), stream );
678         }
679         else
680         {
681             /* Continue parsing after conversion specifier */
682             format = rc;
683         }
684     }
685     return status.i;
686 }
687 #endif