package jsontext
import (
"fmt"
"io"
"strconv"
"unsafe"
)
type kind uint8
const (
kindString kind = iota
kindInt
kindUint
kindFloat
kindBool
kindNull
kindObjectStart
kindObjectEnd
kindArrayStart
kindArrayEnd
)
type Token struct {
kind kind
str string
i64 int64
u64 uint64
f64 float64
b bool
}
func String (s string ) Token {
return Token {kind : kindString , str : s }
}
func Int (i int64 ) Token {
return Token {kind : kindInt , i64 : i }
}
func Uint (u uint64 ) Token {
return Token {kind : kindUint , u64 : u }
}
func Float (f float64 ) Token {
return Token {kind : kindFloat , f64 : f }
}
func Bool (b bool ) Token {
return Token {kind : kindBool , b : b }
}
var Null Token = Token {kind : kindNull }
var BeginObject Token = Token {kind : kindObjectStart }
var EndObject Token = Token {kind : kindObjectEnd }
var BeginArray Token = Token {kind : kindArrayStart }
var EndArray Token = Token {kind : kindArrayEnd }
var True Token = Bool (true )
var False Token = Bool (false )
var hexDigits = [16 ]byte {'0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' }
var (
commaByte = []byte ("," )
quoteByte = []byte (`"` )
colonByte = []byte (":" )
trueByte = []byte ("true" )
falseByte = []byte ("false" )
nullByte = []byte ("null" )
openObjectByte = []byte ("{" )
closeObjectByte = []byte ("}" )
openArrayByte = []byte ("[" )
closeArrayByte = []byte ("]" )
newlineByte = []byte ("\n" )
escapeQuote = []byte (`\"` )
escapeBackslash = []byte (`\\` )
escapeBackspace = []byte (`\b` )
escapeFormfeed = []byte (`\f` )
escapeNewline = []byte (`\n` )
escapeCarriage = []byte (`\r` )
escapeTab = []byte (`\t` )
escapeUnicode = []byte (`\u00` )
)
type context struct {
isObject bool
needsComma bool
expectKey bool
}
type Encoder struct {
w io .Writer
buf [64 ]byte
stack []context
}
func NewEncoder (w io .Writer ) *Encoder {
stack := make ([]context , 0 , 8 )
stack = append (stack , context {isObject : false , needsComma : false , expectKey : false })
return &Encoder {
w : w ,
stack : stack ,
}
}
func (e *Encoder ) WriteToken (t Token ) error {
if len (e .stack ) == 0 {
return fmt .Errorf ("empty stack" )
}
curr := &e .stack [len (e .stack )-1 ]
isClosing := t .kind == kindObjectEnd || t .kind == kindArrayEnd
if !isClosing && curr .needsComma {
if _ , err := e .w .Write (commaByte ); err != nil {
return err
}
curr .needsComma = false
}
var err error
switch t .kind {
case kindString :
data := stringToBytes (t .str )
needsEscape := false
for _ , b := range data {
if b == '"' || b == '\\' || b < 0x20 {
needsEscape = true
break
}
}
if !needsEscape {
if _, err = e .w .Write (quoteByte ); err != nil {
return err
}
if _, err = e .w .Write (data ); err != nil {
return err
}
if _, err = e .w .Write (quoteByte ); err != nil {
return err
}
} else {
if _, err = e .w .Write (quoteByte ); err != nil {
return err
}
for i := 0 ; i < len (t .str ); i ++ {
c := t .str [i ]
switch c {
case '"' :
if _, err = e .w .Write (escapeQuote ); err != nil {
return err
}
case '\\' :
if _, err = e .w .Write (escapeBackslash ); err != nil {
return err
}
case '\b' :
if _, err = e .w .Write (escapeBackspace ); err != nil {
return err
}
case '\f' :
if _, err = e .w .Write (escapeFormfeed ); err != nil {
return err
}
case '\n' :
if _, err = e .w .Write (escapeNewline ); err != nil {
return err
}
case '\r' :
if _, err = e .w .Write (escapeCarriage ); err != nil {
return err
}
case '\t' :
if _, err = e .w .Write (escapeTab ); err != nil {
return err
}
default :
if c < 0x20 {
if _, err = e .w .Write (escapeUnicode ); err != nil {
return err
}
if _, err = e .w .Write ([]byte {hexDigits [c >>4 ], hexDigits [c &0xf ]}); err != nil {
return err
}
} else {
if _, err = e .w .Write ([]byte {c }); err != nil {
return err
}
}
}
}
if _, err = e .w .Write (quoteByte ); err != nil {
return err
}
}
if curr .isObject {
if curr .expectKey {
if _, err = e .w .Write (colonByte ); err != nil {
return err
}
curr .expectKey = false
return nil
} else {
e .afterValue ()
}
} else {
e .afterValue ()
}
case kindInt :
b := strconv .AppendInt (e .buf [:0 ], t .i64 , 10 )
if _, err = e .w .Write (b ); err != nil {
return err
}
e .afterValue ()
case kindUint :
b := strconv .AppendUint (e .buf [:0 ], t .u64 , 10 )
if _, err = e .w .Write (b ); err != nil {
return err
}
e .afterValue ()
case kindFloat :
b := strconv .AppendFloat (e .buf [:0 ], t .f64 , 'g' , -1 , 64 )
if _, err = e .w .Write (b ); err != nil {
return err
}
e .afterValue ()
case kindBool :
if t .b {
if _, err = e .w .Write (trueByte ); err != nil {
return err
}
} else {
if _, err = e .w .Write (falseByte ); err != nil {
return err
}
}
e .afterValue ()
case kindNull :
if _, err = e .w .Write (nullByte ); err != nil {
return err
}
e .afterValue ()
case kindObjectStart :
if _, err = e .w .Write (openObjectByte ); err != nil {
return err
}
e .stack = append (e .stack , context {isObject : true , needsComma : false , expectKey : true })
return nil
case kindObjectEnd :
if _, err = e .w .Write (closeObjectByte ); err != nil {
return err
}
e .stack = e .stack [:len (e .stack )-1 ]
e .afterValue ()
if len (e .stack ) == 1 {
if _, err = e .w .Write (newlineByte ); err != nil {
return err
}
}
return nil
case kindArrayStart :
if _, err = e .w .Write (openArrayByte ); err != nil {
return err
}
e .stack = append (e .stack , context {isObject : false , needsComma : false , expectKey : false })
return nil
case kindArrayEnd :
if _, err = e .w .Write (closeArrayByte ); err != nil {
return err
}
e .stack = e .stack [:len (e .stack )-1 ]
e .afterValue ()
if len (e .stack ) == 1 {
if _, err = e .w .Write (newlineByte ); err != nil {
return err
}
}
return nil
default :
return fmt .Errorf ("unknown token kind" )
}
return err
}
func (e *Encoder ) afterValue () {
if len (e .stack ) > 1 {
curr := &e .stack [len (e .stack )-1 ]
curr .needsComma = true
if curr .isObject {
curr .expectKey = true
}
}
}
func stringToBytes(s string ) []byte {
return unsafe .Slice (unsafe .StringData (s ), len (s ))
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .