/* * 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 separator / 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) }