1058 lines
26 KiB
Go
1058 lines
26 KiB
Go
package hjson
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
const maxPointerDepth = 512
|
|
|
|
// This limits the max nesting depth to prevent stack overflow.
|
|
const maxNestingDepth = 10000
|
|
|
|
type commentInfo struct {
|
|
hasComment bool
|
|
cmStart int
|
|
cmEnd int
|
|
}
|
|
|
|
// If a destination type implements ElemTyper, Unmarshal() will call ElemType()
|
|
// on the destination when unmarshalling an array or an object, to see if any
|
|
// array element or leaf node should be of type string even if it can be treated
|
|
// as a number, boolean or null. This is most useful if the destination also
|
|
// implements the json.Unmarshaler interface, because then there is no other way
|
|
// for Unmarshal() to know the type of the elements on the destination. If a
|
|
// destination implements ElemTyper all of its elements must be of the same
|
|
// type.
|
|
type ElemTyper interface {
|
|
// Returns the desired type of any elements. If ElemType() is implemented
|
|
// using a pointer receiver it must be possible to call with nil as receiver.
|
|
ElemType() reflect.Type
|
|
}
|
|
|
|
// DecoderOptions defines options for decoding Hjson.
|
|
type DecoderOptions struct {
|
|
// UseJSONNumber causes the Decoder to unmarshal a number into an interface{} as a
|
|
// json.Number instead of as a float64.
|
|
UseJSONNumber bool
|
|
// DisallowUnknownFields causes an error to be returned when the destination
|
|
// is a struct and the input contains object keys which do not match any
|
|
// non-ignored, exported fields in the destination.
|
|
DisallowUnknownFields bool
|
|
// DisallowDuplicateKeys causes an error to be returned if an object (map) in
|
|
// the Hjson input contains duplicate keys. If DisallowDuplicateKeys is set
|
|
// to false, later values will silently overwrite previous values for the
|
|
// same key.
|
|
DisallowDuplicateKeys bool
|
|
// WhitespaceAsComments only has any effect when an hjson.Node struct (or
|
|
// an *hjson.Node pointer) is used as target for Unmarshal. If
|
|
// WhitespaceAsComments is set to true, all whitespace and comments are stored
|
|
// in the Node structs so that linefeeds and custom indentation is kept. If
|
|
// WhitespaceAsComments instead is set to false, only actual comments are
|
|
// stored as comments in Node structs.
|
|
WhitespaceAsComments bool
|
|
}
|
|
|
|
// DefaultDecoderOptions returns the default decoding options.
|
|
func DefaultDecoderOptions() DecoderOptions {
|
|
return DecoderOptions{
|
|
UseJSONNumber: false,
|
|
DisallowUnknownFields: false,
|
|
DisallowDuplicateKeys: false,
|
|
WhitespaceAsComments: true,
|
|
}
|
|
}
|
|
|
|
type hjsonParser struct {
|
|
DecoderOptions
|
|
data []byte
|
|
at int // The index of the current character
|
|
ch byte // The current character
|
|
structTypeCache map[reflect.Type]structFieldMap
|
|
willMarshalToJSON bool
|
|
nodeDestination bool
|
|
nestingDepth int
|
|
}
|
|
|
|
var unmarshalerText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
|
var elemTyper = reflect.TypeOf((*ElemTyper)(nil)).Elem()
|
|
|
|
func (p *hjsonParser) setComment1(pCm *string, ci commentInfo) {
|
|
if ci.hasComment {
|
|
*pCm = string(p.data[ci.cmStart:ci.cmEnd])
|
|
}
|
|
}
|
|
|
|
func (p *hjsonParser) setComment2(pCm *string, ciA, ciB commentInfo) {
|
|
if ciA.hasComment && ciB.hasComment {
|
|
*pCm = string(p.data[ciA.cmStart:ciA.cmEnd]) + string(p.data[ciB.cmStart:ciB.cmEnd])
|
|
} else {
|
|
p.setComment1(pCm, ciA)
|
|
p.setComment1(pCm, ciB)
|
|
}
|
|
}
|
|
|
|
func (p *hjsonParser) resetAt() {
|
|
p.at = 0
|
|
p.nestingDepth = 0
|
|
p.next()
|
|
}
|
|
|
|
func isPunctuatorChar(c byte) bool {
|
|
return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':'
|
|
}
|
|
|
|
func (p *hjsonParser) errAt(message string) error {
|
|
if p.at <= len(p.data) {
|
|
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]))
|
|
}
|
|
return errors.New(message)
|
|
}
|
|
|
|
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.at++
|
|
p.ch = 0
|
|
return false
|
|
}
|
|
|
|
func (p *hjsonParser) prev() bool {
|
|
// get the previous character.
|
|
if p.at > 1 {
|
|
p.ch = p.data[p.at-2]
|
|
p.at--
|
|
return true
|
|
}
|
|
|
|
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 unravelDestination(dest reflect.Value, t reflect.Type) (reflect.Value, reflect.Type) {
|
|
if dest.IsValid() {
|
|
for a := 0; a < maxPointerDepth && (dest.Kind() == reflect.Ptr ||
|
|
dest.Kind() == reflect.Interface) && !dest.IsNil(); a++ {
|
|
|
|
dest = dest.Elem()
|
|
}
|
|
|
|
if dest.IsValid() {
|
|
t = dest.Type()
|
|
}
|
|
}
|
|
|
|
for a := 0; a < maxPointerDepth && t != nil && t.Kind() == reflect.Ptr; a++ {
|
|
t = t.Elem()
|
|
}
|
|
|
|
return dest, 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) commonWhite(onlyAfter bool) (commentInfo, bool) {
|
|
ci := commentInfo{
|
|
false,
|
|
p.at - 1,
|
|
0,
|
|
}
|
|
var hasLineFeed bool
|
|
|
|
for p.ch > 0 {
|
|
// Skip whitespace.
|
|
for p.ch > 0 && p.ch <= ' ' {
|
|
if p.ch == '\n' {
|
|
hasLineFeed = true
|
|
if onlyAfter {
|
|
ci.cmEnd = p.at - 1
|
|
// Skip EOL.
|
|
p.next()
|
|
return ci, hasLineFeed
|
|
}
|
|
}
|
|
p.next()
|
|
}
|
|
// Hjson allows comments
|
|
if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' {
|
|
ci.hasComment = p.nodeDestination
|
|
for p.ch > 0 && p.ch != '\n' {
|
|
p.next()
|
|
}
|
|
} else if p.ch == '/' && p.peek(0) == '*' {
|
|
ci.hasComment = p.nodeDestination
|
|
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
|
|
}
|
|
}
|
|
|
|
// cmEnd is the first char after the comment (i.e. not included in the comment).
|
|
ci.cmEnd = p.at - 1
|
|
|
|
return ci, hasLineFeed
|
|
}
|
|
|
|
func (p *hjsonParser) white() commentInfo {
|
|
ci, _ := p.commonWhite(false)
|
|
|
|
ci.hasComment = (ci.hasComment || (p.WhitespaceAsComments && (ci.cmEnd > ci.cmStart)))
|
|
|
|
return ci
|
|
}
|
|
|
|
func (p *hjsonParser) whiteAfterComma() commentInfo {
|
|
ci, hasLineFeed := p.commonWhite(true)
|
|
|
|
ci.hasComment = (ci.hasComment || (p.WhitespaceAsComments &&
|
|
hasLineFeed && (ci.cmEnd > ci.cmStart)))
|
|
|
|
return ci
|
|
}
|
|
|
|
func (p *hjsonParser) getCommentAfter() commentInfo {
|
|
ci, _ := p.commonWhite(true)
|
|
|
|
ci.hasComment = (ci.hasComment || (p.WhitespaceAsComments && (ci.cmEnd > ci.cmStart)))
|
|
|
|
return ci
|
|
}
|
|
|
|
func (p *hjsonParser) maybeWrapNode(n *Node, v interface{}) (interface{}, error) {
|
|
if p.nodeDestination {
|
|
n.Value = v
|
|
return n, nil
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
func (p *hjsonParser) readTfnns(dest reflect.Value, t reflect.Type) (interface{}, error) {
|
|
|
|
// Hjson strings can be quoteless
|
|
// returns string, (json.Number or float64), true, false, or null.
|
|
// Or wraps the value in a Node.
|
|
|
|
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
|
|
var node Node
|
|
value := new(bytes.Buffer)
|
|
value.WriteByte(p.ch)
|
|
|
|
var newT reflect.Type
|
|
if !p.nodeDestination {
|
|
// Keep the original dest and t, because we need to check if it implements
|
|
// encoding.TextUnmarshaler.
|
|
_, newT = unravelDestination(dest, t)
|
|
}
|
|
|
|
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) == '*') {
|
|
|
|
// Do not output anything else than a string if our destination is a string.
|
|
// Pointer methods can be called if the destination is addressable,
|
|
// therefore we also check if dest.Addr() implements encoding.TextUnmarshaler.
|
|
// But "null" is a special case: unmarshal it as nil if the original
|
|
// destination type is a pointer.
|
|
if chf == 'n' && !p.nodeDestination && t != nil && t.Kind() == reflect.Ptr &&
|
|
strings.TrimSpace(value.String()) == "null" {
|
|
|
|
return p.maybeWrapNode(&node, nil)
|
|
}
|
|
if (newT == nil || newT.Kind() != reflect.String) &&
|
|
(t == nil || !(t.Implements(unmarshalerText) ||
|
|
dest.CanAddr() && dest.Addr().Type().Implements(unmarshalerText))) {
|
|
|
|
switch chf {
|
|
case 'f':
|
|
if strings.TrimSpace(value.String()) == "false" {
|
|
return p.maybeWrapNode(&node, false)
|
|
}
|
|
case 'n':
|
|
if strings.TrimSpace(value.String()) == "null" {
|
|
return p.maybeWrapNode(&node, nil)
|
|
}
|
|
case 't':
|
|
if strings.TrimSpace(value.String()) == "true" {
|
|
return p.maybeWrapNode(&node, true)
|
|
}
|
|
default:
|
|
if chf == '-' || chf >= '0' && chf <= '9' {
|
|
// Always use json.Number if we will marshal to JSON.
|
|
if n, err := tryParseNumber(
|
|
value.Bytes(),
|
|
false,
|
|
p.willMarshalToJSON || p.DecoderOptions.UseJSONNumber,
|
|
); err == nil {
|
|
return p.maybeWrapNode(&node, n)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if isEol {
|
|
// remove any whitespace at the end (ignored in quoteless strings)
|
|
return p.maybeWrapNode(&node, strings.TrimSpace(value.String()))
|
|
}
|
|
}
|
|
value.WriteByte(p.ch)
|
|
}
|
|
}
|
|
|
|
// t must not have been unraveled
|
|
func getElemTyperType(rv reflect.Value, t reflect.Type) reflect.Type {
|
|
var elemType reflect.Type
|
|
isElemTyper := false
|
|
|
|
if t != nil && t.Implements(elemTyper) {
|
|
isElemTyper = true
|
|
if t.Kind() == reflect.Ptr {
|
|
// If ElemType() has a value receiver we would get a panic if we call it
|
|
// on a nil pointer.
|
|
if !rv.IsValid() || rv.IsNil() {
|
|
rv = reflect.New(t.Elem())
|
|
}
|
|
} else if !rv.IsValid() {
|
|
rv = reflect.Zero(t)
|
|
}
|
|
}
|
|
if !isElemTyper && rv.CanAddr() {
|
|
rv = rv.Addr()
|
|
if rv.Type().Implements(elemTyper) {
|
|
isElemTyper = true
|
|
}
|
|
}
|
|
if !isElemTyper && t != nil {
|
|
pt := reflect.PtrTo(t)
|
|
if pt.Implements(elemTyper) {
|
|
isElemTyper = true
|
|
rv = reflect.Zero(pt)
|
|
}
|
|
}
|
|
if isElemTyper {
|
|
elemType = rv.Interface().(ElemTyper).ElemType()
|
|
}
|
|
|
|
return elemType
|
|
}
|
|
|
|
func (p *hjsonParser) readArray(dest reflect.Value, t reflect.Type) (value interface{}, err error) {
|
|
var node Node
|
|
|
|
if p.nestingDepth > maxNestingDepth {
|
|
return nil, p.errAt(fmt.Sprintf("Exceeded max depth (%d)", maxNestingDepth))
|
|
}
|
|
|
|
array := make([]interface{}, 0, 1)
|
|
|
|
// Skip '['.
|
|
p.next()
|
|
ciBefore := p.getCommentAfter()
|
|
p.setComment1(&node.Cm.InsideFirst, ciBefore)
|
|
ciBefore = p.white()
|
|
|
|
if p.ch == ']' {
|
|
p.setComment1(&node.Cm.InsideLast, ciBefore)
|
|
p.next()
|
|
return p.maybeWrapNode(&node, array) // empty array
|
|
}
|
|
|
|
var elemType reflect.Type
|
|
if !p.nodeDestination {
|
|
elemType = getElemTyperType(dest, t)
|
|
|
|
dest, t = unravelDestination(dest, t)
|
|
|
|
// All elements in any existing slice/array will be removed, so we only care
|
|
// about the type of the new elements that will be created.
|
|
if elemType == nil && t != nil && (t.Kind() == reflect.Slice || t.Kind() == reflect.Array) {
|
|
elemType = t.Elem()
|
|
}
|
|
}
|
|
|
|
for p.ch > 0 {
|
|
var elemNode *Node
|
|
var val interface{}
|
|
if val, err = p.readValue(reflect.Value{}, elemType); err != nil {
|
|
return nil, err
|
|
}
|
|
if p.nodeDestination {
|
|
var ok bool
|
|
if elemNode, ok = val.(*Node); ok {
|
|
p.setComment1(&elemNode.Cm.Before, ciBefore)
|
|
}
|
|
}
|
|
// Check white before comma because comma might be on other line.
|
|
ciAfter := p.white()
|
|
// in Hjson the comma is optional and trailing commas are allowed
|
|
if p.ch == ',' {
|
|
p.next()
|
|
ciAfterComma := p.whiteAfterComma()
|
|
if elemNode != nil {
|
|
existingAfter := elemNode.Cm.After
|
|
p.setComment2(&elemNode.Cm.After, ciAfter, ciAfterComma)
|
|
elemNode.Cm.After = existingAfter + elemNode.Cm.After
|
|
}
|
|
// Any comments starting on the line after the comma.
|
|
ciAfter = p.white()
|
|
}
|
|
if p.ch == ']' {
|
|
p.setComment1(&node.Cm.InsideLast, ciAfter)
|
|
array = append(array, val)
|
|
p.next()
|
|
return p.maybeWrapNode(&node, array)
|
|
}
|
|
array = append(array, val)
|
|
ciBefore = ciAfter
|
|
}
|
|
|
|
return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)")
|
|
}
|
|
|
|
func (p *hjsonParser) readObject(
|
|
withoutBraces bool,
|
|
dest reflect.Value,
|
|
t reflect.Type,
|
|
ciBefore commentInfo,
|
|
) (value interface{}, err error) {
|
|
// Parse an object value.
|
|
var node Node
|
|
var elemNode *Node
|
|
|
|
if p.nestingDepth > maxNestingDepth {
|
|
return nil, p.errAt(fmt.Sprintf("Exceeded max depth (%d)", maxNestingDepth))
|
|
}
|
|
|
|
object := NewOrderedMap()
|
|
|
|
// If withoutBraces == true we use the input argument ciBefore as
|
|
// Before-comment on the first element of this obj, or as InnerLast-comment
|
|
// on this obj if it doesn't contain any elements. If withoutBraces == false
|
|
// we ignore the input ciBefore.
|
|
|
|
if !withoutBraces {
|
|
// assuming ch == '{'
|
|
p.next()
|
|
ciInsideFirst := p.getCommentAfter()
|
|
p.setComment1(&node.Cm.InsideFirst, ciInsideFirst)
|
|
ciBefore = p.white()
|
|
if p.ch == '}' {
|
|
p.setComment1(&node.Cm.InsideLast, ciBefore)
|
|
p.next()
|
|
return p.maybeWrapNode(&node, object) // empty object
|
|
}
|
|
}
|
|
|
|
var stm structFieldMap
|
|
|
|
var elemType reflect.Type
|
|
if !p.nodeDestination {
|
|
elemType = getElemTyperType(dest, t)
|
|
|
|
dest, t = unravelDestination(dest, t)
|
|
|
|
if elemType == nil && t != nil {
|
|
switch t.Kind() {
|
|
case reflect.Struct:
|
|
var ok bool
|
|
stm, ok = p.structTypeCache[t]
|
|
if !ok {
|
|
stm = getStructFieldInfoMap(t)
|
|
p.structTypeCache[t] = stm
|
|
}
|
|
|
|
case reflect.Map:
|
|
// For any key that we find in our loop here below, the new value fully
|
|
// replaces any old value. So no need for us to dig down into a tree.
|
|
// (This is because we are decoding into a map. If we were decoding into
|
|
// a struct we would need to dig down into a tree, to match the behavior
|
|
// of Golang's JSON decoder.)
|
|
elemType = t.Elem()
|
|
}
|
|
}
|
|
}
|
|
|
|
for p.ch > 0 {
|
|
var key string
|
|
if key, err = p.readKeyname(); err != nil {
|
|
return nil, err
|
|
}
|
|
ciKey := p.white()
|
|
if p.ch != ':' {
|
|
return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'")
|
|
}
|
|
p.next()
|
|
|
|
var newDest reflect.Value
|
|
var newDestType reflect.Type
|
|
if stm != nil {
|
|
sfi, ok := stm.getField(key)
|
|
if ok {
|
|
// The field might be found on the root struct or in embedded structs.
|
|
newDest, newDestType = dest, t
|
|
for _, i := range sfi.indexPath {
|
|
newDest, newDestType = unravelDestination(newDest, newDestType)
|
|
|
|
if newDestType == nil {
|
|
return nil, p.errAt("Internal error")
|
|
}
|
|
newDestType = newDestType.Field(i).Type
|
|
elemType = newDestType
|
|
|
|
if newDest.IsValid() {
|
|
if newDest.Kind() != reflect.Struct {
|
|
// We are only keeping track of newDest in case it contains a
|
|
// tree that we will partially update. But here we have not found
|
|
// any tree, so we can ignore newDest and just look at
|
|
// newDestType instead.
|
|
newDest = reflect.Value{}
|
|
} else {
|
|
newDest = newDest.Field(i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// duplicate keys overwrite the previous value
|
|
var val interface{}
|
|
if val, err = p.readValue(newDest, elemType); err != nil {
|
|
return nil, err
|
|
}
|
|
if p.nodeDestination {
|
|
var ok bool
|
|
if elemNode, ok = val.(*Node); ok {
|
|
p.setComment1(&elemNode.Cm.Key, ciKey)
|
|
elemNode.Cm.Key += elemNode.Cm.Before
|
|
elemNode.Cm.Before = ""
|
|
p.setComment1(&elemNode.Cm.Before, ciBefore)
|
|
}
|
|
}
|
|
// Check white before comma because comma might be on other line.
|
|
ciAfter := p.white()
|
|
// in Hjson the comma is optional and trailing commas are allowed
|
|
if p.ch == ',' {
|
|
p.next()
|
|
ciAfterComma := p.whiteAfterComma()
|
|
if elemNode != nil {
|
|
existingAfter := elemNode.Cm.After
|
|
p.setComment2(&elemNode.Cm.After, ciAfter, ciAfterComma)
|
|
elemNode.Cm.After = existingAfter + elemNode.Cm.After
|
|
}
|
|
ciAfter = p.white()
|
|
}
|
|
if p.ch == '}' && !withoutBraces {
|
|
p.setComment1(&node.Cm.InsideLast, ciAfter)
|
|
oldValue, isDuplicate := object.Set(key, val)
|
|
if isDuplicate && p.DisallowDuplicateKeys {
|
|
return nil, p.errAt(fmt.Sprintf("Found duplicate values ('%#v' and '%#v') for key '%v'",
|
|
oldValue, val, key))
|
|
}
|
|
p.next()
|
|
return p.maybeWrapNode(&node, object)
|
|
}
|
|
oldValue, isDuplicate := object.Set(key, val)
|
|
if isDuplicate && p.DisallowDuplicateKeys {
|
|
return nil, p.errAt(fmt.Sprintf("Found duplicate values ('%#v' and '%#v') for key '%v'",
|
|
oldValue, val, key))
|
|
}
|
|
ciBefore = ciAfter
|
|
}
|
|
|
|
if withoutBraces {
|
|
p.setComment1(&node.Cm.InsideLast, ciBefore)
|
|
return p.maybeWrapNode(&node, object)
|
|
}
|
|
return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)")
|
|
}
|
|
|
|
// dest and t must not have been unraveled yet here. In readTfnns we need
|
|
// to check if the original type (or a pointer to it) implements
|
|
// encoding.TextUnmarshaler.
|
|
func (p *hjsonParser) readValue(dest reflect.Value, t reflect.Type) (ret interface{}, err error) {
|
|
ciBefore := p.white()
|
|
// Parse an Hjson value. It could be an object, an array, a string, a number or a word.
|
|
switch p.ch {
|
|
case '{':
|
|
p.nestingDepth++
|
|
ret, err = p.readObject(false, dest, t, ciBefore)
|
|
p.nestingDepth--
|
|
case '[':
|
|
p.nestingDepth++
|
|
ret, err = p.readArray(dest, t)
|
|
p.nestingDepth--
|
|
case '"', '\'':
|
|
s, err := p.readString(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret, err = p.maybeWrapNode(&Node{}, s)
|
|
default:
|
|
ret, err = p.readTfnns(dest, t)
|
|
// Make sure that any comment will include preceding whitespace.
|
|
if p.ch == '#' || p.ch == '/' {
|
|
for p.prev() && p.ch <= ' ' {
|
|
}
|
|
p.next()
|
|
}
|
|
}
|
|
|
|
ciAfter := p.getCommentAfter()
|
|
if p.nodeDestination {
|
|
if node, ok := ret.(*Node); ok {
|
|
p.setComment1(&node.Cm.Before, ciBefore)
|
|
p.setComment1(&node.Cm.After, ciAfter)
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *hjsonParser) rootValue(dest reflect.Value) (ret interface{}, err error) {
|
|
// Braces for the root object are optional
|
|
|
|
// We have checked that dest is a pointer before calling rootValue().
|
|
// Dereference here because readObject() etc will pass on child destinations
|
|
// without creating pointers.
|
|
dest = dest.Elem()
|
|
t := dest.Type()
|
|
|
|
var errSyntax error
|
|
var ciAfter commentInfo
|
|
ciBefore := p.white()
|
|
|
|
switch p.ch {
|
|
case '{':
|
|
ret, err = p.readObject(false, dest, t, ciBefore)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ciAfter, err = p.checkTrailing()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if p.nodeDestination {
|
|
if node, ok := ret.(*Node); ok {
|
|
p.setComment1(&node.Cm.Before, ciBefore)
|
|
p.setComment1(&node.Cm.After, ciAfter)
|
|
}
|
|
}
|
|
return
|
|
case '[':
|
|
ret, err = p.readArray(dest, t)
|
|
if err != nil {
|
|
return
|
|
}
|
|
ciAfter, err = p.checkTrailing()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if p.nodeDestination {
|
|
if node, ok := ret.(*Node); ok {
|
|
p.setComment1(&node.Cm.Before, ciBefore)
|
|
p.setComment1(&node.Cm.After, ciAfter)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
if ret == nil {
|
|
// Assume we have a root object without braces.
|
|
ret, errSyntax = p.readObject(true, dest, t, ciBefore)
|
|
ciAfter, err = p.checkTrailing()
|
|
if errSyntax != nil || err != nil {
|
|
// Syntax error, or maybe a single JSON value.
|
|
ret = nil
|
|
err = nil
|
|
} else {
|
|
if p.nodeDestination {
|
|
if node, ok := ret.(*Node); ok {
|
|
p.setComment1(&node.Cm.After, ciAfter)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
if ret == nil {
|
|
// test if we are dealing with a single JSON value instead (true/false/null/num/"")
|
|
p.resetAt()
|
|
ret, err = p.readValue(dest, t)
|
|
if err == nil {
|
|
ciAfter, err = p.checkTrailing()
|
|
}
|
|
if err == nil {
|
|
if p.nodeDestination {
|
|
if node, ok := ret.(*Node); ok {
|
|
// ciBefore has been read again and set on the node inside the
|
|
// function p.readValue().
|
|
existingAfter := node.Cm.After
|
|
p.setComment1(&node.Cm.After, ciAfter)
|
|
if node.Cm.After != "" {
|
|
existingAfter += "\n"
|
|
}
|
|
node.Cm.After = existingAfter + node.Cm.After
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
}
|
|
|
|
if errSyntax != nil {
|
|
return nil, errSyntax
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *hjsonParser) checkTrailing() (commentInfo, error) {
|
|
ci := p.white()
|
|
if p.ch > 0 {
|
|
return ci, p.errAt("Syntax error, found trailing characters")
|
|
}
|
|
return ci, nil
|
|
}
|
|
|
|
// Unmarshal parses the Hjson-encoded data using default options and stores the
|
|
// result in the value pointed to by v.
|
|
//
|
|
// See UnmarshalWithOptions.
|
|
func Unmarshal(data []byte, v interface{}) error {
|
|
return UnmarshalWithOptions(data, v, DefaultDecoderOptions())
|
|
}
|
|
|
|
func orderedUnmarshal(
|
|
data []byte,
|
|
v interface{},
|
|
options DecoderOptions,
|
|
willMarshalToJSON bool,
|
|
nodeDestination bool,
|
|
) (
|
|
interface{},
|
|
error,
|
|
) {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
|
return nil, fmt.Errorf("Cannot unmarshal into non-pointer %v", reflect.TypeOf(v))
|
|
}
|
|
|
|
parser := &hjsonParser{
|
|
DecoderOptions: options,
|
|
data: data,
|
|
at: 0,
|
|
ch: ' ',
|
|
structTypeCache: map[reflect.Type]structFieldMap{},
|
|
willMarshalToJSON: willMarshalToJSON,
|
|
nodeDestination: nodeDestination,
|
|
nestingDepth: 0,
|
|
}
|
|
parser.resetAt()
|
|
value, err := parser.rootValue(rv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
// UnmarshalWithOptions parses the Hjson-encoded data and stores the result
|
|
// in the value pointed to by v.
|
|
//
|
|
// The Hjson input is internally converted to JSON, which is then used as input
|
|
// to the function json.Unmarshal(). Unless the input argument v is of any of
|
|
// these types:
|
|
//
|
|
// *hjson.OrderedMap
|
|
// **hjson.OrderedMap
|
|
// *hjson.Node
|
|
// **hjson.Node
|
|
//
|
|
// Comments can be read from the Hjson-encoded data, but only if the input
|
|
// argument v is of type *hjson.Node or **hjson.Node.
|
|
//
|
|
// For more details about the output from this function, see the documentation
|
|
// for json.Unmarshal().
|
|
func UnmarshalWithOptions(data []byte, v interface{}, options DecoderOptions) error {
|
|
inOM, destinationIsOrderedMap := v.(*OrderedMap)
|
|
if !destinationIsOrderedMap {
|
|
pInOM, ok := v.(**OrderedMap)
|
|
if ok {
|
|
destinationIsOrderedMap = true
|
|
inOM = &OrderedMap{}
|
|
*pInOM = inOM
|
|
}
|
|
}
|
|
|
|
inNode, destinationIsNode := v.(*Node)
|
|
if !destinationIsNode {
|
|
pInNode, ok := v.(**Node)
|
|
if ok {
|
|
destinationIsNode = true
|
|
inNode = &Node{}
|
|
*pInNode = inNode
|
|
}
|
|
}
|
|
|
|
value, err := orderedUnmarshal(data, v, options, !(destinationIsOrderedMap ||
|
|
destinationIsNode), destinationIsNode)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if destinationIsOrderedMap {
|
|
if outOM, ok := value.(*OrderedMap); ok {
|
|
*inOM = *outOM
|
|
return nil
|
|
}
|
|
return fmt.Errorf("Cannot unmarshal into hjson.OrderedMap: Try %v as destination instead",
|
|
reflect.TypeOf(v))
|
|
}
|
|
|
|
if destinationIsNode {
|
|
if outNode, ok := value.(*Node); ok {
|
|
*inNode = *outNode
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Convert to JSON so we can let json.Unmarshal() handle all destination
|
|
// types (including interfaces json.Unmarshaler and encoding.TextUnmarshaler)
|
|
// and merging.
|
|
buf, err := json.Marshal(value)
|
|
if err != nil {
|
|
return errors.New("Internal error")
|
|
}
|
|
|
|
dec := json.NewDecoder(bytes.NewBuffer(buf))
|
|
if options.UseJSONNumber {
|
|
dec.UseNumber()
|
|
}
|
|
if options.DisallowUnknownFields {
|
|
dec.DisallowUnknownFields()
|
|
}
|
|
|
|
err = dec.Decode(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|