You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fx/internal/jsonx/json.go

387 lines
7.3 KiB
Go

package jsonx
import (
"fmt"
"strconv"
"strings"
"unicode/utf8"
"github.com/antonmedv/fx/internal/utils"
)
type jsonParser struct {
data []byte
end int
lastChar byte
lineNumber uint
sourceTail *ring
depth uint8
skipFirstIdent bool
}
func Parse(data []byte) (head *Node, err error) {
p := &jsonParser{
data: data,
lineNumber: 1,
sourceTail: &ring{},
}
defer func() {
if r := recover(); r != nil {
err = p.errorSnippet(fmt.Sprintf("%v", r))
}
}()
p.next()
var next *Node
for p.lastChar != 0 {
value := p.parseValue()
if head == nil {
head = value
next = head
} else {
value.Index = -1
next.adjacent(value)
next = value
}
}
return
}
func (p *jsonParser) next() {
if p.end < len(p.data) {
p.lastChar = p.data[p.end]
p.end++
} else {
p.lastChar = 0
}
if p.lastChar == '\n' {
p.lineNumber++
}
p.sourceTail.writeByte(p.lastChar)
}
func (p *jsonParser) parseValue() *Node {
p.skipWhitespace()
var l *Node
switch p.lastChar {
case '"':
l = p.parseString()
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-':
l = p.parseNumber()
case '{':
l = p.parseObject()
case '[':
l = p.parseArray()
case 't':
l = p.parseKeyword("true")
case 'f':
l = p.parseKeyword("false")
case 'n':
l = p.parseKeyword("null")
default:
panic(fmt.Sprintf("Unexpected character %q", p.lastChar))
}
p.skipWhitespace()
return l
}
func (p *jsonParser) parseString() *Node {
str := &Node{Depth: p.depth}
start := p.end - 1
p.next()
escaped := false
for {
if escaped {
switch p.lastChar {
case 'u':
var unicode string
for i := 0; i < 4; i++ {
p.next()
if !utils.IsHexDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s%c'", unicode, p.lastChar))
}
unicode += string(p.lastChar)
}
_, err := strconv.ParseInt(unicode, 16, 32)
if err != nil {
panic(fmt.Sprintf("Invalid Unicode escape sequence '\\u%s'", unicode))
}
case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
default:
panic(fmt.Sprintf("Invalid escape sequence '\\%c'", p.lastChar))
}
escaped = false
} else if p.lastChar == '\\' {
escaped = true
} else if p.lastChar == '"' {
break
} else if p.lastChar == 0 {
panic("Unexpected end of input in string")
} else if p.lastChar < 0x1F {
panic(fmt.Sprintf("Invalid character %q in string", p.lastChar))
}
p.next()
}
str.Value = p.data[start:p.end]
p.next()
return str
}
func (p *jsonParser) parseNumber() *Node {
num := &Node{Depth: p.depth}
start := p.end - 1
// Handle negative numbers
if p.lastChar == '-' {
p.next()
if !utils.IsDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
}
}
// Leading zero
if p.lastChar == '0' {
p.next()
} else {
for utils.IsDigit(p.lastChar) {
p.next()
}
}
// Decimal portion
if p.lastChar == '.' {
p.next()
if !utils.IsDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
}
for utils.IsDigit(p.lastChar) {
p.next()
}
}
// Exponent
if p.lastChar == 'e' || p.lastChar == 'E' {
p.next()
if p.lastChar == '+' || p.lastChar == '-' {
p.next()
}
if !utils.IsDigit(p.lastChar) {
panic(fmt.Sprintf("Invalid character %q in number", p.lastChar))
}
for utils.IsDigit(p.lastChar) {
p.next()
}
}
num.Value = p.data[start : p.end-1]
return num
}
func (p *jsonParser) parseObject() *Node {
object := &Node{Depth: p.depth}
object.Value = []byte{'{'}
p.next()
p.skipWhitespace()
// Empty object
if p.lastChar == '}' {
object.Value = append(object.Value, '}')
p.next()
return object
}
for {
// Expecting a key which should be a string
if p.lastChar != '"' {
panic(fmt.Sprintf("Expected object key to be a string, got %q", p.lastChar))
}
p.depth++
key := p.parseString()
key.Key, key.Value = key.Value, nil
object.Size += 1
key.directParent = object
p.skipWhitespace()
// Expecting colon after key
if p.lastChar != ':' {
panic(fmt.Sprintf("Expected colon after object key, got %q", p.lastChar))
}
p.next()
p.skipFirstIdent = true
value := p.parseValue()
p.depth--
key.Value = value.Value
key.Size = value.Size
key.Next = value.Next
if key.Next != nil {
key.Next.Prev = key
}
key.End = value.End
value.indirectParent = key
object.append(key)
p.skipWhitespace()
if p.lastChar == ',' {
object.End.Comma = true
p.next()
p.skipWhitespace()
if p.lastChar == '}' {
object.End.Comma = false
} else {
continue
}
}
if p.lastChar == '}' {
closeBracket := &Node{Depth: p.depth}
closeBracket.Value = []byte{'}'}
closeBracket.directParent = object
closeBracket.Index = -1
object.append(closeBracket)
p.next()
return object
}
panic(fmt.Sprintf("Unexpected character %q in object", p.lastChar))
}
}
func (p *jsonParser) parseArray() *Node {
arr := &Node{Depth: p.depth}
arr.Value = []byte{'['}
p.next()
p.skipWhitespace()
if p.lastChar == ']' {
arr.Value = append(arr.Value, ']')
p.next()
return arr
}
for i := 0; ; i++ {
p.depth++
value := p.parseValue()
value.directParent = arr
arr.Size += 1
value.Index = i
p.depth--
arr.append(value)
p.skipWhitespace()
if p.lastChar == ',' {
arr.End.Comma = true
p.next()
p.skipWhitespace()
if p.lastChar == ']' {
arr.End.Comma = false
} else {
continue
}
}
if p.lastChar == ']' {
closeBracket := &Node{Depth: p.depth}
closeBracket.Value = []byte{']'}
closeBracket.directParent = arr
closeBracket.Index = -1
arr.append(closeBracket)
p.next()
return arr
}
panic(fmt.Sprintf("Invalid character %q in array", p.lastChar))
}
}
func (p *jsonParser) parseKeyword(name string) *Node {
for i := 1; i < len(name); i++ {
p.next()
if p.lastChar != name[i] {
panic(fmt.Sprintf("Unexpected character %q in keyword", p.lastChar))
}
}
p.next()
nextCharIsSpecial := isWhitespace(p.lastChar) || p.lastChar == ',' || p.lastChar == '}' || p.lastChar == ']' || p.lastChar == 0
if nextCharIsSpecial {
keyword := &Node{Depth: p.depth}
keyword.Value = []byte(name)
return keyword
}
panic(fmt.Sprintf("Unexpected character %q in keyword", p.lastChar))
}
func isWhitespace(ch byte) bool {
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'
}
func (p *jsonParser) skipWhitespace() {
for {
switch p.lastChar {
case ' ', '\t', '\n', '\r':
p.next()
case '/':
p.skipComment()
default:
return
}
}
}
func (p *jsonParser) skipComment() {
p.next()
switch p.lastChar {
case '/':
for p.lastChar != '\n' && p.lastChar != 0 {
p.next()
}
case '*':
for {
p.next()
if p.lastChar == '*' {
p.next()
if p.lastChar == '/' {
p.next()
return
}
}
if p.lastChar == 0 {
panic("Unexpected end of input in comment")
}
}
default:
panic(fmt.Sprintf("Invalid comment: '/%c'", p.lastChar))
}
}
func (p *jsonParser) errorSnippet(message string) error {
lines := strings.Split(p.sourceTail.string(), "\n")
lastLineLen := utf8.RuneCountInString(lines[len(lines)-1])
pointer := strings.Repeat(".", lastLineLen-1) + "^"
lines = append(lines, pointer)
paddedLines := make([]string, len(lines))
for i, line := range lines {
paddedLines[i] = " " + line
}
return fmt.Errorf(
"%s on line %d.\n\n%s\n\n",
message,
p.lineNumber,
strings.Join(paddedLines, "\n"),
)
}