Path: blob/master/waterbox/libc/functions/stdio/_PDCLIB_print.c
2 views
/* _PDCLIB_print( const char *, struct _PDCLIB_status_t * )12This file is part of the Public Domain C Library (PDCLib).3Permission is granted to use, modify, and / or redistribute at will.4*/56#include <stdio.h>7#include <stdint.h>8#include <stdarg.h>9#include <string.h>10#include <stdlib.h>11#include <stddef.h>12#include <stdbool.h>13#include <limits.h>1415#ifndef REGTEST16#include "_PDCLIB_io.h"1718/* Using an integer's bits as flags for both the conversion flags and length19modifiers.20*/21/* FIXME: one too many flags to work on a 16-bit machine, join some (e.g. the22width flags) into a combined field.23*/24#define E_minus (1<<0)25#define E_plus (1<<1)26#define E_alt (1<<2)27#define E_space (1<<3)28#define E_zero (1<<4)29#define E_done (1<<5)3031#define E_char (1<<6)32#define E_short (1<<7)33#define E_long (1<<8)34#define E_llong (1<<9)35#define E_intmax (1<<10)36#define E_size (1<<11)37#define E_ptrdiff (1<<12)38#define E_intptr (1<<13)3940#define E_ldouble (1<<14)4142#define E_lower (1<<15)43#define E_unsigned (1<<16)4445#define E_TYPES (E_char | E_short | E_long | E_llong | E_intmax \46| E_size | E_ptrdiff | E_intptr)4748/* returns true if callback-based output succeeded; else false */49static inline bool cbout(50struct _PDCLIB_status_t * status,51const void * buf,52size_t size )53{54size_t rv = status->write( status->ctx, buf, size );55status->i += rv;56status->current += rv;57return rv == size;58}5960/* repeated output of a single character */61static inline bool cbrept(62struct _PDCLIB_status_t * status,63char c,64size_t times )65{66if ( sizeof(size_t) == 8 && CHAR_BIT == 8)67{68uint64_t spread = UINT64_C(0x0101010101010101) * c;69while ( times )70{71size_t n = times > 8 ? 8 : times;72if ( !cbout( status, &spread, n ) )73return false;74times -= n;75}76return true;77}78else if ( sizeof(size_t) == 4 && CHAR_BIT == 8)79{80uint32_t spread = UINT32_C(0x01010101) * c;81while ( times )82{83size_t n = times > 4 ? 4 : times;84if ( !cbout( status, &spread, n ) )85return false;86times -= n;87}88return true;89}90else91{92while ( times )93{94if ( !cbout( status, &c, 1) )95return false;96times--;97}98return true;99}100}101102103/* Maximum number of output characters =104* number of bits in (u)intmax_t / number of bits per character in smallest105* base. Smallest base is octal, 3 bits/char.106*107* Additionally require 2 extra characters for prefixes108*109* Returns false if an I/O error occured.110*/111static const size_t maxIntLen = sizeof(intmax_t) * CHAR_BIT / 3 + 1;112113static bool int2base( uintmax_t value, struct _PDCLIB_status_t * status )114{115char sign = 0;116if ( ! ( status->flags & E_unsigned ) )117{118intmax_t signval = (intmax_t) value;119bool negative = signval < 0;120value = signval < 0 ? -signval : signval;121122if ( negative )123{124sign = '-';125}126else if ( status->flags & E_plus )127{128sign = '+';129}130else if (status->flags & E_space )131{132sign = ' ';133}134}135136// The user could theoretically ask for a silly buffer length here.137// Perhaps after a certain size we should malloc? Or do we refuse to protect138// them from their own stupidity?139size_t bufLen = (status->width > maxIntLen ? status->width : maxIntLen) + 2;140char outbuf[bufLen];141char * outend = outbuf + bufLen;142int written = 0;143144// Build up our output string - backwards145{146const char * digits = (status->flags & E_lower) ?147_PDCLIB_digits : _PDCLIB_Xdigits;148uintmax_t remaining = value;149if(status->prec != 0 || remaining != 0) do {150uintmax_t digit = remaining % status->base;151remaining /= status->base;152153outend[-++written] = digits[digit];154} while(remaining != 0);155}156157// Pad field out to the precision specification158while( (long) written < status->prec ) outend[-++written] = '0';159160// If a field width specified, and zero padding was requested, then pad to161// the field width162unsigned padding = 0;163if ( ( ! ( status->flags & E_minus ) ) && ( status->flags & E_zero ) )164{165while( written < (int) status->width )166{167outend[-++written] = '0';168padding++;169}170}171172// Prefixes173if ( sign != 0 )174{175if ( padding == 0 ) written++;176outend[-written] = sign;177}178else if ( status->flags & E_alt )179{180switch ( status->base )181{182case 8:183if ( outend[-written] != '0' ) outend[-++written] = '0';184break;185case 16:186// No prefix if zero187if ( value == 0 ) break;188189written += padding < 2 ? 2 - padding : 0;190outend[-written ] = '0';191outend[-written + 1] = (status->flags & E_lower) ? 'x' : 'X';192break;193default:194break;195}196}197198// Space padding to field width199if ( ! ( status->flags & ( E_minus | E_zero ) ) )200{201while( written < (int) status->width ) outend[-++written] = ' ';202}203204// Write output205return cbout( status, outend - written, written );206}207208/* print a string. returns false if an I/O error occured */209static bool printstr( const char * str, struct _PDCLIB_status_t * status )210{211size_t len = status->prec >= 0 ? strnlen( str, status-> prec)212: strlen(str);213214if ( status->width == 0 || status->flags & E_minus )215{216// Simple case or left justification217if ( status->prec > 0 )218{219len = (unsigned) status->prec < len ? (unsigned) status->prec : len;220}221222if ( !cbout( status, str, len ) )223return false;224225/* right padding */226if ( status->width > status->current ) {227len = status->width - status->current;228229if ( !cbrept( status, ' ', len ) )230return false;231}232} else {233// Right justification234235if ( status->width > len ) {236size_t padding = status->width - len;237238if ( !cbrept( status, ' ', padding ))239return false;240}241242if ( !cbout( status, str, len ) )243return false;244}245246return true;247}248249static bool printchar( char chr, struct _PDCLIB_status_t * status )250{251if( ! ( status->flags & E_minus ) )252{253// Right justification254if ( status-> width ) {255size_t justification = status->width - status->current - 1;256if ( !cbrept( status, ' ', justification ))257return false;258}259260if ( !cbout( status, &chr, 1 ))261return false;262} else {263// Left justification264265if ( !cbout( status, &chr, 1 ))266return false;267268if ( status->width > status->current ) {269if ( !cbrept( status, ' ', status->width - status->current ) )270return false;271}272}273274return true;275}276277int _PDCLIB_print( const char * spec, struct _PDCLIB_status_t * status )278{279const char * orig_spec = spec;280if ( *(++spec) == '%' )281{282/* %% -> print single '%' */283if ( !cbout(status, spec, 1) )284return -1;285++spec;286return (spec - orig_spec);287}288/* Initializing status structure */289status->flags = 0;290status->base = 0;291status->current = 0;292status->width = 0;293status->prec = EOF;294295/* First come 0..n flags */296do297{298switch ( *spec )299{300case '-':301/* left-aligned output */302status->flags |= E_minus;303++spec;304break;305case '+':306/* positive numbers prefixed with '+' */307status->flags |= E_plus;308++spec;309break;310case '#':311/* alternative format (leading 0x for hex, 0 for octal) */312status->flags |= E_alt;313++spec;314break;315case ' ':316/* positive numbers prefixed with ' ' */317status->flags |= E_space;318++spec;319break;320case '0':321/* right-aligned padding done with '0' instead of ' ' */322status->flags |= E_zero;323++spec;324break;325default:326/* not a flag, exit flag parsing */327status->flags |= E_done;328break;329}330} while ( ! ( status->flags & E_done ) );331332/* Optional field width */333if ( *spec == '*' )334{335/* Retrieve width value from argument stack */336int width = va_arg( status->arg, int );337if ( width < 0 )338{339status->flags |= E_minus;340status->width = abs( width );341}342else343{344status->width = width;345}346++spec;347}348else349{350/* If a width is given, strtol() will return its value. If not given,351strtol() will return zero. In both cases, endptr will point to the352rest of the conversion specifier - just what we need.353*/354status->width = (int)strtol( spec, (char**)&spec, 10 );355}356357/* Optional precision */358if ( *spec == '.' )359{360++spec;361if ( *spec == '*' )362{363/* Retrieve precision value from argument stack. A negative value364is as if no precision is given - as precision is initalized to365EOF (negative), there is no need for testing for negative here.366*/367status->prec = va_arg( status->arg, int );368++spec;369}370else371{372status->prec = (int)strtol( spec, (char**) &spec, 10 );373}374/* Having a precision cancels out any zero flag. */375status->flags &= ~E_zero;376}377378/* Optional length modifier379We step one character ahead in any case, and step back only if we find380there has been no length modifier (or step ahead another character if it381has been "hh" or "ll").382*/383switch ( *(spec++) )384{385case 'h':386if ( *spec == 'h' )387{388/* hh -> char */389status->flags |= E_char;390++spec;391}392else393{394/* h -> short */395status->flags |= E_short;396}397break;398case 'l':399if ( *spec == 'l' )400{401/* ll -> long long */402status->flags |= E_llong;403++spec;404}405else406{407/* k -> long */408status->flags |= E_long;409}410break;411case 'j':412/* j -> intmax_t, which might or might not be long long */413status->flags |= E_intmax;414break;415case 'z':416/* z -> size_t, which might or might not be unsigned int */417status->flags |= E_size;418break;419case 't':420/* t -> ptrdiff_t, which might or might not be long */421status->flags |= E_ptrdiff;422break;423case 'L':424/* L -> long double */425status->flags |= E_ldouble;426break;427default:428--spec;429break;430}431432/* Conversion specifier */433switch ( *spec )434{435case 'd':436/* FALLTHROUGH */437case 'i':438status->base = 10;439break;440case 'o':441status->base = 8;442status->flags |= E_unsigned;443break;444case 'u':445status->base = 10;446status->flags |= E_unsigned;447break;448case 'x':449status->base = 16;450status->flags |= ( E_lower | E_unsigned );451break;452case 'X':453status->base = 16;454status->flags |= E_unsigned;455break;456case 'f':457case 'F':458case 'e':459case 'E':460case 'g':461case 'G':462break;463case 'a':464case 'A':465break;466case 'c':467/* TODO: wide chars. */468if ( !printchar( va_arg( status->arg, int ), status ) )469return -1;470++spec;471return (spec - orig_spec);472case 's':473/* TODO: wide chars. */474{475char * s = va_arg( status->arg, char * );476if ( !printstr( s, status ) )477return -1;478++spec;479return (spec - orig_spec);480}481case 'p':482status->base = 16;483status->flags |= ( E_lower | E_unsigned | E_alt | E_intptr );484break;485case 'n':486{487int * val = va_arg( status->arg, int * );488*val = status->i;489++spec;490return (spec - orig_spec);491}492default:493/* No conversion specifier. Bad conversion. */494return 0;495}496/* Do the actual output based on our findings */497if ( status->base != 0 )498{499/* Integer conversions */500/* TODO: Check for invalid flag combinations. */501if ( status->flags & E_unsigned )502{503/* TODO: Marking the default case _PDCLIB_UNREACHABLE breaks %ju test driver? */504uintmax_t value = 0;505switch ( status->flags & E_TYPES )506{507case E_char:508value = (uintmax_t)(unsigned char)va_arg( status->arg, int );509break;510case E_short:511value = (uintmax_t)(unsigned short)va_arg( status->arg, int );512break;513case 0:514value = (uintmax_t)va_arg( status->arg, unsigned int );515break;516case E_long:517value = (uintmax_t)va_arg( status->arg, unsigned long );518break;519case E_llong:520value = (uintmax_t)va_arg( status->arg, unsigned long long );521break;522case E_size:523value = (uintmax_t)va_arg( status->arg, size_t );524break;525case E_intptr:526value = (uintmax_t)va_arg( status->arg, uintptr_t );527break;528case E_ptrdiff:529value = (uintmax_t)va_arg( status->arg, ptrdiff_t );530break;531case E_intmax:532value = va_arg( status->arg, uintmax_t );533}534if ( !int2base( value, status ) )535return -1;536}537else538{539intmax_t value = 0;540switch ( status->flags & E_TYPES )541{542case E_char:543value = (intmax_t)(char)va_arg( status->arg, int );544break;545case E_short:546value = (intmax_t)(short)va_arg( status->arg, int );547break;548case 0:549value = (intmax_t)va_arg( status->arg, int );550break;551case E_long:552value = (intmax_t)va_arg( status->arg, long );553break;554case E_llong:555value = (intmax_t)va_arg( status->arg, long long );556break;557case E_size:558value = (intmax_t)va_arg( status->arg, size_t );559break;560case E_intptr:561value = (intmax_t)va_arg( status->arg, intptr_t );562break;563case E_ptrdiff:564value = (intmax_t)va_arg( status->arg, ptrdiff_t );565break;566case E_intmax:567value = va_arg( status->arg, intmax_t );568break;569default:570_PDCLIB_UNREACHABLE;571}572573if (!int2base( value, status ) )574return -1;575}576577if ( status->flags & E_minus && status->current < status->width )578{579if (!cbrept( status, ' ', status->width - status->current ))580return -1;581}582}583++spec;584return spec - orig_spec;585}586587#endif588589#ifdef TEST590#define _PDCLIB_FILEID "_PDCLIB/print.c"591#define _PDCLIB_STRINGIO592593#include "_PDCLIB_test.h"594595#ifndef REGTEST596static size_t testcb( void *p, const char *buf, size_t size )597{598char **destbuf = p;599memcpy(*destbuf, buf, size);600*destbuf += size;601return size;602}603604static int testprintf( char * buffer, const char * format, ... )605{606/* Members: base, flags, n, i, current, width, prec, ctx, cb, arg */607struct _PDCLIB_status_t status;608status.base = 0;609status.flags = 0;610status.n = 100;611status.i = 0;612status.current = 0;613status.width = 0;614status.prec = 0;615status.ctx = &buffer;616status.write = testcb;617va_start( status.arg, format );618memset( buffer, '\0', 100 );619if ( _PDCLIB_print( format, &status ) != (int)strlen( format ) )620{621printf( "_PDCLIB_print() did not return end-of-specifier on '%s'.\n", format );622++TEST_RESULTS;623}624va_end( status.arg );625return status.i;626}627#endif628629#define TEST_CONVERSION_ONLY630631int main( void )632{633#ifndef REGTEST634char target[100];635#include "printf_testcases.h"636#endif637return TEST_RESULTS;638}639640#endif641642643