Files
go-mc/nbt/snbt_decode.go
2021-05-26 15:02:49 +08:00

288 lines
6.1 KiB
Go

package nbt
import (
"bytes"
"math"
"strconv"
"strings"
)
type decodeState struct {
data []byte
off int // next read offset in data
opcode int // last read result
scan scanner
}
const phasePanicMsg = "SNBT decoder out of sync - data changing underfoot?"
func (e *Encoder) WriteSNBT(snbt string) error {
d := decodeState{data: []byte(snbt)}
d.scan.reset()
return writeValue(e, &d, "")
}
func writeValue(e *Encoder, d *decodeState, tagName string) error {
d.scanWhile(scanSkipSpace)
switch d.opcode {
default:
panic(phasePanicMsg)
case scanBeginLiteral:
return writeLiteral(e, d, tagName)
case scanBeginCompound:
return writeCompound(e, d, tagName)
case scanBeginList:
return writeListOrArray(e, d, tagName)
}
}
func writeLiteral(e *Encoder, d *decodeState, tagName string) error {
start := d.readIndex()
d.scanWhile(scanContinue)
literal := d.data[start:d.readIndex()]
switch v := parseLiteral(literal); v.(type) {
case string:
str := v.(string)
e.writeTag(TagString, tagName)
e.writeInt16(int16(len(str)))
e.w.Write([]byte(str))
case int8:
e.writeTag(TagByte, tagName)
e.w.Write([]byte{byte(v.(int8))})
case int16:
e.writeTag(TagShort, tagName)
e.writeInt16(v.(int16))
case int32:
e.writeTag(TagInt, tagName)
e.writeInt32(v.(int32))
case int64:
e.writeTag(TagLong, tagName)
e.writeInt64(v.(int64))
case float32:
e.writeTag(TagFloat, tagName)
e.writeInt32(int32(math.Float32bits(v.(float32))))
case float64:
e.writeTag(TagDouble, tagName)
e.writeInt64(int64(math.Float64bits(v.(float64))))
}
return nil
}
func writeCompound(e *Encoder, d *decodeState, tagName string) error {
e.writeTag(TagCompound, tagName)
for {
d.scanWhile(scanSkipSpace)
if d.opcode == scanEndValue {
break
}
if d.opcode != scanBeginLiteral {
panic(phasePanicMsg)
}
// read tag name
start := d.readIndex()
d.scanWhile(scanContinue)
tagName := string(d.data[start:d.readIndex()])
// read value
if d.opcode == scanSkipSpace {
d.scanWhile(scanSkipSpace)
}
if d.opcode != scanCompoundTagName {
panic(phasePanicMsg)
}
writeValue(e, d, tagName)
// Next token must be , or }.
if d.opcode == scanSkipSpace {
d.scanWhile(scanSkipSpace)
}
if d.opcode == scanEndValue {
break
}
if d.opcode != scanCompoundValue {
panic(phasePanicMsg)
}
}
e.w.Write([]byte{TagEnd})
return nil
}
func writeListOrArray(e *Encoder, d *decodeState, tagName string) error {
d.scanWhile(scanSkipSpace)
if d.opcode == scanEndValue { // ']', empty TAG_List
e.writeTag(TagList, tagName)
e.writeInt32(0)
return nil
}
start := d.readIndex()
switch d.opcode {
case scanBeginLiteral:
d.scanWhile(scanContinue)
literal := d.data[start:d.readIndex()]
if d.opcode == scanListType { // TAG_X_Array
//listType := literal[0]
break
}
if d.opcode != scanListValue { // TAG_List<TAG_String>
panic(phasePanicMsg)
}
// TODO
parseLiteral(literal)
fallthrough
case scanBeginList: // TAG_List<TAG_List>
// We don't know the length of the List,
// so we read them into a buffer and count.
var buf bytes.Buffer
e2 := NewEncoder(&buf)
var count int
for {
d.scanWhile(scanSkipSpace)
if d.opcode == scanEndValue {
break
}
if d.opcode == scanListValue {
// TODO
e2.writeInt32(0)
}
}
e.writeListHeader(TagList, tagName, count)
e.w.Write(buf.Bytes())
case scanBeginCompound: // TAG_List<TAG_Compound>
}
return nil
}
// readIndex returns the position of the last byte read.
func (d *decodeState) readIndex() int {
return d.off - 1
}
// scanNext processes the byte at d.data[d.off].
func (d *decodeState) scanNext() {
if d.off < len(d.data) {
d.opcode = d.scan.step(d.data[d.off])
d.off++
} else {
//d.opcode = d.scan.eof()
d.off = len(d.data) + 1 // mark processed EOF with len+1
}
}
// scanWhile processes bytes in d.data[d.off:] until it
// receives a scan code not equal to op.
func (d *decodeState) scanWhile(op int) {
s, data, i := &d.scan, d.data, d.off
for i < len(data) {
newOp := s.step(data[i])
i++
if newOp != op {
d.opcode = newOp
d.off = i
return
}
}
d.off = len(data) + 1 // mark processed EOF with len+1
d.opcode = d.scan.eof()
}
// parseLiteral parse an SNBT literal, might be
// TAG_String, TAG_Int, TAG_Float, ... etc.
// so returned value is one of string, int32, float32 ...
func parseLiteral(literal []byte) interface{} {
switch literal[0] {
case '"', '\'': // Quoted String
var sb strings.Builder
sb.Grow(len(literal) - 2)
for i := 1; ; i++ {
c := literal[i]
switch c {
case literal[0]:
return sb.String()
case '\\':
i++
c = literal[i]
}
sb.WriteByte(c)
}
default:
strlen := len(literal)
integer := true
number := true
unqstr := true
var numberType byte
for i, c := range literal {
if isNumber(c) {
continue
} else if integer {
if i == strlen-1 && isIntegerType(c) {
numberType = c
strlen--
} else if i > 0 || i == 0 && c != '-' {
integer = false
if i == 0 || c != '.' {
number = false
}
}
} else if number {
if i == strlen-1 && isFloatType(c) {
numberType = c
} else {
number = false
}
} else if !isAllowedInUnquotedString(c) {
unqstr = false
}
}
if integer {
num, err := strconv.ParseInt(string(literal[:strlen]), 10, 64)
if err != nil {
panic(err)
}
switch numberType {
case 'B', 'b':
return int8(num)
case 'S', 's':
return int16(num)
default:
return int32(num)
case 'L', 'l':
return num
case 'F', 'f':
return float32(num)
case 'D', 'd':
return float64(num)
}
} else if number {
num, err := strconv.ParseFloat(string(literal[:strlen-1]), 64)
if err != nil {
panic(err)
}
switch numberType {
case 'F', 'f':
return float32(num)
case 'D', 'd':
fallthrough
default:
return num
}
} else if unqstr {
return string(literal)
}
}
panic(phasePanicMsg)
}
func isIntegerType(c byte) bool {
return isFloatType(c) ||
c == 'B' || c == 'b' ||
c == 's' || c == 'S' ||
c == 'L' || c == 'l'
}
func isFloatType(c byte) bool {
return c == 'F' || c == 'f' || c == 'D' || c == 'd'
}