Skip to content

Commit

Permalink
Fix decoding of quoted keys
Browse files Browse the repository at this point in the history
This removes a few issues related to quoted keys:

- Escape sequences now work in table headers and KeyValue keys.
- Empty keys are allowed when quoted.
- The decoder doesn't see the quotes anymore. This already worked
  for (inline) tables because the parser split the key manually after
  decoding, removing quotes. The new version integrates the splitting
  into the grammar so there is only one place that handles keys.

Fixes #37
  • Loading branch information
fjl committed Apr 28, 2017
1 parent ac014c6 commit e6f5723
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 349 deletions.
42 changes: 41 additions & 1 deletion decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ d = 2`, nil,
},
{
data: `"-" = "value"`,
err: lineError(1, fmt.Errorf("field corresponding to `\"-\"' is not defined in toml.testIgnoredFieldStruct")),
err: lineError(1, fmt.Errorf("field corresponding to `-' is not defined in toml.testIgnoredFieldStruct")),
expect: &testIgnoredFieldStruct{},
},
{
Expand Down Expand Up @@ -1192,6 +1192,24 @@ foo = 1
`,
expect: map[string]interface{}{"name": "evan", "foo": int64(1)},
},
{
data: `[""]
a = 1
`,
expect: map[string]interface{}{"": map[string]interface{}{"a": int64(1)}},
},
{
data: `["table"]
a = 1
`,
expect: map[string]interface{}{"table": map[string]interface{}{"a": int64(1)}},
},
{
data: `["\u2222"]
a = 1
`,
expect: map[string]interface{}{"\u2222": map[string]interface{}{"a": int64(1)}},
},
{
data: `[p]
first = "evan"
Expand All @@ -1204,6 +1222,13 @@ bar = 2
`,
expect: map[testTextUnmarshaler]int{"Unmarshaled: foo": 1, "Unmarshaled: bar": 2},
},
{
data: `"foo" = 1
"foo.bar" = 2
`,
expect: map[testTextUnmarshaler]int{"Unmarshaled: foo": 1, "Unmarshaled: foo.bar": 2},
},

{
data: `1 = 1
-2 = 2
Expand All @@ -1229,6 +1254,13 @@ func TestUnmarshal_WithQuotedKeyValue(t *testing.T) {
}

testUnmarshal(t, []testcase{
{data: `"a" = 1`, expect: map[string]int{"a": 1}},
{data: `"a.b" = 1`, expect: map[string]int{"a.b": 1}},
{data: `"\u2222" = 1`, expect: map[string]int{"\u2222": 1}},
{data: `"\"" = 1`, expect: map[string]int{"\"": 1}},
{data: `"" = 1`, expect: map[string]int{"": 1}},
{data: `'a' = 1`, expect: map[string]int{}, err: lineError(1, errParse)},
// Inline tables:
{
data: `
[table]
Expand All @@ -1238,6 +1270,14 @@ func TestUnmarshal_WithQuotedKeyValue(t *testing.T) {
"some.key": {Truthy: true},
}},
},
{
data: `
"some.key" = [{truthy = true}]
`,
expect: map[string][]nestedStruct{
"some.key": {{Truthy: true}},
},
},
})
}

Expand Down
52 changes: 19 additions & 33 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type toml struct {
currentTable *ast.Table
s string
key string
tableKeys []string
val ast.Value
arr *array
stack []*stack
Expand Down Expand Up @@ -180,12 +181,12 @@ func (p *tomlParser) SetArray(begin, end int) {
}

func (p *toml) SetTable(buf []rune, begin, end int) {
p.setTable(p.table, buf, begin, end)
rawName := string(buf[begin:end])
p.setTable(p.table, rawName, p.tableKeys)
p.tableKeys = nil
}

func (p *toml) setTable(parent *ast.Table, buf []rune, begin, end int) {
name := string(buf[begin:end])
names := splitTableKey(name)
func (p *toml) setTable(parent *ast.Table, name string, names []string) {
parent, err := p.lookupTable(parent, names[:len(names)-1])
if err != nil {
p.Error(err)
Expand Down Expand Up @@ -230,12 +231,12 @@ func (p *tomlParser) SetTableString(begin, end int) {
}

func (p *toml) SetArrayTable(buf []rune, begin, end int) {
p.setArrayTable(p.table, buf, begin, end)
rawName := string(buf[begin:end])
p.setArrayTable(p.table, rawName, p.tableKeys)
p.tableKeys = nil
}

func (p *toml) setArrayTable(parent *ast.Table, buf []rune, begin, end int) {
name := string(buf[begin:end])
names := splitTableKey(name)
func (p *toml) setArrayTable(parent *ast.Table, name string, names []string) {
parent, err := p.lookupTable(parent, names[:len(names)-1])
if err != nil {
p.Error(err)
Expand All @@ -260,11 +261,11 @@ func (p *toml) setArrayTable(parent *ast.Table, buf []rune, begin, end int) {
func (p *toml) StartInlineTable() {
p.skip = false
p.stack = append(p.stack, &stack{p.key, p.currentTable})
buf := []rune(p.key)
names := []string{p.key}
if p.arr == nil {
p.setTable(p.currentTable, buf, 0, len(buf))
p.setTable(p.currentTable, names[0], names)
} else {
p.setArrayTable(p.currentTable, buf, 0, len(buf))
p.setArrayTable(p.currentTable, names[0], names)
}
}

Expand All @@ -282,6 +283,13 @@ func (p *toml) AddLineCount(i int) {

func (p *toml) SetKey(buf []rune, begin, end int) {
p.key = string(buf[begin:end])
if len(p.key) > 0 && p.key[0] == '"' {
p.key = p.unquote(p.key)
}
}

func (p *toml) AddTableKey() {
p.tableKeys = append(p.tableKeys, p.key)
}

func (p *toml) AddKeyValue() {
Expand Down Expand Up @@ -352,25 +360,3 @@ func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) {
}
return t, nil
}

func splitTableKey(tk string) []string {
key := make([]byte, 0, 1)
keys := make([]string, 0, 1)
inQuote := false
for i := 0; i < len(tk); i++ {
k := tk[i]
switch {
case k == tableSeparator && !inQuote:
keys = append(keys, string(key))
key = key[:0] // reuse buffer.
case k == '"':
inQuote = !inQuote
case (k == ' ' || k == '\t') && !inQuote:
// skip.
default:
key = append(key, k)
}
}
keys = append(keys, string(key))
return keys
}
6 changes: 4 additions & 2 deletions parse.peg
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ key <- bareKey / quotedKey

bareKey <- <[0-9A-Za-z\-_]+> { p.SetKey(p.buffer, begin, end) }

quotedKey <- '"' <basicChar+> '"' { p.SetKey(p.buffer, begin-1, end+1) }
quotedKey <- < '"' basicChar* '"' > { p.SetKey(p.buffer, begin, end) }

val <- (
<datetime> { p.SetTime(begin, end) }
Expand All @@ -55,7 +55,9 @@ inlineTable <- (

inlineTableKeyValues <- (keyval inlineTableValSep?)*

tableKey <- key (tableKeySep key)*
tableKey <- tableKeyComp (tableKeySep tableKeyComp)*

tableKeyComp <- key { p.AddTableKey() }

tableKeySep <- ws '.' ws

Expand Down
Loading

0 comments on commit e6f5723

Please sign in to comment.