0
Files
aleesa-irc-go/vendor/github.com/hjson/hjson-go/decode.go
2022-09-04 04:29:50 +03:00

481 lines
9.8 KiB
Go

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
}