@@ -5,22 +5,23 @@ import ( "io" ) type context struct { - table [65536]byte - hash uint16 - input []byte + table [1 << 16]byte + buffer [1 << 3]byte + input []byte + hash uint16 } // Returns a closure over the provided writer that compresses data when called. // // It can buffer data as the predictor mandates 8-byte blocks with a header. // A call with no data will force a flush. func Compressor(writer io.Writer) func([]byte) error { var ctx context - ctx.input = make([]byte, 8) + ctx.input = ctx.buffer[:] // Forward declaration as it is required for recursion var write func(data []byte) error write = func(data []byte) error { @@ -96,5 +97,89 @@ return nil } return write } + +type reader func([]byte) (int, error) + +func (r reader) Read(output []byte) (int, error) { + return r(output) +} + +// TODO - document +func Decompressor(reader io.Reader) reader { + var ctx context + ctx.input = ctx.buffer[:0] + + return func(output []byte) (int, error) { + var ( + err error + flags byte + readCount int + ) + + // Sanity check for space to read into + if len(output) == 0 { + return 0, nil + } + + // Check whether we have leftover data in the buffer + if len(ctx.input) > 0 { + readCount = copy(output, ctx.input) + ctx.input = ctx.input[readCount:] + return readCount, nil + } + + // // The buffer will shrink as it empties, restore it if it is needed + // if len(ctx.input) == 0 { + // ctx.input = ctx.buffer[:1] + // } + + // Read the flags + readCount, err = reader.Read(ctx.buffer[:1]) + if readCount == 0 || err != nil { + return readCount, err + } + + // This is single-iteration only but it is fine according to io.Reader's contract ?! + // TODO - read all bytes from a block based on the hamming weight of the flag + // and just shuffle them for predictions instead of bite-sized reads ;) + + flags = ctx.buffer[0] + + var i uint = 0 + for ; i < 8; i++ { + if flags&(1< 0 { + // Guess was right + ctx.buffer[i] = ctx.table[ctx.hash] + } else { + readCount, err = reader.Read(ctx.buffer[i:(i + 1)]) + + if err == io.EOF { + break + } + + if err != nil { + return readCount, err + } + + if readCount == 0 { // treat as EoF + break + } + + ctx.table[ctx.hash] = ctx.buffer[i] + } + + ctx.hash = (ctx.hash << 4) ^ uint16(ctx.buffer[i]) + } + + readCount = copy(output, ctx.buffer[:i]) + + // Place any remaining bytes in the buffer + if uint(readCount) < i { + ctx.input = ctx.buffer[readCount:i] + } + + return readCount, nil + } +}