diff --git a/nbt/snbt_decode.go b/nbt/snbt_decode.go new file mode 100644 index 0000000..4971071 --- /dev/null +++ b/nbt/snbt_decode.go @@ -0,0 +1,92 @@ +package nbt + +import ( + "fmt" +) + +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() + d.scanWhile(scanSkipSpace) + switch d.opcode { + default: + panic(phasePanicMsg) + + case scanBeginLiteral: + + case scanBeginCompound: + panic("not implemented") + + case scanBeginList: + panic("not implemented") + } + return nil +} + +func writeLiteral(e *Encoder, d *decodeState) error { + start := d.readIndex() + d.scanNext() + d.scanWhile(scanContinue) + literal := d.data[start:d.readIndex()] + fmt.Printf("%d %d [%d]- %q\n", start, d.off, d.opcode, literal) + + switch literal[0] { + case '"', '\'': // TAG_String + str := literal // TODO: Parse string + e.writeTag(TagString, "") + e.writeInt16(int16(len(str))) + e.w.Write(str) + + default: + e.w.Write(literal) // TODO: Parse other literal + } + return nil +} + +func writeCompound(e *Encoder, d *decodeState) error { + e.writeTag(TagCompound, "") + 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() +} diff --git a/nbt/snbt_decode_test.go b/nbt/snbt_decode_test.go new file mode 100644 index 0000000..144a418 --- /dev/null +++ b/nbt/snbt_decode_test.go @@ -0,0 +1,16 @@ +package nbt + +import ( + "bytes" + "testing" +) + +func TestEncoder_WriteSNBT(t *testing.T) { + var buf bytes.Buffer + e := NewEncoder(&buf) + if err := e.WriteSNBT(`"12345"`); err != nil { + t.Fatal(err) + } + t.Log(buf.Bytes()) + +} diff --git a/nbt/snbt_scanner.go b/nbt/snbt_scanner.go index 01dd23c..5e26e8d 100644 --- a/nbt/snbt_scanner.go +++ b/nbt/snbt_scanner.go @@ -2,10 +2,12 @@ package nbt import ( "errors" + "sync" ) const ( scanContinue = iota // uninteresting byte + scanBeginLiteral // end implied by next result != scanContinue scanBeginCompound // begin TAG_Compound (after left-brace ) scanBeginList // begin TAG_List (after left-brack) scanListValue // just finished read list value @@ -35,6 +37,7 @@ type scanner struct { step func(c byte) int parseState []int err error + endTop bool } // reset prepares the scanner for use. @@ -44,6 +47,28 @@ func (s *scanner) reset() { s.parseState = s.parseState[0:0] } +var scannerPool = sync.Pool{ + New: func() interface{} { + return &scanner{} + }, +} + +func newScanner() *scanner { + scan := scannerPool.Get().(*scanner) + // scan.reset by design doesn't set bytes to zero + //scan.bytes = 0 + scan.reset() + return scan +} + +func freeScanner(scan *scanner) { + // Avoid hanging on to too much memory in extreme cases. + if len(scan.parseState) > 1024 { + scan.parseState = nil + } + scannerPool.Put(scan) +} + // pushParseState pushes a new parse state p onto the parse stack. // an error state is returned if maxNestingDepth was exceeded, otherwise successState is returned. func (s *scanner) pushParseState(c byte, newParseState int, successState int) int { @@ -67,6 +92,25 @@ func (s *scanner) popParseState() { } } +// eof tells the scanner that the end of input has been reached. +// It returns a scan status just as s.step does. +func (s *scanner) eof() int { + if s.err != nil { + return scanError + } + if s.endTop { + return scanEnd + } + s.step(' ') + if s.endTop { + return scanEnd + } + if s.err == nil { + s.err = errors.New("unexpected end of JSON input") + } + return scanError +} + // stateEndTop is the state after finishing the top-level value, // such as after reading `{}` or `[1,2,3]`. // Only space characters should be seen now. @@ -94,10 +138,11 @@ func (s *scanner) stateBeginValue(c byte) int { return s.stateBeginString(c) case '-': // beginning of negative number s.step = s.stateNeg - return scanContinue + return scanBeginLiteral default: if isNumber(c) { - return s.stateNum1(c) + s.stateNum0(c) + return scanBeginLiteral } if isAllowedInUnquotedString(c) { return s.stateBeginString(c) @@ -125,14 +170,14 @@ func (s *scanner) stateBeginString(c byte) int { switch c { case '\'': s.step = s.stateInSingleQuotedString - return scanContinue + return scanBeginLiteral case '"': s.step = s.stateInDoubleQuotedString - return scanContinue + return scanBeginLiteral default: if isAllowedInUnquotedString(c) { s.step = s.stateInUnquotedString - return scanContinue + return scanBeginLiteral } } return s.error(c, "looking for beginning of string") @@ -211,6 +256,18 @@ func (s *scanner) stateListOrArrayT(c byte) int { } func (s *scanner) stateNeg(c byte) int { + if isNumber(c) { + s.step = s.stateNum0 + return scanBeginLiteral + } + if isAllowedInUnquotedString(c) { + s.step = s.stateInUnquotedString + return scanBeginLiteral + } + return s.error(c, "not a number after '-'") +} + +func (s *scanner) stateNum0(c byte) int { if isNumber(c) { s.step = s.stateNum1 return scanContinue @@ -219,7 +276,7 @@ func (s *scanner) stateNeg(c byte) int { s.step = s.stateInUnquotedString return scanContinue } - return s.error(c, "not a number after '-'") + return s.stateEndNumValue(c) } func (s *scanner) stateNum1(c byte) int { @@ -304,7 +361,7 @@ func (s *scanner) stateEndValue(c byte) int { if n == 0 { // Completed top-level before the current byte. s.step = s.stateEndTop - //s.endTop = true + s.endTop = true return s.stateEndTop(c) } if isSpace(c) { diff --git a/nbt/snbt_scanner_test.go b/nbt/snbt_scanner_test.go index 4af3689..7cc09aa 100644 --- a/nbt/snbt_scanner_test.go +++ b/nbt/snbt_scanner_test.go @@ -5,6 +5,15 @@ import ( "testing" ) +func TestSNBT_checkScanCode(t *testing.T) { + t.SkipNow() + var s scanner + s.reset() + for _, c := range []byte(`{ "a b\"c": {}, def: 12345}`) { + t.Logf("[%c] - %d", c, s.step(c)) + } +} + func TestSNBT_number(t *testing.T) { goods := []string{ "0", "1234567890", "3.1415926",