219 lines
5.1 KiB
Go
219 lines
5.1 KiB
Go
|
package json
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"strconv"
|
||
|
|
||
|
. "github.com/polydawn/refmt/tok"
|
||
|
)
|
||
|
|
||
|
func NewEncoder(wr io.Writer, cfg EncodeOptions) *Encoder {
|
||
|
return &Encoder{
|
||
|
wr: wr,
|
||
|
cfg: cfg,
|
||
|
stack: make([]phase, 0, 10),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *Encoder) Reset() {
|
||
|
d.stack = d.stack[0:0]
|
||
|
d.current = phase_anyExpectValue
|
||
|
d.some = false
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
A json.Encoder is a TokenSink implementation that emits json bytes.
|
||
|
*/
|
||
|
type Encoder struct {
|
||
|
wr io.Writer
|
||
|
cfg EncodeOptions
|
||
|
|
||
|
// Stack, tracking how many array and map opens are outstanding.
|
||
|
// (Values are only 'phase_mapExpectKeyOrEnd' and 'phase_arrExpectValueOrEnd'.)
|
||
|
stack []phase
|
||
|
current phase // shortcut to value at end of stack
|
||
|
some bool // set to true after first value in any context; use to append commas.
|
||
|
|
||
|
// Spare memory, for use in operations on leaf nodes (e.g. temp space for an int serialization).
|
||
|
scratch [64]byte
|
||
|
}
|
||
|
|
||
|
type phase int
|
||
|
|
||
|
const (
|
||
|
phase_anyExpectValue phase = iota
|
||
|
phase_mapExpectKeyOrEnd
|
||
|
phase_mapExpectValue
|
||
|
phase_arrExpectValueOrEnd
|
||
|
)
|
||
|
|
||
|
func (d *Encoder) Step(tok *Token) (done bool, err error) {
|
||
|
switch d.current {
|
||
|
case phase_anyExpectValue:
|
||
|
switch tok.Type {
|
||
|
case TMapOpen:
|
||
|
d.pushPhase(phase_mapExpectKeyOrEnd)
|
||
|
d.wr.Write(wordMapOpen)
|
||
|
return false, nil
|
||
|
case TArrOpen:
|
||
|
d.pushPhase(phase_arrExpectValueOrEnd)
|
||
|
d.wr.Write(wordArrOpen)
|
||
|
return false, nil
|
||
|
case TMapClose:
|
||
|
return true, fmt.Errorf("unexpected mapClose; expected start of value")
|
||
|
case TArrClose:
|
||
|
return true, fmt.Errorf("unexpected arrClose; expected start of value")
|
||
|
default:
|
||
|
// It's a value; handle it.
|
||
|
d.flushValue(tok)
|
||
|
return true, nil
|
||
|
}
|
||
|
case phase_mapExpectKeyOrEnd:
|
||
|
switch tok.Type {
|
||
|
case TMapOpen:
|
||
|
return true, fmt.Errorf("unexpected mapOpen; expected start of key or end of map")
|
||
|
case TArrOpen:
|
||
|
return true, fmt.Errorf("unexpected arrOpen; expected start of key or end of map")
|
||
|
case TMapClose:
|
||
|
if d.some {
|
||
|
d.wr.Write(d.cfg.Line)
|
||
|
for i := 1; i < len(d.stack); i++ {
|
||
|
d.wr.Write(d.cfg.Indent)
|
||
|
}
|
||
|
}
|
||
|
d.wr.Write(wordMapClose)
|
||
|
return d.popPhase()
|
||
|
case TArrClose:
|
||
|
return true, fmt.Errorf("unexpected arrClose; expected start of key or end of map")
|
||
|
default:
|
||
|
// It's a key. It'd better be a string.
|
||
|
switch tok.Type {
|
||
|
case TString:
|
||
|
d.entrySep()
|
||
|
d.emitString(tok.Str)
|
||
|
d.wr.Write(wordColon)
|
||
|
if d.cfg.Line != nil {
|
||
|
d.wr.Write(wordSpace)
|
||
|
}
|
||
|
d.current = phase_mapExpectValue
|
||
|
return false, nil
|
||
|
default:
|
||
|
return true, fmt.Errorf("unexpected token of type %T; expected map key", *tok)
|
||
|
}
|
||
|
}
|
||
|
case phase_mapExpectValue:
|
||
|
switch tok.Type {
|
||
|
case TMapOpen:
|
||
|
d.pushPhase(phase_mapExpectKeyOrEnd)
|
||
|
d.wr.Write(wordMapOpen)
|
||
|
return false, nil
|
||
|
case TArrOpen:
|
||
|
d.pushPhase(phase_arrExpectValueOrEnd)
|
||
|
d.wr.Write(wordArrOpen)
|
||
|
return false, nil
|
||
|
case TMapClose:
|
||
|
return true, fmt.Errorf("unexpected mapClose; expected start of value")
|
||
|
case TArrClose:
|
||
|
return true, fmt.Errorf("unexpected arrClose; expected start of value")
|
||
|
default:
|
||
|
// It's a value; handle it.
|
||
|
d.flushValue(tok)
|
||
|
d.current = phase_mapExpectKeyOrEnd
|
||
|
return false, nil
|
||
|
}
|
||
|
case phase_arrExpectValueOrEnd:
|
||
|
switch tok.Type {
|
||
|
case TMapOpen:
|
||
|
d.entrySep()
|
||
|
d.pushPhase(phase_mapExpectKeyOrEnd)
|
||
|
d.wr.Write(wordMapOpen)
|
||
|
return false, nil
|
||
|
case TArrOpen:
|
||
|
d.entrySep()
|
||
|
d.pushPhase(phase_arrExpectValueOrEnd)
|
||
|
d.wr.Write(wordArrOpen)
|
||
|
return false, nil
|
||
|
case TMapClose:
|
||
|
return true, fmt.Errorf("unexpected mapClose; expected start of value or end of array")
|
||
|
case TArrClose:
|
||
|
if d.some {
|
||
|
d.wr.Write(d.cfg.Line)
|
||
|
for i := 1; i < len(d.stack); i++ {
|
||
|
d.wr.Write(d.cfg.Indent)
|
||
|
}
|
||
|
}
|
||
|
d.wr.Write(wordArrClose)
|
||
|
return d.popPhase()
|
||
|
default:
|
||
|
// It's a value; handle it.
|
||
|
d.entrySep()
|
||
|
d.flushValue(tok)
|
||
|
return false, nil
|
||
|
}
|
||
|
default:
|
||
|
panic("Unreachable")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *Encoder) pushPhase(p phase) {
|
||
|
d.current = p
|
||
|
d.stack = append(d.stack, d.current)
|
||
|
d.some = false
|
||
|
}
|
||
|
|
||
|
// Pop a phase from the stack; return 'true' if stack now empty.
|
||
|
func (d *Encoder) popPhase() (bool, error) {
|
||
|
n := len(d.stack) - 1
|
||
|
if n == 0 {
|
||
|
d.wr.Write(d.cfg.Line)
|
||
|
return true, nil
|
||
|
}
|
||
|
if n < 0 { // the state machines are supposed to have already errored better
|
||
|
panic("jsonEncoder stack overpopped")
|
||
|
}
|
||
|
d.current = d.stack[n-1]
|
||
|
d.stack = d.stack[0:n]
|
||
|
d.some = true
|
||
|
return false, nil
|
||
|
}
|
||
|
|
||
|
// Emit an entry separater (comma), unless we're at the start of an object.
|
||
|
// Mark that we *do* have some content, regardless, so next time will need a sep.
|
||
|
func (d *Encoder) entrySep() {
|
||
|
if d.some {
|
||
|
d.wr.Write(wordComma)
|
||
|
}
|
||
|
d.some = true
|
||
|
d.wr.Write(d.cfg.Line)
|
||
|
for i := 0; i < len(d.stack); i++ {
|
||
|
d.wr.Write(d.cfg.Indent)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *Encoder) flushValue(tok *Token) {
|
||
|
switch tok.Type {
|
||
|
case TString:
|
||
|
d.emitString(tok.Str)
|
||
|
case TBool:
|
||
|
switch tok.Bool {
|
||
|
case true:
|
||
|
d.wr.Write(wordTrue)
|
||
|
case false:
|
||
|
d.wr.Write(wordFalse)
|
||
|
}
|
||
|
case TInt:
|
||
|
b := strconv.AppendInt(d.scratch[:0], tok.Int, 10)
|
||
|
d.wr.Write(b)
|
||
|
case TNull:
|
||
|
d.wr.Write(wordNull)
|
||
|
default:
|
||
|
panic(fmt.Errorf("TODO finish more jsonEncoder primitives support: unhandled token %s", tok))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *Encoder) writeByte(b byte) {
|
||
|
d.scratch[0] = b
|
||
|
d.wr.Write(d.scratch[0:1])
|
||
|
}
|