|  | // TOML Parser. | 
|  |  | 
|  | package toml | 
|  |  | 
|  | import ( | 
|  | "errors" | 
|  | "fmt" | 
|  | "reflect" | 
|  | "regexp" | 
|  | "strconv" | 
|  | "strings" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | type tomlParser struct { | 
|  | flow          chan token | 
|  | tree          *TomlTree | 
|  | tokensBuffer  []token | 
|  | currentTable  []string | 
|  | seenTableKeys []string | 
|  | } | 
|  |  | 
|  | type tomlParserStateFn func() tomlParserStateFn | 
|  |  | 
|  | // Formats and panics an error message based on a token | 
|  | func (p *tomlParser) raiseError(tok *token, msg string, args ...interface{}) { | 
|  | panic(tok.Position.String() + ": " + fmt.Sprintf(msg, args...)) | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) run() { | 
|  | for state := p.parseStart; state != nil; { | 
|  | state = state() | 
|  | } | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) peek() *token { | 
|  | if len(p.tokensBuffer) != 0 { | 
|  | return &(p.tokensBuffer[0]) | 
|  | } | 
|  |  | 
|  | tok, ok := <-p.flow | 
|  | if !ok { | 
|  | return nil | 
|  | } | 
|  | p.tokensBuffer = append(p.tokensBuffer, tok) | 
|  | return &tok | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) assume(typ tokenType) { | 
|  | tok := p.getToken() | 
|  | if tok == nil { | 
|  | p.raiseError(tok, "was expecting token %s, but token stream is empty", tok) | 
|  | } | 
|  | if tok.typ != typ { | 
|  | p.raiseError(tok, "was expecting token %s, but got %s instead", typ, tok) | 
|  | } | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) getToken() *token { | 
|  | if len(p.tokensBuffer) != 0 { | 
|  | tok := p.tokensBuffer[0] | 
|  | p.tokensBuffer = p.tokensBuffer[1:] | 
|  | return &tok | 
|  | } | 
|  | tok, ok := <-p.flow | 
|  | if !ok { | 
|  | return nil | 
|  | } | 
|  | return &tok | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseStart() tomlParserStateFn { | 
|  | tok := p.peek() | 
|  |  | 
|  | // end of stream, parsing is finished | 
|  | if tok == nil { | 
|  | return nil | 
|  | } | 
|  |  | 
|  | switch tok.typ { | 
|  | case tokenDoubleLeftBracket: | 
|  | return p.parseGroupArray | 
|  | case tokenLeftBracket: | 
|  | return p.parseGroup | 
|  | case tokenKey: | 
|  | return p.parseAssign | 
|  | case tokenEOF: | 
|  | return nil | 
|  | default: | 
|  | p.raiseError(tok, "unexpected token") | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseGroupArray() tomlParserStateFn { | 
|  | startToken := p.getToken() // discard the [[ | 
|  | key := p.getToken() | 
|  | if key.typ != tokenKeyGroupArray { | 
|  | p.raiseError(key, "unexpected token %s, was expecting a table array key", key) | 
|  | } | 
|  |  | 
|  | // get or create table array element at the indicated part in the path | 
|  | keys, err := parseKey(key.val) | 
|  | if err != nil { | 
|  | p.raiseError(key, "invalid table array key: %s", err) | 
|  | } | 
|  | p.tree.createSubTree(keys[:len(keys)-1], startToken.Position) // create parent entries | 
|  | destTree := p.tree.GetPath(keys) | 
|  | var array []*TomlTree | 
|  | if destTree == nil { | 
|  | array = make([]*TomlTree, 0) | 
|  | } else if target, ok := destTree.([]*TomlTree); ok && target != nil { | 
|  | array = destTree.([]*TomlTree) | 
|  | } else { | 
|  | p.raiseError(key, "key %s is already assigned and not of type table array", key) | 
|  | } | 
|  | p.currentTable = keys | 
|  |  | 
|  | // add a new tree to the end of the table array | 
|  | newTree := newTomlTree() | 
|  | newTree.position = startToken.Position | 
|  | array = append(array, newTree) | 
|  | p.tree.SetPath(p.currentTable, array) | 
|  |  | 
|  | // remove all keys that were children of this table array | 
|  | prefix := key.val + "." | 
|  | found := false | 
|  | for ii := 0; ii < len(p.seenTableKeys); { | 
|  | tableKey := p.seenTableKeys[ii] | 
|  | if strings.HasPrefix(tableKey, prefix) { | 
|  | p.seenTableKeys = append(p.seenTableKeys[:ii], p.seenTableKeys[ii+1:]...) | 
|  | } else { | 
|  | found = (tableKey == key.val) | 
|  | ii++ | 
|  | } | 
|  | } | 
|  |  | 
|  | // keep this key name from use by other kinds of assignments | 
|  | if !found { | 
|  | p.seenTableKeys = append(p.seenTableKeys, key.val) | 
|  | } | 
|  |  | 
|  | // move to next parser state | 
|  | p.assume(tokenDoubleRightBracket) | 
|  | return p.parseStart | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseGroup() tomlParserStateFn { | 
|  | startToken := p.getToken() // discard the [ | 
|  | key := p.getToken() | 
|  | if key.typ != tokenKeyGroup { | 
|  | p.raiseError(key, "unexpected token %s, was expecting a table key", key) | 
|  | } | 
|  | for _, item := range p.seenTableKeys { | 
|  | if item == key.val { | 
|  | p.raiseError(key, "duplicated tables") | 
|  | } | 
|  | } | 
|  |  | 
|  | p.seenTableKeys = append(p.seenTableKeys, key.val) | 
|  | keys, err := parseKey(key.val) | 
|  | if err != nil { | 
|  | p.raiseError(key, "invalid table array key: %s", err) | 
|  | } | 
|  | if err := p.tree.createSubTree(keys, startToken.Position); err != nil { | 
|  | p.raiseError(key, "%s", err) | 
|  | } | 
|  | p.assume(tokenRightBracket) | 
|  | p.currentTable = keys | 
|  | return p.parseStart | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseAssign() tomlParserStateFn { | 
|  | key := p.getToken() | 
|  | p.assume(tokenEqual) | 
|  |  | 
|  | value := p.parseRvalue() | 
|  | var tableKey []string | 
|  | if len(p.currentTable) > 0 { | 
|  | tableKey = p.currentTable | 
|  | } else { | 
|  | tableKey = []string{} | 
|  | } | 
|  |  | 
|  | // find the table to assign, looking out for arrays of tables | 
|  | var targetNode *TomlTree | 
|  | switch node := p.tree.GetPath(tableKey).(type) { | 
|  | case []*TomlTree: | 
|  | targetNode = node[len(node)-1] | 
|  | case *TomlTree: | 
|  | targetNode = node | 
|  | default: | 
|  | p.raiseError(key, "Unknown table type for path: %s", | 
|  | strings.Join(tableKey, ".")) | 
|  | } | 
|  |  | 
|  | // assign value to the found table | 
|  | keyVals, err := parseKey(key.val) | 
|  | if err != nil { | 
|  | p.raiseError(key, "%s", err) | 
|  | } | 
|  | if len(keyVals) != 1 { | 
|  | p.raiseError(key, "Invalid key") | 
|  | } | 
|  | keyVal := keyVals[0] | 
|  | localKey := []string{keyVal} | 
|  | finalKey := append(tableKey, keyVal) | 
|  | if targetNode.GetPath(localKey) != nil { | 
|  | p.raiseError(key, "The following key was defined twice: %s", | 
|  | strings.Join(finalKey, ".")) | 
|  | } | 
|  | var toInsert interface{} | 
|  |  | 
|  | switch value.(type) { | 
|  | case *TomlTree, []*TomlTree: | 
|  | toInsert = value | 
|  | default: | 
|  | toInsert = &tomlValue{value, key.Position} | 
|  | } | 
|  | targetNode.values[keyVal] = toInsert | 
|  | return p.parseStart | 
|  | } | 
|  |  | 
|  | var numberUnderscoreInvalidRegexp *regexp.Regexp | 
|  |  | 
|  | func cleanupNumberToken(value string) (string, error) { | 
|  | if numberUnderscoreInvalidRegexp.MatchString(value) { | 
|  | return "", errors.New("invalid use of _ in number") | 
|  | } | 
|  | cleanedVal := strings.Replace(value, "_", "", -1) | 
|  | return cleanedVal, nil | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseRvalue() interface{} { | 
|  | tok := p.getToken() | 
|  | if tok == nil || tok.typ == tokenEOF { | 
|  | p.raiseError(tok, "expecting a value") | 
|  | } | 
|  |  | 
|  | switch tok.typ { | 
|  | case tokenString: | 
|  | return tok.val | 
|  | case tokenTrue: | 
|  | return true | 
|  | case tokenFalse: | 
|  | return false | 
|  | case tokenInteger: | 
|  | cleanedVal, err := cleanupNumberToken(tok.val) | 
|  | if err != nil { | 
|  | p.raiseError(tok, "%s", err) | 
|  | } | 
|  | val, err := strconv.ParseInt(cleanedVal, 10, 64) | 
|  | if err != nil { | 
|  | p.raiseError(tok, "%s", err) | 
|  | } | 
|  | return val | 
|  | case tokenFloat: | 
|  | cleanedVal, err := cleanupNumberToken(tok.val) | 
|  | if err != nil { | 
|  | p.raiseError(tok, "%s", err) | 
|  | } | 
|  | val, err := strconv.ParseFloat(cleanedVal, 64) | 
|  | if err != nil { | 
|  | p.raiseError(tok, "%s", err) | 
|  | } | 
|  | return val | 
|  | case tokenDate: | 
|  | val, err := time.ParseInLocation(time.RFC3339Nano, tok.val, time.UTC) | 
|  | if err != nil { | 
|  | p.raiseError(tok, "%s", err) | 
|  | } | 
|  | return val | 
|  | case tokenLeftBracket: | 
|  | return p.parseArray() | 
|  | case tokenLeftCurlyBrace: | 
|  | return p.parseInlineTable() | 
|  | case tokenEqual: | 
|  | p.raiseError(tok, "cannot have multiple equals for the same key") | 
|  | case tokenError: | 
|  | p.raiseError(tok, "%s", tok) | 
|  | } | 
|  |  | 
|  | p.raiseError(tok, "never reached") | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func tokenIsComma(t *token) bool { | 
|  | return t != nil && t.typ == tokenComma | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseInlineTable() *TomlTree { | 
|  | tree := newTomlTree() | 
|  | var previous *token | 
|  | Loop: | 
|  | for { | 
|  | follow := p.peek() | 
|  | if follow == nil || follow.typ == tokenEOF { | 
|  | p.raiseError(follow, "unterminated inline table") | 
|  | } | 
|  | switch follow.typ { | 
|  | case tokenRightCurlyBrace: | 
|  | p.getToken() | 
|  | break Loop | 
|  | case tokenKey: | 
|  | if !tokenIsComma(previous) && previous != nil { | 
|  | p.raiseError(follow, "comma expected between fields in inline table") | 
|  | } | 
|  | key := p.getToken() | 
|  | p.assume(tokenEqual) | 
|  | value := p.parseRvalue() | 
|  | tree.Set(key.val, value) | 
|  | case tokenComma: | 
|  | if previous == nil { | 
|  | p.raiseError(follow, "inline table cannot start with a comma") | 
|  | } | 
|  | if tokenIsComma(previous) { | 
|  | p.raiseError(follow, "need field between two commas in inline table") | 
|  | } | 
|  | p.getToken() | 
|  | default: | 
|  | p.raiseError(follow, "unexpected token type in inline table: %s", follow.typ.String()) | 
|  | } | 
|  | previous = follow | 
|  | } | 
|  | if tokenIsComma(previous) { | 
|  | p.raiseError(previous, "trailing comma at the end of inline table") | 
|  | } | 
|  | return tree | 
|  | } | 
|  |  | 
|  | func (p *tomlParser) parseArray() interface{} { | 
|  | var array []interface{} | 
|  | arrayType := reflect.TypeOf(nil) | 
|  | for { | 
|  | follow := p.peek() | 
|  | if follow == nil || follow.typ == tokenEOF { | 
|  | p.raiseError(follow, "unterminated array") | 
|  | } | 
|  | if follow.typ == tokenRightBracket { | 
|  | p.getToken() | 
|  | break | 
|  | } | 
|  | val := p.parseRvalue() | 
|  | if arrayType == nil { | 
|  | arrayType = reflect.TypeOf(val) | 
|  | } | 
|  | if reflect.TypeOf(val) != arrayType { | 
|  | p.raiseError(follow, "mixed types in array") | 
|  | } | 
|  | array = append(array, val) | 
|  | follow = p.peek() | 
|  | if follow == nil || follow.typ == tokenEOF { | 
|  | p.raiseError(follow, "unterminated array") | 
|  | } | 
|  | if follow.typ != tokenRightBracket && follow.typ != tokenComma { | 
|  | p.raiseError(follow, "missing comma") | 
|  | } | 
|  | if follow.typ == tokenComma { | 
|  | p.getToken() | 
|  | } | 
|  | } | 
|  | // An array of TomlTrees is actually an array of inline | 
|  | // tables, which is a shorthand for a table array. If the | 
|  | // array was not converted from []interface{} to []*TomlTree, | 
|  | // the two notations would not be equivalent. | 
|  | if arrayType == reflect.TypeOf(newTomlTree()) { | 
|  | tomlArray := make([]*TomlTree, len(array)) | 
|  | for i, v := range array { | 
|  | tomlArray[i] = v.(*TomlTree) | 
|  | } | 
|  | return tomlArray | 
|  | } | 
|  | return array | 
|  | } | 
|  |  | 
|  | func parseToml(flow chan token) *TomlTree { | 
|  | result := newTomlTree() | 
|  | result.position = Position{1, 1} | 
|  | parser := &tomlParser{ | 
|  | flow:          flow, | 
|  | tree:          result, | 
|  | tokensBuffer:  make([]token, 0), | 
|  | currentTable:  make([]string, 0), | 
|  | seenTableKeys: make([]string, 0), | 
|  | } | 
|  | parser.run() | 
|  | return result | 
|  | } | 
|  |  | 
|  | func init() { | 
|  | numberUnderscoreInvalidRegexp = regexp.MustCompile(`([^\d]_|_[^\d]|_$|^_)`) | 
|  | } |