json: initial scanner implementation
Currently lacking correct support for unicode text segmentation into grapheme clusters, so it miscounts "Column" in positions. This will be addressed later.
This commit is contained in:
parent
b9183e85e4
commit
b5ce4360cd
8
zcl/json/doc.go
Normal file
8
zcl/json/doc.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Package json is the JSON parser for ZCL. It parses JSON files and returns
|
||||||
|
// implementations of the core ZCL structural interfaces in terms of the
|
||||||
|
// JSON data inside.
|
||||||
|
//
|
||||||
|
// This is not a generic JSON parser. Instead, it deals with the mapping from
|
||||||
|
// the JSON information model to the ZCL information model, using a number
|
||||||
|
// of hard-coded structural conventions.
|
||||||
|
package json
|
281
zcl/json/scanner.go
Normal file
281
zcl/json/scanner.go
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-zcl/zcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type tokenType scanner.go
|
||||||
|
type tokenType rune
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokenBraceO tokenType = '{'
|
||||||
|
tokenBraceC tokenType = '}'
|
||||||
|
tokenBrackO tokenType = '['
|
||||||
|
tokenBrackC tokenType = ']'
|
||||||
|
tokenComma tokenType = ','
|
||||||
|
tokenColon tokenType = ':'
|
||||||
|
tokenKeyword tokenType = 'K'
|
||||||
|
tokenString tokenType = 'S'
|
||||||
|
tokenNumber tokenType = 'N'
|
||||||
|
tokenInvalid tokenType = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
Type tokenType
|
||||||
|
Bytes []byte
|
||||||
|
Range zcl.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
// scan returns the primary tokens for the given JSON buffer in sequence.
|
||||||
|
//
|
||||||
|
// The responsibility of this pass is to just mark the slices of the buffer
|
||||||
|
// as being of various types. It is lax in how it interprets the multi-byte
|
||||||
|
// token types keyword, string and number, preferring to capture erroneous
|
||||||
|
// extra bytes that we presume the user intended to be part of the token
|
||||||
|
// so that we can generate more helpful diagnostics in the parser.
|
||||||
|
func scan(buf []byte, start pos) []token {
|
||||||
|
var tokens []token
|
||||||
|
p := start
|
||||||
|
for {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, p = skipWhitespace(buf, p)
|
||||||
|
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
start = p
|
||||||
|
|
||||||
|
first := buf[0]
|
||||||
|
switch {
|
||||||
|
case first == '{' || first == '}' || first == '[' || first == ']' || first == ',' || first == ':':
|
||||||
|
p.Pos.Column++
|
||||||
|
p.Pos.Byte++
|
||||||
|
tokens = append(tokens, token{
|
||||||
|
Type: tokenType(first),
|
||||||
|
Bytes: buf[0:1],
|
||||||
|
Range: posRange(start, p),
|
||||||
|
})
|
||||||
|
buf = buf[1:]
|
||||||
|
case first == '"':
|
||||||
|
var tokBuf []byte
|
||||||
|
tokBuf, buf, p = scanString(buf, p)
|
||||||
|
tokens = append(tokens, token{
|
||||||
|
Type: tokenString,
|
||||||
|
Bytes: tokBuf,
|
||||||
|
Range: posRange(start, p),
|
||||||
|
})
|
||||||
|
case byteCanStartNumber(first):
|
||||||
|
var tokBuf []byte
|
||||||
|
tokBuf, buf, p = scanNumber(buf, p)
|
||||||
|
tokens = append(tokens, token{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: tokBuf,
|
||||||
|
Range: posRange(start, p),
|
||||||
|
})
|
||||||
|
case byteCanStartKeyword(first):
|
||||||
|
var tokBuf []byte
|
||||||
|
tokBuf, buf, p = scanKeyword(buf, p)
|
||||||
|
tokens = append(tokens, token{
|
||||||
|
Type: tokenKeyword,
|
||||||
|
Bytes: tokBuf,
|
||||||
|
Range: posRange(start, p),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
tokens = append(tokens, token{
|
||||||
|
Type: tokenInvalid,
|
||||||
|
Bytes: buf[:1],
|
||||||
|
Range: start.Range(1, 1),
|
||||||
|
})
|
||||||
|
// If we've encountered an invalid then we might as well stop
|
||||||
|
// scanning since the parser won't proceed beyond this point.
|
||||||
|
return tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteCanStartNumber(b byte) bool {
|
||||||
|
switch b {
|
||||||
|
// We are slightly more tolerant than JSON requires here since we
|
||||||
|
// expect the parser will make a stricter interpretation of the
|
||||||
|
// number bytes, but we specifically don't allow 'e' or 'E' here
|
||||||
|
// since we want the scanner to treat that as the start of an
|
||||||
|
// invalid keyword instead, to produce more intelligible error messages.
|
||||||
|
case '-', '+', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanNumber(buf []byte, start pos) ([]byte, []byte, pos) {
|
||||||
|
// The scanner doesn't check that the sequence of digit-ish bytes is
|
||||||
|
// in a valid order. The parser must do this when decoding a number
|
||||||
|
// token.
|
||||||
|
var i int
|
||||||
|
p := start
|
||||||
|
Byte:
|
||||||
|
for i = 0; i < len(buf); i++ {
|
||||||
|
switch buf[i] {
|
||||||
|
case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
default:
|
||||||
|
break Byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf[:i], buf[i:], p
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteCanStartKeyword(b byte) bool {
|
||||||
|
switch {
|
||||||
|
// We allow any sequence of alphabetical characters here, even though
|
||||||
|
// JSON is more constrained, so that we can collect what we presume
|
||||||
|
// the user intended to be a single keyword and then check its validity
|
||||||
|
// in the parser, where we can generate better diagnostics.
|
||||||
|
// So e.g. we want to be able to say:
|
||||||
|
// unrecognized keyword "True". Did you mean "true"?
|
||||||
|
case b >= 'a' || b <= 'z' || b >= 'A' || b <= 'Z':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanKeyword(buf []byte, start pos) ([]byte, []byte, pos) {
|
||||||
|
var i int
|
||||||
|
p := start
|
||||||
|
Byte:
|
||||||
|
for i = 0; i < len(buf); i++ {
|
||||||
|
b := buf[i]
|
||||||
|
switch {
|
||||||
|
case b >= 'a' || b <= 'z' || b >= 'A' || b <= 'Z' || b == '_':
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
default:
|
||||||
|
break Byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf[:i], buf[i:], p
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanString(buf []byte, start pos) ([]byte, []byte, pos) {
|
||||||
|
// The scanner doesn't validate correct use of escapes, etc. It pays
|
||||||
|
// attention to escapes only for the purpose of identifying the closing
|
||||||
|
// quote character. It's the parser's responsibility to do proper
|
||||||
|
// validation.
|
||||||
|
//
|
||||||
|
// The scanner also doesn't specifically detect unterminated string
|
||||||
|
// literals, though they can be identified in the parser by checking if
|
||||||
|
// the final byte in a string token is the double-quote character.
|
||||||
|
|
||||||
|
// Skip the opening quote symbol
|
||||||
|
i := 1
|
||||||
|
p := start
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
escaping := false
|
||||||
|
Byte:
|
||||||
|
for i < len(buf) {
|
||||||
|
b := buf[i]
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case b == '\\':
|
||||||
|
escaping = !escaping
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
i++
|
||||||
|
case b == '"':
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
i++
|
||||||
|
if !escaping {
|
||||||
|
break Byte
|
||||||
|
}
|
||||||
|
escaping = false
|
||||||
|
case b < 32:
|
||||||
|
break Byte
|
||||||
|
default:
|
||||||
|
// TODO: Use Unicode Text Segmentation spec to advance
|
||||||
|
// Column only once per grapheme cluster, rather than once per
|
||||||
|
// byte.
|
||||||
|
// Consume one or more UTF-8 codepoints that together form
|
||||||
|
// a single grapheme cluster.
|
||||||
|
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
i++
|
||||||
|
|
||||||
|
escaping = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf[:i], buf[i:], p
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipWhitespace(buf []byte, start pos) ([]byte, pos) {
|
||||||
|
var i int
|
||||||
|
p := start
|
||||||
|
Byte:
|
||||||
|
for i = 0; i < len(buf); i++ {
|
||||||
|
switch buf[i] {
|
||||||
|
case ' ':
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column++
|
||||||
|
case '\n':
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column = 1
|
||||||
|
p.Pos.Line++
|
||||||
|
case '\r':
|
||||||
|
// For the purpose of line/column counting we consider a
|
||||||
|
// carriage return to take up no space, assuming that it will
|
||||||
|
// be paired up with a newline (on Windows, for example) that
|
||||||
|
// will account for both of them.
|
||||||
|
p.Pos.Byte++
|
||||||
|
case '\t':
|
||||||
|
// We arbitrarily count a tab as if it were two spaces, because
|
||||||
|
// we need to choose _some_ number here. This means any system
|
||||||
|
// that renders code on-screen with markers must itself treat
|
||||||
|
// tabs as a pair of spaces for rendering purposes, or instead
|
||||||
|
// use the byte offset and back into its own column position.
|
||||||
|
p.Pos.Byte++
|
||||||
|
p.Pos.Column += 2
|
||||||
|
default:
|
||||||
|
break Byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf[i:], p
|
||||||
|
}
|
||||||
|
|
||||||
|
type pos struct {
|
||||||
|
Filename string
|
||||||
|
Pos zcl.Pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pos) Range(byteLen, charLen int) zcl.Range {
|
||||||
|
start := p.Pos
|
||||||
|
end := p.Pos
|
||||||
|
end.Byte += byteLen
|
||||||
|
end.Column += charLen
|
||||||
|
return zcl.Range{
|
||||||
|
Filename: p.Filename,
|
||||||
|
Start: start,
|
||||||
|
End: end,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func posRange(start, end pos) zcl.Range {
|
||||||
|
return zcl.Range{
|
||||||
|
Filename: start.Filename,
|
||||||
|
Start: start.Pos,
|
||||||
|
End: end.Pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t token) GoString() string {
|
||||||
|
return fmt.Sprintf("json.token{json.%s, []byte(%q), %#v}", t.Type, t.Bytes, t.Range)
|
||||||
|
}
|
462
zcl/json/scanner_test.go
Normal file
462
zcl/json/scanner_test.go
Normal file
@ -0,0 +1,462 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-zcl/zcl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScan(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Input string
|
||||||
|
Want []token
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
``,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`{}`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenBraceO,
|
||||||
|
Bytes: []byte(`{`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenBraceC,
|
||||||
|
Bytes: []byte(`}`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`][`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenBrackC,
|
||||||
|
Bytes: []byte(`]`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenBrackO,
|
||||||
|
Bytes: []byte(`[`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`:,`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenColon,
|
||||||
|
Bytes: []byte(`:`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenComma,
|
||||||
|
Bytes: []byte(`,`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`1`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`1`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
` 1`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`1`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 3,
|
||||||
|
Line: 1,
|
||||||
|
Column: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
` 12`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`12`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 4,
|
||||||
|
Line: 1,
|
||||||
|
Column: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`1 2`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`1`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 1,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`2`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 3,
|
||||||
|
Line: 1,
|
||||||
|
Column: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"\n1\n 2",
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`1`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 1,
|
||||||
|
Line: 2,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 2,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`2`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 4,
|
||||||
|
Line: 3,
|
||||||
|
Column: 2,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 5,
|
||||||
|
Line: 3,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`-1 2.5`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`-1`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`2.5`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 3,
|
||||||
|
Line: 1,
|
||||||
|
Column: 4,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 6,
|
||||||
|
Line: 1,
|
||||||
|
Column: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`true`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenKeyword,
|
||||||
|
Bytes: []byte(`true`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 4,
|
||||||
|
Line: 1,
|
||||||
|
Column: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`""`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenString,
|
||||||
|
Bytes: []byte(`""`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 2,
|
||||||
|
Line: 1,
|
||||||
|
Column: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`"hello"`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenString,
|
||||||
|
Bytes: []byte(`"hello"`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 7,
|
||||||
|
Line: 1,
|
||||||
|
Column: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`"he\"llo"`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenString,
|
||||||
|
Bytes: []byte(`"he\"llo"`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 9,
|
||||||
|
Line: 1,
|
||||||
|
Column: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`"hello\\" 1`,
|
||||||
|
[]token{
|
||||||
|
{
|
||||||
|
Type: tokenString,
|
||||||
|
Bytes: []byte(`"hello\\"`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 9,
|
||||||
|
Line: 1,
|
||||||
|
Column: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: tokenNumber,
|
||||||
|
Bytes: []byte(`1`),
|
||||||
|
Range: zcl.Range{
|
||||||
|
Start: zcl.Pos{
|
||||||
|
Byte: 10,
|
||||||
|
Line: 1,
|
||||||
|
Column: 11,
|
||||||
|
},
|
||||||
|
End: zcl.Pos{
|
||||||
|
Byte: 11,
|
||||||
|
Line: 1,
|
||||||
|
Column: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.Input, func(t *testing.T) {
|
||||||
|
buf := []byte(test.Input)
|
||||||
|
start := pos{
|
||||||
|
Filename: "",
|
||||||
|
Pos: zcl.Pos{
|
||||||
|
Byte: 0,
|
||||||
|
Line: 1,
|
||||||
|
Column: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
got := scan(buf, start)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.Want) {
|
||||||
|
errMsg := &bytes.Buffer{}
|
||||||
|
errMsg.WriteString("wrong result\ngot:\n")
|
||||||
|
if len(got) == 0 {
|
||||||
|
errMsg.WriteString(" (empty slice)\n")
|
||||||
|
}
|
||||||
|
for _, tok := range got {
|
||||||
|
fmt.Fprintf(errMsg, " - %#v\n", tok)
|
||||||
|
}
|
||||||
|
errMsg.WriteString("want:\n")
|
||||||
|
if len(test.Want) == 0 {
|
||||||
|
errMsg.WriteString(" (empty slice)\n")
|
||||||
|
}
|
||||||
|
for _, tok := range test.Want {
|
||||||
|
fmt.Fprintf(errMsg, " - %#v\n", tok)
|
||||||
|
}
|
||||||
|
t.Error(errMsg.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
58
zcl/json/tokentype_string.go
Normal file
58
zcl/json/tokentype_string.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Code generated by "stringer -type tokenType scanner.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const (
|
||||||
|
_tokenType_name_0 = "tokenInvalid"
|
||||||
|
_tokenType_name_1 = "tokenComma"
|
||||||
|
_tokenType_name_2 = "tokenColon"
|
||||||
|
_tokenType_name_3 = "tokenKeyword"
|
||||||
|
_tokenType_name_4 = "tokenNumber"
|
||||||
|
_tokenType_name_5 = "tokenString"
|
||||||
|
_tokenType_name_6 = "tokenBrackO"
|
||||||
|
_tokenType_name_7 = "tokenBrackC"
|
||||||
|
_tokenType_name_8 = "tokenBraceO"
|
||||||
|
_tokenType_name_9 = "tokenBraceC"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_tokenType_index_0 = [...]uint8{0, 12}
|
||||||
|
_tokenType_index_1 = [...]uint8{0, 10}
|
||||||
|
_tokenType_index_2 = [...]uint8{0, 10}
|
||||||
|
_tokenType_index_3 = [...]uint8{0, 12}
|
||||||
|
_tokenType_index_4 = [...]uint8{0, 11}
|
||||||
|
_tokenType_index_5 = [...]uint8{0, 11}
|
||||||
|
_tokenType_index_6 = [...]uint8{0, 11}
|
||||||
|
_tokenType_index_7 = [...]uint8{0, 11}
|
||||||
|
_tokenType_index_8 = [...]uint8{0, 11}
|
||||||
|
_tokenType_index_9 = [...]uint8{0, 11}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i tokenType) String() string {
|
||||||
|
switch {
|
||||||
|
case i == 0:
|
||||||
|
return _tokenType_name_0
|
||||||
|
case i == 44:
|
||||||
|
return _tokenType_name_1
|
||||||
|
case i == 58:
|
||||||
|
return _tokenType_name_2
|
||||||
|
case i == 75:
|
||||||
|
return _tokenType_name_3
|
||||||
|
case i == 78:
|
||||||
|
return _tokenType_name_4
|
||||||
|
case i == 83:
|
||||||
|
return _tokenType_name_5
|
||||||
|
case i == 91:
|
||||||
|
return _tokenType_name_6
|
||||||
|
case i == 93:
|
||||||
|
return _tokenType_name_7
|
||||||
|
case i == 123:
|
||||||
|
return _tokenType_name_8
|
||||||
|
case i == 125:
|
||||||
|
return _tokenType_name_9
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("tokenType(%d)", i)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user