mirror of https://github.com/goccy/go-json.git
Refactor compiler for encoder
- Introduced a two phase compilation to calculate Opcode index accurately - Fix display number of Opcode - Improve memory footprint for Opcode
This commit is contained in:
parent
2b98da0634
commit
b5e1478450
|
@ -0,0 +1,877 @@
|
|||
package encoder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/goccy/go-json/internal/runtime"
|
||||
)
|
||||
|
||||
type Code interface {
|
||||
Kind() CodeKind
|
||||
ToOpcode(*compileContext) Opcodes
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
type FloatCode struct {
|
||||
typ *runtime.Type
|
||||
bitSize uint8
|
||||
isString bool
|
||||
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}
|
||||
}
|
||||
|
||||
type StringCode struct {
|
||||
typ *runtime.Type
|
||||
isString bool
|
||||
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}
|
||||
}
|
||||
|
||||
type BoolCode struct {
|
||||
typ *runtime.Type
|
||||
isString bool
|
||||
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}
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type StructCode struct {
|
||||
typ *runtime.Type
|
||||
isPtr bool
|
||||
fields []*StructFieldCode
|
||||
disableIndirectConversion bool
|
||||
isIndirect bool
|
||||
isRecursive bool
|
||||
recursiveCodes Opcodes
|
||||
}
|
||||
|
||||
func (c *StructCode) Kind() CodeKind {
|
||||
return CodeKindStruct
|
||||
}
|
||||
|
||||
func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *Opcode {
|
||||
if field.isAnonymous {
|
||||
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 truely 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 len(codes) > 0 {
|
||||
codes.First().End = endField
|
||||
} else if field.isAnonymous {
|
||||
firstField.End = endField
|
||||
lastField := c.lastAnonymousFieldCode(firstField)
|
||||
lastField.NextField = 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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
field.NumBitSize = value.NumBitSize
|
||||
field.PtrNum = value.PtrNum
|
||||
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
|
||||
field.NumBitSize = value.NumBitSize
|
||||
field.PtrNum = value.PtrNum
|
||||
|
||||
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
|
||||
}
|
||||
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 isEnableStructEndOptimizationType(c.value.Kind()) {
|
||||
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 isEnableStructEndOptimizationType(typ CodeKind) bool {
|
||||
switch typ {
|
||||
case CodeKindInt,
|
||||
CodeKindUint,
|
||||
CodeKindFloat,
|
||||
CodeKindString,
|
||||
CodeKindBool,
|
||||
CodeKindBytes:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type InterfaceCode struct {
|
||||
typ *runtime.Type
|
||||
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)
|
||||
}
|
||||
if c.typ.NumMethod() > 0 {
|
||||
code.Flags |= NonEmptyInterfaceFlags
|
||||
}
|
||||
ctx.incIndex()
|
||||
return Opcodes{code}
|
||||
}
|
||||
|
||||
type MarshalJSONCode struct {
|
||||
typ *runtime.Type
|
||||
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)
|
||||
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}
|
||||
}
|
||||
|
||||
type MarshalTextCode struct {
|
||||
typ *runtime.Type
|
||||
isAddrForMarshaler bool
|
||||
isNilableType bool
|
||||
}
|
||||
|
||||
func (c *MarshalTextCode) Kind() CodeKind {
|
||||
return CodeKindMarshalText
|
||||
}
|
||||
|
||||
func (c *MarshalTextCode) ToOpcode(ctx *compileContext) Opcodes {
|
||||
code := newOpCode(ctx, c.typ, OpMarshalText)
|
||||
if c.isAddrForMarshaler {
|
||||
code.Flags |= AddrForMarshalerFlags
|
||||
}
|
||||
if c.isNilableType {
|
||||
code.Flags |= IsNilableTypeFlags
|
||||
} else {
|
||||
code.Flags &= ^IsNilableTypeFlags
|
||||
}
|
||||
ctx.incIndex()
|
||||
return Opcodes{code}
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,13 +1,8 @@
|
|||
//go:build !race
|
||||
// +build !race
|
||||
|
||||
package encoder
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/goccy/go-json/internal/runtime"
|
||||
)
|
||||
|
||||
func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
|
||||
if typeptr > typeAddr.MaxTypeAddr {
|
||||
return compileToGetCodeSetSlowPath(typeptr)
|
||||
|
@ -16,41 +11,10 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
|
|||
if codeSet := cachedOpcodeSets[index]; codeSet != nil {
|
||||
return codeSet, nil
|
||||
}
|
||||
|
||||
// noescape trick for header.typ ( reflect.*rtype )
|
||||
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
|
||||
|
||||
noescapeKeyCode, err := compileHead(&compileContext{
|
||||
typ: copiedType,
|
||||
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
|
||||
})
|
||||
codeSet, err := newCompiler().compile(typeptr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
escapeKeyCode, err := compileHead(&compileContext{
|
||||
typ: copiedType,
|
||||
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
|
||||
escapeKey: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
noescapeKeyCode = copyOpcode(noescapeKeyCode)
|
||||
escapeKeyCode = copyOpcode(escapeKeyCode)
|
||||
setTotalLengthToInterfaceOp(noescapeKeyCode)
|
||||
setTotalLengthToInterfaceOp(escapeKeyCode)
|
||||
interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode)
|
||||
interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode)
|
||||
codeLength := noescapeKeyCode.TotalLength()
|
||||
codeSet := &OpcodeSet{
|
||||
Type: copiedType,
|
||||
NoescapeKeyCode: noescapeKeyCode,
|
||||
EscapeKeyCode: escapeKeyCode,
|
||||
InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode,
|
||||
InterfaceEscapeKeyCode: interfaceEscapeKeyCode,
|
||||
CodeLength: codeLength,
|
||||
EndCode: ToEndCode(interfaceNoescapeKeyCode),
|
||||
}
|
||||
cachedOpcodeSets[index] = codeSet
|
||||
return codeSet, nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
//go:build race
|
||||
// +build race
|
||||
|
||||
package encoder
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/goccy/go-json/internal/runtime"
|
||||
)
|
||||
|
||||
var setsMu sync.RWMutex
|
||||
|
@ -23,41 +21,10 @@ func CompileToGetCodeSet(typeptr uintptr) (*OpcodeSet, error) {
|
|||
}
|
||||
setsMu.RUnlock()
|
||||
|
||||
// noescape trick for header.typ ( reflect.*rtype )
|
||||
copiedType := *(**runtime.Type)(unsafe.Pointer(&typeptr))
|
||||
|
||||
noescapeKeyCode, err := compileHead(&compileContext{
|
||||
typ: copiedType,
|
||||
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
|
||||
})
|
||||
codeSet, err := newCompiler().compile(typeptr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
escapeKeyCode, err := compileHead(&compileContext{
|
||||
typ: copiedType,
|
||||
structTypeToCompiledCode: map[uintptr]*CompiledCode{},
|
||||
escapeKey: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
noescapeKeyCode = copyOpcode(noescapeKeyCode)
|
||||
escapeKeyCode = copyOpcode(escapeKeyCode)
|
||||
setTotalLengthToInterfaceOp(noescapeKeyCode)
|
||||
setTotalLengthToInterfaceOp(escapeKeyCode)
|
||||
interfaceNoescapeKeyCode := copyToInterfaceOpcode(noescapeKeyCode)
|
||||
interfaceEscapeKeyCode := copyToInterfaceOpcode(escapeKeyCode)
|
||||
codeLength := noescapeKeyCode.TotalLength()
|
||||
codeSet := &OpcodeSet{
|
||||
Type: copiedType,
|
||||
NoescapeKeyCode: noescapeKeyCode,
|
||||
EscapeKeyCode: escapeKeyCode,
|
||||
InterfaceNoescapeKeyCode: interfaceNoescapeKeyCode,
|
||||
InterfaceEscapeKeyCode: interfaceEscapeKeyCode,
|
||||
CodeLength: codeLength,
|
||||
EndCode: ToEndCode(interfaceNoescapeKeyCode),
|
||||
}
|
||||
setsMu.Lock()
|
||||
cachedOpcodeSets[index] = codeSet
|
||||
setsMu.Unlock()
|
||||
|
|
|
@ -9,44 +9,20 @@ import (
|
|||
)
|
||||
|
||||
type compileContext struct {
|
||||
typ *runtime.Type
|
||||
opcodeIndex uint32
|
||||
ptrIndex int
|
||||
indent uint32
|
||||
escapeKey bool
|
||||
structTypeToCompiledCode map[uintptr]*CompiledCode
|
||||
|
||||
parent *compileContext
|
||||
structTypeToCodes map[uintptr]Opcodes
|
||||
recursiveCodes *Opcodes
|
||||
}
|
||||
|
||||
func (c *compileContext) context() *compileContext {
|
||||
return &compileContext{
|
||||
typ: c.typ,
|
||||
opcodeIndex: c.opcodeIndex,
|
||||
ptrIndex: c.ptrIndex,
|
||||
indent: c.indent,
|
||||
escapeKey: c.escapeKey,
|
||||
structTypeToCompiledCode: c.structTypeToCompiledCode,
|
||||
parent: c,
|
||||
}
|
||||
func (c *compileContext) incIndent() {
|
||||
c.indent++
|
||||
}
|
||||
|
||||
func (c *compileContext) withType(typ *runtime.Type) *compileContext {
|
||||
ctx := c.context()
|
||||
ctx.typ = typ
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *compileContext) incIndent() *compileContext {
|
||||
ctx := c.context()
|
||||
ctx.indent++
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (c *compileContext) decIndent() *compileContext {
|
||||
ctx := c.context()
|
||||
ctx.indent--
|
||||
return ctx
|
||||
func (c *compileContext) decIndent() {
|
||||
c.indent--
|
||||
}
|
||||
|
||||
func (c *compileContext) incIndex() {
|
||||
|
@ -61,30 +37,18 @@ func (c *compileContext) decIndex() {
|
|||
|
||||
func (c *compileContext) incOpcodeIndex() {
|
||||
c.opcodeIndex++
|
||||
if c.parent != nil {
|
||||
c.parent.incOpcodeIndex()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compileContext) decOpcodeIndex() {
|
||||
c.opcodeIndex--
|
||||
if c.parent != nil {
|
||||
c.parent.decOpcodeIndex()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compileContext) incPtrIndex() {
|
||||
c.ptrIndex++
|
||||
if c.parent != nil {
|
||||
c.parent.incPtrIndex()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compileContext) decPtrIndex() {
|
||||
c.ptrIndex--
|
||||
if c.parent != nil {
|
||||
c.parent.decPtrIndex()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -38,7 +38,6 @@ type Opcode struct {
|
|||
Flags OpFlags
|
||||
|
||||
Type *runtime.Type // go type
|
||||
PrevField *Opcode // prev struct field
|
||||
Jmp *CompiledCode // for recursive call
|
||||
ElemIdx uint32 // offset to access array/slice/map elem
|
||||
Length uint32 // offset to access slice/map length or array length
|
||||
|
@ -50,18 +49,40 @@ type Opcode struct {
|
|||
DisplayKey string // key text to display
|
||||
}
|
||||
|
||||
func (c *Opcode) IsStructHeadOp() bool {
|
||||
if c == nil {
|
||||
return false
|
||||
func (c *Opcode) Validate() error {
|
||||
var prevIdx uint32
|
||||
for code := c; !code.IsEnd(); {
|
||||
if prevIdx != 0 {
|
||||
if code.DisplayIdx != prevIdx+1 {
|
||||
return fmt.Errorf(
|
||||
"invalid index. previous display index is %d but next is %d. dump = %s",
|
||||
prevIdx, code.DisplayIdx, c.Dump(),
|
||||
)
|
||||
}
|
||||
return strings.Contains(c.Op.String(), "Head")
|
||||
}
|
||||
prevIdx = code.DisplayIdx
|
||||
code = code.IterNext()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Opcode) IsRecursiveOp() bool {
|
||||
func (c *Opcode) IterNext() *Opcode {
|
||||
if c == nil {
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
return strings.Contains(c.Op.String(), "Recursive")
|
||||
switch c.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
return c.End
|
||||
default:
|
||||
return c.Next
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Opcode) IsEnd() bool {
|
||||
if c == nil {
|
||||
return true
|
||||
}
|
||||
return c.Op == OpEnd || c.Op == OpInterfaceEnd || c.Op == OpRecursiveEnd
|
||||
}
|
||||
|
||||
func (c *Opcode) MaxIdx() uint32 {
|
||||
|
@ -287,82 +308,27 @@ func (c *Opcode) ToFieldType(isString bool) OpType {
|
|||
return OpStructField
|
||||
}
|
||||
|
||||
func newOpCode(ctx *compileContext, op OpType) *Opcode {
|
||||
return newOpCodeWithNext(ctx, op, newEndOp(ctx))
|
||||
func newOpCode(ctx *compileContext, typ *runtime.Type, op OpType) *Opcode {
|
||||
return newOpCodeWithNext(ctx, typ, op, newEndOp(ctx, typ))
|
||||
}
|
||||
|
||||
func opcodeOffset(idx int) uint32 {
|
||||
return uint32(idx) * uintptrSize
|
||||
}
|
||||
|
||||
func getCodeAddrByIdx(head *Opcode, idx uint32) *Opcode {
|
||||
addr := uintptr(unsafe.Pointer(head)) + uintptr(idx)*unsafe.Sizeof(Opcode{})
|
||||
return *(**Opcode)(unsafe.Pointer(&addr))
|
||||
}
|
||||
|
||||
func copyOpcode(code *Opcode) *Opcode {
|
||||
codeMap := map[uintptr]*Opcode{}
|
||||
return code.copy(codeMap)
|
||||
}
|
||||
|
||||
func setTotalLengthToInterfaceOp(code *Opcode) {
|
||||
codeNum := ToEndCode(code).DisplayIdx + 1
|
||||
codeSlice := make([]Opcode, codeNum)
|
||||
head := (*Opcode)((*runtime.SliceHeader)(unsafe.Pointer(&codeSlice)).Data)
|
||||
ptr := head
|
||||
c := code
|
||||
for c.Op != OpEnd && c.Op != OpInterfaceEnd {
|
||||
if c.Op == OpInterface {
|
||||
c.Length = uint32(code.TotalLength())
|
||||
}
|
||||
switch c.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
c = c.End
|
||||
default:
|
||||
c = c.Next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ToEndCode(code *Opcode) *Opcode {
|
||||
c := code
|
||||
for c.Op != OpEnd && c.Op != OpInterfaceEnd {
|
||||
switch c.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
c = c.End
|
||||
default:
|
||||
c = c.Next
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func copyToInterfaceOpcode(code *Opcode) *Opcode {
|
||||
copied := copyOpcode(code)
|
||||
c := copied
|
||||
c = ToEndCode(c)
|
||||
c.Idx += uintptrSize
|
||||
c.ElemIdx = c.Idx + uintptrSize
|
||||
c.Length = c.Idx + 2*uintptrSize
|
||||
c.Op = OpInterfaceEnd
|
||||
return copied
|
||||
}
|
||||
|
||||
func newOpCodeWithNext(ctx *compileContext, op OpType, next *Opcode) *Opcode {
|
||||
return &Opcode{
|
||||
Op: op,
|
||||
Idx: opcodeOffset(ctx.ptrIndex),
|
||||
Next: next,
|
||||
Type: ctx.typ,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
Indent: ctx.indent,
|
||||
}
|
||||
}
|
||||
|
||||
func newEndOp(ctx *compileContext) *Opcode {
|
||||
return newOpCodeWithNext(ctx, OpEnd, nil)
|
||||
}
|
||||
|
||||
func (c *Opcode) copy(codeMap map[uintptr]*Opcode) *Opcode {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
addr := uintptr(unsafe.Pointer(c))
|
||||
if code, exists := codeMap[addr]; exists {
|
||||
return code
|
||||
}
|
||||
copied := &Opcode{
|
||||
for {
|
||||
*ptr = Opcode{
|
||||
Op: c.Op,
|
||||
Key: c.Key,
|
||||
PtrNum: c.PtrNum,
|
||||
|
@ -379,37 +345,73 @@ func (c *Opcode) copy(codeMap map[uintptr]*Opcode) *Opcode {
|
|||
MapPos: c.MapPos,
|
||||
Size: c.Size,
|
||||
Indent: c.Indent,
|
||||
Jmp: c.Jmp,
|
||||
}
|
||||
codeMap[addr] = copied
|
||||
copied.End = c.End.copy(codeMap)
|
||||
copied.PrevField = c.PrevField.copy(codeMap)
|
||||
copied.NextField = c.NextField.copy(codeMap)
|
||||
copied.Next = c.Next.copy(codeMap)
|
||||
copied.Jmp = c.Jmp
|
||||
if c.End != nil {
|
||||
ptr.End = getCodeAddrByIdx(head, c.End.DisplayIdx)
|
||||
}
|
||||
if c.NextField != nil {
|
||||
ptr.NextField = getCodeAddrByIdx(head, c.NextField.DisplayIdx)
|
||||
}
|
||||
if c.Next != nil {
|
||||
ptr.Next = getCodeAddrByIdx(head, c.Next.DisplayIdx)
|
||||
}
|
||||
if c.IsEnd() {
|
||||
break
|
||||
}
|
||||
ptr = getCodeAddrByIdx(head, c.DisplayIdx+1)
|
||||
c = c.IterNext()
|
||||
}
|
||||
return head
|
||||
}
|
||||
|
||||
func setTotalLengthToInterfaceOp(code *Opcode) {
|
||||
for c := code; !c.IsEnd(); {
|
||||
if c.Op == OpInterface {
|
||||
c.Length = uint32(code.TotalLength())
|
||||
}
|
||||
c = c.IterNext()
|
||||
}
|
||||
}
|
||||
|
||||
func ToEndCode(code *Opcode) *Opcode {
|
||||
c := code
|
||||
for !c.IsEnd() {
|
||||
c = c.IterNext()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func copyToInterfaceOpcode(code *Opcode) *Opcode {
|
||||
copied := copyOpcode(code)
|
||||
c := copied
|
||||
c = ToEndCode(c)
|
||||
c.Idx += uintptrSize
|
||||
c.ElemIdx = c.Idx + uintptrSize
|
||||
c.Length = c.Idx + 2*uintptrSize
|
||||
c.Op = OpInterfaceEnd
|
||||
return copied
|
||||
}
|
||||
|
||||
func (c *Opcode) BeforeLastCode() *Opcode {
|
||||
code := c
|
||||
for {
|
||||
var nextCode *Opcode
|
||||
switch code.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
nextCode = code.End
|
||||
default:
|
||||
nextCode = code.Next
|
||||
func newOpCodeWithNext(ctx *compileContext, typ *runtime.Type, op OpType, next *Opcode) *Opcode {
|
||||
return &Opcode{
|
||||
Op: op,
|
||||
Idx: opcodeOffset(ctx.ptrIndex),
|
||||
Next: next,
|
||||
Type: typ,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
Indent: ctx.indent,
|
||||
}
|
||||
if nextCode.Op == OpEnd {
|
||||
return code
|
||||
}
|
||||
code = nextCode
|
||||
}
|
||||
|
||||
func newEndOp(ctx *compileContext, typ *runtime.Type) *Opcode {
|
||||
return newOpCodeWithNext(ctx, typ, OpEnd, nil)
|
||||
}
|
||||
|
||||
func (c *Opcode) TotalLength() int {
|
||||
var idx int
|
||||
code := c
|
||||
for code.Op != OpEnd && code.Op != OpInterfaceEnd {
|
||||
for !code.IsEnd() {
|
||||
maxIdx := int(code.MaxIdx() / uintptrSize)
|
||||
if idx < maxIdx {
|
||||
idx = maxIdx
|
||||
|
@ -417,12 +419,7 @@ func (c *Opcode) TotalLength() int {
|
|||
if code.Op == OpRecursiveEnd {
|
||||
break
|
||||
}
|
||||
switch code.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
code = code.End
|
||||
default:
|
||||
code = code.Next
|
||||
}
|
||||
code = code.IterNext()
|
||||
}
|
||||
maxIdx := int(code.MaxIdx() / uintptrSize)
|
||||
if idx < maxIdx {
|
||||
|
@ -432,7 +429,7 @@ func (c *Opcode) TotalLength() int {
|
|||
}
|
||||
|
||||
func (c *Opcode) decOpcodeIndex() {
|
||||
for code := c; code.Op != OpEnd; {
|
||||
for code := c; !code.IsEnd(); {
|
||||
code.DisplayIdx--
|
||||
if code.Idx > 0 {
|
||||
code.Idx -= uintptrSize
|
||||
|
@ -446,24 +443,14 @@ func (c *Opcode) decOpcodeIndex() {
|
|||
if code.Length > 0 && code.Op.CodeType() != CodeArrayHead && code.Op.CodeType() != CodeArrayElem {
|
||||
code.Length -= uintptrSize
|
||||
}
|
||||
switch code.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
code = code.End
|
||||
default:
|
||||
code = code.Next
|
||||
}
|
||||
code = code.IterNext()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Opcode) decIndent() {
|
||||
for code := c; code.Op != OpEnd; {
|
||||
for code := c; !code.IsEnd(); {
|
||||
code.Indent--
|
||||
switch code.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
code = code.End
|
||||
default:
|
||||
code = code.Next
|
||||
}
|
||||
code = code.IterNext()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -475,7 +462,7 @@ func (c *Opcode) dumpHead(code *Opcode) string {
|
|||
length = code.Length / uintptrSize
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -487,7 +474,7 @@ func (c *Opcode) dumpHead(code *Opcode) string {
|
|||
|
||||
func (c *Opcode) dumpMapHead(code *Opcode) string {
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -500,7 +487,7 @@ func (c *Opcode) dumpMapHead(code *Opcode) string {
|
|||
|
||||
func (c *Opcode) dumpMapEnd(code *Opcode) string {
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][mapPos:%d][length:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][mapPos:%d][length:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -518,7 +505,7 @@ func (c *Opcode) dumpElem(code *Opcode) string {
|
|||
length = code.Length / uintptrSize
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][size:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -531,7 +518,7 @@ func (c *Opcode) dumpElem(code *Opcode) string {
|
|||
|
||||
func (c *Opcode) dumpField(code *Opcode) string {
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][key:%s][offset:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][key:%s][offset:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -543,7 +530,7 @@ func (c *Opcode) dumpField(code *Opcode) string {
|
|||
|
||||
func (c *Opcode) dumpKey(code *Opcode) string {
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][elemIdx:%d][length:%d][mapIter:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -556,7 +543,7 @@ func (c *Opcode) dumpKey(code *Opcode) string {
|
|||
|
||||
func (c *Opcode) dumpValue(code *Opcode) string {
|
||||
return fmt.Sprintf(
|
||||
`[%d]%s%s ([idx:%d][mapIter:%d])`,
|
||||
`[%03d]%s%s ([idx:%d][mapIter:%d])`,
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -567,7 +554,7 @@ func (c *Opcode) dumpValue(code *Opcode) string {
|
|||
|
||||
func (c *Opcode) Dump() string {
|
||||
codes := []string{}
|
||||
for code := c; code.Op != OpEnd && code.Op != OpInterfaceEnd; {
|
||||
for code := c; !code.IsEnd(); {
|
||||
switch code.Op.CodeType() {
|
||||
case CodeSliceHead:
|
||||
codes = append(codes, c.dumpHead(code))
|
||||
|
@ -595,7 +582,7 @@ func (c *Opcode) Dump() string {
|
|||
code = code.Next
|
||||
default:
|
||||
codes = append(codes, fmt.Sprintf(
|
||||
"[%d]%s%s ([idx:%d])",
|
||||
"[%03d]%s%s ([idx:%d])",
|
||||
code.DisplayIdx,
|
||||
strings.Repeat("-", int(code.Indent)),
|
||||
code.Op,
|
||||
|
@ -607,44 +594,7 @@ func (c *Opcode) Dump() string {
|
|||
return strings.Join(codes, "\n")
|
||||
}
|
||||
|
||||
func prevField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode {
|
||||
if _, exists := removedFields[code]; exists {
|
||||
return prevField(code.PrevField, removedFields)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func nextField(code *Opcode, removedFields map[*Opcode]struct{}) *Opcode {
|
||||
if _, exists := removedFields[code]; exists {
|
||||
return nextField(code.NextField, removedFields)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func linkPrevToNextField(cur *Opcode, removedFields map[*Opcode]struct{}) {
|
||||
prev := prevField(cur.PrevField, removedFields)
|
||||
prev.NextField = nextField(cur.NextField, removedFields)
|
||||
code := prev
|
||||
fcode := cur
|
||||
for {
|
||||
var nextCode *Opcode
|
||||
switch code.Op.CodeType() {
|
||||
case CodeArrayElem, CodeSliceElem, CodeMapKey:
|
||||
nextCode = code.End
|
||||
default:
|
||||
nextCode = code.Next
|
||||
}
|
||||
if nextCode == fcode {
|
||||
code.Next = fcode.NextField
|
||||
break
|
||||
} else if nextCode.Op == OpEnd {
|
||||
break
|
||||
}
|
||||
code = nextCode
|
||||
}
|
||||
}
|
||||
|
||||
func newSliceHeaderCode(ctx *compileContext) *Opcode {
|
||||
func newSliceHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode {
|
||||
idx := opcodeOffset(ctx.ptrIndex)
|
||||
ctx.incPtrIndex()
|
||||
elemIdx := opcodeOffset(ctx.ptrIndex)
|
||||
|
@ -652,6 +602,7 @@ func newSliceHeaderCode(ctx *compileContext) *Opcode {
|
|||
length := opcodeOffset(ctx.ptrIndex)
|
||||
return &Opcode{
|
||||
Op: OpSlice,
|
||||
Type: typ,
|
||||
Idx: idx,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: elemIdx,
|
||||
|
@ -660,9 +611,10 @@ func newSliceHeaderCode(ctx *compileContext) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode {
|
||||
func newSliceElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, size uintptr) *Opcode {
|
||||
return &Opcode{
|
||||
Op: OpSliceElem,
|
||||
Type: typ,
|
||||
Idx: head.Idx,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: head.ElemIdx,
|
||||
|
@ -672,12 +624,13 @@ func newSliceElemCode(ctx *compileContext, head *Opcode, size uintptr) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode {
|
||||
func newArrayHeaderCode(ctx *compileContext, typ *runtime.Type, alen int) *Opcode {
|
||||
idx := opcodeOffset(ctx.ptrIndex)
|
||||
ctx.incPtrIndex()
|
||||
elemIdx := opcodeOffset(ctx.ptrIndex)
|
||||
return &Opcode{
|
||||
Op: OpArray,
|
||||
Type: typ,
|
||||
Idx: idx,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: elemIdx,
|
||||
|
@ -686,9 +639,10 @@ func newArrayHeaderCode(ctx *compileContext, alen int) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintptr) *Opcode {
|
||||
func newArrayElemCode(ctx *compileContext, typ *runtime.Type, head *Opcode, length int, size uintptr) *Opcode {
|
||||
return &Opcode{
|
||||
Op: OpArrayElem,
|
||||
Type: typ,
|
||||
Idx: head.Idx,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: head.ElemIdx,
|
||||
|
@ -698,7 +652,7 @@ func newArrayElemCode(ctx *compileContext, head *Opcode, length int, size uintpt
|
|||
}
|
||||
}
|
||||
|
||||
func newMapHeaderCode(ctx *compileContext) *Opcode {
|
||||
func newMapHeaderCode(ctx *compileContext, typ *runtime.Type) *Opcode {
|
||||
idx := opcodeOffset(ctx.ptrIndex)
|
||||
ctx.incPtrIndex()
|
||||
elemIdx := opcodeOffset(ctx.ptrIndex)
|
||||
|
@ -708,8 +662,8 @@ func newMapHeaderCode(ctx *compileContext) *Opcode {
|
|||
mapIter := opcodeOffset(ctx.ptrIndex)
|
||||
return &Opcode{
|
||||
Op: OpMap,
|
||||
Type: typ,
|
||||
Idx: idx,
|
||||
Type: ctx.typ,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: elemIdx,
|
||||
Length: length,
|
||||
|
@ -718,9 +672,10 @@ func newMapHeaderCode(ctx *compileContext) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode {
|
||||
func newMapKeyCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode {
|
||||
return &Opcode{
|
||||
Op: OpMapKey,
|
||||
Type: typ,
|
||||
Idx: opcodeOffset(ctx.ptrIndex),
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: head.ElemIdx,
|
||||
|
@ -730,9 +685,10 @@ func newMapKeyCode(ctx *compileContext, head *Opcode) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode {
|
||||
func newMapValueCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode {
|
||||
return &Opcode{
|
||||
Op: OpMapValue,
|
||||
Type: typ,
|
||||
Idx: opcodeOffset(ctx.ptrIndex),
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
ElemIdx: head.ElemIdx,
|
||||
|
@ -742,14 +698,15 @@ func newMapValueCode(ctx *compileContext, head *Opcode) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode {
|
||||
func newMapEndCode(ctx *compileContext, typ *runtime.Type, head *Opcode) *Opcode {
|
||||
mapPos := opcodeOffset(ctx.ptrIndex)
|
||||
ctx.incPtrIndex()
|
||||
idx := opcodeOffset(ctx.ptrIndex)
|
||||
return &Opcode{
|
||||
Op: OpMapEnd,
|
||||
Type: typ,
|
||||
Idx: idx,
|
||||
Next: newEndOp(ctx),
|
||||
Next: newEndOp(ctx, typ),
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
Length: head.Length,
|
||||
MapPos: mapPos,
|
||||
|
@ -757,28 +714,12 @@ func newMapEndCode(ctx *compileContext, head *Opcode) *Opcode {
|
|||
}
|
||||
}
|
||||
|
||||
func newInterfaceCode(ctx *compileContext) *Opcode {
|
||||
var flag OpFlags
|
||||
if ctx.typ.NumMethod() > 0 {
|
||||
flag |= NonEmptyInterfaceFlags
|
||||
}
|
||||
return &Opcode{
|
||||
Op: OpInterface,
|
||||
Idx: opcodeOffset(ctx.ptrIndex),
|
||||
Next: newEndOp(ctx),
|
||||
Type: ctx.typ,
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
Indent: ctx.indent,
|
||||
Flags: flag,
|
||||
}
|
||||
}
|
||||
|
||||
func newRecursiveCode(ctx *compileContext, jmp *CompiledCode) *Opcode {
|
||||
func newRecursiveCode(ctx *compileContext, typ *runtime.Type, jmp *CompiledCode) *Opcode {
|
||||
return &Opcode{
|
||||
Op: OpRecursive,
|
||||
Type: typ,
|
||||
Idx: opcodeOffset(ctx.ptrIndex),
|
||||
Next: newEndOp(ctx),
|
||||
Type: ctx.typ,
|
||||
Next: newEndOp(ctx, typ),
|
||||
DisplayIdx: ctx.opcodeIndex,
|
||||
Indent: ctx.indent,
|
||||
Jmp: jmp,
|
||||
|
|
|
@ -11,8 +11,8 @@ func TestOpcodeSize(t *testing.T) {
|
|||
const uintptrSize = 4 << (^uintptr(0) >> 63)
|
||||
if uintptrSize == 8 {
|
||||
size := unsafe.Sizeof(encoder.Opcode{})
|
||||
if size != 128 {
|
||||
t.Fatalf("unexpected opcode size: expected 128bytes but got %dbytes", size)
|
||||
if size != 120 {
|
||||
t.Fatalf("unexpected opcode size: expected 120bytes but got %dbytes", size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1840,6 +1841,7 @@ func TestCoverArray(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1853,6 +1855,7 @@ func TestCoverArray(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package json_test
|
|||
import (
|
||||
"bytes"
|
||||
stdjson "encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2653,6 +2654,7 @@ func TestCoverBool(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2666,6 +2668,7 @@ func TestCoverBool(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1807,6 +1808,7 @@ func TestCoverBytes(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1820,6 +1822,7 @@ func TestCoverBytes(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2334,6 +2335,7 @@ func TestCoverFloat32(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2347,6 +2349,7 @@ func TestCoverFloat32(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2334,6 +2335,7 @@ func TestCoverFloat64(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2347,6 +2349,7 @@ func TestCoverFloat64(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1811,6 +1812,7 @@ func TestCoverInt16(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1824,6 +1826,7 @@ func TestCoverInt16(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverInt32(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverInt32(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverInt64(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverInt64(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverInt8(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverInt8(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2410,6 +2411,7 @@ func TestCoverInt(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2423,6 +2425,7 @@ func TestCoverInt(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1881,6 +1882,7 @@ func TestCoverMap(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1894,6 +1896,7 @@ func TestCoverMap(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3690,6 +3690,7 @@ func TestCoverMarshalJSON(t *testing.T) {
|
|||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -3703,6 +3704,7 @@ func TestCoverMarshalJSON(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -3527,6 +3528,7 @@ func TestCoverMarshalText(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -3540,6 +3542,7 @@ func TestCoverMarshalText(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2334,6 +2335,7 @@ func TestCoverNumber(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2347,6 +2349,7 @@ func TestCoverNumber(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2047,9 +2048,9 @@ func TestCoverSlice(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2063,8 +2064,8 @@ func TestCoverSlice(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2370,6 +2371,7 @@ func TestCoverString(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2383,6 +2385,7 @@ func TestCoverString(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverUint16(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverUint16(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%v,indent:%v): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, indent, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverUint32(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverUint32(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverUint64(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverUint64(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -1788,6 +1789,7 @@ func TestCoverUint8(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -1801,6 +1803,7 @@ func TestCoverUint8(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package json_test
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
|
@ -2334,6 +2335,7 @@ func TestCoverUint(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
for _, indent := range []bool{true, false} {
|
||||
for _, htmlEscape := range []bool{true, false} {
|
||||
t.Run(fmt.Sprintf("%s_indent_%t_escape_%t", test.name, indent, htmlEscape), func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(htmlEscape)
|
||||
|
@ -2347,6 +2349,7 @@ func TestCoverUint(t *testing.T) {
|
|||
if buf.String() != stdresult {
|
||||
t.Errorf("%s(htmlEscape:%T): doesn't compatible with encoding/json. expected %q but got %q", test.name, htmlEscape, stdresult, buf.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue