Skip to content

Commit 4d03ee3

Browse files
committed
Allow posix positional arguments to be unreferenced
Fixes #55
1 parent 0c50d22 commit 4d03ee3

File tree

2 files changed

+25
-14
lines changed

2 files changed

+25
-14
lines changed

tinyformat.h

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -658,10 +658,7 @@ inline const char* streamStateFromFormat(std::ostream& out, bool& positionalMode
658658
const detail::FormatArg* args,
659659
int& argIndex, int numArgs)
660660
{
661-
if (*fmtStart != '%') {
662-
TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string");
663-
return fmtStart;
664-
}
661+
TINYFORMAT_ASSERT(*fmtStart == '%');
665662
// Reset stream state to defaults.
666663
out.width(0);
667664
out.precision(6);
@@ -873,19 +870,26 @@ inline void formatImpl(std::ostream& out, const char* fmt,
873870
std::ios::fmtflags origFlags = out.flags();
874871
char origFill = out.fill();
875872

873+
// "Positional mode" means all format specs should be of the form "%n$..."
874+
// with `n` an integer. We detect this in `streamStateFromFormat`.
876875
bool positionalMode = false;
877-
for (int argIndex = 0; positionalMode || argIndex < numArgs; ++argIndex) {
878-
// Parse the format string
876+
int argIndex = 0;
877+
while (true) {
879878
fmt = printFormatStringLiteral(out, fmt);
880-
if (positionalMode && *fmt == '\0')
879+
if (*fmt == '\0') {
880+
if (!positionalMode && argIndex < numArgs) {
881+
TINYFORMAT_ERROR("tinyformat: Not enough conversion specifiers in format string");
882+
}
881883
break;
884+
}
882885
bool spacePadPositive = false;
883886
int ntrunc = -1;
884887
const char* fmtEnd = streamStateFromFormat(out, positionalMode, spacePadPositive, ntrunc, fmt,
885888
args, argIndex, numArgs);
889+
// NB: argIndex may be incremented by reading variable width/precision
890+
// in `streamStateFromFormat`, so do the bounds check here.
886891
if (argIndex >= numArgs) {
887-
// Check args remain after reading any variable width/precision
888-
TINYFORMAT_ERROR("tinyformat: Not enough format arguments");
892+
TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string");
889893
return;
890894
}
891895
const FormatArg& arg = args[argIndex];
@@ -909,14 +913,11 @@ inline void formatImpl(std::ostream& out, const char* fmt,
909913
}
910914
out << result;
911915
}
916+
if (!positionalMode)
917+
++argIndex;
912918
fmt = fmtEnd;
913919
}
914920

915-
// Print remaining part of format string.
916-
fmt = printFormatStringLiteral(out, fmt);
917-
if (*fmt != '\0')
918-
TINYFORMAT_ERROR("tinyformat: Too many conversion specifiers in format string");
919-
920921
// Restore stream state
921922
out.width(origWidth);
922923
out.precision(origPrecision);

tinyformat_test.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ int unitTests()
173173
CHECK_EQUAL(tfm::format("%s", true), "true");
174174
CHECK_EQUAL(tfm::format("%d", true), "1");
175175

176+
//------------------------------------------------------------
177+
// Simple tests of posix positional arguments
178+
CHECK_EQUAL(tfm::format("%2$d %1$d", 10, 20), "20 10");
179+
// Allow positional arguments to be unreferenced. This is a slight
180+
// generalization of posix printf, which appears to allow only trailing
181+
// arguments to be unreferenced. See
182+
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html
183+
CHECK_EQUAL(tfm::format("%1$d", 10, 20), "10");
184+
CHECK_EQUAL(tfm::format("%2$d", 10, 20), "20");
185+
176186
//------------------------------------------------------------
177187
// Test precision & width
178188
CHECK_EQUAL(tfm::format("%10d", -10), " -10");

0 commit comments

Comments
 (0)