ipld-eth-server/vendor/github.com/polydawn/refmt/cbor/cborEncoder.go

277 lines
9.1 KiB
Go

package cbor
import (
"io"
. "github.com/polydawn/refmt/tok"
)
type Encoder struct {
w quickWriter
stack []encoderPhase // When empty, and step returns done, all done.
current encoderPhase // Shortcut to end of stack.
// Note unlike decoder, we need no statekeeping space for definite-len map and array.
spareBytes []byte
}
func NewEncoder(w io.Writer) (d *Encoder) {
d = &Encoder{
w: newQuickWriterStream(w),
stack: make([]encoderPhase, 0, 10),
current: phase_anyExpectValue,
spareBytes: make([]byte, 8),
}
return
}
func (d *Encoder) Reset() {
d.stack = d.stack[0:0]
d.current = phase_anyExpectValue
}
type encoderPhase byte
// There's about twice as many phases that the cbor encoder can be in compared to the json encoder
// because the presense of indefinite vs definite length maps and arrays effectively adds a dimension to those.
const (
phase_anyExpectValue encoderPhase = iota
phase_mapDefExpectKeyOrEnd // must not yield break at end
phase_mapDefExpectValue // only necessary to flip back to DefExpectKey
phase_mapIndefExpectKeyOrEnd // must yield break at end
phase_mapIndefExpectValue // only necessary to flip back to IndefExpectKey
phase_arrDefExpectValueOrEnd // must not yield break at end
phase_arrIndefExpectValueOrEnd // must yield break at end
)
func (d *Encoder) pushPhase(p encoderPhase) {
d.current = p
d.stack = append(d.stack, d.current)
}
// Pop a phase from the stack; return 'true' if stack now empty.
func (d *Encoder) popPhase() bool {
n := len(d.stack) - 1
if n == 0 {
return true
}
if n < 0 { // the state machines are supposed to have already errored better
panic("cborEncoder stack overpopped")
}
d.current = d.stack[n-1]
d.stack = d.stack[0:n]
return false
}
func (d *Encoder) Step(tokenSlot *Token) (done bool, err error) {
/*
Though it reads somewhat backwards from how a human would probably intuit
cause and effect, switching on the token type we got first,
*then* switching for whether it is acceptable for our current phase... is by
far the shorter volume of code to write.
*/
phase := d.current
switch tokenSlot.Type {
case TMapOpen:
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
if tokenSlot.Length >= 0 {
d.pushPhase(phase_mapDefExpectKeyOrEnd)
d.emitMajorPlusLen(cborMajorMap, uint64(tokenSlot.Length))
} else {
d.pushPhase(phase_mapIndefExpectKeyOrEnd)
d.w.writen1(cborSigilIndefiniteMap)
}
return false, d.w.checkErr()
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
case TMapClose:
switch phase {
case phase_mapDefExpectKeyOrEnd:
return d.popPhase(), nil
case phase_mapIndefExpectKeyOrEnd:
d.w.writen1(cborSigilBreak)
return d.popPhase(), d.w.checkErr()
case phase_anyExpectValue, phase_mapDefExpectValue, phase_mapIndefExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForValue}
default:
panic("unreachable phase")
}
case TArrOpen:
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
if tokenSlot.Length >= 0 {
d.pushPhase(phase_arrDefExpectValueOrEnd)
d.emitMajorPlusLen(cborMajorArray, uint64(tokenSlot.Length))
} else {
d.pushPhase(phase_arrIndefExpectValueOrEnd)
d.w.writen1(cborSigilIndefiniteArray)
}
return false, d.w.checkErr()
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
case TArrClose:
switch phase {
case phase_arrDefExpectValueOrEnd:
return d.popPhase(), nil
case phase_arrIndefExpectValueOrEnd:
d.w.writen1(cborSigilBreak)
return d.popPhase(), d.w.checkErr()
case phase_anyExpectValue, phase_mapDefExpectValue, phase_mapIndefExpectValue:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForValue}
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
case TNull: // terminal value; not accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.w.writen1(cborSigilNil)
return phase == phase_anyExpectValue, d.w.checkErr()
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
case TString: // terminal value; YES, accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
goto emitStr
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
d.current += 1
goto emitStr
default:
panic("unreachable phase")
}
emitStr:
{
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.encodeString(tokenSlot.Str)
return phase == phase_anyExpectValue, d.w.checkErr()
}
case TBytes: // terminal value; not accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.encodeBytes(tokenSlot.Bytes)
return phase == phase_anyExpectValue, d.w.checkErr()
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
case TBool: // terminal value; not accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.encodeBool(tokenSlot.Bool)
return phase == phase_anyExpectValue, d.w.checkErr()
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
case TInt: // terminal value; YES, accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
goto emitInt
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
d.current += 1
goto emitInt
default:
panic("unreachable phase")
}
emitInt:
{
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.encodeInt64(tokenSlot.Int)
return phase == phase_anyExpectValue, d.w.checkErr()
}
case TUint: // terminal value; YES, accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
goto emitUint
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
d.current += 1
goto emitUint
default:
panic("unreachable phase")
}
emitUint:
{
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.encodeUint64(tokenSlot.Uint)
return phase == phase_anyExpectValue, d.w.checkErr()
}
case TFloat64: // terminal value; not accepted as map key.
switch phase {
case phase_mapDefExpectValue, phase_mapIndefExpectValue:
d.current -= 1
fallthrough
case phase_anyExpectValue, phase_arrDefExpectValueOrEnd, phase_arrIndefExpectValueOrEnd:
if tokenSlot.Tagged {
d.emitMajorPlusLen(cborMajorTag, uint64(tokenSlot.Tag))
}
d.encodeFloat64(tokenSlot.Float64)
return phase == phase_anyExpectValue, d.w.checkErr()
case phase_mapDefExpectKeyOrEnd, phase_mapIndefExpectKeyOrEnd:
return true, &ErrInvalidTokenStream{Got: *tokenSlot, Acceptable: tokenTypesForKey}
default:
panic("unreachable phase")
}
default:
panic("unhandled token type")
}
}