mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-23 12:16:38 +00:00
66b77acb1c
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
316 lines
9.2 KiB
Go
316 lines
9.2 KiB
Go
/*
|
|
* Copyright 2021 ByteDance Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package ast
|
|
|
|
import (
|
|
`encoding/json`
|
|
|
|
`github.com/bytedance/sonic/internal/native/types`
|
|
)
|
|
|
|
// Visitor handles the callbacks during preorder traversal of a JSON AST.
|
|
//
|
|
// According to the JSON RFC8259, a JSON AST can be defined by
|
|
// the following rules without seperator / whitespace tokens.
|
|
//
|
|
// JSON-AST = value
|
|
// value = false / null / true / object / array / number / string
|
|
// object = begin-object [ member *( member ) ] end-object
|
|
// member = string value
|
|
// array = begin-array [ value *( value ) ] end-array
|
|
//
|
|
type Visitor interface {
|
|
|
|
// OnNull handles a JSON null value.
|
|
OnNull() error
|
|
|
|
// OnBool handles a JSON true / false value.
|
|
OnBool(v bool) error
|
|
|
|
// OnString handles a JSON string value.
|
|
OnString(v string) error
|
|
|
|
// OnInt64 handles a JSON number value with int64 type.
|
|
OnInt64(v int64, n json.Number) error
|
|
|
|
// OnFloat64 handles a JSON number value with float64 type.
|
|
OnFloat64(v float64, n json.Number) error
|
|
|
|
// OnObjectBegin handles the beginning of a JSON object value with a
|
|
// suggested capacity that can be used to make your custom object container.
|
|
//
|
|
// After this point the visitor will receive a sequence of callbacks like
|
|
// [string, value, string, value, ......, ObjectEnd].
|
|
//
|
|
// Note:
|
|
// 1. This is a recursive definition which means the value can
|
|
// also be a JSON object / array described by a sequence of callbacks.
|
|
// 2. The suggested capacity will be 0 if current object is empty.
|
|
// 3. Currently sonic use a fixed capacity for non-empty object (keep in
|
|
// sync with ast.Node) which might not be very suitable. This may be
|
|
// improved in future version.
|
|
OnObjectBegin(capacity int) error
|
|
|
|
// OnObjectKey handles a JSON object key string in member.
|
|
OnObjectKey(key string) error
|
|
|
|
// OnObjectEnd handles the ending of a JSON object value.
|
|
OnObjectEnd() error
|
|
|
|
// OnArrayBegin handles the beginning of a JSON array value with a
|
|
// suggested capacity that can be used to make your custom array container.
|
|
//
|
|
// After this point the visitor will receive a sequence of callbacks like
|
|
// [value, value, value, ......, ArrayEnd].
|
|
//
|
|
// Note:
|
|
// 1. This is a recursive definition which means the value can
|
|
// also be a JSON object / array described by a sequence of callbacks.
|
|
// 2. The suggested capacity will be 0 if current array is empty.
|
|
// 3. Currently sonic use a fixed capacity for non-empty array (keep in
|
|
// sync with ast.Node) which might not be very suitable. This may be
|
|
// improved in future version.
|
|
OnArrayBegin(capacity int) error
|
|
|
|
// OnArrayEnd handles the ending of a JSON array value.
|
|
OnArrayEnd() error
|
|
}
|
|
|
|
// VisitorOptions contains all Visitor's options. The default value is an
|
|
// empty VisitorOptions{}.
|
|
type VisitorOptions struct {
|
|
// OnlyNumber indicates parser to directly return number value without
|
|
// conversion, then the first argument of OnInt64 / OnFloat64 will always
|
|
// be zero.
|
|
OnlyNumber bool
|
|
}
|
|
|
|
var defaultVisitorOptions = &VisitorOptions{}
|
|
|
|
// Preorder decodes the whole JSON string and callbacks each AST node to visitor
|
|
// during preorder traversal. Any visitor method with an error returned will
|
|
// break the traversal and the given error will be directly returned. The opts
|
|
// argument can be reused after every call.
|
|
func Preorder(str string, visitor Visitor, opts *VisitorOptions) error {
|
|
if opts == nil {
|
|
opts = defaultVisitorOptions
|
|
}
|
|
// process VisitorOptions first to guarantee that all options will be
|
|
// constant during decoding and make options more readable.
|
|
var (
|
|
optDecodeNumber = !opts.OnlyNumber
|
|
)
|
|
|
|
tv := &traverser{
|
|
parser: Parser{
|
|
s: str,
|
|
noLazy: true,
|
|
skipValue: false,
|
|
},
|
|
visitor: visitor,
|
|
}
|
|
|
|
if optDecodeNumber {
|
|
tv.parser.decodeNumber(true)
|
|
}
|
|
|
|
err := tv.decodeValue()
|
|
|
|
if optDecodeNumber {
|
|
tv.parser.decodeNumber(false)
|
|
}
|
|
return err
|
|
}
|
|
|
|
type traverser struct {
|
|
parser Parser
|
|
visitor Visitor
|
|
}
|
|
|
|
// NOTE: keep in sync with (*Parser).Parse method.
|
|
func (self *traverser) decodeValue() error {
|
|
switch val := self.parser.decodeValue(); val.Vt {
|
|
case types.V_EOF:
|
|
return types.ERR_EOF
|
|
case types.V_NULL:
|
|
return self.visitor.OnNull()
|
|
case types.V_TRUE:
|
|
return self.visitor.OnBool(true)
|
|
case types.V_FALSE:
|
|
return self.visitor.OnBool(false)
|
|
case types.V_STRING:
|
|
return self.decodeString(val.Iv, val.Ep)
|
|
case types.V_DOUBLE:
|
|
return self.visitor.OnFloat64(val.Dv,
|
|
json.Number(self.parser.s[val.Ep:self.parser.p]))
|
|
case types.V_INTEGER:
|
|
return self.visitor.OnInt64(val.Iv,
|
|
json.Number(self.parser.s[val.Ep:self.parser.p]))
|
|
case types.V_ARRAY:
|
|
return self.decodeArray()
|
|
case types.V_OBJECT:
|
|
return self.decodeObject()
|
|
default:
|
|
return types.ParsingError(-val.Vt)
|
|
}
|
|
}
|
|
|
|
// NOTE: keep in sync with (*Parser).decodeArray method.
|
|
func (self *traverser) decodeArray() error {
|
|
sp := self.parser.p
|
|
ns := len(self.parser.s)
|
|
|
|
/* check for EOF */
|
|
self.parser.p = self.parser.lspace(sp)
|
|
if self.parser.p >= ns {
|
|
return types.ERR_EOF
|
|
}
|
|
|
|
/* check for empty array */
|
|
if self.parser.s[self.parser.p] == ']' {
|
|
self.parser.p++
|
|
if err := self.visitor.OnArrayBegin(0); err != nil {
|
|
return err
|
|
}
|
|
return self.visitor.OnArrayEnd()
|
|
}
|
|
|
|
/* allocate array space and parse every element */
|
|
if err := self.visitor.OnArrayBegin(_DEFAULT_NODE_CAP); err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
/* decode the value */
|
|
if err := self.decodeValue(); err != nil {
|
|
return err
|
|
}
|
|
self.parser.p = self.parser.lspace(self.parser.p)
|
|
|
|
/* check for EOF */
|
|
if self.parser.p >= ns {
|
|
return types.ERR_EOF
|
|
}
|
|
|
|
/* check for the next character */
|
|
switch self.parser.s[self.parser.p] {
|
|
case ',':
|
|
self.parser.p++
|
|
case ']':
|
|
self.parser.p++
|
|
return self.visitor.OnArrayEnd()
|
|
default:
|
|
return types.ERR_INVALID_CHAR
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: keep in sync with (*Parser).decodeObject method.
|
|
func (self *traverser) decodeObject() error {
|
|
sp := self.parser.p
|
|
ns := len(self.parser.s)
|
|
|
|
/* check for EOF */
|
|
self.parser.p = self.parser.lspace(sp)
|
|
if self.parser.p >= ns {
|
|
return types.ERR_EOF
|
|
}
|
|
|
|
/* check for empty object */
|
|
if self.parser.s[self.parser.p] == '}' {
|
|
self.parser.p++
|
|
if err := self.visitor.OnObjectBegin(0); err != nil {
|
|
return err
|
|
}
|
|
return self.visitor.OnObjectEnd()
|
|
}
|
|
|
|
/* allocate object space and decode each pair */
|
|
if err := self.visitor.OnObjectBegin(_DEFAULT_NODE_CAP); err != nil {
|
|
return err
|
|
}
|
|
for {
|
|
var njs types.JsonState
|
|
var err types.ParsingError
|
|
|
|
/* decode the key */
|
|
if njs = self.parser.decodeValue(); njs.Vt != types.V_STRING {
|
|
return types.ERR_INVALID_CHAR
|
|
}
|
|
|
|
/* extract the key */
|
|
idx := self.parser.p - 1
|
|
key := self.parser.s[njs.Iv:idx]
|
|
|
|
/* check for escape sequence */
|
|
if njs.Ep != -1 {
|
|
if key, err = unquote(key); err != 0 {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := self.visitor.OnObjectKey(key); err != nil {
|
|
return err
|
|
}
|
|
|
|
/* expect a ':' delimiter */
|
|
if err = self.parser.delim(); err != 0 {
|
|
return err
|
|
}
|
|
|
|
/* decode the value */
|
|
if err := self.decodeValue(); err != nil {
|
|
return err
|
|
}
|
|
|
|
self.parser.p = self.parser.lspace(self.parser.p)
|
|
|
|
/* check for EOF */
|
|
if self.parser.p >= ns {
|
|
return types.ERR_EOF
|
|
}
|
|
|
|
/* check for the next character */
|
|
switch self.parser.s[self.parser.p] {
|
|
case ',':
|
|
self.parser.p++
|
|
case '}':
|
|
self.parser.p++
|
|
return self.visitor.OnObjectEnd()
|
|
default:
|
|
return types.ERR_INVALID_CHAR
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: keep in sync with (*Parser).decodeString method.
|
|
func (self *traverser) decodeString(iv int64, ep int) error {
|
|
p := self.parser.p - 1
|
|
s := self.parser.s[iv:p]
|
|
|
|
/* fast path: no escape sequence */
|
|
if ep == -1 {
|
|
return self.visitor.OnString(s)
|
|
}
|
|
|
|
/* unquote the string */
|
|
out, err := unquote(s)
|
|
if err != 0 {
|
|
return err
|
|
}
|
|
return self.visitor.OnString(out)
|
|
}
|