Initial commit
This commit is contained in:
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
||||
package hjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type hjsonParser struct {
|
||||
data []byte
|
||||
at int // The index of the current character
|
||||
ch byte // The current character
|
||||
}
|
||||
|
||||
func (p *hjsonParser) resetAt() {
|
||||
p.at = 0
|
||||
p.ch = ' '
|
||||
}
|
||||
|
||||
func isPunctuatorChar(c byte) bool {
|
||||
return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':'
|
||||
}
|
||||
|
||||
func (p *hjsonParser) errAt(message string) error {
|
||||
var i int
|
||||
col := 0
|
||||
line := 1
|
||||
for i = p.at - 1; i > 0 && p.data[i] != '\n'; i-- {
|
||||
col++
|
||||
}
|
||||
for ; i > 0; i-- {
|
||||
if p.data[i] == '\n' {
|
||||
line++
|
||||
}
|
||||
}
|
||||
samEnd := p.at - col + 20
|
||||
if samEnd > len(p.data) {
|
||||
samEnd = len(p.data)
|
||||
}
|
||||
return fmt.Errorf("%s at line %d,%d >>> %s", message, line, col, string(p.data[p.at-col:samEnd]))
|
||||
}
|
||||
|
||||
func (p *hjsonParser) next() bool {
|
||||
// get the next character.
|
||||
if p.at < len(p.data) {
|
||||
p.ch = p.data[p.at]
|
||||
p.at++
|
||||
return true
|
||||
}
|
||||
p.ch = 0
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *hjsonParser) peek(offs int) byte {
|
||||
pos := p.at + offs
|
||||
if pos >= 0 && pos < len(p.data) {
|
||||
return p.data[p.at+offs]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var escapee = map[byte]byte{
|
||||
'"': '"',
|
||||
'\'': '\'',
|
||||
'\\': '\\',
|
||||
'/': '/',
|
||||
'b': '\b',
|
||||
'f': '\f',
|
||||
'n': '\n',
|
||||
'r': '\r',
|
||||
't': '\t',
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readString(allowML bool) (string, error) {
|
||||
|
||||
// Parse a string value.
|
||||
res := new(bytes.Buffer)
|
||||
|
||||
// callers make sure that (ch === '"' || ch === "'")
|
||||
// When parsing for string values, we must look for " and \ characters.
|
||||
exitCh := p.ch
|
||||
for p.next() {
|
||||
if p.ch == exitCh {
|
||||
p.next()
|
||||
if allowML && exitCh == '\'' && p.ch == '\'' && res.Len() == 0 {
|
||||
// ''' indicates a multiline string
|
||||
p.next()
|
||||
return p.readMLString()
|
||||
} else {
|
||||
return res.String(), nil
|
||||
}
|
||||
}
|
||||
if p.ch == '\\' {
|
||||
p.next()
|
||||
if p.ch == 'u' {
|
||||
uffff := 0
|
||||
for i := 0; i < 4; i++ {
|
||||
p.next()
|
||||
var hex int
|
||||
if p.ch >= '0' && p.ch <= '9' {
|
||||
hex = int(p.ch - '0')
|
||||
} else if p.ch >= 'a' && p.ch <= 'f' {
|
||||
hex = int(p.ch - 'a' + 0xa)
|
||||
} else if p.ch >= 'A' && p.ch <= 'F' {
|
||||
hex = int(p.ch - 'A' + 0xa)
|
||||
} else {
|
||||
return "", p.errAt("Bad \\u char " + string(p.ch))
|
||||
}
|
||||
uffff = uffff*16 + hex
|
||||
}
|
||||
res.WriteRune(rune(uffff))
|
||||
} else if ech, ok := escapee[p.ch]; ok {
|
||||
res.WriteByte(ech)
|
||||
} else {
|
||||
return "", p.errAt("Bad escape \\" + string(p.ch))
|
||||
}
|
||||
} else if p.ch == '\n' || p.ch == '\r' {
|
||||
return "", p.errAt("Bad string containing newline")
|
||||
} else {
|
||||
res.WriteByte(p.ch)
|
||||
}
|
||||
}
|
||||
return "", p.errAt("Bad string")
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readMLString() (value string, err error) {
|
||||
|
||||
// Parse a multiline string value.
|
||||
res := new(bytes.Buffer)
|
||||
triple := 0
|
||||
|
||||
// we are at ''' +1 - get indent
|
||||
indent := 0
|
||||
for {
|
||||
c := p.peek(-indent - 5)
|
||||
if c == 0 || c == '\n' {
|
||||
break
|
||||
}
|
||||
indent++
|
||||
}
|
||||
|
||||
skipIndent := func() {
|
||||
skip := indent
|
||||
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' && skip > 0 {
|
||||
skip--
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
|
||||
// skip white/to (newline)
|
||||
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' {
|
||||
p.next()
|
||||
}
|
||||
if p.ch == '\n' {
|
||||
p.next()
|
||||
skipIndent()
|
||||
}
|
||||
|
||||
// When parsing multiline string values, we must look for ' characters.
|
||||
lastLf := false
|
||||
for {
|
||||
if p.ch == 0 {
|
||||
return "", p.errAt("Bad multiline string")
|
||||
} else if p.ch == '\'' {
|
||||
triple++
|
||||
p.next()
|
||||
if triple == 3 {
|
||||
sres := res.Bytes()
|
||||
if lastLf {
|
||||
return string(sres[0 : len(sres)-1]), nil // remove last EOL
|
||||
}
|
||||
return string(sres), nil
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
for triple > 0 {
|
||||
res.WriteByte('\'')
|
||||
triple--
|
||||
lastLf = false
|
||||
}
|
||||
}
|
||||
if p.ch == '\n' {
|
||||
res.WriteByte('\n')
|
||||
lastLf = true
|
||||
p.next()
|
||||
skipIndent()
|
||||
} else {
|
||||
if p.ch != '\r' {
|
||||
res.WriteByte(p.ch)
|
||||
lastLf = false
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readKeyname() (string, error) {
|
||||
|
||||
// quotes for keys are optional in Hjson
|
||||
// unless they include {}[],: or whitespace.
|
||||
|
||||
if p.ch == '"' || p.ch == '\'' {
|
||||
return p.readString(false)
|
||||
}
|
||||
|
||||
name := new(bytes.Buffer)
|
||||
start := p.at
|
||||
space := -1
|
||||
for {
|
||||
if p.ch == ':' {
|
||||
if name.Len() == 0 {
|
||||
return "", p.errAt("Found ':' but no key name (for an empty key name use quotes)")
|
||||
} else if space >= 0 && space != name.Len() {
|
||||
p.at = start + space
|
||||
return "", p.errAt("Found whitespace in your key name (use quotes to include)")
|
||||
}
|
||||
return name.String(), nil
|
||||
} else if p.ch <= ' ' {
|
||||
if p.ch == 0 {
|
||||
return "", p.errAt("Found EOF while looking for a key name (check your syntax)")
|
||||
}
|
||||
if space < 0 {
|
||||
space = name.Len()
|
||||
}
|
||||
} else {
|
||||
if isPunctuatorChar(p.ch) {
|
||||
return "", p.errAt("Found '" + string(p.ch) + "' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)")
|
||||
}
|
||||
name.WriteByte(p.ch)
|
||||
}
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hjsonParser) white() {
|
||||
for p.ch > 0 {
|
||||
// Skip whitespace.
|
||||
for p.ch > 0 && p.ch <= ' ' {
|
||||
p.next()
|
||||
}
|
||||
// Hjson allows comments
|
||||
if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' {
|
||||
for p.ch > 0 && p.ch != '\n' {
|
||||
p.next()
|
||||
}
|
||||
} else if p.ch == '/' && p.peek(0) == '*' {
|
||||
p.next()
|
||||
p.next()
|
||||
for p.ch > 0 && !(p.ch == '*' && p.peek(0) == '/') {
|
||||
p.next()
|
||||
}
|
||||
if p.ch > 0 {
|
||||
p.next()
|
||||
p.next()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readTfnns() (interface{}, error) {
|
||||
|
||||
// Hjson strings can be quoteless
|
||||
// returns string, true, false, or null.
|
||||
|
||||
if isPunctuatorChar(p.ch) {
|
||||
return nil, p.errAt("Found a punctuator character '" + string(p.ch) + "' when expecting a quoteless string (check your syntax)")
|
||||
}
|
||||
chf := p.ch
|
||||
value := new(bytes.Buffer)
|
||||
value.WriteByte(p.ch)
|
||||
|
||||
for {
|
||||
p.next()
|
||||
isEol := p.ch == '\r' || p.ch == '\n' || p.ch == 0
|
||||
if isEol ||
|
||||
p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||
p.ch == '#' ||
|
||||
p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||
switch chf {
|
||||
case 'f':
|
||||
if strings.TrimSpace(value.String()) == "false" {
|
||||
return false, nil
|
||||
}
|
||||
case 'n':
|
||||
if strings.TrimSpace(value.String()) == "null" {
|
||||
return nil, nil
|
||||
}
|
||||
case 't':
|
||||
if strings.TrimSpace(value.String()) == "true" {
|
||||
return true, nil
|
||||
}
|
||||
default:
|
||||
if chf == '-' || chf >= '0' && chf <= '9' {
|
||||
if n, err := tryParseNumber(value.Bytes(), false); err == nil {
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if isEol {
|
||||
// remove any whitespace at the end (ignored in quoteless strings)
|
||||
return strings.TrimSpace(value.String()), nil
|
||||
}
|
||||
}
|
||||
value.WriteByte(p.ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readArray() (value interface{}, err error) {
|
||||
|
||||
// Parse an array value.
|
||||
// assuming ch == '['
|
||||
|
||||
array := make([]interface{}, 0, 1)
|
||||
|
||||
p.next()
|
||||
p.white()
|
||||
|
||||
if p.ch == ']' {
|
||||
p.next()
|
||||
return array, nil // empty array
|
||||
}
|
||||
|
||||
for p.ch > 0 {
|
||||
var val interface{}
|
||||
if val, err = p.readValue(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
array = append(array, val)
|
||||
p.white()
|
||||
// in Hjson the comma is optional and trailing commas are allowed
|
||||
if p.ch == ',' {
|
||||
p.next()
|
||||
p.white()
|
||||
}
|
||||
if p.ch == ']' {
|
||||
p.next()
|
||||
return array, nil
|
||||
}
|
||||
p.white()
|
||||
}
|
||||
|
||||
return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)")
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readObject(withoutBraces bool) (value interface{}, err error) {
|
||||
// Parse an object value.
|
||||
|
||||
object := make(map[string]interface{})
|
||||
|
||||
if !withoutBraces {
|
||||
// assuming ch == '{'
|
||||
p.next()
|
||||
}
|
||||
|
||||
p.white()
|
||||
if p.ch == '}' && !withoutBraces {
|
||||
p.next()
|
||||
return object, nil // empty object
|
||||
}
|
||||
for p.ch > 0 {
|
||||
var key string
|
||||
if key, err = p.readKeyname(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.white()
|
||||
if p.ch != ':' {
|
||||
return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'")
|
||||
}
|
||||
p.next()
|
||||
// duplicate keys overwrite the previous value
|
||||
var val interface{}
|
||||
if val, err = p.readValue(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object[key] = val
|
||||
p.white()
|
||||
// in Hjson the comma is optional and trailing commas are allowed
|
||||
if p.ch == ',' {
|
||||
p.next()
|
||||
p.white()
|
||||
}
|
||||
if p.ch == '}' && !withoutBraces {
|
||||
p.next()
|
||||
return object, nil
|
||||
}
|
||||
p.white()
|
||||
}
|
||||
|
||||
if withoutBraces {
|
||||
return object, nil
|
||||
}
|
||||
return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)")
|
||||
}
|
||||
|
||||
func (p *hjsonParser) readValue() (interface{}, error) {
|
||||
|
||||
// Parse a Hjson value. It could be an object, an array, a string, a number or a word.
|
||||
|
||||
p.white()
|
||||
switch p.ch {
|
||||
case '{':
|
||||
return p.readObject(false)
|
||||
case '[':
|
||||
return p.readArray()
|
||||
case '"', '\'':
|
||||
return p.readString(true)
|
||||
default:
|
||||
return p.readTfnns()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *hjsonParser) rootValue() (interface{}, error) {
|
||||
// Braces for the root object are optional
|
||||
|
||||
p.white()
|
||||
switch p.ch {
|
||||
case '{':
|
||||
return p.checkTrailing(p.readObject(false))
|
||||
case '[':
|
||||
return p.checkTrailing(p.readArray())
|
||||
}
|
||||
|
||||
// assume we have a root object without braces
|
||||
res, err := p.checkTrailing(p.readObject(true))
|
||||
if err == nil {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// test if we are dealing with a single JSON value instead (true/false/null/num/"")
|
||||
p.resetAt()
|
||||
if res2, err2 := p.checkTrailing(p.readValue()); err2 == nil {
|
||||
return res2, nil
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (p *hjsonParser) checkTrailing(v interface{}, err error) (interface{}, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.white()
|
||||
if p.ch > 0 {
|
||||
return nil, p.errAt("Syntax error, found trailing characters")
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Unmarshal parses the Hjson-encoded data and stores the result
|
||||
// in the value pointed to by v.
|
||||
//
|
||||
// Unmarshal uses the inverse of the encodings that
|
||||
// Marshal uses, allocating maps, slices, and pointers as necessary.
|
||||
//
|
||||
func Unmarshal(data []byte, v interface{}) (err error) {
|
||||
var value interface{}
|
||||
parser := &hjsonParser{data, 0, ' '}
|
||||
parser.resetAt()
|
||||
value, err = parser.rootValue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
||||
return fmt.Errorf("non-pointer %v", reflect.TypeOf(v))
|
||||
}
|
||||
for rv.Kind() == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}()
|
||||
rv.Set(reflect.ValueOf(value))
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user