You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
8.6 KiB
362 lines
8.6 KiB
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 |
|
tableKeys []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) { |
|
rawName := string(buf[begin:end]) |
|
p.setTable(p.table, rawName, p.tableKeys) |
|
p.tableKeys = nil |
|
} |
|
|
|
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) |
|
} |
|
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) { |
|
rawName := string(buf[begin:end]) |
|
p.setArrayTable(p.table, rawName, p.tableKeys) |
|
p.tableKeys = nil |
|
} |
|
|
|
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) |
|
} |
|
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}) |
|
names := []string{p.key} |
|
if p.arr == nil { |
|
p.setTable(p.currentTable, names[0], names) |
|
} else { |
|
p.setArrayTable(p.currentTable, names[0], names) |
|
} |
|
} |
|
|
|
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]) |
|
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() { |
|
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 |
|
}
|
|
|