Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
5.2 KiB
227 lines
5.2 KiB
// 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 |
|
}
|
|
|