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]) }