Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lexer/parser changes for inline table support #71

Merged
merged 3 commits into from
Mar 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,44 @@ name = "Rice"

}

func TestDecodeInlineTable(t *testing.T) {
inlineToml := `
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inlineTOML, but just input would be a fine name.

[CookieJar]
Types = {Chocolate = "yummy", Oatmeal = "best ever"}

[Seasons]
Locations = {NY = {Temp = "not cold", Rating = 4},
MI = {Temp = "freezing", Rating = 9}}
`
var ex struct {
CookieJar struct {
Types map[string]string
}
Seasons struct {
Locations map[string]struct {
Temp string
Rating int
}
}
}

meta, err := Decode(inlineToml, &ex)
if err != nil {
t.Error("Failed to correctly decode inline table, err: ", err)
}
if len(ex.CookieJar.Types) != 2 || len(ex.Seasons.Locations) != 2 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write out the full expected value, and do reflect.DeepEqual(ex, want)?

t.Error("Failed to correctly decode inline table, fields are missing")
}
if len(meta.keys) != 12 {
t.Error("Wrong # of keys when decoding inline table, expected 12, got: ",
len(meta.keys))
}
if len(meta.types) != 12 {
t.Error("Wrong # of types when decoding inline table, expected 12, got: ",
len(meta.types))
}
}

type menu struct {
Dishes map[string]dish
}
Expand Down
90 changes: 75 additions & 15 deletions lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,29 @@ const (
itemArrayTableEnd
itemKeyStart
itemCommentStart
itemInlineTableStart
itemInlineTableEnd
)

const (
eof = 0
tableStart = '['
tableEnd = ']'
arrayTableStart = '['
arrayTableEnd = ']'
tableSep = '.'
keySep = '='
arrayStart = '['
arrayEnd = ']'
arrayValTerm = ','
commentStart = '#'
stringStart = '"'
stringEnd = '"'
rawStringStart = '\''
rawStringEnd = '\''
eof = 0
tableStart = '['
tableEnd = ']'
arrayTableStart = '['
arrayTableEnd = ']'
tableSep = '.'
keySep = '='
arrayStart = '['
arrayEnd = ']'
arrayValTerm = ','
commentStart = '#'
stringStart = '"'
stringEnd = '"'
rawStringStart = '\''
rawStringEnd = '\''
inlineTableStart = '{'
inlineTableEnd = '}'
inlineTableValTerm = ','
)

type stateFn func(lx *lexer) stateFn
Expand Down Expand Up @@ -382,6 +387,10 @@ func lexValue(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemArray)
return lexArrayValue
case r == inlineTableStart:
lx.ignore()
lx.emit(itemInlineTableStart)
return lexInlineTableValue
case r == stringStart:
if lx.accept(stringStart) {
if lx.accept(stringStart) {
Expand Down Expand Up @@ -467,6 +476,57 @@ func lexArrayEnd(lx *lexer) stateFn {
return lx.pop()
}

// lexInlineTableValue consumes one key/value pair in an inline able. It
// assumes that '{' or ',' have already been consumed. All whitespace and
// new lines are ignored.
func lexInlineTableValue(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r) || isNL(r):
return lexSkip(lx, lexInlineTableValue)
case r == commentStart:
lx.push(lexInlineTableValue)
return lexCommentStart
case r == inlineTableValTerm:
return lx.errorf("Unexpected inline table value terminator %q.",
inlineTableValTerm)
case r == inlineTableEnd:
return lexInlineTableEnd
}
lx.backup()
lx.push(lexInlineTableValueEnd)
return lexKeyStart
}

// lexInlineTableValueEnd consumes the cruft between values of an inline
// table. Namely, it ignores whitespace and expects either a ',' or a '}'.
func lexInlineTableValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r) || isNL(r):
return lexSkip(lx, lexInlineTableValueEnd)
case r == commentStart:
lx.push(lexInlineTableValueEnd)
return lexCommentStart
case r == inlineTableValTerm:
lx.ignore()
return lexInlineTableValue
case r == inlineTableEnd:
return lexInlineTableEnd
}
return lx.errorf("Expected an inline table value terminator %q or an "+
"inline table terminator %q, but got %q instead.", inlineTableValTerm,
inlineTableEnd, r)
}

// lexInlineTableEnd finishes the lexing of an inline table. It assumes that a '}' has
// just been consumed.
func lexInlineTableEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemInlineTableEnd)
return lx.pop()
}

// lexString consumes the inner contents of a string. It assumes that the
// beginning '"' has already been consumed and ignored.
func lexString(lx *lexer) stateFn {
Expand Down
35 changes: 35 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,41 @@ func (p *parser) value(it item) (interface{}, tomlType) {
types = append(types, typ)
}
return array, p.typeOfArray(types)
case itemInlineTableStart:
var (
hash = make(map[string]interface{})
outerContext = p.context
outerKey = p.currentKey
)

p.context = append(p.context, p.currentKey)
p.currentKey = ""
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
if it.typ != itemKeyStart {
p.bug("Expected key start but instead found %q, around line %d",
it.val, p.approxLine)
}
if it.typ == itemCommentStart {
p.expect(itemText)
continue
}

// retrieve key
k := p.next()
p.approxLine = k.line
kname := p.keyString(k)

// retrieve value
p.currentKey = kname
val, typ := p.value(p.next())
// make sure we keep metadata up to date
p.setType(kname, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[kname] = val
}
p.context = outerContext
p.currentKey = outerKey
return hash, tomlHash
}
p.bug("Unexpected value type: %s", it.typ)
panic("unreachable")
Expand Down