summaryrefslogtreecommitdiff
path: root/vendor/github.com/bytedance/sonic/ast/visitor.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/bytedance/sonic/ast/visitor.go')
-rw-r--r--vendor/github.com/bytedance/sonic/ast/visitor.go315
1 files changed, 315 insertions, 0 deletions
diff --git a/vendor/github.com/bytedance/sonic/ast/visitor.go b/vendor/github.com/bytedance/sonic/ast/visitor.go
new file mode 100644
index 000000000..4019c31a2
--- /dev/null
+++ b/vendor/github.com/bytedance/sonic/ast/visitor.go
@@ -0,0 +1,315 @@
+/*
+ * 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)
+}