1018 lines
22 KiB
Go
1018 lines
22 KiB
Go
|
package encoder
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"reflect"
|
||
|
"unsafe"
|
||
|
|
||
|
"github.com/goccy/go-json/internal/runtime"
|
||
|
)
|
||
|
|
||
|
type Code interface {
|
||
|
Kind() CodeKind
|
||
|
ToOpcode(*compileContext) Opcodes
|
||
|
Filter(*FieldQuery) Code
|
||
|
}
|
||
|
|
||
|
type AnonymousCode interface {
|
||
|
ToAnonymousOpcode(*compileContext) Opcodes
|
||
|
}
|
||
|
|
||
|
type Opcodes []*Opcode
|
||
|
|
||
|
func (o Opcodes) First() *Opcode {
|
||
|
if len(o) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
return o[0]
|
||
|
}
|
||
|
|
||
|
func (o Opcodes) Last() *Opcode {
|
||
|
if len(o) == 0 {
|
||
|
return nil
|
||
|
}
|
||
|
return o[len(o)-1]
|
||
|
}
|
||
|
|
||
|
func (o Opcodes) Add(codes ...*Opcode) Opcodes {
|
||
|
return append(o, codes...)
|
||
|
}
|
||
|
|
||
|
type CodeKind int
|
||
|
|
||
|
const (
|
||
|
CodeKindInterface CodeKind = iota
|
||
|
CodeKindPtr
|
||
|
CodeKindInt
|
||
|
CodeKindUint
|
||
|
CodeKindFloat
|
||
|
CodeKindString
|
||
|
CodeKindBool
|
||
|
CodeKindStruct
|
||
|
CodeKindMap
|
||
|
CodeKindSlice
|
||
|
CodeKindArray
|
||
|
CodeKindBytes
|
||
|
CodeKindMarshalJSON
|
||
|
CodeKindMarshalText
|
||
|
CodeKindRecursive
|
||
|
)
|
||
|
|
||
|
type IntCode struct {
|
||
|
typ *runtime.Type
|
||
|
bitSize uint8
|
||
|
isString bool
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *IntCode) Kind() CodeKind {
|
||
|
return CodeKindInt
|
||
|
}
|
||
|
|
||
|
func (c *IntCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
var code *Opcode
|
||
|
switch {
|
||
|
case c.isPtr:
|
||
|
code = newOpCode(ctx, c.typ, OpIntPtr)
|
||
|
case c.isString:
|
||
|
code = newOpCode(ctx, c.typ, OpIntString)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpInt)
|
||
|
}
|
||
|
code.NumBitSize = c.bitSize
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *IntCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type UintCode struct {
|
||
|
typ *runtime.Type
|
||
|
bitSize uint8
|
||
|
isString bool
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *UintCode) Kind() CodeKind {
|
||
|
return CodeKindUint
|
||
|
}
|
||
|
|
||
|
func (c *UintCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
var code *Opcode
|
||
|
switch {
|
||
|
case c.isPtr:
|
||
|
code = newOpCode(ctx, c.typ, OpUintPtr)
|
||
|
case c.isString:
|
||
|
code = newOpCode(ctx, c.typ, OpUintString)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpUint)
|
||
|
}
|
||
|
code.NumBitSize = c.bitSize
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *UintCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type FloatCode struct {
|
||
|
typ *runtime.Type
|
||
|
bitSize uint8
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *FloatCode) Kind() CodeKind {
|
||
|
return CodeKindFloat
|
||
|
}
|
||
|
|
||
|
func (c *FloatCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
var code *Opcode
|
||
|
switch {
|
||
|
case c.isPtr:
|
||
|
switch c.bitSize {
|
||
|
case 32:
|
||
|
code = newOpCode(ctx, c.typ, OpFloat32Ptr)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpFloat64Ptr)
|
||
|
}
|
||
|
default:
|
||
|
switch c.bitSize {
|
||
|
case 32:
|
||
|
code = newOpCode(ctx, c.typ, OpFloat32)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpFloat64)
|
||
|
}
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *FloatCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type StringCode struct {
|
||
|
typ *runtime.Type
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *StringCode) Kind() CodeKind {
|
||
|
return CodeKindString
|
||
|
}
|
||
|
|
||
|
func (c *StringCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
isJSONNumberType := c.typ == runtime.Type2RType(jsonNumberType)
|
||
|
var code *Opcode
|
||
|
if c.isPtr {
|
||
|
if isJSONNumberType {
|
||
|
code = newOpCode(ctx, c.typ, OpNumberPtr)
|
||
|
} else {
|
||
|
code = newOpCode(ctx, c.typ, OpStringPtr)
|
||
|
}
|
||
|
} else {
|
||
|
if isJSONNumberType {
|
||
|
code = newOpCode(ctx, c.typ, OpNumber)
|
||
|
} else {
|
||
|
code = newOpCode(ctx, c.typ, OpString)
|
||
|
}
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *StringCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type BoolCode struct {
|
||
|
typ *runtime.Type
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *BoolCode) Kind() CodeKind {
|
||
|
return CodeKindBool
|
||
|
}
|
||
|
|
||
|
func (c *BoolCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
var code *Opcode
|
||
|
switch {
|
||
|
case c.isPtr:
|
||
|
code = newOpCode(ctx, c.typ, OpBoolPtr)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpBool)
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *BoolCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type BytesCode struct {
|
||
|
typ *runtime.Type
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *BytesCode) Kind() CodeKind {
|
||
|
return CodeKindBytes
|
||
|
}
|
||
|
|
||
|
func (c *BytesCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
var code *Opcode
|
||
|
switch {
|
||
|
case c.isPtr:
|
||
|
code = newOpCode(ctx, c.typ, OpBytesPtr)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpBytes)
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *BytesCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type SliceCode struct {
|
||
|
typ *runtime.Type
|
||
|
value Code
|
||
|
}
|
||
|
|
||
|
func (c *SliceCode) Kind() CodeKind {
|
||
|
return CodeKindSlice
|
||
|
}
|
||
|
|
||
|
func (c *SliceCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
// header => opcode => elem => end
|
||
|
// ^ |
|
||
|
// |________|
|
||
|
size := c.typ.Elem().Size()
|
||
|
header := newSliceHeaderCode(ctx, c.typ)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
ctx.incIndent()
|
||
|
codes := c.value.ToOpcode(ctx)
|
||
|
ctx.decIndent()
|
||
|
|
||
|
codes.First().Flags |= IndirectFlags
|
||
|
elemCode := newSliceElemCode(ctx, c.typ.Elem(), header, size)
|
||
|
ctx.incIndex()
|
||
|
end := newOpCode(ctx, c.typ, OpSliceEnd)
|
||
|
ctx.incIndex()
|
||
|
header.End = end
|
||
|
header.Next = codes.First()
|
||
|
codes.Last().Next = elemCode
|
||
|
elemCode.Next = codes.First()
|
||
|
elemCode.End = end
|
||
|
return Opcodes{header}.Add(codes...).Add(elemCode).Add(end)
|
||
|
}
|
||
|
|
||
|
func (c *SliceCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type ArrayCode struct {
|
||
|
typ *runtime.Type
|
||
|
value Code
|
||
|
}
|
||
|
|
||
|
func (c *ArrayCode) Kind() CodeKind {
|
||
|
return CodeKindArray
|
||
|
}
|
||
|
|
||
|
func (c *ArrayCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
// header => opcode => elem => end
|
||
|
// ^ |
|
||
|
// |________|
|
||
|
elem := c.typ.Elem()
|
||
|
alen := c.typ.Len()
|
||
|
size := elem.Size()
|
||
|
|
||
|
header := newArrayHeaderCode(ctx, c.typ, alen)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
ctx.incIndent()
|
||
|
codes := c.value.ToOpcode(ctx)
|
||
|
ctx.decIndent()
|
||
|
|
||
|
codes.First().Flags |= IndirectFlags
|
||
|
|
||
|
elemCode := newArrayElemCode(ctx, elem, header, alen, size)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
end := newOpCode(ctx, c.typ, OpArrayEnd)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
header.End = end
|
||
|
header.Next = codes.First()
|
||
|
codes.Last().Next = elemCode
|
||
|
elemCode.Next = codes.First()
|
||
|
elemCode.End = end
|
||
|
|
||
|
return Opcodes{header}.Add(codes...).Add(elemCode).Add(end)
|
||
|
}
|
||
|
|
||
|
func (c *ArrayCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type MapCode struct {
|
||
|
typ *runtime.Type
|
||
|
key Code
|
||
|
value Code
|
||
|
}
|
||
|
|
||
|
func (c *MapCode) Kind() CodeKind {
|
||
|
return CodeKindMap
|
||
|
}
|
||
|
|
||
|
func (c *MapCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
// header => code => value => code => key => code => value => code => end
|
||
|
// ^ |
|
||
|
// |_______________________|
|
||
|
header := newMapHeaderCode(ctx, c.typ)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
keyCodes := c.key.ToOpcode(ctx)
|
||
|
|
||
|
value := newMapValueCode(ctx, c.typ.Elem(), header)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
ctx.incIndent()
|
||
|
valueCodes := c.value.ToOpcode(ctx)
|
||
|
ctx.decIndent()
|
||
|
|
||
|
valueCodes.First().Flags |= IndirectFlags
|
||
|
|
||
|
key := newMapKeyCode(ctx, c.typ.Key(), header)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
end := newMapEndCode(ctx, c.typ, header)
|
||
|
ctx.incIndex()
|
||
|
|
||
|
header.Next = keyCodes.First()
|
||
|
keyCodes.Last().Next = value
|
||
|
value.Next = valueCodes.First()
|
||
|
valueCodes.Last().Next = key
|
||
|
key.Next = keyCodes.First()
|
||
|
|
||
|
header.End = end
|
||
|
key.End = end
|
||
|
value.End = end
|
||
|
return Opcodes{header}.Add(keyCodes...).Add(value).Add(valueCodes...).Add(key).Add(end)
|
||
|
}
|
||
|
|
||
|
func (c *MapCode) Filter(_ *FieldQuery) Code {
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
type StructCode struct {
|
||
|
typ *runtime.Type
|
||
|
fields []*StructFieldCode
|
||
|
isPtr bool
|
||
|
disableIndirectConversion bool
|
||
|
isIndirect bool
|
||
|
isRecursive bool
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) Kind() CodeKind {
|
||
|
return CodeKindStruct
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode {
|
||
|
if isEmbeddedStruct(field) {
|
||
|
return c.lastAnonymousFieldCode(firstField)
|
||
|
}
|
||
|
lastField := firstField
|
||
|
for lastField.NextField != nil {
|
||
|
lastField = lastField.NextField
|
||
|
}
|
||
|
return lastField
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode {
|
||
|
// firstField is special StructHead operation for anonymous structure.
|
||
|
// So, StructHead's next operation is truly struct head operation.
|
||
|
lastField := firstField.Next
|
||
|
for lastField.NextField != nil {
|
||
|
lastField = lastField.NextField
|
||
|
}
|
||
|
return lastField
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
// header => code => structField => code => end
|
||
|
// ^ |
|
||
|
// |__________|
|
||
|
if c.isRecursive {
|
||
|
recursive := newRecursiveCode(ctx, c.typ, &CompiledCode{})
|
||
|
recursive.Type = c.typ
|
||
|
ctx.incIndex()
|
||
|
*ctx.recursiveCodes = append(*ctx.recursiveCodes, recursive)
|
||
|
return Opcodes{recursive}
|
||
|
}
|
||
|
codes := Opcodes{}
|
||
|
var prevField *Opcode
|
||
|
ctx.incIndent()
|
||
|
for idx, field := range c.fields {
|
||
|
isFirstField := idx == 0
|
||
|
isEndField := idx == len(c.fields)-1
|
||
|
fieldCodes := field.ToOpcode(ctx, isFirstField, isEndField)
|
||
|
for _, code := range fieldCodes {
|
||
|
if c.isIndirect {
|
||
|
code.Flags |= IndirectFlags
|
||
|
}
|
||
|
}
|
||
|
firstField := fieldCodes.First()
|
||
|
if len(codes) > 0 {
|
||
|
codes.Last().Next = firstField
|
||
|
firstField.Idx = codes.First().Idx
|
||
|
}
|
||
|
if prevField != nil {
|
||
|
prevField.NextField = firstField
|
||
|
}
|
||
|
if isEndField {
|
||
|
endField := fieldCodes.Last()
|
||
|
if isEmbeddedStruct(field) {
|
||
|
firstField.End = endField
|
||
|
lastField := c.lastAnonymousFieldCode(firstField)
|
||
|
lastField.NextField = endField
|
||
|
}
|
||
|
if len(codes) > 0 {
|
||
|
codes.First().End = endField
|
||
|
} else {
|
||
|
firstField.End = endField
|
||
|
}
|
||
|
codes = codes.Add(fieldCodes...)
|
||
|
break
|
||
|
}
|
||
|
prevField = c.lastFieldCode(field, firstField)
|
||
|
codes = codes.Add(fieldCodes...)
|
||
|
}
|
||
|
if len(codes) == 0 {
|
||
|
head := &Opcode{
|
||
|
Op: OpStructHead,
|
||
|
Idx: opcodeOffset(ctx.ptrIndex),
|
||
|
Type: c.typ,
|
||
|
DisplayIdx: ctx.opcodeIndex,
|
||
|
Indent: ctx.indent,
|
||
|
}
|
||
|
ctx.incOpcodeIndex()
|
||
|
end := &Opcode{
|
||
|
Op: OpStructEnd,
|
||
|
Idx: opcodeOffset(ctx.ptrIndex),
|
||
|
DisplayIdx: ctx.opcodeIndex,
|
||
|
Indent: ctx.indent,
|
||
|
}
|
||
|
head.NextField = end
|
||
|
head.Next = end
|
||
|
head.End = end
|
||
|
codes = codes.Add(head, end)
|
||
|
ctx.incIndex()
|
||
|
}
|
||
|
ctx.decIndent()
|
||
|
ctx.structTypeToCodes[uintptr(unsafe.Pointer(c.typ))] = codes
|
||
|
return codes
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) ToAnonymousOpcode(ctx *compileContext) Opcodes {
|
||
|
// header => code => structField => code => end
|
||
|
// ^ |
|
||
|
// |__________|
|
||
|
if c.isRecursive {
|
||
|
recursive := newRecursiveCode(ctx, c.typ, &CompiledCode{})
|
||
|
recursive.Type = c.typ
|
||
|
ctx.incIndex()
|
||
|
*ctx.recursiveCodes = append(*ctx.recursiveCodes, recursive)
|
||
|
return Opcodes{recursive}
|
||
|
}
|
||
|
codes := Opcodes{}
|
||
|
var prevField *Opcode
|
||
|
for idx, field := range c.fields {
|
||
|
isFirstField := idx == 0
|
||
|
isEndField := idx == len(c.fields)-1
|
||
|
fieldCodes := field.ToAnonymousOpcode(ctx, isFirstField, isEndField)
|
||
|
for _, code := range fieldCodes {
|
||
|
if c.isIndirect {
|
||
|
code.Flags |= IndirectFlags
|
||
|
}
|
||
|
}
|
||
|
firstField := fieldCodes.First()
|
||
|
if len(codes) > 0 {
|
||
|
codes.Last().Next = firstField
|
||
|
firstField.Idx = codes.First().Idx
|
||
|
}
|
||
|
if prevField != nil {
|
||
|
prevField.NextField = firstField
|
||
|
}
|
||
|
if isEndField {
|
||
|
lastField := fieldCodes.Last()
|
||
|
if len(codes) > 0 {
|
||
|
codes.First().End = lastField
|
||
|
} else {
|
||
|
firstField.End = lastField
|
||
|
}
|
||
|
}
|
||
|
prevField = firstField
|
||
|
codes = codes.Add(fieldCodes...)
|
||
|
}
|
||
|
return codes
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) removeFieldsByTags(tags runtime.StructTags) {
|
||
|
fields := make([]*StructFieldCode, 0, len(c.fields))
|
||
|
for _, field := range c.fields {
|
||
|
if field.isAnonymous {
|
||
|
structCode := field.getAnonymousStruct()
|
||
|
if structCode != nil && !structCode.isRecursive {
|
||
|
structCode.removeFieldsByTags(tags)
|
||
|
if len(structCode.fields) > 0 {
|
||
|
fields = append(fields, field)
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
if tags.ExistsKey(field.key) {
|
||
|
continue
|
||
|
}
|
||
|
fields = append(fields, field)
|
||
|
}
|
||
|
c.fields = fields
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) enableIndirect() {
|
||
|
if c.isIndirect {
|
||
|
return
|
||
|
}
|
||
|
c.isIndirect = true
|
||
|
if len(c.fields) == 0 {
|
||
|
return
|
||
|
}
|
||
|
structCode := c.fields[0].getStruct()
|
||
|
if structCode == nil {
|
||
|
return
|
||
|
}
|
||
|
structCode.enableIndirect()
|
||
|
}
|
||
|
|
||
|
func (c *StructCode) Filter(query *FieldQuery) Code {
|
||
|
fieldMap := map[string]*FieldQuery{}
|
||
|
for _, field := range query.Fields {
|
||
|
fieldMap[field.Name] = field
|
||
|
}
|
||
|
fields := make([]*StructFieldCode, 0, len(c.fields))
|
||
|
for _, field := range c.fields {
|
||
|
query, exists := fieldMap[field.key]
|
||
|
if !exists {
|
||
|
continue
|
||
|
}
|
||
|
fieldCode := &StructFieldCode{
|
||
|
typ: field.typ,
|
||
|
key: field.key,
|
||
|
tag: field.tag,
|
||
|
value: field.value,
|
||
|
offset: field.offset,
|
||
|
isAnonymous: field.isAnonymous,
|
||
|
isTaggedKey: field.isTaggedKey,
|
||
|
isNilableType: field.isNilableType,
|
||
|
isNilCheck: field.isNilCheck,
|
||
|
isAddrForMarshaler: field.isAddrForMarshaler,
|
||
|
isNextOpPtrType: field.isNextOpPtrType,
|
||
|
}
|
||
|
if len(query.Fields) > 0 {
|
||
|
fieldCode.value = fieldCode.value.Filter(query)
|
||
|
}
|
||
|
fields = append(fields, fieldCode)
|
||
|
}
|
||
|
return &StructCode{
|
||
|
typ: c.typ,
|
||
|
fields: fields,
|
||
|
isPtr: c.isPtr,
|
||
|
disableIndirectConversion: c.disableIndirectConversion,
|
||
|
isIndirect: c.isIndirect,
|
||
|
isRecursive: c.isRecursive,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type StructFieldCode struct {
|
||
|
typ *runtime.Type
|
||
|
key string
|
||
|
tag *runtime.StructTag
|
||
|
value Code
|
||
|
offset uintptr
|
||
|
isAnonymous bool
|
||
|
isTaggedKey bool
|
||
|
isNilableType bool
|
||
|
isNilCheck bool
|
||
|
isAddrForMarshaler bool
|
||
|
isNextOpPtrType bool
|
||
|
isMarshalerContext bool
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) getStruct() *StructCode {
|
||
|
value := c.value
|
||
|
ptr, ok := value.(*PtrCode)
|
||
|
if ok {
|
||
|
value = ptr.value
|
||
|
}
|
||
|
structCode, ok := value.(*StructCode)
|
||
|
if ok {
|
||
|
return structCode
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) getAnonymousStruct() *StructCode {
|
||
|
if !c.isAnonymous {
|
||
|
return nil
|
||
|
}
|
||
|
return c.getStruct()
|
||
|
}
|
||
|
|
||
|
func optimizeStructHeader(code *Opcode, tag *runtime.StructTag) OpType {
|
||
|
headType := code.ToHeaderType(tag.IsString)
|
||
|
if tag.IsOmitEmpty {
|
||
|
headType = headType.HeadToOmitEmptyHead()
|
||
|
}
|
||
|
return headType
|
||
|
}
|
||
|
|
||
|
func optimizeStructField(code *Opcode, tag *runtime.StructTag) OpType {
|
||
|
fieldType := code.ToFieldType(tag.IsString)
|
||
|
if tag.IsOmitEmpty {
|
||
|
fieldType = fieldType.FieldToOmitEmptyField()
|
||
|
}
|
||
|
return fieldType
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) headerOpcodes(ctx *compileContext, field *Opcode, valueCodes Opcodes) Opcodes {
|
||
|
value := valueCodes.First()
|
||
|
op := optimizeStructHeader(value, c.tag)
|
||
|
field.Op = op
|
||
|
if value.Flags&MarshalerContextFlags != 0 {
|
||
|
field.Flags |= MarshalerContextFlags
|
||
|
}
|
||
|
field.NumBitSize = value.NumBitSize
|
||
|
field.PtrNum = value.PtrNum
|
||
|
field.FieldQuery = value.FieldQuery
|
||
|
fieldCodes := Opcodes{field}
|
||
|
if op.IsMultipleOpHead() {
|
||
|
field.Next = value
|
||
|
fieldCodes = fieldCodes.Add(valueCodes...)
|
||
|
} else {
|
||
|
ctx.decIndex()
|
||
|
}
|
||
|
return fieldCodes
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) fieldOpcodes(ctx *compileContext, field *Opcode, valueCodes Opcodes) Opcodes {
|
||
|
value := valueCodes.First()
|
||
|
op := optimizeStructField(value, c.tag)
|
||
|
field.Op = op
|
||
|
if value.Flags&MarshalerContextFlags != 0 {
|
||
|
field.Flags |= MarshalerContextFlags
|
||
|
}
|
||
|
field.NumBitSize = value.NumBitSize
|
||
|
field.PtrNum = value.PtrNum
|
||
|
field.FieldQuery = value.FieldQuery
|
||
|
|
||
|
fieldCodes := Opcodes{field}
|
||
|
if op.IsMultipleOpField() {
|
||
|
field.Next = value
|
||
|
fieldCodes = fieldCodes.Add(valueCodes...)
|
||
|
} else {
|
||
|
ctx.decIndex()
|
||
|
}
|
||
|
return fieldCodes
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) addStructEndCode(ctx *compileContext, codes Opcodes) Opcodes {
|
||
|
end := &Opcode{
|
||
|
Op: OpStructEnd,
|
||
|
Idx: opcodeOffset(ctx.ptrIndex),
|
||
|
DisplayIdx: ctx.opcodeIndex,
|
||
|
Indent: ctx.indent,
|
||
|
}
|
||
|
codes.Last().Next = end
|
||
|
codes.First().NextField = end
|
||
|
codes = codes.Add(end)
|
||
|
ctx.incOpcodeIndex()
|
||
|
return codes
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) structKey(ctx *compileContext) string {
|
||
|
if ctx.escapeKey {
|
||
|
rctx := &RuntimeContext{Option: &Option{Flag: HTMLEscapeOption}}
|
||
|
return fmt.Sprintf(`%s:`, string(AppendString(rctx, []byte{}, c.key)))
|
||
|
}
|
||
|
return fmt.Sprintf(`"%s":`, c.key)
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) flags() OpFlags {
|
||
|
var flags OpFlags
|
||
|
if c.isTaggedKey {
|
||
|
flags |= IsTaggedKeyFlags
|
||
|
}
|
||
|
if c.isNilableType {
|
||
|
flags |= IsNilableTypeFlags
|
||
|
}
|
||
|
if c.isNilCheck {
|
||
|
flags |= NilCheckFlags
|
||
|
}
|
||
|
if c.isAddrForMarshaler {
|
||
|
flags |= AddrForMarshalerFlags
|
||
|
}
|
||
|
if c.isNextOpPtrType {
|
||
|
flags |= IsNextOpPtrTypeFlags
|
||
|
}
|
||
|
if c.isAnonymous {
|
||
|
flags |= AnonymousKeyFlags
|
||
|
}
|
||
|
if c.isMarshalerContext {
|
||
|
flags |= MarshalerContextFlags
|
||
|
}
|
||
|
return flags
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) toValueOpcodes(ctx *compileContext) Opcodes {
|
||
|
if c.isAnonymous {
|
||
|
anonymCode, ok := c.value.(AnonymousCode)
|
||
|
if ok {
|
||
|
return anonymCode.ToAnonymousOpcode(ctx)
|
||
|
}
|
||
|
}
|
||
|
return c.value.ToOpcode(ctx)
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) ToOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes {
|
||
|
field := &Opcode{
|
||
|
Idx: opcodeOffset(ctx.ptrIndex),
|
||
|
Flags: c.flags(),
|
||
|
Key: c.structKey(ctx),
|
||
|
Offset: uint32(c.offset),
|
||
|
Type: c.typ,
|
||
|
DisplayIdx: ctx.opcodeIndex,
|
||
|
Indent: ctx.indent,
|
||
|
DisplayKey: c.key,
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
valueCodes := c.toValueOpcodes(ctx)
|
||
|
if isFirstField {
|
||
|
codes := c.headerOpcodes(ctx, field, valueCodes)
|
||
|
if isEndField {
|
||
|
codes = c.addStructEndCode(ctx, codes)
|
||
|
}
|
||
|
return codes
|
||
|
}
|
||
|
codes := c.fieldOpcodes(ctx, field, valueCodes)
|
||
|
if isEndField {
|
||
|
if isEnableStructEndOptimization(c.value) {
|
||
|
field.Op = field.Op.FieldToEnd()
|
||
|
} else {
|
||
|
codes = c.addStructEndCode(ctx, codes)
|
||
|
}
|
||
|
}
|
||
|
return codes
|
||
|
}
|
||
|
|
||
|
func (c *StructFieldCode) ToAnonymousOpcode(ctx *compileContext, isFirstField, isEndField bool) Opcodes {
|
||
|
field := &Opcode{
|
||
|
Idx: opcodeOffset(ctx.ptrIndex),
|
||
|
Flags: c.flags() | AnonymousHeadFlags,
|
||
|
Key: c.structKey(ctx),
|
||
|
Offset: uint32(c.offset),
|
||
|
Type: c.typ,
|
||
|
DisplayIdx: ctx.opcodeIndex,
|
||
|
Indent: ctx.indent,
|
||
|
DisplayKey: c.key,
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
valueCodes := c.toValueOpcodes(ctx)
|
||
|
if isFirstField {
|
||
|
return c.headerOpcodes(ctx, field, valueCodes)
|
||
|
}
|
||
|
return c.fieldOpcodes(ctx, field, valueCodes)
|
||
|
}
|
||
|
|
||
|
func isEnableStructEndOptimization(value Code) bool {
|
||
|
switch value.Kind() {
|
||
|
case CodeKindInt,
|
||
|
CodeKindUint,
|
||
|
CodeKindFloat,
|
||
|
CodeKindString,
|
||
|
CodeKindBool,
|
||
|
CodeKindBytes:
|
||
|
return true
|
||
|
case CodeKindPtr:
|
||
|
return isEnableStructEndOptimization(value.(*PtrCode).value)
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type InterfaceCode struct {
|
||
|
typ *runtime.Type
|
||
|
fieldQuery *FieldQuery
|
||
|
isPtr bool
|
||
|
}
|
||
|
|
||
|
func (c *InterfaceCode) Kind() CodeKind {
|
||
|
return CodeKindInterface
|
||
|
}
|
||
|
|
||
|
func (c *InterfaceCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
var code *Opcode
|
||
|
switch {
|
||
|
case c.isPtr:
|
||
|
code = newOpCode(ctx, c.typ, OpInterfacePtr)
|
||
|
default:
|
||
|
code = newOpCode(ctx, c.typ, OpInterface)
|
||
|
}
|
||
|
code.FieldQuery = c.fieldQuery
|
||
|
if c.typ.NumMethod() > 0 {
|
||
|
code.Flags |= NonEmptyInterfaceFlags
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *InterfaceCode) Filter(query *FieldQuery) Code {
|
||
|
return &InterfaceCode{
|
||
|
typ: c.typ,
|
||
|
fieldQuery: query,
|
||
|
isPtr: c.isPtr,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type MarshalJSONCode struct {
|
||
|
typ *runtime.Type
|
||
|
fieldQuery *FieldQuery
|
||
|
isAddrForMarshaler bool
|
||
|
isNilableType bool
|
||
|
isMarshalerContext bool
|
||
|
}
|
||
|
|
||
|
func (c *MarshalJSONCode) Kind() CodeKind {
|
||
|
return CodeKindMarshalJSON
|
||
|
}
|
||
|
|
||
|
func (c *MarshalJSONCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
code := newOpCode(ctx, c.typ, OpMarshalJSON)
|
||
|
code.FieldQuery = c.fieldQuery
|
||
|
if c.isAddrForMarshaler {
|
||
|
code.Flags |= AddrForMarshalerFlags
|
||
|
}
|
||
|
if c.isMarshalerContext {
|
||
|
code.Flags |= MarshalerContextFlags
|
||
|
}
|
||
|
if c.isNilableType {
|
||
|
code.Flags |= IsNilableTypeFlags
|
||
|
} else {
|
||
|
code.Flags &= ^IsNilableTypeFlags
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *MarshalJSONCode) Filter(query *FieldQuery) Code {
|
||
|
return &MarshalJSONCode{
|
||
|
typ: c.typ,
|
||
|
fieldQuery: query,
|
||
|
isAddrForMarshaler: c.isAddrForMarshaler,
|
||
|
isNilableType: c.isNilableType,
|
||
|
isMarshalerContext: c.isMarshalerContext,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type MarshalTextCode struct {
|
||
|
typ *runtime.Type
|
||
|
fieldQuery *FieldQuery
|
||
|
isAddrForMarshaler bool
|
||
|
isNilableType bool
|
||
|
}
|
||
|
|
||
|
func (c *MarshalTextCode) Kind() CodeKind {
|
||
|
return CodeKindMarshalText
|
||
|
}
|
||
|
|
||
|
func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
code := newOpCode(ctx, c.typ, OpMarshalText)
|
||
|
code.FieldQuery = c.fieldQuery
|
||
|
if c.isAddrForMarshaler {
|
||
|
code.Flags |= AddrForMarshalerFlags
|
||
|
}
|
||
|
if c.isNilableType {
|
||
|
code.Flags |= IsNilableTypeFlags
|
||
|
} else {
|
||
|
code.Flags &= ^IsNilableTypeFlags
|
||
|
}
|
||
|
ctx.incIndex()
|
||
|
return Opcodes{code}
|
||
|
}
|
||
|
|
||
|
func (c *MarshalTextCode) Filter(query *FieldQuery) Code {
|
||
|
return &MarshalTextCode{
|
||
|
typ: c.typ,
|
||
|
fieldQuery: query,
|
||
|
isAddrForMarshaler: c.isAddrForMarshaler,
|
||
|
isNilableType: c.isNilableType,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type PtrCode struct {
|
||
|
typ *runtime.Type
|
||
|
value Code
|
||
|
ptrNum uint8
|
||
|
}
|
||
|
|
||
|
func (c *PtrCode) Kind() CodeKind {
|
||
|
return CodeKindPtr
|
||
|
}
|
||
|
|
||
|
func (c *PtrCode) ToOpcode(ctx *compileContext) Opcodes {
|
||
|
codes := c.value.ToOpcode(ctx)
|
||
|
codes.First().Op = convertPtrOp(codes.First())
|
||
|
codes.First().PtrNum = c.ptrNum
|
||
|
return codes
|
||
|
}
|
||
|
|
||
|
func (c *PtrCode) ToAnonymousOpcode(ctx *compileContext) Opcodes {
|
||
|
var codes Opcodes
|
||
|
anonymCode, ok := c.value.(AnonymousCode)
|
||
|
if ok {
|
||
|
codes = anonymCode.ToAnonymousOpcode(ctx)
|
||
|
} else {
|
||
|
codes = c.value.ToOpcode(ctx)
|
||
|
}
|
||
|
codes.First().Op = convertPtrOp(codes.First())
|
||
|
codes.First().PtrNum = c.ptrNum
|
||
|
return codes
|
||
|
}
|
||
|
|
||
|
func (c *PtrCode) Filter(query *FieldQuery) Code {
|
||
|
return &PtrCode{
|
||
|
typ: c.typ,
|
||
|
value: c.value.Filter(query),
|
||
|
ptrNum: c.ptrNum,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func convertPtrOp(code *Opcode) OpType {
|
||
|
ptrHeadOp := code.Op.HeadToPtrHead()
|
||
|
if code.Op != ptrHeadOp {
|
||
|
if code.PtrNum > 0 {
|
||
|
// ptr field and ptr head
|
||
|
code.PtrNum--
|
||
|
}
|
||
|
return ptrHeadOp
|
||
|
}
|
||
|
switch code.Op {
|
||
|
case OpInt:
|
||
|
return OpIntPtr
|
||
|
case OpUint:
|
||
|
return OpUintPtr
|
||
|
case OpFloat32:
|
||
|
return OpFloat32Ptr
|
||
|
case OpFloat64:
|
||
|
return OpFloat64Ptr
|
||
|
case OpString:
|
||
|
return OpStringPtr
|
||
|
case OpBool:
|
||
|
return OpBoolPtr
|
||
|
case OpBytes:
|
||
|
return OpBytesPtr
|
||
|
case OpNumber:
|
||
|
return OpNumberPtr
|
||
|
case OpArray:
|
||
|
return OpArrayPtr
|
||
|
case OpSlice:
|
||
|
return OpSlicePtr
|
||
|
case OpMap:
|
||
|
return OpMapPtr
|
||
|
case OpMarshalJSON:
|
||
|
return OpMarshalJSONPtr
|
||
|
case OpMarshalText:
|
||
|
return OpMarshalTextPtr
|
||
|
case OpInterface:
|
||
|
return OpInterfacePtr
|
||
|
case OpRecursive:
|
||
|
return OpRecursivePtr
|
||
|
}
|
||
|
return code.Op
|
||
|
}
|
||
|
|
||
|
func isEmbeddedStruct(field *StructFieldCode) bool {
|
||
|
if !field.isAnonymous {
|
||
|
return false
|
||
|
}
|
||
|
t := field.typ
|
||
|
if t.Kind() == reflect.Ptr {
|
||
|
t = t.Elem()
|
||
|
}
|
||
|
return t.Kind() == reflect.Struct
|
||
|
}
|