Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
1687 lines
46 KiB
Go
1687 lines
46 KiB
Go
package unstable
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// ParserError describes an error relative to the content of the document.
|
|
//
|
|
// It cannot outlive the instance of Parser it refers to, and may cause panics
|
|
// if the parser is reset.
|
|
type ParserError struct {
|
|
Highlight []byte
|
|
Message string
|
|
Key []string // optional
|
|
}
|
|
|
|
// Error is the implementation of the error interface.
|
|
func (e *ParserError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
// NewParserError is a convenience function to create a ParserError
|
|
//
|
|
// Warning: Highlight needs to be a subslice of Parser.data, so only slices
|
|
// returned by Parser.Raw are valid candidates.
|
|
func NewParserError(highlight []byte, format string, args ...interface{}) error {
|
|
return &ParserError{
|
|
Highlight: highlight,
|
|
Message: fmt.Errorf(format, args...).Error(),
|
|
}
|
|
}
|
|
|
|
// Parser scans over a TOML-encoded document and generates an iterative AST.
|
|
//
|
|
// To prime the Parser, first reset it with the contents of a TOML document.
|
|
// Then, process all top-level expressions sequentially. See Example.
|
|
//
|
|
// Don't forget to check Error() after you're done parsing.
|
|
//
|
|
// Each top-level expression needs to be fully processed before calling
|
|
// NextExpression() again. Otherwise, calls to various Node methods may panic
|
|
// if the parser has moved on the next expression.
|
|
//
|
|
// For performance reasons, go-toml doesn't make a copy of the input bytes to
|
|
// the parser. Make sure to copy all the bytes you need to outlive the slice
|
|
// given to the parser.
|
|
type Parser struct {
|
|
KeepComments bool
|
|
|
|
data []byte
|
|
left []byte
|
|
nodes []Node
|
|
err error
|
|
|
|
// nesting is the current depth of nested arrays and inline tables being
|
|
// parsed. It guards parseVal/parseValArray/parseInlineTable against
|
|
// unbounded mutual recursion, which would otherwise let a deeply nested
|
|
// document overflow the goroutine stack (an unrecoverable fatal error).
|
|
nesting int
|
|
}
|
|
|
|
// maxValueNesting is the maximum depth of nested arrays and inline tables the
|
|
// parser accepts. Beyond it, parsing fails with a ParserError instead of
|
|
// risking a stack overflow. Real-world TOML documents nest only a handful of
|
|
// levels deep, so this bound is generous; it matches the limit used by the
|
|
// standard library's encoding/json.
|
|
const maxValueNesting = 10000
|
|
|
|
// Data returns the slice provided to the last call to Reset.
|
|
func (p *Parser) Data() []byte {
|
|
return p.data
|
|
}
|
|
|
|
// Range returns a range description that corresponds to a given slice of the
|
|
// input. If the argument is not a subslice of the parser input, this function
|
|
// panics.
|
|
func (p *Parser) Range(b []byte) Range {
|
|
// b is a subslice of p.data if and only if they share the same backing
|
|
// array. In that case, because subslicing cannot extend capacity, the
|
|
// number of bytes between the start of b and the end of the backing array
|
|
// (its capacity) identifies the offset of b within data.
|
|
offset := cap(p.data) - cap(b)
|
|
if offset < 0 || offset+len(b) > len(p.data) {
|
|
panic(errors.New("not a slice of the data slice"))
|
|
}
|
|
return Range{
|
|
Offset: uint32(offset), //nolint:gosec // TOML documents are small
|
|
Length: uint32(len(b)), //nolint:gosec // TOML documents are small
|
|
}
|
|
}
|
|
|
|
// Raw returns the slice corresponding to the bytes in the given range.
|
|
func (p *Parser) Raw(raw Range) []byte {
|
|
return p.data[raw.Offset : raw.Offset+raw.Length]
|
|
}
|
|
|
|
// Reset brings the parser to its initial state for a given input. It wipes an
|
|
// reuses internal storage to reduce allocation.
|
|
func (p *Parser) Reset(b []byte) {
|
|
p.data = b
|
|
p.left = b
|
|
p.nodes = p.nodes[:0]
|
|
p.err = nil
|
|
p.nesting = 0
|
|
}
|
|
|
|
// Error returns any error that has occurred during parsing.
|
|
func (p *Parser) Error() error {
|
|
return p.err
|
|
}
|
|
|
|
// Range of bytes in the document.
|
|
type Range struct {
|
|
Offset uint32
|
|
Length uint32
|
|
}
|
|
|
|
// Position describes a position in the input.
|
|
type Position struct {
|
|
// Number of bytes from the beginning of the input.
|
|
Offset int
|
|
// Line number, starting at 1.
|
|
Line int
|
|
// Column number, starting at 1.
|
|
Column int
|
|
}
|
|
|
|
// Shape describes the position of a range in the input.
|
|
type Shape struct {
|
|
Start Position
|
|
End Position
|
|
}
|
|
|
|
func (p *Parser) position(offset int) Position {
|
|
pos := Position{
|
|
Offset: offset,
|
|
Line: 1,
|
|
Column: 1,
|
|
}
|
|
b := p.data[:offset]
|
|
for {
|
|
idx := bytes.IndexByte(b, '\n')
|
|
if idx < 0 {
|
|
break
|
|
}
|
|
pos.Line++
|
|
b = b[idx+1:]
|
|
}
|
|
pos.Column = len(b) + 1
|
|
return pos
|
|
}
|
|
|
|
// Shape returns the shape of the given range in the input. Will
|
|
// panic if the range is not a subslice of the input.
|
|
func (p *Parser) Shape(r Range) Shape {
|
|
raw := p.Raw(r)
|
|
return Shape{
|
|
Start: p.position(int(r.Offset)),
|
|
End: p.position(int(r.Offset) + len(raw)),
|
|
}
|
|
}
|
|
|
|
// Expression returns a pointer to the node representing the last successfully
|
|
// parsed expression.
|
|
func (p *Parser) Expression() *Node {
|
|
if len(p.nodes) == 0 {
|
|
return nil
|
|
}
|
|
return &p.nodes[0]
|
|
}
|
|
|
|
// push appends a node to the arena and returns its handle (1-based index).
|
|
func (p *Parser) push(n Node) int32 {
|
|
if len(p.nodes) == cap(p.nodes) {
|
|
// Grow by 2x: large expressions (huge arrays) would otherwise grow
|
|
// the arena in small steps, copying it repeatedly.
|
|
newCap := 2 * cap(p.nodes)
|
|
if newCap < 64 {
|
|
newCap = 64
|
|
}
|
|
nodes := make([]Node, len(p.nodes), newCap)
|
|
copy(nodes, p.nodes)
|
|
p.nodes = nodes
|
|
}
|
|
n.parser = p
|
|
p.nodes = append(p.nodes, n)
|
|
return int32(len(p.nodes)) //nolint:gosec // node counts are bounded by document size
|
|
}
|
|
|
|
// at returns a pointer to the node with the given handle. Only valid until
|
|
// the next call to push.
|
|
func (p *Parser) at(handle int32) *Node {
|
|
return &p.nodes[handle-1]
|
|
}
|
|
|
|
// offsetOf returns the offset of b within the parser's data. b must be a
|
|
// subslice of p.data.
|
|
func (p *Parser) offsetOf(b []byte) int {
|
|
return cap(p.data) - cap(b)
|
|
}
|
|
|
|
// rangeFrom returns the Range covering bytes from the start of `from` to the
|
|
// start of `to`. Both must be subslices of p.data.
|
|
func (p *Parser) rangeFrom(from, to []byte) Range {
|
|
start := p.offsetOf(from)
|
|
end := p.offsetOf(to)
|
|
return Range{
|
|
Offset: uint32(start), //nolint:gosec // TOML documents are small
|
|
Length: uint32(end - start), //nolint:gosec // TOML documents are small
|
|
}
|
|
}
|
|
|
|
// NextExpression parses the next top-level expression. If an expression was
|
|
// successfully parsed, it returns true. If the parser is at the end of the
|
|
// document or an error occurred, it returns false.
|
|
//
|
|
// Retrieve the parsed expression with Expression().
|
|
func (p *Parser) NextExpression() bool {
|
|
if p.err != nil {
|
|
return false
|
|
}
|
|
|
|
p.nodes = p.nodes[:0]
|
|
|
|
for {
|
|
b := skipWhitespace(p.left)
|
|
if len(b) == 0 {
|
|
p.left = b
|
|
return false
|
|
}
|
|
|
|
var err error
|
|
switch b[0] {
|
|
case '\n':
|
|
p.left = b[1:]
|
|
continue
|
|
case '\r':
|
|
if len(b) > 1 && b[1] == '\n' {
|
|
p.left = b[2:]
|
|
continue
|
|
}
|
|
err = NewParserError(b[:1], "expected newline but got %#U", b[0])
|
|
case '#':
|
|
var comment, rest []byte
|
|
comment, rest, err = scanComment(b)
|
|
if err == nil {
|
|
rest, err = consumeEOL(rest)
|
|
}
|
|
if err == nil {
|
|
if p.KeepComments {
|
|
p.push(Node{
|
|
Kind: Comment,
|
|
Raw: p.Range(comment),
|
|
Data: comment,
|
|
})
|
|
p.left = rest
|
|
return true
|
|
}
|
|
p.left = rest
|
|
continue
|
|
}
|
|
case '[':
|
|
var rest []byte
|
|
rest, err = p.parseExprTable(b)
|
|
if err == nil {
|
|
p.left = rest
|
|
return true
|
|
}
|
|
default:
|
|
var rest []byte
|
|
rest, err = p.parseExprKeyval(b)
|
|
if err == nil {
|
|
p.left = rest
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Errors at the end of the input have an empty highlight. Extend
|
|
// them to the last byte of the input so that they carry a usable
|
|
// position.
|
|
var perr *ParserError
|
|
if errors.As(err, &perr) && len(perr.Highlight) == 0 {
|
|
if offset := p.offsetOf(perr.Highlight); offset > 0 && offset == len(p.data) {
|
|
perr.Highlight = p.data[offset-1 : offset]
|
|
}
|
|
}
|
|
|
|
p.err = err
|
|
return false
|
|
}
|
|
}
|
|
|
|
// consumeEOL consumes a newline (LF or CRLF) or end of input.
|
|
func consumeEOL(b []byte) ([]byte, error) {
|
|
if len(b) == 0 {
|
|
return b, nil
|
|
}
|
|
switch b[0] {
|
|
case '\n':
|
|
return b[1:], nil
|
|
case '\r':
|
|
if len(b) > 1 && b[1] == '\n' {
|
|
return b[2:], nil
|
|
}
|
|
}
|
|
return nil, NewParserError(b[:1], "expected newline but got %#U", b[0])
|
|
}
|
|
|
|
// finishLine handles `ws [comment] (newline|eof)` after a top-level
|
|
// expression. If a comment is present and KeepComments is set, it is attached
|
|
// as the next sibling of the expression's root node.
|
|
func (p *Parser) finishLine(root int32, b []byte) ([]byte, error) {
|
|
b = skipWhitespace(b)
|
|
if len(b) > 0 && b[0] == '#' {
|
|
comment, rest, err := scanComment(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if p.KeepComments {
|
|
h := p.push(Node{
|
|
Kind: Comment,
|
|
Raw: p.Range(comment),
|
|
Data: comment,
|
|
})
|
|
p.at(root).next = h
|
|
}
|
|
b = rest
|
|
}
|
|
return consumeEOL(b)
|
|
}
|
|
|
|
// parseExprKeyval parses a top-level `key = value` expression, including its
|
|
// line termination.
|
|
func (p *Parser) parseExprKeyval(b []byte) ([]byte, error) {
|
|
root, rest, err := p.parseKeyval(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.finishLine(root, rest)
|
|
}
|
|
|
|
// parseExprTable parses a `[table]` or `[[array table]]` expression,
|
|
// including its line termination. b starts at '['.
|
|
func (p *Parser) parseExprTable(b []byte) ([]byte, error) {
|
|
var root int32
|
|
var err error
|
|
var rest []byte
|
|
if len(b) > 1 && b[1] == '[' {
|
|
root, rest, err = p.parseArrayTableHeader(b)
|
|
} else {
|
|
root, rest, err = p.parseTableHeader(b)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.finishLine(root, rest)
|
|
}
|
|
|
|
// parseTableHeader parses `[ ws key ws ]`. b starts at '['.
|
|
func (p *Parser) parseTableHeader(b []byte) (int32, []byte, error) {
|
|
root := p.push(Node{Kind: Table})
|
|
|
|
first, b, err := p.parseKey(skipWhitespace(b[1:]))
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
p.at(root).child = first
|
|
|
|
if len(b) == 0 || b[0] != ']' {
|
|
return 0, nil, NewParserError(highlight1(b), "expected ']' to close table name")
|
|
}
|
|
return root, b[1:], nil
|
|
}
|
|
|
|
// parseArrayTableHeader parses `[[ ws key ws ]]`. b starts at '[['.
|
|
func (p *Parser) parseArrayTableHeader(b []byte) (int32, []byte, error) {
|
|
root := p.push(Node{Kind: ArrayTable})
|
|
|
|
first, b, err := p.parseKey(skipWhitespace(b[2:]))
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
p.at(root).child = first
|
|
|
|
if len(b) < 2 || b[0] != ']' || b[1] != ']' {
|
|
return 0, nil, NewParserError(highlight1(b), "expected ']]' to close array table name")
|
|
}
|
|
return root, b[2:], nil
|
|
}
|
|
|
|
// parseKeyval parses `key keyval-sep val`. Returns the handle to the KeyValue
|
|
// node.
|
|
func (p *Parser) parseKeyval(b []byte) (int32, []byte, error) {
|
|
root := p.push(Node{Kind: KeyValue})
|
|
start := b
|
|
|
|
firstKey, b, err := p.parseKey(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
if len(b) == 0 || b[0] != '=' {
|
|
return 0, nil, NewParserError(highlight1(b), "expected '=' after key")
|
|
}
|
|
b = skipWhitespace(b[1:])
|
|
|
|
value, b, err := p.parseVal(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
p.at(root).child = value
|
|
p.at(value).next = firstKey
|
|
p.at(root).Raw = p.rangeFrom(start, b)
|
|
return root, b, nil
|
|
}
|
|
|
|
// parseKey parses a potentially dotted key. It consumes the whitespace
|
|
// following the key, so that the caller can directly check for the next
|
|
// expected character ('=', ']', ...). Returns the handle of the first Key
|
|
// node; subsequent parts are chained via next.
|
|
func (p *Parser) parseKey(b []byte) (int32, []byte, error) {
|
|
var first, last int32
|
|
for {
|
|
h, rest, err := p.parseSimpleKey(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if first == 0 {
|
|
first = h
|
|
} else {
|
|
p.at(last).next = h
|
|
}
|
|
last = h
|
|
|
|
b = skipWhitespace(rest)
|
|
if len(b) > 0 && b[0] == '.' {
|
|
b = skipWhitespace(b[1:])
|
|
continue
|
|
}
|
|
return first, b, nil
|
|
}
|
|
}
|
|
|
|
func isUnquotedKeyChar(c byte) bool {
|
|
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_'
|
|
}
|
|
|
|
// parseSimpleKey parses one key part: either a bare key or a quoted key.
|
|
func (p *Parser) parseSimpleKey(b []byte) (int32, []byte, error) {
|
|
raw, value, rest, err := p.scanSimpleKey(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
h := p.push(Node{Kind: Key, Raw: p.Range(raw), Data: value})
|
|
return h, rest, nil
|
|
}
|
|
|
|
// scanSimpleKey scans one key part (bare or quoted) without building an AST
|
|
// node. It returns the raw bytes, the decoded key value, and the rest of the
|
|
// input.
|
|
func (p *Parser) scanSimpleKey(b []byte) (raw, value, rest []byte, err error) {
|
|
if len(b) == 0 {
|
|
return nil, nil, nil, NewParserError(b, "expected key but reached end of input")
|
|
}
|
|
|
|
switch b[0] {
|
|
case '\'':
|
|
return p.parseLiteralString(b)
|
|
case '"':
|
|
return p.parseBasicString(b)
|
|
default:
|
|
i := 0
|
|
for i < len(b) && isUnquotedKeyChar(b[i]) {
|
|
i++
|
|
}
|
|
if i == 0 {
|
|
return nil, nil, nil, NewParserError(b[:1], "invalid character at start of key: %#U", b[0])
|
|
}
|
|
return b[:i], b[:i], b[i:], nil
|
|
}
|
|
}
|
|
|
|
// parseVal parses a TOML value and returns the handle to its node.
|
|
func (p *Parser) parseVal(b []byte) (int32, []byte, error) {
|
|
if len(b) == 0 {
|
|
return 0, nil, NewParserError(b, "expected value, not end of input")
|
|
}
|
|
|
|
c := b[0]
|
|
switch {
|
|
case c == '"':
|
|
var raw, value, rest []byte
|
|
var err error
|
|
if len(b) > 2 && b[1] == '"' && b[2] == '"' {
|
|
raw, value, rest, err = p.parseMultilineBasicString(b)
|
|
} else {
|
|
raw, value, rest, err = p.parseBasicString(b)
|
|
}
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
h := p.push(Node{Kind: String, Raw: p.Range(raw), Data: value})
|
|
return h, rest, nil
|
|
case c == '\'':
|
|
var raw, value, rest []byte
|
|
var err error
|
|
if len(b) > 2 && b[1] == '\'' && b[2] == '\'' {
|
|
raw, value, rest, err = p.parseMultilineLiteralString(b)
|
|
} else {
|
|
raw, value, rest, err = p.parseLiteralString(b)
|
|
}
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
h := p.push(Node{Kind: String, Raw: p.Range(raw), Data: value})
|
|
return h, rest, nil
|
|
case c == 't':
|
|
return p.parseKeyword(b, "true", Bool)
|
|
case c == 'f':
|
|
return p.parseKeyword(b, "false", Bool)
|
|
case c == 'i':
|
|
return p.parseKeyword(b, "inf", Float)
|
|
case c == 'n':
|
|
return p.parseKeyword(b, "nan", Float)
|
|
case c == '[' || c == '{':
|
|
// Arrays and inline tables recurse back into parseVal for each of
|
|
// their elements. Bound that recursion so a document with millions of
|
|
// nested brackets or braces cannot overflow the goroutine stack.
|
|
if p.nesting >= maxValueNesting {
|
|
return 0, nil, NewParserError(b[:1], "arrays and inline tables are nested more than the maximum of %d levels deep", maxValueNesting)
|
|
}
|
|
p.nesting++
|
|
var h int32
|
|
var rest []byte
|
|
var err error
|
|
if c == '[' {
|
|
h, rest, err = p.parseValArray(b)
|
|
} else {
|
|
h, rest, err = p.parseInlineTable(b)
|
|
}
|
|
p.nesting--
|
|
return h, rest, err
|
|
case c == '+' || c == '-':
|
|
return p.parseIntOrFloat(b)
|
|
case c >= '0' && c <= '9':
|
|
if isDateTimeStart(b) {
|
|
return p.parseDateTime(b)
|
|
}
|
|
return p.parseIntOrFloat(b)
|
|
default:
|
|
return 0, nil, NewParserError(b[:1], "unexpected character %#U at start of value", c)
|
|
}
|
|
}
|
|
|
|
// scanScalar scans a single scalar TOML value (string, integer, float,
|
|
// boolean, or date/time) without building any AST node. It returns the kind of
|
|
// the value, its raw bytes, its decoded value bytes (for strings: quotes
|
|
// removed and escapes resolved; identical to raw for the other kinds), and the
|
|
// rest of the input. Arrays and inline tables are not scalars and produce an
|
|
// error: use parseValue for those.
|
|
//
|
|
// It is exposed to the root toml package through internal/parserbridge for the
|
|
// fused generic-decode path; it is not part of the public API.
|
|
func (p *Parser) scanScalar(b []byte) (kind Kind, raw, value, rest []byte, err error) {
|
|
if len(b) == 0 {
|
|
return Invalid, nil, nil, nil, NewParserError(b, "expected value, not end of input")
|
|
}
|
|
|
|
c := b[0]
|
|
switch {
|
|
case c == '"':
|
|
if len(b) > 2 && b[1] == '"' && b[2] == '"' {
|
|
raw, value, rest, err = p.parseMultilineBasicString(b)
|
|
} else {
|
|
raw, value, rest, err = p.parseBasicString(b)
|
|
}
|
|
return String, raw, value, rest, err
|
|
case c == '\'':
|
|
if len(b) > 2 && b[1] == '\'' && b[2] == '\'' {
|
|
raw, value, rest, err = p.parseMultilineLiteralString(b)
|
|
} else {
|
|
raw, value, rest, err = p.parseLiteralString(b)
|
|
}
|
|
return String, raw, value, rest, err
|
|
case c == 't':
|
|
return scanKeyword(b, "true", Bool)
|
|
case c == 'f':
|
|
return scanKeyword(b, "false", Bool)
|
|
case c == 'i':
|
|
return scanKeyword(b, "inf", Float)
|
|
case c == 'n':
|
|
return scanKeyword(b, "nan", Float)
|
|
case c == '+' || c == '-':
|
|
return scanIntOrFloat(b)
|
|
case c >= '0' && c <= '9':
|
|
if isDateTimeStart(b) {
|
|
return scanDateTime(b)
|
|
}
|
|
return scanIntOrFloat(b)
|
|
default:
|
|
return Invalid, nil, nil, nil, NewParserError(b[:1], "unexpected character %#U at start of value", c)
|
|
}
|
|
}
|
|
|
|
// scanKey scans a potentially dotted key without building AST nodes,
|
|
// appending the decoded value of each part to dst (pass dst[:0] to reuse a
|
|
// buffer). It consumes the whitespace following the key, so the caller can
|
|
// directly check for the next expected character ('=', ']', ...). It returns
|
|
// the parts, the raw bytes spanning the whole key (from the first part to the
|
|
// end of the last one, excluding trailing whitespace, usable as an error
|
|
// highlight), the rest of the input, and any error.
|
|
//
|
|
// It is exposed to the root toml package through internal/parserbridge for the
|
|
// fused generic-decode path; it is not part of the public API.
|
|
func (p *Parser) scanKey(b []byte, dst [][]byte) (parts [][]byte, raw, rest []byte, err error) {
|
|
parts = dst
|
|
start := b
|
|
for {
|
|
_, value, r, err := p.scanSimpleKey(b)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
parts = append(parts, value)
|
|
|
|
// r points just past the current part: the key spans from start to
|
|
// here, ignoring any whitespace that follows.
|
|
raw = start[:len(start)-len(r)]
|
|
|
|
b = skipWhitespace(r)
|
|
if len(b) > 0 && b[0] == '.' {
|
|
b = skipWhitespace(b[1:])
|
|
continue
|
|
}
|
|
return parts, raw, b, nil
|
|
}
|
|
}
|
|
|
|
// parseValue parses a single TOML value, which may be an array or inline table,
|
|
// into the parser's arena. It returns the root node of the value and the rest
|
|
// of the input. It resets the arena, so any node returned by a previous call to
|
|
// parseValue, Expression, or NextExpression is invalidated.
|
|
//
|
|
// It is exposed to the root toml package through internal/parserbridge for the
|
|
// fused generic-decode path; it is not part of the public API.
|
|
func (p *Parser) parseValue(b []byte) (*Node, []byte, error) {
|
|
p.nodes = p.nodes[:0]
|
|
h, rest, err := p.parseVal(b)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return &p.nodes[h-1], rest, nil
|
|
}
|
|
|
|
func (p *Parser) parseKeyword(b []byte, kw string, kind Kind) (int32, []byte, error) {
|
|
k, raw, _, rest, err := scanKeyword(b, kw, kind)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
h := p.push(Node{Kind: k, Raw: p.Range(raw), Data: raw})
|
|
return h, rest, nil
|
|
}
|
|
|
|
// scanKeyword scans a keyword value (true, false, inf, nan) without building
|
|
// an AST node. raw and value are identical (the keyword bytes).
|
|
func scanKeyword(b []byte, kw string, kind Kind) (Kind, []byte, []byte, []byte, error) {
|
|
if len(b) < len(kw) || string(b[:len(kw)]) != kw {
|
|
n := len(kw)
|
|
if len(b) < n {
|
|
n = len(b)
|
|
}
|
|
return Invalid, nil, nil, nil, NewParserError(b[:n], "expected keyword %q", kw)
|
|
}
|
|
return kind, b[:len(kw)], b[:len(kw)], b[len(kw):], nil
|
|
}
|
|
|
|
// parseValArray parses an array value. b starts at '['.
|
|
func (p *Parser) parseValArray(b []byte) (int32, []byte, error) {
|
|
arr := p.push(Node{Kind: Array})
|
|
b = b[1:]
|
|
|
|
var lastChild int32
|
|
appendChild := func(h int32) {
|
|
if lastChild == 0 {
|
|
p.at(arr).child = h
|
|
} else {
|
|
p.at(lastChild).next = h
|
|
}
|
|
lastChild = h
|
|
}
|
|
|
|
// Comments inside the array are attached as follows: the first comment
|
|
// of a "run" (consecutive comments with no value in between) becomes a
|
|
// child of the array, interleaved with values; subsequent comments of the
|
|
// run are attached as children of the first one.
|
|
var runFirst, runLast int32
|
|
|
|
// afterValue is true when a value has been parsed and a comma (or the
|
|
// closing bracket) is expected before the next one.
|
|
afterValue := false
|
|
for {
|
|
b = skipWhitespace(b)
|
|
if len(b) == 0 {
|
|
return 0, nil, NewParserError(b, "array is incomplete")
|
|
}
|
|
|
|
switch b[0] {
|
|
case ']':
|
|
return arr, b[1:], nil
|
|
case '\n':
|
|
b = b[1:]
|
|
continue
|
|
case '\r':
|
|
if len(b) > 1 && b[1] == '\n' {
|
|
b = b[2:]
|
|
continue
|
|
}
|
|
return 0, nil, NewParserError(b[:1], "expected newline but got %#U", b[0])
|
|
case '#':
|
|
comment, rest, err := scanComment(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if p.KeepComments {
|
|
h := p.push(Node{Kind: Comment, Raw: p.Range(comment), Data: comment})
|
|
switch {
|
|
case runFirst == 0:
|
|
appendChild(h)
|
|
runFirst = h
|
|
case runLast == runFirst:
|
|
p.at(runFirst).child = h
|
|
default:
|
|
p.at(runLast).next = h
|
|
}
|
|
runLast = h
|
|
}
|
|
b = rest
|
|
continue
|
|
case ',':
|
|
if !afterValue {
|
|
return 0, nil, NewParserError(b[:1], "expected value but got %#U", b[0])
|
|
}
|
|
afterValue = false
|
|
b = b[1:]
|
|
continue
|
|
default:
|
|
if afterValue {
|
|
return 0, nil, NewParserError(b[:1], "expected ',' or ']' after array value")
|
|
}
|
|
h, rest, err := p.parseVal(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
appendChild(h)
|
|
afterValue = true
|
|
runFirst, runLast = 0, 0
|
|
b = rest
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseInlineTable parses an inline table value. b starts at '{'.
|
|
//
|
|
// Per TOML v1.1.0, inline tables may span multiple lines (whitespace,
|
|
// comments and newlines are allowed between elements) and may contain a
|
|
// trailing comma.
|
|
func (p *Parser) parseInlineTable(b []byte) (int32, []byte, error) {
|
|
tbl := p.push(Node{Kind: InlineTable, Raw: p.Range(b[:1])})
|
|
b = b[1:]
|
|
|
|
var lastChild int32
|
|
appendChild := func(h int32) {
|
|
if lastChild == 0 {
|
|
p.at(tbl).child = h
|
|
} else {
|
|
p.at(lastChild).next = h
|
|
}
|
|
lastChild = h
|
|
}
|
|
|
|
// Comments are attached as in arrays: the first comment of a "run"
|
|
// (consecutive comments with no key-value in between) becomes a child of
|
|
// the table, interleaved with key-values; subsequent comments of the run
|
|
// hang off the first one.
|
|
var runFirst, runLast int32
|
|
|
|
// afterValue is true when a key-value has been parsed and a comma (or the
|
|
// closing brace) is expected before the next one.
|
|
afterValue := false
|
|
for {
|
|
b = skipWhitespace(b)
|
|
if len(b) == 0 {
|
|
return 0, nil, NewParserError(b, "inline table is incomplete")
|
|
}
|
|
|
|
switch b[0] {
|
|
case '}':
|
|
return tbl, b[1:], nil
|
|
case '\n':
|
|
b = b[1:]
|
|
continue
|
|
case '\r':
|
|
if len(b) > 1 && b[1] == '\n' {
|
|
b = b[2:]
|
|
continue
|
|
}
|
|
return 0, nil, NewParserError(b[:1], "expected newline but got %#U", b[0])
|
|
case '#':
|
|
comment, rest, err := scanComment(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if p.KeepComments {
|
|
h := p.push(Node{Kind: Comment, Raw: p.Range(comment), Data: comment})
|
|
switch {
|
|
case runFirst == 0:
|
|
appendChild(h)
|
|
runFirst = h
|
|
case runLast == runFirst:
|
|
p.at(runFirst).child = h
|
|
default:
|
|
p.at(runLast).next = h
|
|
}
|
|
runLast = h
|
|
}
|
|
b = rest
|
|
continue
|
|
case ',':
|
|
if !afterValue {
|
|
return 0, nil, NewParserError(b[:1], "unexpected comma in inline table")
|
|
}
|
|
afterValue = false
|
|
b = b[1:]
|
|
continue
|
|
default:
|
|
if afterValue {
|
|
return 0, nil, NewParserError(b[:1], "expected ',' or '}' after inline table key-value")
|
|
}
|
|
h, rest, err := p.parseKeyval(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
appendChild(h)
|
|
afterValue = true
|
|
runFirst, runLast = 0, 0
|
|
b = rest
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func isDigit(c byte) bool {
|
|
return c >= '0' && c <= '9'
|
|
}
|
|
|
|
func isHexDigit(c byte) bool {
|
|
return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
|
|
}
|
|
|
|
// isDateTimeStart reports whether b looks like the start of a date or time
|
|
// value instead of a number. Values starting with two digits followed by a
|
|
// colon are times; values starting with four digits followed by a dash are
|
|
// dates.
|
|
func isDateTimeStart(b []byte) bool {
|
|
if len(b) >= 3 && isDigit(b[1]) && b[2] == ':' {
|
|
return true
|
|
}
|
|
if len(b) >= 5 && isDigit(b[1]) && isDigit(b[2]) && isDigit(b[3]) && b[4] == '-' {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// expectDigits checks that the n first bytes of b are digits.
|
|
// parseDateTime parses date and/or time values. b starts with a digit.
|
|
//
|
|
// The parser is lenient: it scans the characters that can be part of a date
|
|
// and/or time value to delimit and classify the token, but leaves the
|
|
// validation of its contents to the document consumer. This keeps the value
|
|
// in one piece, so that errors about its content can point at the right
|
|
// place.
|
|
func (p *Parser) parseDateTime(b []byte) (int32, []byte, error) {
|
|
kind, raw, _, rest, err := scanDateTime(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
h := p.push(Node{Kind: kind, Raw: p.Range(raw), Data: raw})
|
|
return h, rest, nil
|
|
}
|
|
|
|
// scanDateTime classifies and delimits a date/time value without building an
|
|
// AST node. raw and value are identical (the token bytes).
|
|
func scanDateTime(b []byte) (Kind, []byte, []byte, []byte, error) {
|
|
// Greedily scan the characters that may compose a date/time value. A
|
|
// space is part of the value only when it serves as the delimiter
|
|
// between the date and the time, which is approximated by requiring a
|
|
// digit right after it.
|
|
i := 0
|
|
delim := -1
|
|
for i < len(b) {
|
|
c := b[i]
|
|
if isDigit(c) || c == ':' || c == '-' || c == '+' || c == '.' || c == 'Z' || c == 'z' {
|
|
i++
|
|
continue
|
|
}
|
|
if c == 'T' || c == 't' || (c == ' ' && i+1 < len(b) && isDigit(b[i+1])) {
|
|
if delim < 0 {
|
|
delim = i
|
|
}
|
|
i++
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
tok := b[:i]
|
|
|
|
var kind Kind
|
|
switch {
|
|
case tok[2] == ':':
|
|
kind = LocalTime
|
|
case delim < 0:
|
|
kind = LocalDate
|
|
case bytes.ContainsAny(tok[delim+1:], "Zz+-"):
|
|
kind = DateTime
|
|
default:
|
|
kind = LocalDateTime
|
|
}
|
|
|
|
return kind, tok, tok, b[i:], nil
|
|
}
|
|
|
|
// scanDigitsWithUnderscores scans a run of digits potentially separated by
|
|
// underscores. b starts right after the first digit of the run. isInRange
|
|
// selects the kind of digits. Returns the index after the run.
|
|
func scanDigitsWithUnderscores(b []byte, i int, isInRange func(byte) bool) (int, error) {
|
|
for i < len(b) {
|
|
c := b[i]
|
|
if isInRange(c) {
|
|
i++
|
|
continue
|
|
}
|
|
if c == '_' {
|
|
if i+1 >= len(b) || !isInRange(b[i+1]) {
|
|
end := i + 2
|
|
if end > len(b) {
|
|
end = len(b)
|
|
}
|
|
return 0, NewParserError(b[i:end], "number must have at least one digit between underscores")
|
|
}
|
|
i += 2
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
// parseIntOrFloat parses integer and float values, including the special
|
|
// values inf and nan with an optional sign.
|
|
func (p *Parser) parseIntOrFloat(b []byte) (int32, []byte, error) {
|
|
kind, raw, _, rest, err := scanIntOrFloat(b)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
h := p.push(Node{Kind: kind, Raw: p.Range(raw), Data: raw})
|
|
return h, rest, nil
|
|
}
|
|
|
|
// scanIntOrFloat delimits and classifies an integer or float value (including
|
|
// the special floats inf and nan with an optional sign) without building an
|
|
// AST node. raw and value are identical (the token bytes).
|
|
func scanIntOrFloat(b []byte) (Kind, []byte, []byte, []byte, error) {
|
|
i := 0
|
|
if b[i] == '+' || b[i] == '-' {
|
|
i++
|
|
}
|
|
if i >= len(b) {
|
|
return Invalid, nil, nil, nil, NewParserError(b, "expected number after sign")
|
|
}
|
|
|
|
// special floats
|
|
if b[i] == 'i' || b[i] == 'n' {
|
|
kw := "inf"
|
|
if b[i] == 'n' {
|
|
kw = "nan"
|
|
}
|
|
if len(b) < i+3 || string(b[i:i+3]) != kw {
|
|
return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "expected %q", kw)
|
|
}
|
|
i += 3
|
|
return Float, b[:i], b[:i], b[i:], nil
|
|
}
|
|
|
|
if !isDigit(b[i]) {
|
|
return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "expected digit but got %#U", b[i])
|
|
}
|
|
|
|
// radix prefixes
|
|
if b[i] == '0' && i+1 < len(b) && (b[i+1] == 'x' || b[i+1] == 'o' || b[i+1] == 'b') {
|
|
if i != 0 {
|
|
return Invalid, nil, nil, nil, NewParserError(b[:2], "sign is not allowed on numbers with a radix prefix")
|
|
}
|
|
var isInRange func(byte) bool
|
|
switch b[1] {
|
|
case 'x':
|
|
isInRange = isHexDigit
|
|
case 'o':
|
|
isInRange = func(c byte) bool { return c >= '0' && c <= '7' }
|
|
case 'b':
|
|
isInRange = func(c byte) bool { return c == '0' || c == '1' }
|
|
}
|
|
i = 2
|
|
if i >= len(b) || !isInRange(b[i]) {
|
|
return Invalid, nil, nil, nil, NewParserError(b[:2], "radix prefix must be followed by at least one digit")
|
|
}
|
|
i++
|
|
var err error
|
|
i, err = scanDigitsWithUnderscores(b, i, isInRange)
|
|
if err != nil {
|
|
return Invalid, nil, nil, nil, err
|
|
}
|
|
return Integer, b[:i], b[:i], b[i:], nil
|
|
}
|
|
|
|
// decimal integer part
|
|
leadingZero := b[i] == '0'
|
|
digitsStart := i
|
|
i++
|
|
var err error
|
|
i, err = scanDigitsWithUnderscores(b, i, isDigit)
|
|
if err != nil {
|
|
return Invalid, nil, nil, nil, err
|
|
}
|
|
if leadingZero && i > digitsStart+1 {
|
|
return Invalid, nil, nil, nil, NewParserError(b[digitsStart:digitsStart+2], "integers cannot have leading zeroes")
|
|
}
|
|
|
|
kind := Integer
|
|
|
|
// fractional part
|
|
if i < len(b) && b[i] == '.' {
|
|
i++
|
|
if i >= len(b) || !isDigit(b[i]) {
|
|
return Invalid, nil, nil, nil, NewParserError(highlight1(b[i:]), "decimal point must be followed by a digit")
|
|
}
|
|
i++
|
|
i, err = scanDigitsWithUnderscores(b, i, isDigit)
|
|
if err != nil {
|
|
return Invalid, nil, nil, nil, err
|
|
}
|
|
kind = Float
|
|
}
|
|
|
|
// exponent
|
|
if i < len(b) && (b[i] == 'e' || b[i] == 'E') {
|
|
i++
|
|
if i < len(b) && (b[i] == '+' || b[i] == '-') {
|
|
i++
|
|
}
|
|
if i >= len(b) || !isDigit(b[i]) {
|
|
return Invalid, nil, nil, nil, NewParserError(highlight1(b[i:]), "exponent must contain at least one digit")
|
|
}
|
|
i++
|
|
i, err = scanDigitsWithUnderscores(b, i, isDigit)
|
|
if err != nil {
|
|
return Invalid, nil, nil, nil, err
|
|
}
|
|
kind = Float
|
|
}
|
|
|
|
// A letter right after the number means it was meant to be a string that
|
|
// was left unquoted (e.g. "20s"). Report that instead of the misleading
|
|
// "expected newline" raised later (issue #413).
|
|
if i < len(b) {
|
|
if c := b[i]; (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') {
|
|
return Invalid, nil, nil, nil, NewParserError(b[i:i+1], "strings must be quoted")
|
|
}
|
|
}
|
|
|
|
return kind, b[:i], b[:i], b[i:], nil
|
|
}
|
|
|
|
// highlight1 returns a 1-byte highlight at the start of b, or b itself if it
|
|
// is empty.
|
|
func highlight1(b []byte) []byte {
|
|
if len(b) > 0 {
|
|
return b[:1]
|
|
}
|
|
return b
|
|
}
|
|
|
|
func skipWhitespace(b []byte) []byte {
|
|
for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') {
|
|
b = b[1:]
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Word-at-a-time byte scanning helpers. These detect, within an 8-byte word,
|
|
// the presence of bytes that need special handling, so that runs of plain
|
|
// ASCII characters can be skipped 8 bytes at a time.
|
|
const (
|
|
lsb = 0x0101010101010101
|
|
msb = 0x8080808080808080
|
|
)
|
|
|
|
// hasByteBelow reports whether any byte of the word x is strictly below n.
|
|
// Only meaningful when combined with a check that no byte has its high bit
|
|
// set.
|
|
func hasByteBelow(x uint64, n uint64) uint64 {
|
|
return (x - n*lsb) & ^x & msb
|
|
}
|
|
|
|
// hasByteEqual reports whether any byte of the word x equals c. Only
|
|
// meaningful for bytes without their high bit set.
|
|
func hasByteEqual(x uint64, c uint64) uint64 {
|
|
y := x ^ (c * lsb)
|
|
return (y - lsb) & ^y & msb
|
|
}
|
|
|
|
// scanComment parses a comment, starting at the '#' character. It returns the
|
|
// comment bytes (including '#', excluding the line ending) and the rest of
|
|
// the input.
|
|
func scanComment(b []byte) ([]byte, []byte, error) {
|
|
i := 1
|
|
for i < len(b) {
|
|
// Fast path: skip 8 bytes at a time as long as they are all plain
|
|
// printable ASCII.
|
|
for i+8 <= len(b) {
|
|
x := binary.LittleEndian.Uint64(b[i:])
|
|
if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, 0x7f) != 0 {
|
|
break
|
|
}
|
|
i += 8
|
|
}
|
|
if i >= len(b) {
|
|
break
|
|
}
|
|
|
|
c := b[i]
|
|
if c >= 0x80 {
|
|
var ok bool
|
|
i, ok = scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in comment")
|
|
}
|
|
continue
|
|
}
|
|
switch {
|
|
case c >= 0x20 && c < 0x7f:
|
|
i++
|
|
case c == '\n':
|
|
return b[:i], b[i:], nil
|
|
case c == '\r':
|
|
if i+1 < len(b) && b[i+1] == '\n' {
|
|
return b[:i], b[i:], nil
|
|
}
|
|
return nil, nil, NewParserError(b[i:i+1], "carriage returns are not allowed in comments")
|
|
case c == '\t':
|
|
i++
|
|
default:
|
|
return nil, nil, NewParserError(b[i:i+1], "control characters are not allowed in comments")
|
|
}
|
|
}
|
|
return b[:i], b[i:], nil
|
|
}
|
|
|
|
// parseLiteralString parses a single-line literal string, starting at the
|
|
// opening quote. Returns the raw bytes (with quotes), the string value
|
|
// (without quotes) and the rest of the input.
|
|
func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) {
|
|
i := 1
|
|
for {
|
|
// Fast path over plain ASCII.
|
|
for i+8 <= len(b) {
|
|
x := binary.LittleEndian.Uint64(b[i:])
|
|
if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, '\'')|hasByteEqual(x, 0x7f) != 0 {
|
|
break
|
|
}
|
|
i += 8
|
|
}
|
|
if i >= len(b) {
|
|
return nil, nil, nil, NewParserError(b[len(b):], "unterminated literal string")
|
|
}
|
|
|
|
c := b[i]
|
|
switch {
|
|
case c == '\'':
|
|
return b[:i+1], b[1:i], b[i+1:], nil
|
|
case c >= 0x20 && c < 0x7f:
|
|
i++
|
|
case c == '\t':
|
|
i++
|
|
case c == '\n' || c == '\r':
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "literal strings cannot have new lines")
|
|
case c < 0x80:
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "literal strings cannot have control characters")
|
|
default:
|
|
var ok bool
|
|
i, ok = scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in literal string")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseMultilineLiteralString parses a multi-line literal string, starting at
|
|
// the opening triple quote.
|
|
func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) {
|
|
i := 3
|
|
// trim the newline right after the opening delimiter
|
|
if i < len(b) && b[i] == '\n' {
|
|
i++
|
|
} else if i+1 < len(b) && b[i] == '\r' && b[i+1] == '\n' {
|
|
i += 2
|
|
}
|
|
contentStart := i
|
|
|
|
for i < len(b) {
|
|
c := b[i]
|
|
switch {
|
|
case c == '\'':
|
|
// count consecutive quotes
|
|
j := i
|
|
for j < len(b) && b[j] == '\'' {
|
|
j++
|
|
}
|
|
n := j - i
|
|
if n >= 3 {
|
|
if n > 5 {
|
|
return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline literal string")
|
|
}
|
|
// n-3 quotes belong to the content; the last 3 close the
|
|
// string.
|
|
contentEnd := i + n - 3
|
|
return b[:j], b[contentStart:contentEnd], b[j:], nil
|
|
}
|
|
i = j
|
|
case c >= 0x20 && c < 0x7f:
|
|
i++
|
|
case c == '\t' || c == '\n':
|
|
i++
|
|
case c == '\r':
|
|
if i+1 < len(b) && b[i+1] == '\n' {
|
|
i += 2
|
|
continue
|
|
}
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character")
|
|
case c < 0x80:
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "multiline literal strings cannot have control characters")
|
|
default:
|
|
var ok bool
|
|
i, ok = scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline literal string")
|
|
}
|
|
}
|
|
}
|
|
return nil, nil, nil, NewParserError(b[len(b):], "multiline literal string not terminated by '''")
|
|
}
|
|
|
|
// parseBasicString parses a single-line basic string, starting at the opening
|
|
// quote. The value is a subslice of the input if the string contains no
|
|
// escape sequence, or a new allocation otherwise.
|
|
func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
|
|
i := 1
|
|
// First pass: handle strings without escape sequences without allocating.
|
|
for {
|
|
for i+8 <= len(b) {
|
|
x := binary.LittleEndian.Uint64(b[i:])
|
|
if (x&msb)|hasByteBelow(x, 0x20)|hasByteEqual(x, '"')|hasByteEqual(x, '\\')|hasByteEqual(x, 0x7f) != 0 {
|
|
break
|
|
}
|
|
i += 8
|
|
}
|
|
if i >= len(b) {
|
|
return nil, nil, nil, NewParserError(b[len(b):], "unterminated basic string")
|
|
}
|
|
|
|
c := b[i]
|
|
switch {
|
|
case c == '"':
|
|
return b[:i+1], b[1:i], b[i+1:], nil
|
|
case c == '\\':
|
|
// switch to the escape-aware parser, copying what has been
|
|
// scanned so far
|
|
return p.parseBasicStringEscaped(b, i)
|
|
case c >= 0x20 && c < 0x7f:
|
|
i++
|
|
case c == '\t':
|
|
i++
|
|
case c == '\n' || c == '\r':
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines")
|
|
case c < 0x80:
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have control characters")
|
|
default:
|
|
var ok bool
|
|
i, ok = scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in basic string")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// findBasicStringEnd returns the index of the quote closing a basic string,
|
|
// or -1 if the string is not terminated. i is the index of the first
|
|
// character after the opening quote. It does not validate the content: it
|
|
// only skips over escape sequences so that escaped quotes do not terminate
|
|
// the string.
|
|
func findBasicStringEnd(b []byte, i int) int {
|
|
for i < len(b) {
|
|
switch b[i] {
|
|
case '"':
|
|
return i
|
|
case '\\':
|
|
i += 2
|
|
default:
|
|
i++
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// parseBasicStringEscaped continues parsing a basic string that contains
|
|
// escape sequences. i is the index of the first backslash.
|
|
func (p *Parser) parseBasicStringEscaped(b []byte, i int) ([]byte, []byte, []byte, error) {
|
|
// Escape sequences only ever shrink, so the content length before
|
|
// unescaping is enough to never reallocate.
|
|
bufCap := len(b) - 1
|
|
if end := findBasicStringEnd(b, i); end >= 0 {
|
|
bufCap = end - 1
|
|
}
|
|
buf := make([]byte, i-1, bufCap)
|
|
copy(buf, b[1:i])
|
|
|
|
for i < len(b) {
|
|
c := b[i]
|
|
switch {
|
|
case c == '"':
|
|
return b[:i+1], buf, b[i+1:], nil
|
|
case c == '\\':
|
|
i++
|
|
if i >= len(b) {
|
|
return nil, nil, nil, NewParserError(b[i-1:], `need a character after \`)
|
|
}
|
|
var err error
|
|
buf, i, err = unescape(buf, b, i)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
case c >= 0x20 && c < 0x7f:
|
|
buf = append(buf, c)
|
|
i++
|
|
case c == '\t':
|
|
buf = append(buf, c)
|
|
i++
|
|
case c == '\n' || c == '\r':
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines")
|
|
case c < 0x80:
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "basic strings cannot have control characters")
|
|
default:
|
|
j, ok := scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in basic string")
|
|
}
|
|
buf = append(buf, b[i:j]...)
|
|
i = j
|
|
}
|
|
}
|
|
return nil, nil, nil, NewParserError(b[len(b):], "unterminated basic string")
|
|
}
|
|
|
|
// unescape processes one escape sequence. i is the index of the character
|
|
// right after the backslash. It returns the updated buffer and index.
|
|
func unescape(buf []byte, b []byte, i int) ([]byte, int, error) {
|
|
c := b[i]
|
|
switch c {
|
|
case '"':
|
|
return append(buf, '"'), i + 1, nil
|
|
case '\\':
|
|
return append(buf, '\\'), i + 1, nil
|
|
case 'b':
|
|
return append(buf, '\b'), i + 1, nil
|
|
case 'f':
|
|
return append(buf, '\f'), i + 1, nil
|
|
case 'n':
|
|
return append(buf, '\n'), i + 1, nil
|
|
case 'r':
|
|
return append(buf, '\r'), i + 1, nil
|
|
case 't':
|
|
return append(buf, '\t'), i + 1, nil
|
|
case 'e':
|
|
// TOML v1.1.0: \e is the escape character (U+001B).
|
|
return append(buf, 0x1B), i + 1, nil
|
|
case 'x':
|
|
// TOML v1.1.0: \xHH is a two-digit hexadecimal code point.
|
|
return unescapeUnicode(buf, b, i+1, 2)
|
|
case 'u':
|
|
return unescapeUnicode(buf, b, i+1, 4)
|
|
case 'U':
|
|
return unescapeUnicode(buf, b, i+1, 8)
|
|
default:
|
|
return nil, 0, NewParserError(b[i-1:i+1], "invalid escape character %#U", c)
|
|
}
|
|
}
|
|
|
|
// unescapeUnicode handles \uXXXX and \UXXXXXXXX escape sequences. i is the
|
|
// index of the first hex digit.
|
|
func unescapeUnicode(buf []byte, b []byte, i int, n int) ([]byte, int, error) {
|
|
if i+n > len(b) {
|
|
return nil, 0, NewParserError(b[i-2:], "unicode escape sequence is too short")
|
|
}
|
|
var r uint32
|
|
for k := 0; k < n; k++ {
|
|
c := b[i+k]
|
|
var d uint32
|
|
switch {
|
|
case c >= '0' && c <= '9':
|
|
d = uint32(c - '0')
|
|
case c >= 'a' && c <= 'f':
|
|
d = uint32(c-'a') + 10
|
|
case c >= 'A' && c <= 'F':
|
|
d = uint32(c-'A') + 10
|
|
default:
|
|
return nil, 0, NewParserError(b[i+k:i+k+1], "invalid hexadecimal digit in unicode escape sequence")
|
|
}
|
|
r = r<<4 | d
|
|
}
|
|
if r > utf8.MaxRune || (r >= 0xD800 && r <= 0xDFFF) {
|
|
return nil, 0, NewParserError(b[i-2:i+n], "escape sequence is not a valid unicode code point")
|
|
}
|
|
return utf8.AppendRune(buf, rune(r)), i + n, nil
|
|
}
|
|
|
|
// parseMultilineBasicString parses a multi-line basic string, starting at the
|
|
// opening triple quote.
|
|
func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) {
|
|
i := 3
|
|
// trim the newline right after the opening delimiter
|
|
if i < len(b) && b[i] == '\n' {
|
|
i++
|
|
} else if i+1 < len(b) && b[i] == '\r' && b[i+1] == '\n' {
|
|
i += 2
|
|
}
|
|
contentStart := i
|
|
|
|
// First pass without allocating, until an escape sequence is found.
|
|
for i < len(b) {
|
|
c := b[i]
|
|
switch {
|
|
case c == '"':
|
|
j := i
|
|
for j < len(b) && b[j] == '"' {
|
|
j++
|
|
}
|
|
n := j - i
|
|
if n >= 3 {
|
|
if n > 5 {
|
|
return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline basic string")
|
|
}
|
|
contentEnd := i + n - 3
|
|
return b[:j], b[contentStart:contentEnd], b[j:], nil
|
|
}
|
|
i = j
|
|
case c == '\\':
|
|
return p.parseMultilineBasicStringEscaped(b, contentStart, i)
|
|
case c >= 0x20 && c < 0x7f:
|
|
i++
|
|
case c == '\t' || c == '\n':
|
|
i++
|
|
case c == '\r':
|
|
if i+1 < len(b) && b[i+1] == '\n' {
|
|
i += 2
|
|
continue
|
|
}
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character")
|
|
case c < 0x80:
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "multiline basic strings cannot have control characters")
|
|
default:
|
|
var ok bool
|
|
i, ok = scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline basic string")
|
|
}
|
|
}
|
|
}
|
|
return nil, nil, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`)
|
|
}
|
|
|
|
// findMultilineBasicStringEnd returns the index of the first quote of the
|
|
// run of quotes closing a multi-line basic string, or -1 if the string is
|
|
// not terminated. It does not validate the content: it only skips over
|
|
// escape sequences so that escaped quotes do not terminate the string.
|
|
func findMultilineBasicStringEnd(b []byte, i int) int {
|
|
for {
|
|
j := bytes.IndexAny(b[i:], "\"\\")
|
|
if j < 0 {
|
|
return -1
|
|
}
|
|
i += j
|
|
if b[i] == '\\' {
|
|
i += 2
|
|
if i > len(b) {
|
|
return -1
|
|
}
|
|
continue
|
|
}
|
|
j = i
|
|
for j < len(b) && b[j] == '"' {
|
|
j++
|
|
}
|
|
if j-i >= 3 {
|
|
return i
|
|
}
|
|
i = j
|
|
}
|
|
}
|
|
|
|
// parseMultilineBasicStringEscaped continues parsing a multi-line basic
|
|
// string that contains escape sequences. i is the index of the first
|
|
// backslash; content starts at contentStart.
|
|
func (p *Parser) parseMultilineBasicStringEscaped(b []byte, contentStart, i int) ([]byte, []byte, []byte, error) {
|
|
// Escape sequences only ever shrink, so the content length before
|
|
// unescaping is enough to never reallocate. The closing run of quotes
|
|
// can lend up to two quotes to the content.
|
|
bufCap := len(b) - contentStart
|
|
if end := findMultilineBasicStringEnd(b, i); end >= 0 {
|
|
bufCap = end + 2 - contentStart
|
|
}
|
|
buf := make([]byte, i-contentStart, bufCap)
|
|
copy(buf, b[contentStart:i])
|
|
|
|
for i < len(b) {
|
|
c := b[i]
|
|
switch {
|
|
case c == '"':
|
|
j := i
|
|
for j < len(b) && b[j] == '"' {
|
|
j++
|
|
}
|
|
n := j - i
|
|
if n >= 3 {
|
|
if n > 5 {
|
|
return nil, nil, nil, NewParserError(b[i:j], "too many quotes at the end of a multiline basic string")
|
|
}
|
|
buf = append(buf, b[i:i+n-3]...)
|
|
return b[:j], buf, b[j:], nil
|
|
}
|
|
buf = append(buf, b[i:j]...)
|
|
i = j
|
|
case c == '\\':
|
|
i++
|
|
if i >= len(b) {
|
|
return nil, nil, nil, NewParserError(b[i-1:], `need a character after \`)
|
|
}
|
|
// Escaped newline: backslash, optional whitespace, newline,
|
|
// then all following whitespace and newlines are trimmed.
|
|
if b[i] == ' ' || b[i] == '\t' || b[i] == '\n' || b[i] == '\r' {
|
|
j := i
|
|
for j < len(b) && (b[j] == ' ' || b[j] == '\t') {
|
|
j++
|
|
}
|
|
if j < len(b) && b[j] == '\r' {
|
|
j++
|
|
}
|
|
if j >= len(b) || b[j] != '\n' {
|
|
return nil, nil, nil, NewParserError(b[i-1:i+1], "invalid escape character %#U", b[i])
|
|
}
|
|
j++
|
|
for j < len(b) && (b[j] == ' ' || b[j] == '\t' || b[j] == '\n' || b[j] == '\r') {
|
|
// note: a lone \r not followed by \n will be caught on
|
|
// the next iteration of the outer loop.
|
|
if b[j] == '\r' {
|
|
if j+1 >= len(b) || b[j+1] != '\n' {
|
|
break
|
|
}
|
|
j++
|
|
}
|
|
j++
|
|
}
|
|
i = j
|
|
continue
|
|
}
|
|
var err error
|
|
buf, i, err = unescape(buf, b, i)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
case c >= 0x20 && c < 0x7f:
|
|
buf = append(buf, c)
|
|
i++
|
|
case c == '\t' || c == '\n':
|
|
buf = append(buf, c)
|
|
i++
|
|
case c == '\r':
|
|
if i+1 < len(b) && b[i+1] == '\n' {
|
|
buf = append(buf, '\r', '\n')
|
|
i += 2
|
|
continue
|
|
}
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "carriage returns must be followed by a newline character")
|
|
case c < 0x80:
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "multiline basic strings cannot have control characters")
|
|
default:
|
|
j, ok := scanUtf8Run(b, i)
|
|
if !ok {
|
|
return nil, nil, nil, NewParserError(b[i:i+1], "invalid UTF-8 character in multiline basic string")
|
|
}
|
|
buf = append(buf, b[i:j]...)
|
|
i = j
|
|
}
|
|
}
|
|
return nil, nil, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`)
|
|
}
|
|
|
|
// scanUtf8Run consumes a run of valid non-ASCII UTF-8 runes starting at
|
|
// b[i]. It returns the index of the first byte after the run, and whether
|
|
// the run was entirely valid. Processing whole runs amortizes the cost of
|
|
// the call compared to validating rune by rune.
|
|
func scanUtf8Run(b []byte, i int) (int, bool) {
|
|
for i < len(b) {
|
|
c := b[i]
|
|
switch {
|
|
case c < 0x80:
|
|
return i, true
|
|
case c < 0xC2:
|
|
return i, false
|
|
case c < 0xE0:
|
|
if i+1 >= len(b) || b[i+1]&0xC0 != 0x80 {
|
|
return i, false
|
|
}
|
|
i += 2
|
|
case c < 0xF0:
|
|
if i+2 >= len(b) || b[i+2]&0xC0 != 0x80 {
|
|
return i, false
|
|
}
|
|
b1 := b[i+1]
|
|
switch c {
|
|
case 0xE0:
|
|
if b1 < 0xA0 || b1 > 0xBF {
|
|
return i, false
|
|
}
|
|
case 0xED:
|
|
// exclude surrogates
|
|
if b1 < 0x80 || b1 > 0x9F {
|
|
return i, false
|
|
}
|
|
default:
|
|
if b1&0xC0 != 0x80 {
|
|
return i, false
|
|
}
|
|
}
|
|
i += 3
|
|
case c < 0xF5:
|
|
if i+3 >= len(b) || b[i+2]&0xC0 != 0x80 || b[i+3]&0xC0 != 0x80 {
|
|
return i, false
|
|
}
|
|
b1 := b[i+1]
|
|
switch c {
|
|
case 0xF0:
|
|
if b1 < 0x90 || b1 > 0xBF {
|
|
return i, false
|
|
}
|
|
case 0xF4:
|
|
if b1 < 0x80 || b1 > 0x8F {
|
|
return i, false
|
|
}
|
|
default:
|
|
if b1&0xC0 != 0x80 {
|
|
return i, false
|
|
}
|
|
}
|
|
i += 4
|
|
default:
|
|
return i, false
|
|
}
|
|
}
|
|
return i, true
|
|
}
|