aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/naoina/toml/parse.go
blob: e6f95001e5d8dabdd719fe84a767e7067e9ad364 (plain) (tree)























































































































































































































































































































































































                                                                                                                
package toml

import (
    "errors"
    "fmt"
    "strconv"
    "strings"

    "github.com/naoina/toml/ast"
)

// The parser is generated by github.com/pointlander/peg. To regenerate it, do:
//
//     go get -u github.com/pointlander/peg
//     go generate .

//go:generate peg -switch -inline parse.peg

var errParse = errors.New("invalid TOML syntax")

// Parse returns an AST representation of TOML.
// The toplevel is represented by a table.
func Parse(data []byte) (*ast.Table, error) {
    d := &parseState{p: &tomlParser{Buffer: string(data)}}
    d.init()

    if err := d.parse(); err != nil {
        return nil, err
    }

    return d.p.toml.table, nil
}

type parseState struct {
    p *tomlParser
}

func (d *parseState) init() {
    d.p.Init()
    d.p.toml.init(d.p.buffer)
}

func (d *parseState) parse() error {
    if err := d.p.Parse(); err != nil {
        if err, ok := err.(*parseError); ok {
            return lineError(err.Line(), errParse)
        }
        return err
    }
    return d.execute()
}

func (d *parseState) execute() (err error) {
    defer func() {
        if e := recover(); e != nil {
            lerr, ok := e.(*LineError)
            if !ok {
                panic(e)
            }
            err = lerr
        }
    }()
    d.p.Execute()
    return nil
}

func (e *parseError) Line() int {
    tokens := []token32{e.max}
    positions, p := make([]int, 2*len(tokens)), 0
    for _, token := range tokens {
        positions[p], p = int(token.begin), p+1
        positions[p], p = int(token.end), p+1
    }
    for _, t := range translatePositions(e.p.buffer, positions) {
        if e.p.line < t.line {
            e.p.line = t.line
        }
    }
    return e.p.line
}

type stack struct {
    key   string
    table *ast.Table
}

type array struct {
    parent  *array
    child   *array
    current *ast.Array
    line    int
}

type toml struct {
    table        *ast.Table
    line         int
    currentTable *ast.Table
    s            string
    key          string
    val          ast.Value
    arr          *array
    stack        []*stack
    skip         bool
}

func (p *toml) init(data []rune) {
    p.line = 1
    p.table = p.newTable(ast.TableTypeNormal, "")
    p.table.Position.End = len(data) - 1
    p.table.Data = data[:len(data)-1] // truncate the end_symbol added by PEG parse generator.
    p.currentTable = p.table
}

func (p *toml) Error(err error) {
    panic(lineError(p.line, err))
}

func (p *tomlParser) SetTime(begin, end int) {
    p.val = &ast.Datetime{
        Position: ast.Position{Begin: begin, End: end},
        Data:     p.buffer[begin:end],
        Value:    string(p.buffer[begin:end]),
    }
}

func (p *tomlParser) SetFloat64(begin, end int) {
    p.val = &ast.Float{
        Position: ast.Position{Begin: begin, End: end},
        Data:     p.buffer[begin:end],
        Value:    underscoreReplacer.Replace(string(p.buffer[begin:end])),
    }
}

func (p *tomlParser) SetInt64(begin, end int) {
    p.val = &ast.Integer{
        Position: ast.Position{Begin: begin, End: end},
        Data:     p.buffer[begin:end],
        Value:    underscoreReplacer.Replace(string(p.buffer[begin:end])),
    }
}

func (p *tomlParser) SetString(begin, end int) {
    p.val = &ast.String{
        Position: ast.Position{Begin: begin, End: end},
        Data:     p.buffer[begin:end],
        Value:    p.s,
    }
    p.s = ""
}

func (p *tomlParser) SetBool(begin, end int) {
    p.val = &ast.Boolean{
        Position: ast.Position{Begin: begin, End: end},
        Data:     p.buffer[begin:end],
        Value:    string(p.buffer[begin:end]),
    }
}

func (p *tomlParser) StartArray() {
    if p.arr == nil {
        p.arr = &array{line: p.line, current: &ast.Array{}}
        return
    }
    p.arr.child = &array{parent: p.arr, line: p.line, current: &ast.Array{}}
    p.arr = p.arr.child
}

func (p *tomlParser) AddArrayVal() {
    if p.arr.current == nil {
        p.arr.current = &ast.Array{}
    }
    p.arr.current.Value = append(p.arr.current.Value, p.val)
}

func (p *tomlParser) SetArray(begin, end int) {
    p.arr.current.Position = ast.Position{Begin: begin, End: end}
    p.arr.current.Data = p.buffer[begin:end]
    p.val = p.arr.current
    p.arr = p.arr.parent
}

func (p *toml) SetTable(buf []rune, begin, end int) {
    p.setTable(p.table, buf, begin, end)
}

func (p *toml) setTable(parent *ast.Table, buf []rune, begin, end int) {
    name := string(buf[begin:end])
    names := splitTableKey(name)
    parent, err := p.lookupTable(parent, names[:len(names)-1])
    if err != nil {
        p.Error(err)
    }
    last := names[len(names)-1]
    tbl := p.newTable(ast.TableTypeNormal, last)
    switch v := parent.Fields[last].(type) {
    case nil:
        parent.Fields[last] = tbl
    case []*ast.Table:
        p.Error(fmt.Errorf("table `%s' is in conflict with array table in line %d", name, v[0].Line))
    case *ast.Table:
        if (v.Position == ast.Position{}) {
            // This table was created as an implicit parent.
            // Replace it with the real defined table.
            tbl.Fields = v.Fields
            parent.Fields[last] = tbl
        } else {
            p.Error(fmt.Errorf("table `%s' is in conflict with table in line %d", name, v.Line))
        }
    case *ast.KeyValue:
        p.Error(fmt.Errorf("table `%s' is in conflict with line %d", name, v.Line))
    default:
        p.Error(fmt.Errorf("BUG: table `%s' is in conflict but it's unknown type `%T'", last, v))
    }
    p.currentTable = tbl
}

func (p *toml) newTable(typ ast.TableType, name string) *ast.Table {
    return &ast.Table{
        Line:   p.line,
        Name:   name,
        Type:   typ,
        Fields: make(map[string]interface{}),
    }
}

func (p *tomlParser) SetTableString(begin, end int) {
    p.currentTable.Data = p.buffer[begin:end]
    p.currentTable.Position.Begin = begin
    p.currentTable.Position.End = end
}

func (p *toml) SetArrayTable(buf []rune, begin, end int) {
    p.setArrayTable(p.table, buf, begin, end)
}

func (p *toml) setArrayTable(parent *ast.Table, buf []rune, begin, end int) {
    name := string(buf[begin:end])
    names := splitTableKey(name)
    parent, err := p.lookupTable(parent, names[:len(names)-1])
    if err != nil {
        p.Error(err)
    }
    last := names[len(names)-1]
    tbl := p.newTable(ast.TableTypeArray, last)
    switch v := parent.Fields[last].(type) {
    case nil:
        parent.Fields[last] = []*ast.Table{tbl}
    case []*ast.Table:
        parent.Fields[last] = append(v, tbl)
    case *ast.Table:
        p.Error(fmt.Errorf("array table `%s' is in conflict with table in line %d", name, v.Line))
    case *ast.KeyValue:
        p.Error(fmt.Errorf("array table `%s' is in conflict with line %d", name, v.Line))
    default:
        p.Error(fmt.Errorf("BUG: array table `%s' is in conflict but it's unknown type `%T'", name, v))
    }
    p.currentTable = tbl
}

func (p *toml) StartInlineTable() {
    p.skip = false
    p.stack = append(p.stack, &stack{p.key, p.currentTable})
    buf := []rune(p.key)
    if p.arr == nil {
        p.setTable(p.currentTable, buf, 0, len(buf))
    } else {
        p.setArrayTable(p.currentTable, buf, 0, len(buf))
    }
}

func (p *toml) EndInlineTable() {
    st := p.stack[len(p.stack)-1]
    p.key, p.currentTable = st.key, st.table
    p.stack[len(p.stack)-1] = nil
    p.stack = p.stack[:len(p.stack)-1]
    p.skip = true
}

func (p *toml) AddLineCount(i int) {
    p.line += i
}

func (p *toml) SetKey(buf []rune, begin, end int) {
    p.key = string(buf[begin:end])
}

func (p *toml) AddKeyValue() {
    if p.skip {
        p.skip = false
        return
    }
    if val, exists := p.currentTable.Fields[p.key]; exists {
        switch v := val.(type) {
        case *ast.Table:
            p.Error(fmt.Errorf("key `%s' is in conflict with table in line %d", p.key, v.Line))
        case *ast.KeyValue:
            p.Error(fmt.Errorf("key `%s' is in conflict with line %xd", p.key, v.Line))
        default:
            p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", p.key, v))
        }
    }
    p.currentTable.Fields[p.key] = &ast.KeyValue{Key: p.key, Value: p.val, Line: p.line}
}

func (p *toml) SetBasicString(buf []rune, begin, end int) {
    p.s = p.unquote(string(buf[begin:end]))
}

func (p *toml) SetMultilineString() {
    p.s = p.unquote(`"` + escapeReplacer.Replace(strings.TrimLeft(p.s, "\r\n")) + `"`)
}

func (p *toml) AddMultilineBasicBody(buf []rune, begin, end int) {
    p.s += string(buf[begin:end])
}

func (p *toml) SetLiteralString(buf []rune, begin, end int) {
    p.s = string(buf[begin:end])
}

func (p *toml) SetMultilineLiteralString(buf []rune, begin, end int) {
    p.s = strings.TrimLeft(string(buf[begin:end]), "\r\n")
}

func (p *toml) unquote(s string) string {
    s, err := strconv.Unquote(s)
    if err != nil {
        p.Error(err)
    }
    return s
}

func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) {
    for _, s := range keys {
        val, exists := t.Fields[s]
        if !exists {
            tbl := p.newTable(ast.TableTypeNormal, s)
            t.Fields[s] = tbl
            t = tbl
            continue
        }
        switch v := val.(type) {
        case *ast.Table:
            t = v
        case []*ast.Table:
            t = v[len(v)-1]
        case *ast.KeyValue:
            return nil, fmt.Errorf("key `%s' is in conflict with line %d", s, v.Line)
        default:
            return nil, fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", s, v)
        }
    }
    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
}