diff options
Diffstat (limited to 'vendor/github.com/bytedance/sonic/ast/visitor.go')
| -rw-r--r-- | vendor/github.com/bytedance/sonic/ast/visitor.go | 315 |
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) +} |
