// Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt #include "StringF.h" #include #include #include #include #include // --------------------------------------------------------------------------- // ## FormatSpec FormatSpec::FormatSpec() : format("") { for (int i = 0; i <= MAX_PARAMS; ++i) params[i] = 0; } FormatSpec::FormatSpec(const char *format_) : format(format_) { assert(format_); parseFormat(strlen(format)); } FormatSpec::FormatSpec(const char *format_, int formatlen_) : format(format_) { assert(format_ && (formatlen_ >= 0)); parseFormat(formatlen_); } bool FormatSpec::empty() const { return (params[MAX_PARAMS] == 0); } bool FormatSpec::specifierIs(const char *specifier) const { int len = params[0]; if (len && (format[len - 1] == ':')) --len; return (strncmp(specifier, format, len) == 0); } int FormatSpec::paramCount() const { int i = 0; while ((i < MAX_PARAMS) && (params[i] < params[i + 1])) ++i; return i; } std::string FormatSpec::param(int idx) const { const char *beg, *end; paramPtr(idx, beg, end); std::string str; str.reserve(end - beg); const char *c = beg; while (c != end) { // scan to the next escape character or the end of the segment while ((c != end) && (*c != '\\')) ++c; // append this run of unescaped characters str.append(beg, (c - beg)); // if we're not at the end, we must have hit a backslash if (c != end) { ++c; // skip the backslash if (c != end) { beg = c; // start the next run from the escaped character (whatever it is) ++c; // skip the escaped character (important so we don't interpret a second '\\' as another escape char) } else { // XXX warning here? or even assert that the format spec doesn't have backslash as the last character? str += '\\'; } } } return str; } void FormatSpec::paramPtr(int idx, const char *&begin, const char *&end) const { assert(idx >= 0 && idx < MAX_PARAMS); begin = &format[params[idx]]; end = &format[params[idx + 1]]; // if it's not the last parameter, then we need to shift end back one, // to account for the '|' character that separates parameters // note: the last character of a parameter may legitimately be a '|', e.g. // in %foo{bar:qux\|} // param 0 (before escape processing) is exactly ``qux\|'' if ((idx + 1 < MAX_PARAMS) && (params[idx + 2] > params[idx + 1])) --end; } void FormatSpec::parseFormat(int length) { // length limit due to the type used for the params[] array assert((length >= 0) && (length <= USHRT_MAX)); assert(format); // format should already have been set const char *c = format; const char *end = &format[length]; // scan the specifier while ((c != end) && isalpha(*c)) ++c; // skip the optional ':' separating the specifier and the first parameter if (*c == ':') ++c; for (int i = 0; i < MAX_PARAMS; ++i) { params[i] = (c - format); // scan to the beginning of the next parameter while (c != end) { if (*c == '\\') { ++c; if (c != end) ++c; } else if (*c == '|') { ++c; break; } else ++c; } } params[MAX_PARAMS] = (c - format); assert((c == end) && "FormatSpec::MAX_PARAMS isn't big enough"); } // --------------------------------------------------------------------------- // ## to_string() implementations and helpers struct PrintfSpec { enum Flags { AlignLeft = 1 << 0, // corresponds with '-' flag ForceSign = 1 << 1, // corresponds with '+' flag PadSign = 1 << 2, // corresponds with ' ' flag PadZero = 1 << 3, // corresponds with '0' flag ShowType = 1 << 4 // corresponds with '#' flag }; int width; int precision; int flags; PrintfSpec() : width(-1), precision(-1), flags(0) {} }; static const char *parse_printfspec(PrintfSpec &spec, const char *fmt, const char *end = 0) { const char *c = fmt; //at_flags: while ((c != end) && *c) { // skip backslash if (*c == '\\') ++c; if (c == end) return c; int addflag = 0; switch (*c) { // printf flags case '-': addflag = PrintfSpec::AlignLeft; break; case '+': addflag = PrintfSpec::ForceSign; break; case ' ': addflag = PrintfSpec::PadSign; break; case '0': addflag = PrintfSpec::PadZero; break; case '#': addflag = PrintfSpec::ShowType; break; // valid characters for the width specifier case '*': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': goto at_width; // valid characters for the precision specifier case '.': goto at_precision; // unknown char default: return c; } if (addflag && !(spec.flags & addflag)) { spec.flags |= addflag; } else { // duplicate flag return c; } ++c; } at_width: // variable-width specifier not supported if ((c == end) || (*c == '*')) return c; if (isdigit(*c)) { int width = 0; while ((c != end) && isdigit(*c)) { width *= 10; width += (*c - '0'); ++c; } spec.width = width; } at_precision: if ((c != end) && (*c == '.')) { ++c; // variable-precision specifier not supported if ((c == end) || (*c == '*')) return c; int prec = 0; while ((c != end) && isdigit(*c)) { prec *= 10; prec += (*c - '0'); ++c; } spec.precision = prec; } return c; } void init_iosflags(std::ostream &ss, const PrintfSpec &spec) { if (spec.width != -1) ss.width(spec.width); if (spec.precision != -1) ss.precision(spec.precision); if (spec.flags & PrintfSpec::AlignLeft) ss.setf(std::ios::left, std::ios::adjustfield); else { if (spec.flags & PrintfSpec::PadZero) ss.setf(std::ios::internal, std::ios::adjustfield); else ss.setf(std::ios::right, std::ios::adjustfield); } if (spec.flags & PrintfSpec::PadZero) ss.fill('0'); if (spec.flags & PrintfSpec::ForceSign) ss.setf(std::ios::showpos); if (spec.flags & PrintfSpec::ShowType) { ss.setf(std::ios::showpoint); ss.setf(std::ios::showbase); } } /* if (spec.flags & PrintfSpec::AlignLeft) { ss << '-'; } if (spec.flags & PrintfSpec::PadZero) { ss << '0'; } if (spec.flags & PrintfSpec::ForceSign) { ss << '+'; } if (spec.flags & PrintfSpec::PadSign) { ss << ' '; } if (spec.flags & PrintfSpec::ShowType) { ss << '#'; } if (spec.width != -1) { ss << spec.width; } if (spec.precision != -1) { ss << '.' << spec.precision; } */ std::string to_string(int64_t value, const FormatSpec &fmt) { std::ostringstream ss; PrintfSpec spec; if (!fmt.empty()) { if (fmt.specifierIs("d") || fmt.specifierIs("i")) { ss.setf(std::ios::dec, std::ios::basefield); } else return std::string("%(err: bad format)"); const char *fmtbegin, *fmtend; fmt.paramPtr(0, fmtbegin, fmtend); if (fmtend != fmtbegin) parse_printfspec(spec, fmtbegin, fmtend); } // sign padding is not supported if (spec.flags & PrintfSpec::PadSign) return std::string("%(err: bad format)"); // precision is ignore for integer arguments if (spec.precision != -1) return std::string("%(err: bad format)"); init_iosflags(ss, spec); ss << value; return ss.str(); } std::string to_string(uint64_t value, const FormatSpec &fmt) { std::ostringstream ss; PrintfSpec spec; if (!fmt.empty()) { if (fmt.specifierIs("u")) { ss.setf(std::ios::dec, std::ios::basefield); } else if (fmt.specifierIs("x")) { ss.setf(std::ios::hex, std::ios::basefield); } else if (fmt.specifierIs("X")) { ss.setf(std::ios::hex, std::ios::basefield); ss.setf(std::ios::uppercase); } else if (fmt.specifierIs("o")) { ss.setf(std::ios::oct, std::ios::basefield); } else return std::string("%(err: bad format)"); const char *fmtbegin, *fmtend; fmt.paramPtr(0, fmtbegin, fmtend); if (fmtend != fmtbegin) parse_printfspec(spec, fmtbegin, fmtend); } // sign padding is not supported if (spec.flags & PrintfSpec::PadSign) return std::string("%(err: bad format)"); // precision is ignore for integer arguments if (spec.precision != -1) return std::string("%(err: bad format)"); init_iosflags(ss, spec); ss << value; return ss.str(); } std::string to_string(double value, const FormatSpec &fmt) { std::ostringstream ss; PrintfSpec spec; if (!fmt.empty()) { if (fmt.specifierIs("f")) { ss.setf(std::ios::fixed, std::ios::floatfield); } else if (fmt.specifierIs("g")) { } else if (fmt.specifierIs("G")) { ss.setf(std::ios::uppercase); } else if (fmt.specifierIs("e")) { ss.setf(std::ios::scientific, std::ios::floatfield); } else if (fmt.specifierIs("E")) { ss.setf(std::ios::scientific, std::ios::floatfield); ss.setf(std::ios::uppercase); } else return std::string("%(err: bad format)"); const char *fmtbegin, *fmtend; fmt.paramPtr(0, fmtbegin, fmtend); if (fmtend != fmtbegin) parse_printfspec(spec, fmtbegin, fmtend); } // sign padding is not supported if (spec.flags & PrintfSpec::PadSign) return std::string("%(err: bad format)"); init_iosflags(ss, spec); ss << value; return ss.str(); } std::string to_string(const char *value, const FormatSpec &fmt) { if (fmt.empty()) { return std::string(value); } else { return std::string("%(err: bad format)"); } } std::string to_string(const std::string &value, const FormatSpec &fmt) { if (fmt.empty()) { return value; } else { return std::string("%(err: bad format)"); } } // --------------------------------------------------------------------------- // ## string_format() and helpers // static inline const char* scan_unformatted(const char* fmt) { // const char *c = fmt; // return c; // } static inline const char *scan_int(const char *fmt, int &value) { value = 0; const char *c = fmt; while (*c >= 0 && isdigit(*c)) { value *= 10; value += (*c - '0'); ++c; } return c; } static inline const char *scan_ident(const char *fmt) { const char *c = fmt; if (isalpha(*c) || (*c == '_')) { ++c; while (isalnum(*c) || (*c == '_')) ++c; } return c; } static inline const char *scan_bracetext(const char *fmt) { const char *c = fmt; while (*c && (*c != '}')) { if (*c == '\\') { ++c; if (*c) ++c; } else ++c; } return c; } std::string string_format(const char *fmt, int numargs, FormatArg const *const args[]) { std::string out; const char *c = fmt; while (*c) { while (*c && *c != '%') ++c; if (c != fmt) out.append(fmt, c - fmt); fmt = c; if (*c == '%') { ++c; if (*c == '%') { fmt = c; ++c; } else { int argid = -1; const char *identBegin = c; const char *identEnd = c; // parse and match reference if (isdigit(*c)) { c = identEnd = scan_int(identBegin = c, argid); } else { if (*c == '{') { identBegin = ++c; while (*c && (*c != '}')) ++c; identEnd = c; if (*c == '}') ++c; else { out.append("%(err: unfinished reference)"); goto bad_reference; } } else if (isalpha(*c) || (*c == '_')) { c = identEnd = scan_ident(identBegin = c); } else { out.append("%(err: unfinished reference)"); goto bad_reference; } if (identBegin == identEnd) { out.append("%(err: blank reference)"); goto bad_reference; } for (int i = 0; i < numargs; ++i) { size_t identLen = (identEnd - identBegin); if (args[i]->name && (strncmp(args[i]->name, identBegin, identLen) == 0) && (args[i]->name[identLen] == '\0')) { argid = i; break; } } } if (argid >= 0 && argid < numargs) { const FormatArg &arg = *args[argid]; const char *fmtBegin = 0; const char *fmtEnd = 0; // scan format specifier, if provided if (*c == '{') { ++c; if (!*c) { out.append("%(err: unfinished format)"); goto bad_reference; } c = fmtEnd = scan_bracetext(fmtBegin = c); if (fmtBegin == fmtEnd) { fmtBegin = fmtEnd = 0; } if (!*c) { out.append("%(err: unfinished format)"); goto bad_reference; } else ++c; } if (fmtBegin) { assert(fmtEnd > fmtBegin); FormatSpec fspec(fmtBegin, fmtEnd - fmtBegin); out.append(arg.format(fspec)); } else if (arg.defaultformat) { FormatSpec fspec(arg.defaultformat); out.append(arg.format(fspec)); } else out.append(arg.format(FormatSpec())); } else { out.append("%(err: unknown arg '"); out.append(identBegin, identEnd - identBegin); out.append("')"); goto bad_reference; } bad_reference: fmt = c; } } } // append the final range if (c != fmt) out.append(fmt, c - fmt); return out; }