From db227c1822c42bb5d1b9748b12b4dbd40fcf66a2 Mon Sep 17 00:00:00 2001 From: syntheticpp Date: Sat, 27 Aug 2005 10:44:02 +0000 Subject: [PATCH] adding SafeFormat.h git-svn-id: svn://svn.code.sf.net/p/loki-lib/code/trunk@218 7ec92016-0320-0410-acc4-a06ded1c099a --- include/loki/SafeFormat.h | 507 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100755 include/loki/SafeFormat.h diff --git a/include/loki/SafeFormat.h b/include/loki/SafeFormat.h new file mode 100755 index 0000000..76afd4b --- /dev/null +++ b/include/loki/SafeFormat.h @@ -0,0 +1,507 @@ +//////////////////////////////////////////////////////////////////////////////// +// Copyright (c) 2005 by Andrei Alexandrescu +// Permission to use, copy, modify, distribute, and sell this software for any +// purpose is hereby granted without fee, provided that the above copyright +// notice appear in all copies and that both that copyright notice and this +// permission notice appear in supporting documentation. +// The author makes no representations about the suitability of this software +// for any purpose. It is provided "as is" without express or implied +// warranty. +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// This file contains definitions for SafePrintf. SafeScanf coming soon (the +// design is similar). +// See Alexandrescu, Andrei: Type-safe Formatting, C/C++ Users Journal, Jul 2005 +//////////////////////////////////////////////////////////////////////////////// + +#ifndef SAFEFORMAT_H_ +#define SAFEFORMAT_H_ + +#include +#include +#include +#include + +// Crude writing method: writes straight to the file, unbuffered +// Must be combined with a buffer to work properly (and efficiently) + +void write(std::FILE* f, const char* from, const char* to) { + assert(from <= to); + fwrite(from, 1, to - from, f); +} + +// Write to a string + +void write(std::string& s, const char* from, const char* to) { + assert(from <= to); + s.append(from, to); +} + +// Write to a fixed-size buffer + +template +void write(std::pair& s, const Char* from, const Char* to) { + assert(from <= to); + if (from + s.second > to) throw std::overflow_error(""); + s.first = copy(from, to, s.first); + s.second -= to - from; +} + +//////////////////////////////////////////////////////////////////////////////// +// PrintfState class template +// Holds the formatting state, and implements operator() to format stuff +// Todo: make sure errors are handled properly +//////////////////////////////////////////////////////////////////////////////// + +template +struct PrintfState { + PrintfState(Device dev, const Char * format) + : device_(dev) + , format_(format) + , result_(0) { + Advance(); + } + + ~PrintfState() { + } + + #define PRINTF_STATE_FORWARD(type) \ + PrintfState& operator()(type par) {\ + return (*this)(static_cast< unsigned long >(par)); \ + } + + PRINTF_STATE_FORWARD(bool) + PRINTF_STATE_FORWARD(char) + PRINTF_STATE_FORWARD(signed char) + PRINTF_STATE_FORWARD(unsigned char) + PRINTF_STATE_FORWARD(short) + PRINTF_STATE_FORWARD(unsigned short) + PRINTF_STATE_FORWARD(int) + PRINTF_STATE_FORWARD(unsigned) + PRINTF_STATE_FORWARD(long) + + // Print (or gobble in case of the "*" specifier) an int + PrintfState& operator()(unsigned long i) { + if (result_ == -1) return *this; // don't even bother + // % [flags] [width] [.prec] [modifier] type_char + // Fetch the flags + ReadFlags(); + if (*format_ == '*') { + // read the width and get out + SetWidth(static_cast(i)); + ++format_; + return *this; + } + ReadWidth(); + // precision + if (*format_ == '.') { + // deal with precision + if (format_[1] == '*') { + // read the precision and get out + SetPrec(static_cast(i)); + format_ += 2; + return *this; + } + ReadPrecision(); + } + ReadModifiers(); + // input size modifier + if (ForceShort()) { + // short int + const Char c = *format_; + if (c == 'x' || c == 'X' || c == 'u' || c == 'o') { + i = static_cast(static_cast(i)); + } + } + FormatWithCurrentFlags(i); + return *this; + } + + PrintfState& operator()(double n) { + if (result_ == -1) return *this; // don't even bother + PrintFloatingPoint(n); + return *this; + } + + PrintfState& operator()(long double n) { + if (result_ == -1) return *this; // don't even bother + PrintFloatingPoint(n); + return *this; + } + + // Store the number of characters printed so far + PrintfState& operator()(int * pi) { + return StoreCountHelper(pi); + } + + // Store the number of characters printed so far + PrintfState& operator()(short * pi) { + return StoreCountHelper(pi); + } + + // Store the number of characters printed so far + PrintfState& operator()(long * pi) { + return StoreCountHelper(pi); + } + + PrintfState& operator()(const char *const s) { + if (result_ == -1) return *this; + ReadLeaders(); + const char fmt = *format_; + if (fmt == 'p') { + FormatWithCurrentFlags(reinterpret_cast(s)); + return *this; + } + if (fmt != 's') { + result_ = -1; + return *this; + } + const size_t len = std::min(strlen(s), prec_); + if (width_ > len) { + if (LeftJustify()) { + Write(s, s + len); + Fill(' ', width_ - len); + } else { + Fill(' ', width_ - len); + Write(s, s + len); + } + } else { + Write(s, s + len); + } + Next(); + return *this; + } + + PrintfState& operator()(const void *const p) { + return (*this)(reinterpret_cast(p)); + } + + // read the result + operator int() const { + return result_; + } + +private: + template + PrintfState& StoreCountHelper(T *const pi) { + if (result_ == -1) return *this; // don't even bother + ReadLeaders(); + const char fmt = *format_; + if (fmt == 'p') { // pointer + FormatWithCurrentFlags(reinterpret_cast(pi)); + return *this; + } + if (fmt != 'n') { + result_ = -1; + return *this; + } + assert(pi != 0); + *pi = result_; + Next(); + return *this; + } + + void FormatWithCurrentFlags(const unsigned long i) { + // look at the format character + Char formatChar = *format_; + bool isSigned = formatChar == 'd' || formatChar == 'i'; + if (formatChar == 'p') { + formatChar = 'x'; // pointers go to hex + SetAlternateForm(); // printed with '0x' in front + isSigned = true; // that's what gcc does + } + if (!strchr("cdiuoxX", formatChar)) { + result_ = -1; + return; + } + Char buf[ + sizeof(unsigned long) * 3 // digits + + 1 // sign or ' ' + + 2 // 0x or 0X + + 1]; // terminating zero + const Char *const bufEnd = buf + (sizeof(buf) / sizeof(Char)); + Char * bufLast = buf + (sizeof(buf) / sizeof(Char) - 1); + Char signChar = 0; + unsigned int base = 10; + + if (formatChar == 'c') { + // Format only one character + // The 'fill with zeros' flag is ignored + ResetFillZeros(); + *bufLast = static_cast(i); + } else { + // TODO: inefficient code, refactor + const bool negative = isSigned && static_cast(i) < 0; + if (formatChar == 'o') base = 8; + else if (formatChar == 'x' || formatChar == 'X') base = 16; + bufLast = isSigned + ? RenderWithoutSign(static_cast(i), bufLast, base, + formatChar == 'X') + : RenderWithoutSign(i, bufLast, base, + formatChar == 'X'); + // Add the sign + if (isSigned) { + negative ? signChar = '-' + : ShowSignAlways() ? signChar = '+' + : Blank() ? signChar = ' ' + : 0; + } + } + // precision + size_t + countDigits = bufEnd - bufLast, + countZeros = prec_ != size_t(-1) && countDigits < prec_ && + formatChar != 'c' + ? prec_ - countDigits + : 0, + countBase = base != 10 && AlternateForm() && i != 0 + ? (base == 16 ? 2 : countZeros > 0 ? 0 : 1) + : 0, + countSign = (signChar != 0), + totalPrintable = countDigits + countZeros + countBase + countSign; + size_t countPadLeft = 0, countPadRight = 0; + if (width_ > totalPrintable) { + if (LeftJustify()) { + countPadRight = width_ - totalPrintable; + countPadLeft = 0; + } else { + countPadLeft = width_ - totalPrintable; + countPadRight = 0; + } + } + if (FillZeros() && prec_ == size_t(-1)) { + // pad with zeros and no precision - transfer padding to precision + countZeros = countPadLeft; + countPadLeft = 0; + } + // ok, all computed, ready to print to device + Fill(' ', countPadLeft); + if (signChar != 0) Write(&signChar, &signChar + 1); + if (countBase > 0) Fill('0', 1); + if (countBase == 2) Fill(formatChar, 1); + Fill('0', countZeros); + Write(bufLast, bufEnd); + Fill(' ', countPadRight); + // done, advance + Next(); + } + + void Write(const Char* b, const Char* e) { + if (result_ < 0) return; + const ptrdiff_t x = e - b; + write(device_, b, e); + result_ += x; + } + + template + void PrintFloatingPoint(Double n) { + const Char *const fmt = format_ - 1; + assert(*fmt == '%'); + // enforce format string validity + ReadLeaders(); + // enforce format spec + if (!strchr("eEfgG", *format_)) { + result_ = -1; + return; + } + // format char validated, copy it to a temp and use legacy sprintf + ++format_; + Char fmtBuf[128], resultBuf[1024]; + if (format_ >= fmt + sizeof(fmtBuf) / sizeof(Char)) { + result_ = -1; + return; + } + memcpy(fmtBuf, fmt, (format_ - fmt) * sizeof(Char)); + fmtBuf[format_ - fmt] = 0; + const int stored = snprintf(resultBuf, + sizeof(resultBuf) / sizeof(Char), fmtBuf, n); + if (stored < 0) { + result_ = -1; + return; + } + Write(resultBuf, resultBuf + strlen(resultBuf)); + Advance(); // output stuff to the next format directive + } + + void Fill(const Char c, size_t n) { + for (; n > 0; --n) { + Write(&c, &c + 1); + } + } + + Char* RenderWithoutSign(unsigned long n, char* bufLast, + unsigned int base, bool uppercase) { + const Char hex1st = uppercase ? 'A' : 'a'; + for (;;) { + const unsigned long next = n / base; + Char c = n - next * base; + c += (c <= 9) ? '0' : hex1st - 10; + *bufLast = c; + n = next; + if (n == 0) break; + --bufLast; + } + return bufLast; + } + + char* RenderWithoutSign(long n, char* bufLast, unsigned int base, + bool uppercase) { + if (n != LONG_MIN) { + return RenderWithoutSign(static_cast(n < 0 ? -n : n), + bufLast, base, uppercase); + } + // annoying corner case + char* save = bufLast; + ++n; + bufLast = RenderWithoutSign(static_cast(n), + bufLast, base, uppercase); + --(*save); + return bufLast; + } + + void Next() { + ++format_; + Advance(); + } + + void Advance() { + ResetAll(); + const Char* begin = format_; + for (;;) { + if (*format_ == '%') { + if (format_[1] != '%') { // It's a format specifier + Write(begin, format_); + ++format_; + break; + } + // It's a "%%" + Write(begin, ++format_); + begin = ++format_; + continue; + } + if (*format_ == 0) { + Write(begin, format_); + break; + } + ++format_; + } + } + + void ReadFlags() { + for (;; ++format_) { + switch (*format_) { + case '-': SetLeftJustify(); break; + case '+': SetShowSignAlways(); break; + case ' ': SetBlank(); break; + case '#': SetAlternateForm(); break; + case '0': SetFillZeros(); break; + default: return; + } + } + } + + void ParseDecimalUInt(unsigned int& dest) { + if (!std::isdigit(*format_)) return; + unsigned int r = 0; + do { + // TODO: inefficient - rewrite + r *= 10; + r += *format_ - '0'; + ++format_; + } while (std::isdigit(*format_)); + dest = r; + } + + void ReadWidth() { + ParseDecimalUInt(width_); + } + + void ReadPrecision() { + assert(*format_ == '.'); + ++format_; + ParseDecimalUInt(prec_); + } + + void ReadModifiers() { + switch (*format_) { + case 'h': SetForceShort(); ++format_; break; + case 'l': ++format_; break; + // more (C99 and platform-specific modifiers) to come + } + } + + void ReadLeaders() { + ReadFlags(); + ReadWidth(); + if (*format_ == '.') ReadPrecision(); + ReadModifiers(); + } + + enum { + leftJustify = 1, + showSignAlways = 2, + blank = 4, + alternateForm = 8, + fillZeros = 16, + forceShort = 32, + }; + + bool LeftJustify() const { return (flags_ & leftJustify) != 0; } + bool ShowSignAlways() const { return (flags_ & showSignAlways) != 0; } + void SetWidth(size_t w) { width_ = w; } + void SetLeftJustify() { flags_ |= leftJustify; } + void SetShowSignAlways() { flags_ |= showSignAlways; } + bool Blank() const { return (flags_ & blank) != 0; } + bool AlternateForm() const { return (flags_ & alternateForm) != 0; } + bool FillZeros() const { return (flags_ & fillZeros) != 0; } + bool ForceShort() const { return (flags_ & forceShort) != 0; } + + void SetPrec(size_t p) { prec_ = p; } + void SetBlank() { flags_ |= blank; } + void SetAlternateForm() { flags_ |= alternateForm; } + void SetFillZeros() { flags_ |= fillZeros; } + void ResetFillZeros() { flags_ &= ~fillZeros; } + void SetForceShort() { flags_ |= forceShort; } + + void ResetAll() { + assert(result_ != EOF); + width_ = 0; + prec_ = size_t(-1); + flags_ = 0; + } + + // state + Device device_; + const Char* format_; + size_t width_; + size_t prec_; + unsigned int flags_; + int result_; +}; + +PrintfState Printf(const char* format) { + return PrintfState(stdout, format); +} + +PrintfState FPrintf(FILE* f, const char* format) { + return PrintfState(f, format); +} + +PrintfState SPrintf(std::string& s, const char* format) { + return PrintfState(s, format); +} + +template +PrintfState XPrintf(T& device, const Char* format) { + return PrintfState(device, format); +} + +template +PrintfState, Char> +BufPrintf(Char (&buf)[N], const Char* format) { + std::pair temp(buf, N); + return PrintfState, Char>(temp, format); +} + +#endif //SAFEFORMAT_H_