146 lines
3.1 KiB
Go
146 lines
3.1 KiB
Go
package nbt
|
|
|
|
import (
|
|
"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:
|
|
panic("not implemented")
|
|
}
|
|
}
|
|
|
|
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))
|
|
}
|
|
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)
|
|
}
|
|
d.scanWhile(scanSkipSpace)
|
|
writeLiteral(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
|
|
}
|
|
|
|
// 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
|
|
for i := 1; ; i++ {
|
|
c := literal[i]
|
|
switch c {
|
|
case literal[0]:
|
|
return sb.String()
|
|
case '\\':
|
|
i++
|
|
c = literal[i]
|
|
}
|
|
sb.WriteByte(c)
|
|
}
|
|
default:
|
|
|
|
}
|
|
panic(phasePanicMsg)
|
|
}
|