/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #define FLAG_ALT (1 << 0) #define FLAG_ZERO_EXTEND (1 << 1) #define FLAG_IS_SIGNED (1 << 2) #define FLAG_NEG_PAD (1 << 3) #define FLAG_CAPS (1 << 4) struct PrintfData { uint64_t number; void *userData; uint32_t fieldWidth; uint32_t precision; uint32_t flags; uint8_t posChar; uint8_t base; }; static uint32_t StrPrvPrintfEx_number(printf_write_c putc_, struct PrintfData *data, bool *bail) { char buf[64]; uint32_t idx = sizeof(buf) - 1; uint32_t chr, i; uint32_t numPrinted = 0; *bail = false; #ifdef USE_PRINTF_FLAG_CHARS if (data->fieldWidth > sizeof(buf) - 1) data->fieldWidth = sizeof(buf) - 1; if (data->precision > sizeof(buf) - 1) data->precision = sizeof(buf) - 1; #endif buf[idx--] = 0; //terminate if (data->flags & FLAG_IS_SIGNED) { if (((int64_t)data->number) < 0) { data->posChar = '-'; data->number = -data->number; } } do { if (data->base == 8) { chr = (data->number & 0x07) + '0'; data->number >>= 3; } else if (data->base == 10) { uint64_t t = U64_DIV_BY_CONST_U16(data->number, 10); chr = (data->number - t * 10) + '0'; data->number = t; } else { chr = data->number & 0x0F; data->number >>= 4; chr = (chr >= 10) ? (chr + (data->flags & FLAG_CAPS ? 'A' : 'a') - 10) : (chr + '0'); } buf[idx--] = chr; numPrinted++; } while (data->number); #ifdef USE_PRINTF_FLAG_CHARS while (data->precision > numPrinted) { buf[idx--] = '0'; numPrinted++; } if (data->flags & FLAG_ALT) { if (data->base == 8) { if (buf[idx+1] != '0') { buf[idx--] = '0'; numPrinted++; } } else if (data->base == 16) { buf[idx--] = data->flags & FLAG_CAPS ? 'X' : 'x'; numPrinted++; buf[idx--] = '0'; numPrinted++; } } if (!(data->flags & FLAG_NEG_PAD)) { if (data->fieldWidth > 0 && data->posChar != '\0') data->fieldWidth--; while (data->fieldWidth > numPrinted) { buf[idx--] = data->flags & FLAG_ZERO_EXTEND ? '0' : ' '; numPrinted++; } } #endif if (data->posChar != '\0') { buf[idx--] = data->posChar; numPrinted++; } idx++; for(i = 0; i < numPrinted; i++) { if (!putc_(data->userData,(buf + idx)[i])) { *bail = true; break; } } #ifdef USE_PRINTF_FLAG_CHARS if (!*bail && data->flags & FLAG_NEG_PAD) { for(i = numPrinted; i < data->fieldWidth; i++) { if (!putc_(data->userData, ' ')) { *bail = true; break; } } } #endif return i; } static uint32_t StrVPrintf_StrLen_withMax(const char* s, uint32_t max) { uint32_t len = 0; while ((*s++) && (len < max)) len++; return len; } static uint32_t StrVPrintf_StrLen(const char* s) { uint32_t len = 0; while (*s++) len++; return len; } static inline char prvGetChar(const char** fmtP) { return *(*fmtP)++; } uint32_t cvprintf(printf_write_c putc_f, uint32_t flags, void* userData, const char* fmtStr, va_list vl) { char c, t; uint32_t numPrinted = 0; double dbl; long double ldbl; struct PrintfData data; data.userData = userData; #define putc_(_ud,_c) \ do { \ if (!putc_f(_ud,_c)) \ goto out; \ } while(0) while ((c = prvGetChar(&fmtStr)) != 0) { if (c == '\n') { putc_(userData,c); numPrinted++; } else if (c == '%') { uint32_t len, i; const char* str; bool useChar = false, useShort = false, useLong = false, useLongLong = false, useLongDouble =false, useSizeT = false, usePtrdiffT = false; bool havePrecision = false, bail = false; data.fieldWidth = 0; data.precision = 0; data.flags = 0; data.posChar = 0; more_fmt: c = prvGetChar(&fmtStr); switch(c) { case '%': putc_(userData,c); numPrinted++; break; case 'c': t = va_arg(vl,unsigned int); putc_(userData,t); numPrinted++; break; case 's': str = va_arg(vl,char*); if (!str) str = "(null)"; if (data.precision) len = StrVPrintf_StrLen_withMax(str,data.precision); else len = StrVPrintf_StrLen(str); #ifdef USE_PRINTF_FLAG_CHARS if (!(data.flags & FLAG_NEG_PAD)) { for(i = len; i < data.fieldWidth; i++) { putc_(userData, ' '); numPrinted++; } } #endif for(i = 0; i < len; i++) { putc_(userData,*str++); numPrinted++; } #ifdef USE_PRINTF_FLAG_CHARS if (data.flags & FLAG_NEG_PAD) { for(i = len; i < data.fieldWidth; i++) { putc_(userData, ' '); numPrinted++; } } #endif break; case '.': havePrecision = true; goto more_fmt; case '0': if (!(data.flags & FLAG_ZERO_EXTEND) && !data.fieldWidth && !havePrecision) { data.flags |= FLAG_ZERO_EXTEND; goto more_fmt; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (havePrecision) data.precision = (data.precision * 10) + c - '0'; else data.fieldWidth = (data.fieldWidth * 10) + c - '0'; goto more_fmt; case '#': data.flags |= FLAG_ALT; goto more_fmt; case '-': data.flags |= FLAG_NEG_PAD; goto more_fmt; case '+': data.posChar = '+'; goto more_fmt; case ' ': if (data.posChar != '+') data.posChar = ' '; goto more_fmt; #define GET_UVAL64() \ useSizeT ? va_arg(vl, size_t) : \ usePtrdiffT ? va_arg(vl, ptrdiff_t) : \ useLongLong ? va_arg(vl, unsigned long long) : \ useLong ? va_arg(vl, unsigned long) : \ useChar ? (unsigned char)va_arg(vl, unsigned int) : \ useShort ? (unsigned short)va_arg(vl, unsigned int) : \ va_arg(vl, unsigned int) #define GET_SVAL64() \ useSizeT ? va_arg(vl, size_t) : \ usePtrdiffT ? va_arg(vl, ptrdiff_t) : \ useLongLong ? va_arg(vl, signed long long) : \ useLong ? va_arg(vl, signed long) : \ useChar ? (signed char)va_arg(vl, signed int) : \ useShort ? (signed short)va_arg(vl, signed int) : \ va_arg(vl, signed int) case 'u': data.number = GET_UVAL64(); data.base = 10; data.flags &= ~(FLAG_ALT | FLAG_CAPS); numPrinted += StrPrvPrintfEx_number(putc_f, &data, &bail); if (bail) goto out; break; case 'd': case 'i': data.number = GET_SVAL64(); data.base = 10; data.flags &= ~(FLAG_ALT | FLAG_CAPS); data.flags |= FLAG_IS_SIGNED; numPrinted += StrPrvPrintfEx_number(putc_f, &data, &bail); if (bail) goto out; break; case 'o': data.number = GET_UVAL64(); data.base = 8; data.flags &= ~FLAG_CAPS; data.posChar = '\0'; numPrinted += StrPrvPrintfEx_number(putc_f, &data, &bail); if (bail) goto out; break; case 'X': data.flags |= FLAG_CAPS; case 'x': data.number = GET_UVAL64(); data.base = 16; data.posChar = '\0'; numPrinted += StrPrvPrintfEx_number(putc_f, &data, &bail); if (bail) goto out; break; case 'p': data.number = (uintptr_t)va_arg(vl, const void*); data.base = 16; data.flags &= ~FLAG_CAPS; data.flags |= FLAG_ALT; data.posChar = '\0'; numPrinted += StrPrvPrintfEx_number(putc_f, &data, &bail); if (bail) goto out; break; #undef GET_UVAL64 #undef GET_SVAL64 case 'F': data.flags |= FLAG_CAPS; case 'f': if (flags & PRINTF_FLAG_CHRE) { if (flags & PRINTF_FLAG_SHORT_DOUBLE) { if (useLongDouble) { dbl = va_arg(vl, double); data.number = *(uint64_t *)(&dbl); } else { // just grab the 32-bits data.number = va_arg(vl, uint32_t); } } else { if (useLongDouble) { ldbl = va_arg(vl, long double); data.number = *(uint64_t *)(&ldbl); } else { dbl = va_arg(vl, double); data.number = *(uint64_t *)(&dbl); } } data.base = 16; data.flags |= FLAG_ALT; data.posChar = '\0'; numPrinted += StrPrvPrintfEx_number(putc_f, &data, &bail); } else { bail = true; } if (bail) goto out; break; case 'h': if (useShort) useChar = true; useShort = true; goto more_fmt; case 'L': useLongDouble = true; goto more_fmt; case 'l': if (useLong) useLongLong = true; useLong = true; goto more_fmt; case 'z': useSizeT = true; goto more_fmt; case 't': usePtrdiffT = true; goto more_fmt; default: putc_(userData,c); numPrinted++; break; } } else { putc_(userData,c); numPrinted++; } } out: return numPrinted; }