Files
doll/vendor/github.com/hjson/hjson-go/v4/decode.go
2025-10-26 23:36:16 +03:00

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
}