forked from Shiloh/githaven
228 lines
5.2 KiB
Go
228 lines
5.2 KiB
Go
|
// Copyright (c) 2017 Couchbase, Inc.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package vellum
|
||
|
|
||
|
import (
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
)
|
||
|
|
||
|
const versionV1 = 1
|
||
|
const oneTransition = 1 << 7
|
||
|
const transitionNext = 1 << 6
|
||
|
const stateFinal = 1 << 6
|
||
|
const footerSizeV1 = 16
|
||
|
|
||
|
func init() {
|
||
|
registerEncoder(versionV1, func(w io.Writer) encoder {
|
||
|
return newEncoderV1(w)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
type encoderV1 struct {
|
||
|
bw *writer
|
||
|
}
|
||
|
|
||
|
func newEncoderV1(w io.Writer) *encoderV1 {
|
||
|
return &encoderV1{
|
||
|
bw: newWriter(w),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) reset(w io.Writer) {
|
||
|
e.bw.Reset(w)
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) start() error {
|
||
|
header := make([]byte, headerSize)
|
||
|
binary.LittleEndian.PutUint64(header, versionV1)
|
||
|
binary.LittleEndian.PutUint64(header[8:], uint64(0)) // type
|
||
|
n, err := e.bw.Write(header)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if n != headerSize {
|
||
|
return fmt.Errorf("short write of header %d/%d", n, headerSize)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) encodeState(s *builderNode, lastAddr int) (int, error) {
|
||
|
if len(s.trans) == 0 && s.final && s.finalOutput == 0 {
|
||
|
return 0, nil
|
||
|
} else if len(s.trans) != 1 || s.final {
|
||
|
return e.encodeStateMany(s)
|
||
|
} else if !s.final && s.trans[0].out == 0 && s.trans[0].addr == lastAddr {
|
||
|
return e.encodeStateOneFinish(s, transitionNext)
|
||
|
}
|
||
|
return e.encodeStateOne(s)
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) encodeStateOne(s *builderNode) (int, error) {
|
||
|
start := uint64(e.bw.counter)
|
||
|
outPackSize := 0
|
||
|
if s.trans[0].out != 0 {
|
||
|
outPackSize = packedSize(s.trans[0].out)
|
||
|
err := e.bw.WritePackedUintIn(s.trans[0].out, outPackSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
delta := deltaAddr(start, uint64(s.trans[0].addr))
|
||
|
transPackSize := packedSize(delta)
|
||
|
err := e.bw.WritePackedUintIn(delta, transPackSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
packSize := encodePackSize(transPackSize, outPackSize)
|
||
|
err = e.bw.WriteByte(packSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
return e.encodeStateOneFinish(s, 0)
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) encodeStateOneFinish(s *builderNode, next byte) (int, error) {
|
||
|
enc := encodeCommon(s.trans[0].in)
|
||
|
|
||
|
// not a common input
|
||
|
if enc == 0 {
|
||
|
err := e.bw.WriteByte(s.trans[0].in)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
err := e.bw.WriteByte(oneTransition | next | enc)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
return e.bw.counter - 1, nil
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) encodeStateMany(s *builderNode) (int, error) {
|
||
|
start := uint64(e.bw.counter)
|
||
|
transPackSize := 0
|
||
|
outPackSize := packedSize(s.finalOutput)
|
||
|
anyOutputs := s.finalOutput != 0
|
||
|
for i := range s.trans {
|
||
|
delta := deltaAddr(start, uint64(s.trans[i].addr))
|
||
|
tsize := packedSize(delta)
|
||
|
if tsize > transPackSize {
|
||
|
transPackSize = tsize
|
||
|
}
|
||
|
osize := packedSize(s.trans[i].out)
|
||
|
if osize > outPackSize {
|
||
|
outPackSize = osize
|
||
|
}
|
||
|
anyOutputs = anyOutputs || s.trans[i].out != 0
|
||
|
}
|
||
|
if !anyOutputs {
|
||
|
outPackSize = 0
|
||
|
}
|
||
|
|
||
|
if anyOutputs {
|
||
|
// output final value
|
||
|
if s.final {
|
||
|
err := e.bw.WritePackedUintIn(s.finalOutput, outPackSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
// output transition values (in reverse)
|
||
|
for j := len(s.trans) - 1; j >= 0; j-- {
|
||
|
err := e.bw.WritePackedUintIn(s.trans[j].out, outPackSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// output transition dests (in reverse)
|
||
|
for j := len(s.trans) - 1; j >= 0; j-- {
|
||
|
delta := deltaAddr(start, uint64(s.trans[j].addr))
|
||
|
err := e.bw.WritePackedUintIn(delta, transPackSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// output transition keys (in reverse)
|
||
|
for j := len(s.trans) - 1; j >= 0; j-- {
|
||
|
err := e.bw.WriteByte(s.trans[j].in)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
packSize := encodePackSize(transPackSize, outPackSize)
|
||
|
err := e.bw.WriteByte(packSize)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
numTrans := encodeNumTrans(len(s.trans))
|
||
|
|
||
|
// if number of transitions wont fit in edge header byte
|
||
|
// write out separately
|
||
|
if numTrans == 0 {
|
||
|
if len(s.trans) == 256 {
|
||
|
// this wouldn't fit in single byte, but reuse value 1
|
||
|
// which would have always fit in the edge header instead
|
||
|
err = e.bw.WriteByte(1)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
} else {
|
||
|
err = e.bw.WriteByte(byte(len(s.trans)))
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// finally write edge header
|
||
|
if s.final {
|
||
|
numTrans |= stateFinal
|
||
|
}
|
||
|
err = e.bw.WriteByte(numTrans)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
return e.bw.counter - 1, nil
|
||
|
}
|
||
|
|
||
|
func (e *encoderV1) finish(count, rootAddr int) error {
|
||
|
footer := make([]byte, footerSizeV1)
|
||
|
binary.LittleEndian.PutUint64(footer, uint64(count)) // root addr
|
||
|
binary.LittleEndian.PutUint64(footer[8:], uint64(rootAddr)) // root addr
|
||
|
n, err := e.bw.Write(footer)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if n != footerSizeV1 {
|
||
|
return fmt.Errorf("short write of footer %d/%d", n, footerSizeV1)
|
||
|
}
|
||
|
err = e.bw.Flush()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|