function testformat(expected, template, ...) local t = {...} testCall("string.format: " .. template, function() local output = string.format(template, unpack(t)) local inputs = "" for i = 1, #t do inputs = string.format("%s, %q", inputs, t[i]) end local msg = string.format("string.format(%q%s) == %q, expected %q", template, inputs, output, expected) assert(output == expected, msg) end) end testformat("-0.0", "%.1f", tonumber("-0")) testformat("-inf", "%.1f", tonumber("-inf")) testformat("inf", "%.1f", tonumber("inf")) testformat("-inf", "%.1f", tonumber("-inF")) testformat("inf", "%.1f", tonumber("iNf")) testformat("nan", "%.1f", tonumber("nan")) testformat("5,8,13,21", "%d,%d,%d,%d", 5,8,13,21) testformat(" hello", "%10s", "hello") testformat(" hello", "%010s", "hello") -- zero padding only for numbers! testformat("hello", "%1s", "hello") testformat("00012", "%05d", 12) testformat("12 ", "%-05d", 12) testformat("12 ", "%-5d", 12) testformat("a ", "%-5s", "a") testformat(" +12", "%+5d", 12) testformat("+12", "%+1d", 12) testformat("12.1", "%1.1f", 12.125) testformat("12.12", "%1.2f", 12.125) testformat("12.125", "%1.3f", 12.125) testformat("12.1250", "%1.4f", 12.125) testformat(" 12.1250", "%8.4f", 12.125) testformat("12.1250 ", "%-8.4f", 12.125) testformat("+12.1250 ", "%-+9.4f", 12.125) testformat("' hell'", "'%20.4s'", "hello") testformat("!12.!", "!%#.0f!", 12.3456) testformat("!12!", "!%.0f!", 12.3456) testformat("!12.3!", "!%.1f!", 12.3456) testformat("!12.35!", "!%.2f!", 12.3456) testformat("!12.346!", "!%.3f!", 12.3456) testformat("!12.3456!", "!%.4f!", 12.3456) testformat("!12.34560!", "!%.5f!", 12.3456) testformat("!12.345600!", "!%.6f!", 12.3456) testformat("0173", "%.4o", 123) testformat("2322", "%.4o", 1234) testformat("0173", "%#.4o", 123) testformat("02322", "%#.4o", 1234) testformat("-0.1", "%+1.1f", -0.1) testformat("+0.1", "%+1.1f", 0.1) testformat(" 0.1", "% 1.1f", 0.1) testformat("-123", "% 1.0f", -123) testformat(" 123", "% 1.0f", 123) testformat("+000012.23", "%+010.2f", 12.23) testformat("0.0000001000", "%.10f", 0.0000001) testformat("0.0000001235", "%.10f", 0.00000012347) testformat("1.e+00", "%#.e", 1.0) testformat("1.235e+00", "%#.3e", 1.23456) testformat("1.235E+03", "%#.3E", 1234.56) testformat("+00000000001.235E+17", "%+020.3E", 123456789123456789) testformat("+00000000001.235E-14", "%+020.3E", 0.0000000000000123456789123456789) testformat("0.0001", "%g", 0.0001) testformat("0.000100000", "%#g", 0.0001) testformat("-0.000100000", "%#g", -0.0001) testformat("1e-05", "%g", 0.00001) testformat("1.00000e-05", "%#g", 0.00001) testformat("-1.00000e-05", "%#g", -0.00001) testformat("1.", "%#.0g", 1.2345678) testformat("1.", "%#.1g", 1.2345678) testformat("1.2", "%#.2g", 1.2345678) testformat("1.23", "%#.3g", 1.2345678) testformat("1.", "%#.0g", 1) testformat("1.", "%#.1g", 1) testformat("1.0", "%#.2g", 1) testformat("1.00", "%#.3g", 1) testformat("100.", "%#.3g", 100) testformat("100.0", "%#.4g", 100) testformat("100.00", "%#.5g", 100) testformat("100", "%.4g", 100) testformat("1e+07", "%.4g", 10000000) testformat(" 1e+07", "% .4g", 10000000) testformat("+1e+07", "%+.4g", 10000000) testformat("1.000e+07", "%#.4g", 10000000) testformat("1.09692", "%#g", 1.0969153711619073) testformat("5.1", "%#.2g", 5.061974531368081) testformat("1.23e+06", "%g", 1230000) testformat("1.23000e+06", "%#g", 1230000) testformat(" -025", "%#20.3d", -25.66494878296039) testformat(" -1.e+02", "%#20.0e", -95.05946110773807) testformat("2", "%.0f", 2.5) testformat("4", "%.0f", 3.5) testformat("4", "%.0f", 4.5) testformat("6", "%.0f", 5.5) testformat("0", "%d", 0.2) testformat("0", "%d", 0.9) testformat("1", "%d", 1.9) testformat(" 19", "% 3.2d", 19) testformat("", "%.0u", 0.4) testformat("", "%.0o", 0.4) testformat("", "%.0x", 0.4) testformat("", "%.0X", 0.4) testformat("", "%.0d", 0.4) testformat("", "%.0i", 0.4) testformat("0", "%#.0o", 0) testformat("", "%#.0x", 0) testformat("", "%#.0X", 0) testformat("1", "%#.0u", 1.4) testformat("01", "%#.0o", 1.4) testformat("0x1", "%#.0x", 1.4) testformat("0X1", "%#.0X", 1.4) testformat("1", "%#.0d", 1.4) testformat("1", "%#.0i", 1.4) testformat("0x07", "%#.2x", 7) testformat("0X07", "%#.2X", 7) testformat("07", "%#.2o", 7) testformat("0x7", "%#x", 7) testformat("0X7", "%#X", 7) testformat("07", "%#o", 7) testformat("000", "%#.3x", 0) testformat("0000000001", "%#010o", 1) testformat(" 01", "%#010.0o", 1) testformat(" 00001", "%#010.5o", 1) testformat(" 0x00001", "%#010.5x", 1) testformat(" 0x00001", "%#10.5x", 1) testformat(" 00001", "%010.5x", 1) testformat(" 00001", "%10.5x", 1) assert(string.format("%.0x", -1):sub(1, 8) == "ffffffff") do local zero = 0 local one = 1 local inf = one / zero local plusinf = inf local neginf = -plusinf local nan = zero / zero assert(tostring(plusinf) == "inf", "expected inf, got " .. tostring(plusinf)) assert(tostring(neginf) == "-inf", "expected -inf, got " .. tostring(neginf)) assert(tostring(nan) == "nan", "expected nan, got " .. tostring(nan)) testformat("inf", "%f", plusinf) testformat("-inf", "%f", neginf) testformat("nan", "%f", nan) testformat("+inf", "%+f", plusinf) testformat("+inf", "%+e", plusinf) testformat("+INF", "%+E", plusinf) testformat("+inf", "%+g", plusinf) testformat("+INF", "%+G", plusinf) testformat("-inf", "%+f", neginf) testformat("-inf", "%+e", neginf) testformat("-INF", "%+E", neginf) testformat("-inf", "%+g", neginf) testformat("-INF", "%+G", neginf) end -- this fails in some libc implementations testformat("1.0e+02", "%#.2g", 99.9) -- %, s, q, c, d, E, e, f, g, G, i, o, u, X, and x testcases = { ["%"]={"%"}, ["c"]={[255]=string.char(255), [120]='x'}, ["d"]={[-1]='-1', [1]='1', ["1"]='1', [1.1]='1', [10]='10', [255]='255'}, ["e"]={[-1]="-1.000000e+00", [1]="1.000000e+00", ["1"]="1.000000e+00", [1.1]="1.100000e+00", [10]="1.000000e+01", [255]="2.550000e+02"}, ["E"]={[-1]="-1.000000E+00", [1]="1.000000E+00", ["1"]="1.000000E+00", [1.1]="1.100000E+00", [10]="1.000000E+01", [255]="2.550000E+02"}, ["f"]={[-1]="-1.000000", [1]="1.000000", ["1"]="1.000000", [1.1]="1.100000", [10]="10.000000", [255]="255.000000"}, ["g"]={[-1]='-1', [1]='1', ["1"]='1', [1.1]='1.1', [10]='10', [255]='255'}, ["G"]={[-1]='-1', [1]='1', ["1"]='1', [1.1]='1.1', [10]='10', [255]='255'}, ["i"]={[-1]='-1', [1]='1', ["1"]='1', [1.1]='1', [10]='10', [255]='255'}, ["o"]={[1]='1', ["1"]='1', [1.1]='1', [10]='12', [255]="377"}, ["q"]={["\n"]='"\\\n"', ["\r"]='"\\r"', ["\""]='"\\""', ["\t"]='"\t"'}, ["s"]={[""]="", ["abc"]="abc", [1]="1"}, ["u"]={[1]='1', ["1"]='1', [1.1]='1', [10]='10', [255]='255'}, ["x"]={[1]='1', ["1"]='1', [1.1]='1', [10]='a', [255]="ff"}, ["X"]={[1]='1', ["1"]='1', [1.1]='1', [10]='A', [255]='FF'}, } for t, c in pairs(testcases) do local template = "%" .. t for k, v in pairs(c) do testformat(v, template, k) --print(string.format("string.format(%q, %q) == %q", template, k, result)) end end function verifyinvalidpattern(pattern) local status, err = pcall(function() string.format(pattern) end) assert(not status) end function verifyinvalidpatterns(first, ...) if first then verifyinvalidpattern(first) return verifyinvalidpatterns(...) end end verifyinvalidpatterns("%", "% ", "%.", "%..f", "%...f", "%111", "%111.111")