diff -r f9d30fac3da0 Doc/library/string.rst --- a/Doc/library/string.rst Mon Jul 04 11:44:17 2011 -0700 +++ b/Doc/library/string.rst Wed Jul 06 15:34:44 2011 -0700 @@ -196,9 +196,9 @@ .. productionlist:: sf replacement_field: "{" [`field_name`] ["!" `conversion`] [":" `format_spec`] "}" field_name: arg_name ("." `attribute_name` | "[" `element_index` "]")* - arg_name: [`identifier` | `integer`] + arg_name: [`identifier` | `decimalinteger`] attribute_name: `identifier` - element_index: `integer` | `index_string` + element_index: `decimalinteger` | `index_string` index_string: + conversion: "r" | "s" | "a" format_spec: diff -r f9d30fac3da0 Lib/test/test_unicode.py --- a/Lib/test/test_unicode.py Mon Jul 04 11:44:17 2011 -0700 +++ b/Lib/test/test_unicode.py Wed Jul 06 15:34:44 2011 -0700 @@ -550,6 +550,9 @@ self.assertEqual("{0[foo-bar]}".format({'foo-bar':'baz'}), 'baz') self.assertEqual("{0[foo bar]}".format({'foo bar':'baz'}), 'baz') self.assertEqual("{0[ ]}".format({' ':3}), '3') + self.assertEqual("{0[{]}".format({'{':'foo'}), 'foo') + self.assertEqual("{0[}]}".format({'}':'baz'}), 'baz') + self.assertEqual("{0[!:]}".format({'!:':'bar'}), 'bar') self.assertEqual('{foo._x}'.format(foo=C(20)), '20') self.assertEqual('{1}{0}'.format(D(10), D(20)), '2010') @@ -559,6 +562,10 @@ self.assertEqual('{0[1][0]}'.format(['abc', ['def']]), 'def') self.assertEqual('{0[1][0].x}'.format(['abc', [D('def')]]), 'def') + #empty arg name + self.assertEqual("{._x.x}".format(C(D('abc'))), 'abc') + self.assertEqual("{[0]} {._x}".format(['abc'], C('cba')), 'abc cba') + # strings self.assertEqual('{0:.3s}'.format('abc'), 'abc') self.assertEqual('{0:.3s}'.format('ab'), 'ab') @@ -651,16 +658,16 @@ self.assertRaises(ValueError, "}{".format) self.assertRaises(ValueError, "abc{0:{}".format) self.assertRaises(ValueError, "{0".format) - self.assertRaises(IndexError, "{0.}".format) + self.assertRaises(ValueError, "{0.}".format) self.assertRaises(ValueError, "{0.}".format, 0) self.assertRaises(IndexError, "{0[}".format) self.assertRaises(ValueError, "{0[}".format, []) - self.assertRaises(KeyError, "{0]}".format) + self.assertRaises(ValueError, "{0]}".format) self.assertRaises(ValueError, "{0.[]}".format, 0) self.assertRaises(ValueError, "{0..foo}".format, 0) self.assertRaises(ValueError, "{0[0}".format, 0) self.assertRaises(ValueError, "{0[0:foo}".format, 0) - self.assertRaises(KeyError, "{c]}".format) + self.assertRaises(ValueError, "{c]}".format) self.assertRaises(ValueError, "{{ {{{0}}".format, 0) self.assertRaises(ValueError, "{0}}".format, 0) self.assertRaises(KeyError, "{foo}".format, bar=3) @@ -696,6 +703,12 @@ self.assertRaises(ValueError, format, '', '#') self.assertRaises(ValueError, format, '', '#20') + f = lambda: None + setattr(f, ' ', 'bar') + self.assertRaises(ValueError, "{0. }".format, f) + self.assertRaises(ValueError, "{sp am}".format) + self.assertRaises(ValueError, "{sp am".format, **{"sp am":3}) + def test_format_map(self): self.assertEqual(''.format_map({}), '') self.assertEqual('a'.format_map({}), 'a') diff -r f9d30fac3da0 Misc/ACKS --- a/Misc/ACKS Mon Jul 04 11:44:17 2011 -0700 +++ b/Misc/ACKS Wed Jul 06 15:34:44 2011 -0700 @@ -1017,6 +1017,7 @@ Klaus-Juergen Wolf Dan Wolfe Richard Wolff +Ben Wolfson Adam Woodbeck Darren Worrall Gordon Worley diff -r f9d30fac3da0 Objects/stringlib/string_format.h --- a/Objects/stringlib/string_format.h Mon Jul 04 11:44:17 2011 -0700 +++ b/Objects/stringlib/string_format.h Wed Jul 06 15:34:44 2011 -0700 @@ -628,6 +628,34 @@ return ok; } +static void +advance_beyond_field(SubString *str) +{ + if (str->ptr > str->end) return; + switch(*++str->ptr) { + case '[': + while (str->ptr < str->end && *str->ptr != ']') + str->ptr++; + advance_beyond_field(str); + break; + case '.': + while (str->ptr < str->end) + switch (*++str->ptr) { + case ':': + case '!': + str->ptr--; + return; + case '[': + advance_beyond_field(str); + str->ptr--; + break; + default: continue; + } + break; + default: return; + } +} + static int parse_field(SubString *str, SubString *field_name, SubString *format_spec, STRINGLIB_CHAR *conversion) @@ -647,6 +675,12 @@ field_name->ptr = str->ptr; while (str->ptr < str->end) { switch (c = *(str->ptr++)) { + case '.': + case '[': + /* advance_beyond_field expects to move forward onto a . or a [ */ + str->ptr -= 2; + advance_beyond_field(str); + continue; case ':': case '!': break; @@ -714,6 +748,46 @@ return 1; } +static int +verify_identifier(STRINGLIB_CHAR *start, STRINGLIB_CHAR *end) +{ + PyObject *s; + int result; + s = STRINGLIB_NEW(start, end-start); + if (s == NULL) { + if (PyErr_ExceptionMatches(PyExc_UnicodeDecodeError)) { + PyErr_Clear(); + } + return 0; + } + result = PyUnicode_IsIdentifier(s); + Py_DECREF(s); + return result; +} + +static int +verify_integer(STRINGLIB_CHAR *start, STRINGLIB_CHAR *end) +{ + if (start + 1 == end && *start >= '0' && *start <= '9') return 1; + if (*start < '1' || *start > '9') return 0; + while (start++ < end) + if (*start < '0' || *start > '9') + return 0; + return 1; +} + +static int +check_arg_name(STRINGLIB_CHAR *start, STRINGLIB_CHAR *end) +{ + int result; + if (start == end) result = 1; + else result = verify_integer(start, end)||verify_identifier(start, end); + if (!result) + PyErr_SetString(PyExc_ValueError, "Argument name in format string " + "must be an integer or an identifier"); + return result; +} + /* returns 0 on error, 1 on non-error termination, and 2 if it got a string (or something to be expanded) */ static int @@ -725,6 +799,9 @@ int at_end; STRINGLIB_CHAR c = 0; STRINGLIB_CHAR *start; + STRINGLIB_CHAR *ident_start; + int arg_name_done = 0; + int field_name_done = 0; int count; Py_ssize_t len; int markup_follows = 0; @@ -806,13 +883,72 @@ about that case */ while (self->str.ptr < self->str.end) { switch (c = *(self->str.ptr++)) { + case '[': + if (!arg_name_done) { + arg_name_done = 1; + if (!check_arg_name(start, self->str.ptr-1)) return 0; + } + if (!field_name_done) { + while (self->str.ptr < self->str.end-1 && *self->str.ptr != ']') + self->str.ptr++; + } + continue; + case '.': + if (!arg_name_done) { + arg_name_done = 1; + if (!check_arg_name(start, self->str.ptr-1)) return 0; + } + if (!field_name_done) { + ident_start = self->str.ptr; + switch(*ident_start) { + case '[': case '.': + case '!': case '}': + PyErr_SetString(PyExc_ValueError, "Attribute name " + "in format string must be " + "nonempty"); + return 0; + } + while (self->str.ptr < self->str.end) { + switch(*++self->str.ptr) { + case '[': + case '.': + case '!': + case ':': + case '}': + if (!verify_identifier(ident_start, self->str.ptr)) { + PyErr_SetString(PyExc_ValueError, "Attribute name " + "in format string must be " + "an identifier"); + return 0; + } + break; + default: continue; + } + break; + } + } + break; + case ':': + case '!': + if (!arg_name_done) { + arg_name_done = 1; + if (!check_arg_name(start, self->str.ptr-1)) return 0; + } + field_name_done = 1; + continue; case '{': - /* the format spec needs to be recursively expanded. - this is an optimization, and not strictly needed */ - *format_spec_needs_expanding = 1; - count++; + if (field_name_done) { + /* the format spec needs to be recursively expanded. + this is an optimization, and not strictly needed */ + *format_spec_needs_expanding = 1; + count++; + } break; case '}': + if (!arg_name_done) { + arg_name_done = 1; + if (!check_arg_name(start, self->str.ptr-1)) return 0; + } count--; if (count <= 0) { /* we're done. parse and get out */