diff options
author | alandonovan <adonovan@google.com> | 2017-11-28 22:08:16 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-28 22:08:16 -0500 |
commit | 71da662e0b778bcc80721ed7b372d6d45417b300 (patch) | |
tree | 0d505a50b5f430126f6f7b5deea5295e96956793 | |
parent | 1b9d0e7119bc2aca0cd6be14ea0b8b184dc12bd2 (diff) | |
download | starlark-go-71da662e0b778bcc80721ed7b372d6d45417b300.tar.gz |
string.format: handle "}}" and "{-0}" correctly (#46)
Fixes #18
-rw-r--r-- | library.go | 32 | ||||
-rw-r--r-- | testdata/string.sky | 12 |
2 files changed, 38 insertions, 6 deletions
@@ -1571,13 +1571,29 @@ func string_format(fnname string, recv_ Value, args Tuple, kwargs []Tuple) (Valu var buf bytes.Buffer index := 0 for { - // TODO(adonovan): replace doubled "}}" with "}" and reject single '}'. + literal := format i := strings.IndexByte(format, '{') + if i >= 0 { + literal = format[:i] + } + + // Replace "}}" with "}" in non-field portion, rejecting a lone '}'. + for { + j := strings.IndexByte(literal, '}') + if j < 0 { + buf.WriteString(literal) + break + } + if len(literal) == j+1 || literal[j+1] != '}' { + return nil, fmt.Errorf("single '}' in format") + } + buf.WriteString(literal[:j+1]) + literal = literal[j+2:] + } + if i < 0 { - buf.WriteString(format) - break + break // end of format string } - buf.WriteString(format[:i]) if i+1 < len(format) && format[i+1] == '{' { // "{{" means a literal '{' @@ -1632,7 +1648,7 @@ func string_format(fnname string, recv_ Value, args Tuple, kwargs []Tuple) (Valu } arg = args[index] index++ - } else if num, err := strconv.Atoi(name); err == nil { + } else if num, err := strconv.Atoi(name); err == nil && !strings.HasPrefix(name, "-") { // positional argument if auto { return nil, fmt.Errorf("cannot switch from automatic field numbering to manual field specification") @@ -1652,13 +1668,17 @@ func string_format(fnname string, recv_ Value, args Tuple, kwargs []Tuple) (Valu } } if arg == nil { - // Skylark does not support Python's x.y or a[i] syntaxes. + // Skylark does not support Python's x.y or a[i] syntaxes, + // or nested use of {...}. if strings.Contains(name, ".") { return nil, fmt.Errorf("attribute syntax x.y is not supported in replacement fields: %s", name) } if strings.Contains(name, "[") { return nil, fmt.Errorf("element syntax a[i] is not supported in replacement fields: %s", name) } + if strings.Contains(name, "{") { + return nil, fmt.Errorf("nested replacement fields not supported") + } return nil, fmt.Errorf("keyword %s not found", name) } } diff --git a/testdata/string.sky b/testdata/string.sky index 1df8ee9..f92dadf 100644 --- a/testdata/string.sky +++ b/testdata/string.sky @@ -162,8 +162,13 @@ assert.fails(lambda: "%c" % -1, "requires a valid Unicode code point") assert.eq("a{}b".format(123), "a123b") assert.eq("a{}b{}c{}d{}".format(1, 2, 3, 4), "a1b2c3d4") assert.eq("a{{b".format(), "a{b") +assert.eq("a}}b".format(), "a}b") +assert.eq("a{{b}}c".format(), "a{b}c") assert.eq("a{x}b{y}c{}".format(1, x=2, y=3), "a2b3c1") assert.fails(lambda: "a{z}b".format(x=1), "keyword z not found") +assert.fails(lambda: "{-1}".format(1), "keyword -1 not found") +assert.fails(lambda: "{-0}".format(1), "keyword -0 not found") +assert.fails(lambda: '{0,1} and {1}'.format(1, 2), "keyword 0,1 not found") assert.fails(lambda: "a{123}b".format(), "tuple index out of range") assert.fails(lambda: "a{}b{}c".format(1), "tuple index out of range") assert.eq("a{010}b".format(0,1,2,3,4,5,6,7,8,9,10), "a10b") # index is decimal @@ -173,6 +178,13 @@ assert.eq("a{!r}c".format("b"), r'a"b"c') assert.eq("a{x!r}c".format(x='b'), r'a"b"c') assert.fails(lambda: "{x!}".format(x=1), "unknown conversion") assert.fails(lambda: "{x!:}".format(x=1), "unknown conversion") +assert.fails(lambda: '{a.b}'.format(1), "syntax x.y is not supported") +assert.fails(lambda: '{a[0]}'.format(1), "syntax a\[i\] is not supported") +assert.fails(lambda: '{ {} }'.format(1), "nested replacement fields not supported") +assert.fails(lambda: '{{}'.format(1), "single '}' in format") +assert.fails(lambda: '{}}'.format(1), "single '}' in format") +assert.fails(lambda: '}}{'.format(1), "unmatched '{' in format") +assert.fails(lambda: '}{{'.format(1), "single '}' in format") # str.split, str.rsplit assert.eq("a.b.c.d".split("."), ["a", "b", "c", "d"]) |