Scan compound

This commit is contained in:
Tnze
2021-05-25 12:49:57 +08:00
parent 6846e10bb6
commit 0c0459d69d
2 changed files with 231 additions and 34 deletions

View File

@ -1,5 +1,9 @@
package nbt package nbt
import (
"errors"
)
type token int type token int
const ( const (
@ -32,18 +36,33 @@ const (
) )
const ( const (
scanContinue = iota scanContinue = iota // uninteresting byte
scanSkipSpace scanBeginCompound // begin compound (after left-brace )
scanCompoundTagName // just finished read tag name (before colon)
scanCompoundValue // just finished read value (before comma or right-brace )
scanSkipSpace // space byte; can skip; known to be last "continue" result
scanEndValue scanEndValue
scanEnd scanEnd
scanError scanError
) )
// These values are stored in the parseState stack.
// They give the current state of a composite value
// being scanned. If the parser is inside a nested value
// the parseState describes the nested state, outermost at entry 0.
const (
parseCompoundName = iota // parsing tag name (before colon)
parseCompoundValue // parsing value (after colon)
parseListValue // parsing list
)
const maxNestingDepth = 10000 const maxNestingDepth = 10000
type scanner struct { type scanner struct {
step func(c byte) int step func(c byte) int
parseState []int parseState []int
err error
} }
// reset prepares the scanner for use. // reset prepares the scanner for use.
@ -82,30 +101,134 @@ func (s *scanner) popParseState() {
func (s *scanner) stateEndTop(c byte) int { func (s *scanner) stateEndTop(c byte) int {
if !isSpace(c) { if !isSpace(c) {
// Complain about non-space byte on next call. // Complain about non-space byte on next call.
//s.error(c, "after top-level value") s.error(c, "after top-level value")
} }
return scanEnd return scanEnd
} }
func (s *scanner) stateBeginValue(c byte) int { func (s *scanner) stateBeginValue(c byte) int {
if isSpace(c) {
return scanSkipSpace
}
switch c { switch c {
case '{': // beginning of TAG_Compound case '{': // beginning of TAG_Compound
s.step = s.stateCompound s.step = s.stateCompoundOrEmpty
return s.pushParseState(c, parseCompoundName, scanBeginCompound)
case '[': // beginning of TAG_List case '[': // beginning of TAG_List
s.step = s.stateList s.step = s.stateList
case '"', '\'': // beginning of TAG_String case '"', '\'': // beginning of TAG_String
s.step = s.stateBeginString
return scanContinue
case '-': // beginning of negative number
s.step = s.stateNeg
return scanContinue
default: default:
if c >= '0' && c <= '9' { if isNumber(c) {
s.step = s.stateNum1 s.step = s.stateNum1
return scanContinue return scanContinue
} }
} }
return scanError return s.error(c, "looking for beginning of value")
}
func (s *scanner) stateCompoundOrEmpty(c byte) int {
if isSpace(c) {
return scanSkipSpace
}
if c == '}' {
n := len(s.parseState)
s.parseState[n-1] = parseCompoundValue
return s.stateEndValue(c)
}
return s.stateBeginString(c)
}
func (s *scanner) stateBeginString(c byte) int {
switch c {
case '\'':
s.step = s.stateInSqString
return scanContinue
case '"':
s.step = s.stateInDqString
return scanContinue
default:
if isNumOrLetter(c) {
s.step = s.stateInPureString
return scanContinue
}
}
return s.error(c, "looking for beginning of tag name string")
}
func (s *scanner) stateInSqString(c byte) int {
if c == '\\' {
s.step = s.stateInSqStringEsc
return scanContinue
}
if c == '\'' {
s.step = s.stateEndValue
return scanContinue
}
if isNumOrLetter(c) {
return scanContinue
}
return s.stateEndValue(c)
}
func (s *scanner) stateInSqStringEsc(c byte) int {
switch c {
case 'b', 'f', 'n', 'r', 't', '\\', '/', '\'':
s.step = s.stateInSqString
return scanContinue
}
return s.error(c, "in string escape code")
}
func (s *scanner) stateInDqString(c byte) int {
if c == '\\' {
s.step = s.stateInDqStringEsc
return scanContinue
}
if c == '"' {
s.step = s.stateEndValue
return scanContinue
}
if isNumOrLetter(c) {
return scanContinue
}
return s.stateEndValue(c)
}
func (s *scanner) stateInDqStringEsc(c byte) int {
switch c {
case 'b', 'f', 'n', 'r', 't', '\\', '/', '"':
s.step = s.stateInDqString
return scanContinue
}
return s.error(c, "in string escape code")
}
func (s *scanner) stateInPureString(c byte) int {
if isNumOrLetter(c) {
return scanContinue
}
return s.stateEndValue(c)
}
func (s *scanner) stateList(c byte) int {
return s.error(c, "not implemented")
}
func (s *scanner) stateNeg(c byte) int {
if !isNumber(c) {
s.error(c, "not a number after '-'")
}
s.step = s.stateNum1
return scanContinue
} }
func (s *scanner) stateNum1(c byte) int { func (s *scanner) stateNum1(c byte) int {
if c >= '0' || c <= '9' { if isNumber(c) {
s.step = s.stateNum1 s.step = s.stateNum1
return scanContinue return scanContinue
} }
@ -113,37 +236,29 @@ func (s *scanner) stateNum1(c byte) int {
s.step = s.stateNumDot s.step = s.stateNumDot
return scanContinue return scanContinue
} }
return s.stateEndValue(c) return s.stateEndNumValue(c)
} }
// stateDot is the state after reading the integer and decimal point in a number, // stateDot is the state after reading the integer and decimal point in a number,
// such as after reading `1.`. // such as after reading `1.`.
func (s *scanner) stateNumDot(c byte) int { func (s *scanner) stateNumDot(c byte) int {
if c >= '0' || c <= '9' { if isNumber(c) {
s.step = s.stateNumDot0 s.step = s.stateNumDot0
return scanContinue return scanContinue
} }
return scanError return s.error(c, "after decimal point in numeric literal")
} }
// stateNumDot0 is the state after reading the integer, decimal point, and subsequent // stateNumDot0 is the state after reading the integer, decimal point, and subsequent
// digits of a number, such as after reading `3.14`. // digits of a number, such as after reading `3.14`.
func (s *scanner) stateNumDot0(c byte) int { func (s *scanner) stateNumDot0(c byte) int {
if c >= '0' || c <= '9' { if isNumber(c) {
s.step = s.stateNumDot0 s.step = s.stateNumDot0
return scanContinue return scanContinue
} }
return s.stateEndNumDotValue(c) return s.stateEndNumDotValue(c)
} }
func (s *scanner) stateCompound(c byte) int {
return s.error(c, "not implemented")
}
func (s *scanner) stateList(c byte) int {
return s.error(c, "not implemented")
}
func (s *scanner) stateEndNumValue(c byte) int { func (s *scanner) stateEndNumValue(c byte) int {
if isSpace(c) { if isSpace(c) {
s.step = s.stateEndValue s.step = s.stateEndValue
@ -152,13 +267,13 @@ func (s *scanner) stateEndNumValue(c byte) int {
switch c { switch c {
case 'b', 'B': // TAG_Byte case 'b', 'B': // TAG_Byte
s.step = s.stateEndValue s.step = s.stateEndValue
return scanSkipSpace return scanContinue
case 's', 'S': // TAG_Short case 's', 'S': // TAG_Short
s.step = s.stateEndValue s.step = s.stateEndValue
return scanSkipSpace return scanContinue
case 'l', 'L': // TAG_Long case 'l', 'L': // TAG_Long
s.step = s.stateEndValue s.step = s.stateEndValue
return scanSkipSpace return scanContinue
case 'f', 'F', 'd', 'D': case 'f', 'F', 'd', 'D':
return s.stateEndNumDotValue(c) return s.stateEndNumDotValue(c)
} }
@ -169,20 +284,51 @@ func (s *scanner) stateEndNumDotValue(c byte) int {
switch c { switch c {
case 'f', 'F': // TAG_Float case 'f', 'F': // TAG_Float
s.step = s.stateEndValue s.step = s.stateEndValue
return scanSkipSpace return scanContinue
case 'd', 'D': // TAG_Double case 'd', 'D': // TAG_Double
s.step = s.stateEndValue s.step = s.stateEndValue
return scanSkipSpace return scanContinue
} }
return s.stateEndValue(c) return s.stateEndValue(c)
} }
func (s *scanner) stateEndValue(c byte) int { func (s *scanner) stateEndValue(c byte) int {
n := len(s.parseState)
if n == 0 {
// Completed top-level before the current byte.
s.step = s.stateEndTop
//s.endTop = true
return s.stateEndTop(c)
}
if isSpace(c) {
s.step = s.stateEndValue
return scanSkipSpace
}
ps := s.parseState[n-1]
switch ps {
case parseCompoundName:
if c == ':' {
s.parseState[n-1] = parseCompoundValue
s.step = s.stateBeginValue
return scanCompoundTagName
}
return s.error(c, "after object key")
case parseCompoundValue:
switch c {
case ',':
s.step = s.stateBeginString
return scanCompoundValue
case '}':
s.popParseState()
return scanEndValue
}
}
return s.error(c, "not implemented") return s.error(c, "not implemented")
} }
func (s *scanner) error(c byte, context string) int { func (s *scanner) error(c byte, context string) int {
s.step = s.stateError s.step = s.stateError
s.err = errors.New(context)
return scanError return scanError
} }
@ -195,3 +341,17 @@ func (s *scanner) stateError(c byte) int {
func isSpace(c byte) bool { func isSpace(c byte) bool {
return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n') return c <= ' ' && (c == ' ' || c == '\t' || c == '\r' || c == '\n')
} }
func isNumber(c byte) bool {
if c >= '0' && c <= '9' {
return true
}
return false
}
func isNumOrLetter(c byte) bool {
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || isNumber(c) {
return true
}
return false
}

View File

@ -2,22 +2,59 @@ package nbt
import "testing" import "testing"
func TestSNBT(t *testing.T) { func TestSNBT_number(t *testing.T) {
var s scanner goods := []string{
for _, str := range []string{
"0", "1234567890", "3.1415926", "0", "1234567890", "3.1415926",
"-0", "-1234567890", "-3.1415926",
"255B", "1234s", "6666L", "255B", "1234s", "6666L",
"314F", "3.14f", "3.14159265358979323846264D", "314F", "3.14f", "3.14159265358979323846264D",
} { }
bads := []string{
".0", "1234.5678.90",
"25-5B", "1234.s",
}
var s scanner
scan := func(str string) bool {
s.reset() s.reset()
var scanCodes []int
for _, c := range []byte(str) { for _, c := range []byte(str) {
res := s.step(c) res := s.step(c)
if res == scanError { if res == scanError {
t.Errorf("scan error") return false
} }
scanCodes = append(scanCodes, res)
} }
t.Logf("scancodes: %v", scanCodes) return true
}
for _, str := range goods {
if scan(str) == false {
t.Errorf("scan valid data %q error: %v", str, s.err)
}
}
for _, str := range bads {
if scan(str) {
t.Errorf("scan invalid data %q success", str)
}
}
}
func TestSNBT_compound(t *testing.T) {
goods := []string{
`{}`, `{name:3.14f}`, `{ "name" : 12345 }`,
`{ abc: {}}`,
}
var s scanner
scan := func(str string) bool {
s.reset()
for _, c := range []byte(str) {
res := s.step(c)
if res == scanError {
return false
}
}
return true
}
for _, str := range goods {
if scan(str) == false {
t.Errorf("scan valid data %q error: %v", str, s.err)
}
} }
} }