| #!python3 | |
| """Program to dump contents of Brotli compressed files showing the compression format. | |
| Jurjen N.E. Bos, 2016. | |
| I found the following issues with the Brotli format: | |
| - The distance alphabet has size 16+(48<<POSTFIX), | |
| but the last symbols are useless. | |
| It could be lowered to 16+(44-POSTFIX<<POSTFIX), and this could matter. | |
| - The block type code is useless if NBLTYPES==2, you would only need 1 symbol | |
| anyway, so why don't you just switch to "the other" type? | |
| """ | |
| # ruff: noqa | |
| import struct | |
| from operator import itemgetter, methodcaller | |
| from itertools import accumulate, repeat | |
| from collections import defaultdict, deque | |
| from functools import partial | |
| DICTIONARY_PATH = 'dictionary.bin' | |
| class InvalidStream(Exception): pass | |
| #lookup table | |
| L, I, D = "literal", "insert©", "distance" | |
| pL, pI, pD = 'P'+L, 'P'+I, 'P'+D | |
| def outputCharFormatter(c): | |
| """Show character in readable format | |
| """ | |
| #TODO 2: allow hex only output | |
| if 32<c<127: return chr(c) | |
| elif c==10: return '\\n' | |
| elif c==13: return '\\r' | |
| elif c==32: return '" "' | |
| else: return '\\x{:02x}'.format(c) | |
| def outputFormatter(s): | |
| """Show string or char. | |
| """ | |
| result = '' | |
| def formatSubString(s): | |
| for c in s: | |
| if c==32: yield ' ' | |
| else: yield outputCharFormatter(c) | |
| if len(result)<200: return ''.join(formatSubString(s)) | |
| else: | |
| return ''.join(formatSubString(s[:100]))+'...'+ \ | |
| ''.join(formatSubString(s[-100:])) | |
| class BitStream: | |
| """Represent a bytes object. Can read bits and prefix codes the way | |
| Brotli does. | |
| """ | |
| def __init__(self, byteString): | |
| self.data = byteString | |
| #position in bits: byte pos is pos>>3, bit pos is pos&7 | |
| self.pos = 0 | |
| def __repr__(self): | |
| """Representation | |
| >>> olleke | |
| BitStream(pos=0:0) | |
| """ | |
| return "BitStream(pos={:x}:{})".format(self.pos>>3, self.pos&7) | |
| def read(self, n): | |
| """Read n bits from the stream and return as an integer. | |
| Produces zero bits beyond the stream. | |
| >>> olleke.data[0]==27 | |
| True | |
| >>> olleke.read(5) | |
| 27 | |
| >>> olleke | |
| BitStream(pos=0:5) | |
| """ | |
| value = self.peek(n) | |
| self.pos += n | |
| if self.pos>len(self.data)*8: | |
| raise ValueError('Read past end of stream') | |
| return value | |
| def peek(self, n): | |
| """Peek an n bit integer from the stream without updating the pointer. | |
| It is not an error to read beyond the end of the stream. | |
| >>> olleke.data[:2]==b'\x1b\x2e' and 0x2e1b==11803 | |
| True | |
| >>> olleke.peek(15) | |
| 11803 | |
| >>> hex(olleke.peek(32)) | |
| '0x2e1b' | |
| """ | |
| #read bytes that contain the data: self.data[self.pos>>3:self.pos+n+7>>3] | |
| #convert to int: int.from_bytes(..., 'little') | |
| #shift out the bits from the first byte: >>(self.pos&7) | |
| #mask unwanted bits: & (1<<n)-1 | |
| return int.from_bytes( | |
| self.data[self.pos>>3:self.pos+n+7>>3], | |
| 'little')>>(self.pos&7) & (1<<n)-1 | |
| def readBytes(self, n): | |
| """Read n bytes from the stream on a byte boundary. | |
| """ | |
| if self.pos&7: raise ValueError('readBytes: need byte boundary') | |
| result = self.data[self.pos>>3:(self.pos>>3)+n] | |
| self.pos += 8*n | |
| return result | |
| #-----------------------Symbol------------------------------------------- | |
| class Symbol: | |
| """A symbol in a code. | |
| Refers back to the code that contains it. | |
| Index is the place in the alphabet of the symbol. | |
| """ | |
| __slots__ = 'code', 'index' | |
| def __init__(self, code, value): | |
| self.code = code | |
| self.index = value | |
| def __repr__(self): | |
| return 'Symbol({}, {})'.format(self.code.name, self.index) | |
| def __len__(self): | |
| """Number of bits in the prefix notation of this symbol | |
| """ | |
| return self.code.length(self.index) | |
| def __int__(self): | |
| return self.index | |
| #these routines call equivalent routine in Code class | |
| def bitPattern(self): | |
| """Value of the symbol in the stream | |
| """ | |
| return self.code.bitPattern(self.index) | |
| def extraBits(self): | |
| """Number of extra bits to read for this symbol | |
| """ | |
| return self.code.extraBits(self.index) | |
| def __str__(self): | |
| """Short descriptor of the symbol without extra bits. | |
| """ | |
| return self.code.mnemonic(self.index) | |
| #requiring optional extra bits, if self.code supports them | |
| def value(self, extra=None): | |
| """The value used for processing. Can be a tuple. | |
| with optional extra bits | |
| """ | |
| if isinstance(self.code, WithExtra): | |
| if not 0<=extra<1<<self.extraBits(): | |
| raise ValueError("value: extra value doesn't fit in extraBits") | |
| return self.code.value(self.index, extra) | |
| if extra is not None: | |
| raise ValueError('value: no extra bits for this code') | |
| return self.code.value(self.index) | |
| def explanation(self, extra=None): | |
| """Long explanation of the value from the numeric value | |
| with optional extra bits | |
| Used by Layout.verboseRead when printing the value | |
| """ | |
| if isinstance(self.code, WithExtra): | |
| return self.code.callback(self, extra) | |
| return self.code.callback(self) | |
| #========================Code definitions================================== | |
| class RangeDecoder: | |
| """A decoder for the Code class that assumes the symbols | |
| are encoded consecutively in binary. | |
| It all depends on the "alphabetSize" property. | |
| The range runs from 0 to alphabetSize-1. | |
| This is the default decoder. | |
| """ | |
| def __init__(self, *, alphabetSize=None, bitLength=None, **args): | |
| if bitLength is not None: alphabetSize = 1<<bitLength | |
| if alphabetSize is not None: | |
| self.alphabetSize = alphabetSize | |
| self.maxLength = (alphabetSize-1).bit_length() | |
| def __len__(self): | |
| return self.alphabetSize | |
| def __iter__(self): | |
| """Produce all symbols. | |
| """ | |
| return map(partial(Symbol, self), range(len(self))) | |
| def __getitem__(self, index): | |
| if index>=self.alphabetSize: raise ValueError('index out of range') | |
| return Symbol(self, index) | |
| def bitPattern(self, index): | |
| return '{:0{}b}'.format(index, self.maxLength) | |
| def length(self, index): | |
| """Encoding length of given symbol. | |
| Does not depend on index in this case. | |
| """ | |
| return self.maxLength | |
| def decodePeek(self, data): | |
| """Find which symbol index matches the given data (from peek, as a number) | |
| and return the number of bits decoded. | |
| Can also be used to figure out length of a symbol. | |
| """ | |
| return self.maxLength, Symbol(self, data&(1<<self.maxLength)-1) | |
| class PrefixDecoder: | |
| """A decoder for the Code class that uses a prefix code. | |
| The code is determined by encoding: | |
| encode[p] gives the index corresponding to bit pattern p. | |
| Used setDecode(decodeTable) to switch the decoder from the default | |
| to a prefix decoder, or pass decodeTable at init. | |
| You can also use setLength(lengthTable) | |
| to define the encoding from the lengths. | |
| The set of symbol values does not need to be consecutive. | |
| """ | |
| def __init__(self, *, decodeTable=None, **args): | |
| if decodeTable is not None: self.setDecode(decodeTable) | |
| def __len__(self): | |
| return len(self.decodeTable) | |
| def __iter__(self): | |
| def revBits(index): | |
| return self.bitPattern(index)[::-1] | |
| return ( | |
| Symbol(self, index) | |
| for index in sorted(self.decodeTable.values(), key=revBits) | |
| ) | |
| def __getitem__(self, index): | |
| if index not in self.lengthTable: | |
| raise ValueError('No symbol {}[{}]'.format( | |
| self.__class__.__name__, index)) | |
| return Symbol(self, index) | |
| def bitPattern(self, index): | |
| bits = next(b for (b,s) in self.decodeTable.items() if s==index) | |
| return '{:0{}b}'.format(bits, self.length(index)) | |
| def length(self, index): | |
| """Encoding length of given symbol. | |
| """ | |
| return self.lengthTable[index] | |
| def decodePeek(self, data): | |
| """Find which symbol index matches the given data (from peek, as a number) | |
| and return the number of bits decoded. | |
| Can also be used to figure out length of a symbol. | |
| """ | |
| #do binary search for word length | |
| #invariant: lo<=length<=hi | |
| lo, hi = self.minLength, self.maxLength | |
| while lo<=hi: | |
| mid = lo+hi>>1 | |
| #note lo<=mid<hi at this point | |
| mask = (1<<mid)-1 | |
| #lets see what happens if we guess length is mid | |
| try: index = self.decodeTable[data&mask] | |
| except KeyError: | |
| #too many bits specified, reduce estimated length | |
| hi = mid-1 | |
| continue | |
| #we found a symbol, but there could be a longer match | |
| symbolLength = self.lengthTable[index] | |
| if symbolLength<=mid: | |
| #all bits match, symbol must be right | |
| return symbolLength, Symbol(self, index) | |
| #there must be more bits to match | |
| lo = mid+1 | |
| return lo, Symbol(self, index) | |
| #routine to set up the tables | |
| def setDecode(self, decodeTable): | |
| """Store decodeTable, | |
| and compute lengthTable, minLength, maxLength from encodings. | |
| """ | |
| self.decodeTable = decodeTable | |
| #set of symbols with unknown length | |
| todo = set(decodeTable) | |
| #bit size under investigation | |
| maskLength = 0 | |
| lengthTable = {} | |
| while todo: | |
| mask = (1<<maskLength)-1 | |
| #split the encodings that we didn't find yet using b bits | |
| splitSymbols = defaultdict(list) | |
| for s in todo: splitSymbols[s&mask].append(s) | |
| #unique encodings have a length of maskLength bits | |
| #set length, and remove from todo list | |
| for s,subset in splitSymbols.items(): | |
| if len(subset)==1: | |
| lengthTable[self.decodeTable[s]] = maskLength | |
| todo.remove(s) | |
| #now investigate with longer mask | |
| maskLength +=1 | |
| #save result | |
| self.lengthTable = lengthTable | |
| self.minLength = min(lengthTable.values()) | |
| self.maxLength = max(lengthTable.values()) | |
| self.switchToPrefix() | |
| def setLength(self, lengthTable): | |
| """Given the bit pattern lengths for symbols given in lengthTable, | |
| set decodeTable, minLength, maxLength | |
| """ | |
| self.lengthTable = lengthTable | |
| self.minLength = min(lengthTable.values()) | |
| self.maxLength = max(lengthTable.values()) | |
| #compute the backwards codes first; then reverse them | |
| #compute (backwards) first code for every separate lengths | |
| nextCodes = [] | |
| #build codes for each length, from right to left | |
| code = 0 | |
| for bits in range(self.maxLength+1): | |
| code <<= 1 | |
| nextCodes.append(code) | |
| code += sum(x==bits for x in lengthTable.values()) | |
| self.decodeTable = {} | |
| #count codes for each length, and store reversed in the table | |
| for symbol in sorted(lengthTable): | |
| bits = lengthTable[symbol] | |
| bitpattern = '{:0{}b}'.format(nextCodes[bits], bits) | |
| self.decodeTable[int(bitpattern[::-1], 2)] = symbol | |
| nextCodes[bits] += 1 | |
| self.switchToPrefix() | |
| def switchToPrefix(self): | |
| """This routine makes sure the prefix decoder is activated. | |
| """ | |
| self.mode = PrefixDecoder | |
| class Code(RangeDecoder, PrefixDecoder): | |
| """An alphabet of symbols, that can be read from a stream. | |
| If you use setDecode or setLength, you have a prefix code, | |
| otherwise you have a range code. | |
| Features: | |
| code[index] produces symbol with given index | |
| value(index): value of symbol | |
| mnemonic(index): short description of symbol | |
| explanation(index): show meaning of symbol, shown in Layout.verboseRead | |
| iter(code): produce all symbols in some order | |
| name: show as context in Layout.verboseRead | |
| """ | |
| name = '?' | |
| #callback is a function that gets the symbol and the extra bits | |
| #default callback calls explanation | |
| def __init__(self, name=None, *, callback=None, description='', **args): | |
| """Don't forget to set either alphabetSize or decodeTable | |
| """ | |
| #set name when provided, otherwise take class variable | |
| if name is not None: self.name = name | |
| if callback is not None: self.callback = callback | |
| self.description = description | |
| #mode switch | |
| if 'bitLength' in args or 'alphabetSize' in args: | |
| self.mode = RangeDecoder | |
| RangeDecoder.__init__(self, **args) | |
| elif 'decodeTable' in args: | |
| self.mode = PrefixDecoder | |
| PrefixDecoder.__init__(self, **args) | |
| else: | |
| super().__init__(**args) | |
| def __repr__(self): | |
| return self.__class__.__name__+' '+self.name | |
| #the routines that get switched between RangeDecoder and PrefixDecoder | |
| def __len__(self): return self.mode.__len__(self) | |
| def __iter__(self): return self.mode.__iter__(self) | |
| def __getitem__(self, index): return self.mode.__getitem__(self, index) | |
| def bitPattern(self, index): return self.mode.bitPattern(self, index) | |
| def length(self, index): return self.mode.length(self, index) | |
| def decodePeek(self, data): return self.mode.decodePeek(self, data) | |
| #general routines | |
| def value(self, index, extra=None): | |
| """Get value of symbol for computations. | |
| Override where needed. | |
| """ | |
| if extra is not None: | |
| raise ValueError('value: no extra for this symbol') | |
| return index | |
| def mnemonic(self, index): | |
| """Give mnemonic of symbol. | |
| Override where needed. | |
| """ | |
| return str(self.value(index)) | |
| def callback(self, symbol): | |
| return self.explanation(symbol.index) | |
| def explanation(self, index): | |
| """Long explanation of the value from the numeric value | |
| This is a default routine. | |
| You can customize in three ways: | |
| - set description to add some text | |
| - override to get more control | |
| - set callback to make it dependent on you local variables | |
| """ | |
| value = self.value(index) | |
| return '{0}{1}: {2}'.format( | |
| self.description and self.description+': ', | |
| self.bitPattern(index), | |
| value, | |
| ) | |
| def extraBits(self, index): | |
| return 0 | |
| #Routines that use the decode interface | |
| def showCode(self, width=80): | |
| """Show all words of the code in a nice format. | |
| """ | |
| #make table of all symbols with binary strings | |
| symbolStrings = [ | |
| (self.bitPattern(s.index), self.mnemonic(s.index)) | |
| for s in self | |
| ] | |
| #determine column widths the way Lisp programmers do it | |
| leftColWidth, rightColWidth = map(max, map( | |
| map, | |
| repeat(len), | |
| zip(*symbolStrings) | |
| )) | |
| colwidth = leftColWidth+rightColWidth | |
| columns = 81//(colwidth+2) | |
| rows = -(-len(symbolStrings)//columns) | |
| def justify(bs): | |
| b,s = bs | |
| return b.rjust(leftColWidth)+':'+s.ljust(rightColWidth) | |
| for i in range(rows): | |
| print(' '.join(map(justify, symbolStrings[i::rows])).rstrip()) | |
| def readTuple(self, stream): | |
| """Read symbol from stream. Returns symbol, length. | |
| """ | |
| length, symbol = self.decodePeek(stream.peek(self.maxLength)) | |
| stream.pos += length | |
| return length, symbol | |
| def readTupleAndExtra(self, stream): | |
| return self.readTuple(stream)+(0, None) | |
| class WithExtra(Code): | |
| """Extension for Code so that symbol may have extra bits associated. | |
| If you supply an extraTable, you can use extraBits | |
| You can define an extraTable, | |
| which allows to call extraBits to get the number of extraBits. | |
| Otherwise, you can supply extraBits yourself. | |
| Routine readTupleAndExtra now reads the extra bits too. | |
| Value probably needs to be overridden; see Enumerator. | |
| Note: this does not give you an decodeTable. | |
| """ | |
| #redefine these if you don't want to use an extraTable | |
| def extraBits(self, index): | |
| """Get the number of extra bits for this symbol. | |
| """ | |
| return self.extraTable[index] | |
| def mnemonic(self, index): | |
| """This value must be independent of extra. | |
| """ | |
| return str(index) | |
| def readTupleAndExtra(self, stream): | |
| """Read symbol and extrabits from stream. | |
| Returns symbol length, symbol, extraBits, extra | |
| >>> olleke.pos = 6 | |
| >>> MetablockLengthAlphabet().readTupleAndExtra(olleke) | |
| (2, Symbol(MLEN, 4), 16, 46) | |
| """ | |
| length, symbol = self.decodePeek(stream.peek(self.maxLength)) | |
| stream.pos += length | |
| extraBits = self.extraBits(symbol.index) | |
| return length, symbol, extraBits, stream.read(extraBits) | |
| def explanation(self, index, extra=None): | |
| """Expanded version of Code.explanation supporting extra bits. | |
| If you don't supply extra, it is not mentioned. | |
| """ | |
| extraBits = 0 if extra is None else self.extraBits(index) | |
| if not hasattr(self, 'extraTable'): | |
| formatString = '{0}{3}' | |
| lo = hi = value = self.value(index, extra) | |
| elif extraBits==0: | |
| formatString = '{0}{2}: {3}' | |
| lo, hi = self.span(index) | |
| value = lo | |
| else: | |
| formatString = '{0}{1} {2}: {3}-{4}; {3}+{5}={6}' | |
| lo, hi = self.span(index) | |
| value = lo+extra | |
| return formatString.format( | |
| self.description and self.description+': ', | |
| 'x'*extraBits, | |
| self.bitPattern(index), | |
| lo, hi, | |
| extra, | |
| value, | |
| ) | |
| def callback(self, symbol, extra): | |
| return self.explanation(symbol.index, extra) | |
| class BoolCode(Code): | |
| """Same as Code(bitLength=1), but shows a boolean. | |
| """ | |
| def __init__(self, name=None, **args): | |
| super().__init__(name, bitLength=1, **args) | |
| def value(self, index, extra=None): | |
| return bool(super().value(index, extra)) | |
| class Enumerator(WithExtra): | |
| """Code that is defined by the ExtraTable. | |
| extraTable is a class variable that contains | |
| the extraBits of the symbols from 0 | |
| value0 contains the value of symbol 0 | |
| encodings is not necessary, but allowed. | |
| Note: place for FixedCode to make sure extraBits works | |
| """ | |
| def __init__(self, name=None, **args): | |
| #if there is no decodeTable to determine length, compute it ourselves | |
| if 'decodeTable' not in args: | |
| args['alphabetSize'] = len(self.extraTable) | |
| super().__init__(name, **args) | |
| def __len__(self): | |
| return len(self.extraTable) | |
| def __getitem__(self, index): | |
| """Faster than PrefixDecoder | |
| """ | |
| if index>=len(self.extraTable): | |
| raise ValueError("No symbol {}[{}]".format( | |
| self.__class__.__name__, index)) | |
| return Symbol(self, index) | |
| def value(self, index, extra): | |
| """Override if you don't define value0 and extraTable | |
| """ | |
| lower, upper = self.span(index) | |
| value = lower+(extra or 0) | |
| if value>upper: | |
| raise ValueError('value: extra out of range') | |
| return value | |
| def span(self, index): | |
| """Give the range of possible values in a tuple | |
| Useful for mnemonic and explanation | |
| """ | |
| lower = self.value0+sum(1<<x for x in self.extraTable[:index]) | |
| upper = lower+(1<<self.extraTable[index]) | |
| return lower, upper-1 | |
| #======================Code subclasses====================================== | |
| #Alphabets used in the metablock header---------------------------------- | |
| #For prefix codes | |
| class PrefixCodeHeader(WithExtra): | |
| """Header of prefix codes. | |
| """ | |
| def __init__(self, codename): | |
| super().__init__('PFX', bitLength=2) | |
| #this is the name of the code that it describes | |
| self.codename = codename | |
| def extraBits(self, index): | |
| return 2 if index==1 else 0 | |
| def value(self, index, extra): | |
| """Returns ('Simple', #codewords) or ('Complex', HSKIP) | |
| """ | |
| if index==1: | |
| if extra>3: | |
| raise ValueError('value: extra out of range') | |
| return 'Simple', extra+1 | |
| if extra: | |
| raise ValueError('value: extra out of range') | |
| return 'Complex', index | |
| def explanation(self, index, extra): | |
| if index==1: | |
| return '{} is simple with {} code word{}'.format( | |
| self.codename, extra+1, 's' if extra else '') | |
| lengths = [1, 2, 3, 4, 0, 5, 17, 6] | |
| return '{} is complex with lengths {}...'.format( | |
| self.codename, | |
| ','.join( | |
| map(str, lengths[index:index+5])) | |
| ) | |
| class TreeShapeAlphabet(BoolCode): | |
| """The bit used to indicate if four word code is "deep" or "wide" | |
| """ | |
| name = 'SHAPE' | |
| def value(self, index): | |
| return [(2,2,2,2), (1,2,3,3)][index] | |
| def explanation(self, index): | |
| return str(bool(index))+': lengths {},{},{},{}'.format(*self.value(index)) | |
| class LengthOfLengthAlphabet(Code): | |
| """For use in decoding complex code descriptors. | |
| >>> lengthOfLengthAlphabet = LengthOfLengthAlphabet('') | |
| >>> print(lengthOfLengthAlphabet[2]) | |
| coded with 2 bits | |
| >>> len(lengthOfLengthAlphabet[0]) | |
| 2 | |
| >>> [len(lengthOfLengthAlphabet[x]) for x in range(6)] | |
| [2, 4, 3, 2, 2, 4] | |
| >>> lengthOfLengthAlphabet.showCode() | |
| 00:skipped 01:coded with 4 bits 0111:coded with 1 bits | |
| 10:coded with 3 bits 011:coded with 2 bits 1111:coded with 5 bits | |
| """ | |
| decodeTable = { | |
| 0b00:0, 0b10:3, | |
| 0b0111:1, 0b01:4, | |
| 0b011:2, 0b1111:5, | |
| } | |
| def __init__(self, name=None, **args): | |
| super().__init__(name, decodeTable=self.decodeTable, **args) | |
| def mnemonic(self, index): | |
| if index==0: return 'skipped' | |
| return 'coded with {} bits'.format(index) | |
| def explanation(self, index, extra=None): | |
| return self.description+': '+self.mnemonic(index) | |
| class LengthAlphabet(WithExtra): | |
| """Length of symbols | |
| Used during construction of a code. | |
| """ | |
| def __init__(self, name): | |
| super().__init__(name, alphabetSize=18) | |
| def extraBits(self, index): | |
| return {16:2, 17:3}.get(index, 0) | |
| def mnemonic(self, index): | |
| if index==0: return 'unused' | |
| elif index==16: return 'rep xx' | |
| elif index==17: return 'zero xxx' | |
| else: return 'len {}'.format(index) | |
| def explanation(self, index, extra): | |
| return self.description.format(self[index], extra) | |
| def value(self, index, extra): | |
| #the caller got the length already, so extra is enough | |
| return extra | |
| #Stream header | |
| class WindowSizeAlphabet(Code): | |
| """The alphabet used for window size in the stream header. | |
| >>> WindowSizeAlphabet()[10].explanation() | |
| 'windowsize=(1<<10)-16=1008' | |
| """ | |
| decodeTable = { | |
| 0b0100001: 10, 0b1100001: 14, 0b0011: 18, 0b1011: 22, | |
| 0b0110001: 11, 0b1110001: 15, 0b0101: 19, 0b1101: 23, | |
| 0b1000001: 12, 0b0: 16, 0b0111: 20, 0b1111: 24, | |
| 0b1010001: 13, 0b0000001: 17, 0b1001: 21, | |
| 0b0010001: None, | |
| } | |
| name = 'WSIZE' | |
| def __init__(self, name=None): | |
| super().__init__(name, decodeTable=self.decodeTable) | |
| def value(self, index): | |
| #missing value gives index None | |
| if index is None: return None | |
| return (1<<index)-16 | |
| def explanation(self, index): | |
| return 'windowsize=(1<<{})-16={}'.format( | |
| index, (1<<index)-16) | |
| #Metablock | |
| class MetablockLengthAlphabet(WithExtra): | |
| """Used for the meta block length; | |
| also indicates a block with no data | |
| >>> metablockLengthAlphabet = MetablockLengthAlphabet() | |
| >>> metablockLengthAlphabet[0]; str(metablockLengthAlphabet[0]) | |
| Symbol(MLEN, 0) | |
| 'empty' | |
| >>> metablockLengthAlphabet[3] | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: No symbol MetablockLengthAlphabet[3] | |
| >>> print(metablockLengthAlphabet[4]) | |
| hhhh00 | |
| >>> metablockLengthAlphabet[4].value(0x1000) | |
| 4097 | |
| >>> metablockLengthAlphabet[5].value(0x1000) | |
| Traceback (most recent call last): | |
| ... | |
| InvalidStream: Zeros in high nibble of MLEN | |
| >>> metablockLengthAlphabet[5].explanation(0x12345) | |
| 'data length: 12345h+1=74566' | |
| >>> metablockLengthAlphabet.showCode() | |
| 00:hhhh00 10:hhhhhh10 01:hhhhh01 11:empty | |
| """ | |
| decodeTable = {0b11:0, 0b00:4, 0b01:5, 0b10:6} | |
| name = 'MLEN' | |
| def __init__(self, name=None): | |
| super().__init__(name, decodeTable=self.decodeTable) | |
| def extraBits(self, index): | |
| return index*4 | |
| def mnemonic(self, index): | |
| if index==0: return 'empty' | |
| return 'h'*(self.extraBits(index)//4)+self.bitPattern(index) | |
| def value(self, index, extra): | |
| extraBits = self.extraBits(index) | |
| if not 0<=extra<1<<extraBits: | |
| raise ValueError('value: extra out of range') | |
| if index==0: return 0 | |
| if index>4 and extra>>extraBits-4==0: raise InvalidStream( | |
| 'Zeros in high nibble of MLEN') | |
| return extra+1 | |
| def explanation(self, index, extra): | |
| if index==0: return '11: empty block' | |
| extraBits = self.extraBits(index) | |
| return 'data length: {:0{}x}h+1={}'.format(extra, extraBits//4, extra+1) | |
| class ReservedAlphabet(BoolCode): | |
| """The reserved bit that must be zero. | |
| """ | |
| name = 'RSVD' | |
| def value(self, index): | |
| if index: raise ValueError('Reserved bit is not zero') | |
| def explanation(self, index): | |
| return 'Reserved (must be zero)' | |
| class FillerAlphabet(Code): | |
| def __init__(self, *, streamPos): | |
| super().__init__('SKIP', bitLength=(-streamPos)&7) | |
| def explanation(self, index): | |
| return '{} bit{} ignored'.format( | |
| self.length(index), | |
| '' if self.length(index)==1 else 's', | |
| ) | |
| class SkipLengthAlphabet(WithExtra): | |
| """Used for the skip length in an empty metablock | |
| >>> skipLengthAlphabet = SkipLengthAlphabet() | |
| >>> skipLengthAlphabet[0]; str(skipLengthAlphabet[0]) | |
| Symbol(SKIP, 0) | |
| 'empty' | |
| >>> skipLengthAlphabet[4] | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: index out of range | |
| >>> print(skipLengthAlphabet[3]) | |
| hhhhhh11 | |
| >>> skipLengthAlphabet[2].value(0x1000) | |
| 4097 | |
| >>> skipLengthAlphabet[3].value(0x1000) | |
| Traceback (most recent call last): | |
| ... | |
| InvalidStream: Zeros in high byte of SKIPBYTES | |
| >>> skipLengthAlphabet[3].explanation(0x12345) | |
| 'skip length: 12345h+1=74566' | |
| >>> skipLengthAlphabet.showCode() | |
| 00:empty 01:hh01 10:hhhh10 11:hhhhhh11 | |
| """ | |
| def __init__(self): | |
| super().__init__('SKIP', bitLength=2) | |
| def extraBits(self, index): | |
| return index*8 | |
| def mnemonic(self, index): | |
| if index==0: return 'empty' | |
| return 'h'*(self.extraBits(index)//4)+self.bitPattern(index) | |
| def value(self, index, extra): | |
| extraBits = self.extraBits(index) | |
| if not 0<=extra<1<<extraBits: | |
| raise ValueError('value: extra out of range') | |
| if index==0: return 0 | |
| if index>1 and extra>>extraBits-8==0: | |
| raise InvalidStream('Zeros in high byte of SKIPBYTES') | |
| return extra+1 | |
| def explanation(self, index, extra): | |
| if index==0: return '00: no skip' | |
| extraBits = self.extraBits(index) | |
| return 'skip length: {:{}x}h+1={}'.format(extra, extraBits//8, extra+1) | |
| class TypeCountAlphabet(Enumerator): | |
| """Used for giving block type counts and tree counts. | |
| >>> TypeCountAlphabet(description='').showCode() | |
| 0:0 0101:xx,0101 1011:xxxxx,1011 | |
| 0001:0001 1101:xxxxxx,1101 0111:xxx,0111 | |
| 1001:xxxx,1001 0011:x,0011 1111:xxxxxxx,1111 | |
| """ | |
| decodeTable = { | |
| 0b0: 0, 0b1001: 5, | |
| 0b0001: 1, 0b1011: 6, | |
| 0b0011: 2, 0b1101: 7, | |
| 0b0101: 3, 0b1111: 8, | |
| 0b0111: 4, | |
| } | |
| value0 = 1 | |
| extraTable = [0, 0, 1, 2, 3, 4, 5, 6, 7] | |
| name = 'BT#' | |
| def __init__(self, name=None, *, description): | |
| super().__init__( | |
| name, | |
| decodeTable=self.decodeTable, | |
| description=description) | |
| def mnemonic(self, index): | |
| if index==0: return '0' | |
| if index==1: return '0001' | |
| return 'x'*(self.extraBits(index))+','+self.bitPattern(index) | |
| def explanation(self, index, extra): | |
| value = self.value(index, extra) | |
| description = self.description | |
| if value==1: description = description[:-1] | |
| return '{}: {} {}'.format( | |
| self.mnemonic(index), | |
| value, | |
| description) | |
| class BlockTypeAlphabet(Code): | |
| """The block types; this code works for all three kinds. | |
| >>> b = BlockTypeAlphabet('T', NBLTYPES=5) | |
| >>> print(*(x for x in b)) | |
| prev +1 #0 #1 #2 #3 #4 | |
| """ | |
| def __init__(self, name, NBLTYPES, **args): | |
| super().__init__(name, alphabetSize=NBLTYPES+2, **args) | |
| self.NBLTYPES = NBLTYPES | |
| def mnemonic(self, index): | |
| if index==0: return 'prev' | |
| elif index==1: return '+1' | |
| else: return '#'+str(index-2) | |
| def value(self, index): | |
| return index-2 | |
| def explanation(self, index): | |
| if index==0: return '0: previous' | |
| elif index==1: return '1: increment' | |
| else: return 'Set block type to: '+str(index-2) | |
| class BlockCountAlphabet(Enumerator): | |
| """Block counts | |
| >>> b = BlockCountAlphabet('L') | |
| >>> print(b[25]) | |
| [24*x]: BC16625-16793840 | |
| """ | |
| value0 = 1 | |
| extraTable = [2,2,2,2,3, 3,3,3,4,4, 4,4,5,5,5, 5,6,6,7,8, 9,10,11,12,13, 24] | |
| def __init__(self, name, **args): | |
| super().__init__(name, alphabetSize=26, **args) | |
| def mnemonic(self, index): | |
| extraBits = self.extraBits(index) | |
| return '{}: BC{}-{}'.format( | |
| 'x'*extraBits if index<5 else '[{}*x]'.format(extraBits), | |
| *self.span(index)) | |
| def explanation(self, index, extra): | |
| return 'Block count: '+super().explanation(index, extra) | |
| class DistanceParamAlphabet(WithExtra): | |
| """The distance parameters NPOSTFIX and NDIRECT. | |
| Although these are treated as two in the description, this is easier. | |
| """ | |
| def __init__(self): | |
| super().__init__('DIST', bitLength=2) | |
| def extraBits(self, index): | |
| return 4 | |
| def value(self, index, extra): | |
| """Returns NPOSTFIX and NDIRECT<<NPOSTFIX | |
| """ | |
| if extra>15: | |
| raise ValueError('value: extra out of range') | |
| return index, extra<<index | |
| def explanation(self, index, extra): | |
| return '{} postfix bits and {:04b}<<{}={} direct codes'.format( | |
| index, extra, index, extra<<index) | |
| def mnemonic(self, index): | |
| return 'PF'+str(index) | |
| class LiteralContextMode(Code): | |
| """For the literal context modes. | |
| >>> LiteralContextMode().showCode() | |
| 00:LSB6 01:MSB6 10:UTF8 11:Signed | |
| >>> LiteralContextMode().explanation(2) | |
| 'Context mode for type 9: 2(UTF8)' | |
| """ | |
| def __init__(self, *, number=9): | |
| super().__init__('LC'+str(number), bitLength=2) | |
| self.number = number | |
| def mnemonic(self, index): | |
| return ['LSB6', 'MSB6', 'UTF8', 'Signed'][index] | |
| def explanation(self, index): | |
| return 'Context mode for type {}: {}({})'.format( | |
| self.number, | |
| index, | |
| self.mnemonic(index)) | |
| class RLEmaxAlphabet(Enumerator): | |
| """Used for describing the run length encoding used for describing context maps. | |
| >>> RLEmaxAlphabet().showCode() | |
| 0:1 1:more | |
| """ | |
| value0 = 0 | |
| extraTable = [0, 4] | |
| name = 'RLE#' | |
| def mnemonic(self, index): | |
| return ['1', 'more'][index] | |
| def explanation(self, index, extra): | |
| description = self.description and self.description+': ' | |
| if index==0: return description+'No RLE coding' | |
| return '{}xxxx 1: RLEMAX={}'.format(description, extra+1) | |
| class TreeAlphabet(WithExtra): | |
| """The alphabet to enumerate entries (called trees) in the context map. | |
| parameters are RLEMAX and NTREES | |
| >>> t = TreeAlphabet('', RLEMAX=3, NTREES=5) | |
| >>> len(t) | |
| 8 | |
| >>> print(t[2]) | |
| xx+4 zeroes | |
| >>> t[3].explanation(2) | |
| '8+010=10 zeroes' | |
| >>> t[0].value(0) | |
| (1, 0) | |
| """ | |
| name = 'CMI' | |
| def __init__(self, name=None, *, RLEMAX, NTREES, **args): | |
| super().__init__(name, alphabetSize=RLEMAX+NTREES, **args) | |
| self.RLEMAX = RLEMAX | |
| self.NTREES = NTREES | |
| def extraBits(self, index): | |
| if 0<index<=self.RLEMAX: return index | |
| return 0 | |
| def mnemonic(self, index): | |
| if index==0: return 'map #0' | |
| if index<=self.RLEMAX: | |
| return '{}+{} zeroes'.format('x'*index, 1<<index) | |
| return 'map #{}'.format(index-self.RLEMAX) | |
| def value(self, index, extra): | |
| """Give count and value.""" | |
| index = index | |
| if index==0: return 1, 0 | |
| if index<=self.RLEMAX: return (1<<index)+extra, 0 | |
| return 1, index-self.RLEMAX | |
| def explanation(self, index, extra): | |
| description = self.description and self.description+': ' | |
| if index==0: return description+'map #0' | |
| if index<=self.RLEMAX: | |
| return '{}+{:0{}b}={} zeroes'.format( | |
| (1<<index), | |
| extra, self.extraBits(index), | |
| (1<<index)+extra) | |
| return '{}map #{}-{}={}'.format( | |
| description, | |
| index, self.RLEMAX, index-self.RLEMAX) | |
| #Prefix alphabets for the data stream---------------------------------- | |
| class LiteralAlphabet(Code): | |
| """Alphabet of symbols. | |
| """ | |
| minLength = maxLength = 8 | |
| def __init__(self, number): | |
| super().__init__('L'+str(number), alphabetSize=1<<8) | |
| def mnemonic(self, index): | |
| return outputCharFormatter(index) | |
| def value(self, index, extra=None): | |
| return index | |
| def explanation(self, index, extra=None): | |
| return self.mnemonic(index) | |
| class InsertLengthAlphabet(Enumerator): | |
| """Intern code for insert counts | |
| """ | |
| value0 = 0 | |
| extraTable = [0,0,0,0,0, 0,1,1,2,2, 3,3,4,4,5, 5,6,7,8,9, 10,12,14,24] | |
| class CopyLengthAlphabet(Enumerator): | |
| value0 = 2 | |
| extraTable = [0,0,0,0,0, 0,0,0,1,1, 2,2,3,3,4, 4,5,5,6,7, 8,9,10,24] | |
| class InsertAndCopyAlphabet(WithExtra): | |
| """The insert and copy code | |
| >>> for x in range(0,704,704//13): | |
| ... print('{:10b}'.format(x), InsertAndCopyAlphabet()[x]) | |
| 0 I0C2&D=0 | |
| 110110 I6+xC8&D=0 | |
| 1101100 I5C22+xxx&D=0 | |
| 10100010 I4C4 | |
| 11011000 I3C10+x | |
| 100001110 I14+xxC8 | |
| 101000100 I10+xxC22+xxx | |
| 101111010 I98+xxxxxC14+xx | |
| 110110000 I6+xC70+xxxxx | |
| 111100110 I1090+[10*x]C8 | |
| 1000011100 I26+xxxC326+[8*x] | |
| 1001010010 I322+[8*x]C14+xx | |
| 1010001000 I194+[7*x]C70+xxxxx | |
| 1010111110 I22594+[24*x]C1094+[10*x] | |
| """ | |
| insertLengthAlphabet = InsertLengthAlphabet(None) | |
| copyLengthAlphabet = CopyLengthAlphabet(None) | |
| def __init__(self, number=''): | |
| super().__init__('IC'+str(number), bitLength=10) | |
| def __len__(self): | |
| return 704 | |
| def extraBits(self, index): | |
| insertSymbol, copySymbol, dist0 = self.splitSymbol(index) | |
| return InsertLengthAlphabet.extraTable[insertSymbol.index] + \ | |
| CopyLengthAlphabet.extraTable[copySymbol.index] | |
| def splitSymbol(self, index): | |
| """Give relevant values for computations: | |
| (insertSymbol, copySymbol, dist0flag) | |
| """ | |
| #determine insert and copy upper bits from table | |
| row = [0,0,1,1,2,2,1,3,2,3,3][index>>6] | |
| col = [0,1,0,1,0,1,2,0,2,1,2][index>>6] | |
| #determine inserts and copy sub codes | |
| insertLengthCode = row<<3 | index>>3&7 | |
| if row: insertLengthCode -= 8 | |
| copyLengthCode = col<<3 | index&7 | |
| return ( | |
| Symbol(self.insertLengthAlphabet, insertLengthCode), | |
| Symbol(self.copyLengthAlphabet, copyLengthCode), | |
| row==0 | |
| ) | |
| def mnemonic(self, index): | |
| """Make a nice mnemonic | |
| """ | |
| i,c,d0 = self.splitSymbol(index) | |
| iLower, _ = i.code.span(i.index) | |
| iExtra = i.extraBits() | |
| cLower, _ = c.code.span(c.index) | |
| cExtra = c.extraBits() | |
| return 'I{}{}{}C{}{}{}{}'.format( | |
| iLower, | |
| '+' if iExtra else '', | |
| 'x'*iExtra if iExtra<6 else '[{}*x]'.format(iExtra), | |
| cLower, | |
| '+' if cExtra else '', | |
| 'x'*cExtra if cExtra<6 else '[{}*x]'.format(cExtra), | |
| '&D=0' if d0 else '') | |
| def value(self, index, extra): | |
| i,c,d0 = self.splitSymbol(index) | |
| iExtra = i.extraBits() | |
| ce, ie = extra>>iExtra, extra&(1<<iExtra)-1 | |
| insert = i.value(ie) | |
| copy = c.value(ce) | |
| return insert, copy, d0 | |
| def explanation(self, index, extra): | |
| insert, copy, d0 = self.value(index, extra) | |
| if d0: return 'Literal: {}, copy: {}, same distance'.format(insert, copy) | |
| else: return 'Literal: {}, copy: {}'.format(insert, copy) | |
| class DistanceAlphabet(WithExtra): | |
| """Represent the distance encoding. | |
| Dynamically generated alphabet. | |
| This is what the documentation should have said: | |
| Ignoring offsets for the moment, the "long" encoding works as follows: | |
| Write the distance in binary as follows: | |
| 1xy..yz..z, then the distance symbol consists of n..nxz..z | |
| Where: | |
| n is one less than number of bits in y | |
| x is a single bit | |
| y..y are n+1 extra bits (encoded in the bit stream) | |
| z..z is NPOSTFIX bits that are part of the symbol | |
| The offsets are so as to start at the lowest useable value: | |
| if 1xyyyyz = distance +(4<<POSTFIX)-NDIRECT-1 | |
| then n..nxz..z is symbol -NDIRECT-16 | |
| >>> d = DistanceAlphabet('D', NPOSTFIX=2, NDIRECT=10) | |
| >>> print(d[4], d[17], d[34]) | |
| last-1 1 10xx00-5 | |
| >>> [str(d[x]) for x in range(26, 32)] | |
| ['10x00-5', '10x01-5', '10x10-5', '10x11-5', '11x00-5', '11x01-5'] | |
| """ | |
| def __init__(self, number, *, NPOSTFIX, NDIRECT): | |
| self.NPOSTFIX = NPOSTFIX | |
| self.NDIRECT = NDIRECT | |
| #set length | |
| #Actually, not all symbols are used, | |
| #only NDIRECT+16+(44-2*POSTFIX<<NPOSTFIX) | |
| super().__init__('D'+str(number), | |
| alphabetSize=self.NDIRECT+16+(48<<self.NPOSTFIX)) | |
| def extraBits(self, index): | |
| """Indicate how many extra bits are needed to interpret symbol | |
| >>> d = DistanceAlphabet('D', NPOSTFIX=2, NDIRECT=10) | |
| >>> [d[i].extraBits() for i in range(26)] | |
| [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] | |
| >>> [d[i].extraBits() for i in range(26,36)] | |
| [1, 1, 1, 1, 1, 1, 1, 1, 2, 2] | |
| """ | |
| if index<16+self.NDIRECT: return 0 | |
| return 1 + ((index - self.NDIRECT - 16) >> (self.NPOSTFIX + 1)) | |
| def value(self, dcode, dextra): | |
| """Decode value of symbol together with the extra bits. | |
| >>> d = DistanceAlphabet('D', NPOSTFIX=2, NDIRECT=10) | |
| >>> d[34].value(2) | |
| (0, 35) | |
| """ | |
| if dcode<16: | |
| return [(1,0),(2,0),(3,0),(4,0), | |
| (1,-1),(1,+1),(1,-2),(1,+2),(1,-3),(1,+3), | |
| (2,-1),(2,+1),(2,-2),(2,+2),(2,-3),(2,+3) | |
| ][dcode] | |
| if dcode<16+self.NDIRECT: | |
| return (0,dcode-16) | |
| #we use the original formulas, instead of my clear explanation | |
| POSTFIX_MASK = (1 << self.NPOSTFIX) - 1 | |
| ndistbits = 1 + ((dcode - self.NDIRECT - 16) >> (self.NPOSTFIX + 1)) | |
| hcode = (dcode - self.NDIRECT - 16) >> self.NPOSTFIX | |
| lcode = (dcode - self.NDIRECT - 16) & POSTFIX_MASK | |
| offset = ((2 + (hcode & 1)) << ndistbits) - 4 | |
| distance = ((offset + dextra) << self.NPOSTFIX) + lcode + self.NDIRECT + 1 | |
| return (0,distance) | |
| def mnemonic(self, index, verbose=False): | |
| """Give mnemonic representation of meaning. | |
| verbose compresses strings of x's | |
| """ | |
| if index<16: | |
| return ['last', '2last', '3last', '4last', | |
| 'last-1', 'last+1', 'last-2', 'last+2', 'last-3', 'last+3', | |
| '2last-1', '2last+1', '2last-2', '2last+2', '2last-3', '2last+3' | |
| ][index] | |
| if index<16+self.NDIRECT: | |
| return str(index-16) | |
| #construct strings like "1xx01-15" | |
| index -= self.NDIRECT+16 | |
| hcode = index >> self.NPOSTFIX | |
| lcode = index & (1<<self.NPOSTFIX)-1 | |
| if self.NPOSTFIX: formatString = '1{0}{1}{2:0{3}b}{4:+d}' | |
| else: formatString = '1{0}{1}{4:+d}' | |
| return formatString.format( | |
| hcode&1, | |
| 'x'*(2+hcode>>1) if hcode<13 or verbose else '[{}*x]'.format(2+hcode>>1), | |
| lcode, self.NPOSTFIX, | |
| self.NDIRECT+1-(4<<self.NPOSTFIX)) | |
| def explanation(self, index, extra): | |
| """ | |
| >>> d = DistanceAlphabet('D', NPOSTFIX=2, NDIRECT=10) | |
| >>> d[55].explanation(13) | |
| '11[1101]01-5: [0]+240' | |
| """ | |
| extraBits = self.extraBits(index) | |
| extraString = '[{:0{}b}]'.format(extra, extraBits) | |
| return '{0}: [{1[0]}]{1[1]:+d}'.format( | |
| self.mnemonic(index, True).replace('x'*(extraBits or 1), extraString), | |
| self.value(index, extra)) | |
| #Classes for doing actual work------------------------------------------ | |
| class ContextModeKeeper: | |
| """For computing the literal context mode. | |
| You feed it characters, and it computes indices in the context map. | |
| """ | |
| def __init__(self, mode): | |
| self.chars = deque([0,0], maxlen=2) | |
| self.mode = mode | |
| def setContextMode(self, mode): | |
| """Switch to given context mode (0..3)""" | |
| self.mode = mode | |
| def getIndex(self): | |
| if self.mode==0: #LSB6 | |
| return self.chars[1]&0x3f | |
| elif self.mode==1: #MSB6 | |
| return self.chars[1]>>2 | |
| elif self.mode==2: #UTF8: character class of previous and a bit of the second | |
| p2,p1 = self.chars | |
| return self.lut0[p1]|self.lut1[p2] | |
| elif self.mode==3: #Signed: initial bits of last two bytes | |
| p2,p1 = self.chars | |
| return self.lut2[p1]<<3|self.lut2[p2] | |
| def add(self, index): | |
| """Adjust the context for output char (as int).""" | |
| self.chars.append(index) | |
| #0: control #16: quote #32: ,:; #48: AEIOU | |
| #4: tab/lf/cr #20: % #36: . #52: BC..Z | |
| #8: space #24: (<[{ #40: = #56: aeiou | |
| #12:!#$&*+-/?@| #28: )>]} #44: 0-9 #60: bc..z | |
| lut0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 4, 0, 0, | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 8, 12, 16, 12, 12, 20, 12, 16, 24, 28, 12, 12, 32, 12, 36, 12, | |
| 44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 32, 32, 24, 40, 28, 12, | |
| 12, 48, 52, 52, 52, 48, 52, 52, 52, 48, 52, 52, 52, 52, 52, 48, | |
| 52, 52, 52, 52, 52, 48, 52, 52, 52, 52, 52, 24, 12, 28, 12, 12, | |
| 12, 56, 60, 60, 60, 56, 60, 60, 60, 56, 60, 60, 60, 60, 60, 56, | |
| 60, 60, 60, 60, 60, 56, 60, 60, 60, 60, 60, 24, 12, 28, 12, 0 | |
| ]+[0,1]*32+[2,3]*32 | |
| #0: space 1:punctuation 2:digit/upper 3:lower | |
| lut1 = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
| 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, | |
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, | |
| 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, | |
| 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, | |
| 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, | |
| 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 0 | |
| ]+[0]*96+[2]*32 | |
| #initial bits: 8*0, 4*0, 2*0, 1*0, 1*1, 2*1, 4*1, 8*1 | |
| lut2 = [0]+[1]*15+[2]*48+[3]*64+[4]*64+[5]*48+[6]*15+[7] | |
| assert len(lut0)==len(lut1)==len(lut2)==256 | |
| class WordList: | |
| """Word list. | |
| >>> WordList().word(7, 35555) | |
| b'Program to ' | |
| """ | |
| NDBITS = [0, 0, 0, 0, 10, 10, 11, 11, 10, 10, | |
| 10, 10, 10, 9, 9, 8, 7, 7, 8, 7, | |
| 7, 6, 6, 5, 5] | |
| def __init__(self): | |
| self.file = open(DICTIONARY_PATH, 'rb') | |
| self.compileActions() | |
| def word(self, size, dist): | |
| """Get word | |
| """ | |
| #split dist in index and action | |
| ndbits = self.NDBITS[size] | |
| index = dist&(1<<ndbits)-1 | |
| action = dist>>ndbits | |
| #compute position in file | |
| position = sum(n<<self.NDBITS[n] for n in range(4,size))+size*index | |
| self.file.seek(position) | |
| return self.doAction(self.file.read(size), action) | |
| def upperCase1(self, word): | |
| word = word.decode('utf8') | |
| word = word[0].upper()+word[1:] | |
| return word.encode('utf8') | |
| # Super compact form of action table. | |
| # _ means space, .U means UpperCaseAll, U(w) means UpperCaseFirst | |
| # typo:off | |
| actionTable = r""" | |
| 0:w 25:w+_for_ 50:w+\n\t 75:w+. This_100:w+ize_ | |
| 1:w+_ 26:w[3:] 51:w+: 76:w+, 101:w.U+. | |
| 2:_+w+_ 27:w[:-2] 52:_+w+._ 77:.+w+_ 102:\xc2\xa0+w | |
| 3:w[1:] 28:w+_a_ 53:w+ed_ 78:U(w)+( 103:_+w+, | |
| 4:U(w)+_ 29:w+_that_ 54:w[9:] 79:U(w)+. 104:U(w)+=" | |
| 5:w+_the_ 30:_+U(w) 55:w[7:] 80:w+_not_ 105:w.U+=" | |
| 6:_+w 31:w+._ 56:w[:-6] 81:_+w+=" 106:w+ous_ | |
| 7:s_+w+_ 32:.+w 57:w+( 82:w+er_ 107:w.U+,_ | |
| 8:w+_of_ 33:_+w+,_ 58:U(w)+,_ 83:_+w.U+_ 108:U(w)+=\' | |
| 9:U(w) 34:w[4:] 59:w[:-8] 84:w+al_ 109:_+U(w)+, | |
| 10:w+_and_ 35:w+_with_ 60:w+_at_ 85:_+w.U 110:_+w.U+=" | |
| 11:w[2:] 36:w+\' 61:w+ly_ 86:w+=\' 111:_+w.U+,_ | |
| 12:w[:-1] 37:w+_from_ 62:_the_+w+_of_ 87:w.U+" 112:_+w.U+, | |
| 13:,_+w+_ 38:w+_by_ 63:w[:-5] 88:U(w)+._ 113:w.U+( | |
| 14:w+,_ 39:w[5:] 64:w[:-9] 89:_+w+( 114:w.U+._ | |
| 15:_+U(w)+_ 40:w[6:] 65:_+U(w)+,_ 90:w+ful_ 115:_+w.U+. | |
| 16:w+_in_ 41:_the_+w 66:U(w)+" 91:_+U(w)+._116:w.U+=\' | |
| 17:w+_to_ 42:w[:-4] 67:.+w+( 92:w+ive_ 117:_+w.U+._ | |
| 18:e_+w+_ 43:w+. The_ 68:w.U+_ 93:w+less_ 118:_+U(w)+=" | |
| 19:w+" 44:w.U 69:U(w)+"> 94:w.U+\' 119:_+w.U+=\' | |
| 20:w+. 45:w+_on_ 70:w+=" 95:w+est_ 120:_+U(w)+=\' | |
| 21:w+"> 46:w+_as_ 71:_+w+. 96:_+U(w)+. | |
| 22:w+\n 47:w+_is_ 72:.com/+w 97:w.U+"> | |
| 23:w[:-3] 48:w[:-7] 98:_+w+=\' | |
| 24:w+] 49:w[:-1]+ing_ 74:U(w)+\' 99:U(w)+, | |
| """ | |
| # typo:on | |
| def compileActions(self): | |
| """Build the action table from the text above | |
| """ | |
| import re | |
| self.actionList = actions = [None]*121 | |
| #Action 73, which is too long, looks like this when expanded: | |
| actions[73] = "b' the '+w+b' of the '" | |
| #find out what the columns are | |
| actionLines = self.actionTable.splitlines() | |
| colonPositions = [m.start() | |
| for m in re.finditer(':',actionLines[1]) | |
| ]+[100] | |
| columns = [(colonPositions[i]-3,colonPositions[i+1]-3) | |
| for i in range(len(colonPositions)-1)] | |
| for line in self.actionTable.splitlines(keepends=False): | |
| for start,end in columns: | |
| action = line[start:end] | |
| #skip empty actions | |
| if not action or action.isspace(): continue | |
| #chop it up, and check if the colon is properly placed | |
| index, colon, action = action[:3], action[3], action[4:] | |
| assert colon==':' | |
| #remove filler spaces at right | |
| action = action.rstrip() | |
| #replace space symbols | |
| action = action.replace('_', ' ') | |
| wPos = action.index('w') | |
| #add quotes around left string when present | |
| #translation: any pattern from beginning, up to | |
| #(but not including) a + following by a w later on | |
| action = re.sub(r"^(.*)(?=\+[U(]*w)", r"b'\1'", action) | |
| #add quotes around right string when present | |
| #translation: anything with a w in it, followed by a + | |
| #and a pattern up to the end | |
| #(there is no variable lookbehind assertion, | |
| #so we have to copy the pattern) | |
| action = re.sub(r"(w[[:\-1\]).U]*)\+(.*)$", r"\1+b'\2'", action) | |
| #expand shortcut for uppercaseAll | |
| action = action.replace(".U", ".upper()") | |
| #store action | |
| actions[int(index)] = action | |
| def doAction(self, w, action): | |
| """Perform the proper action | |
| """ | |
| #set environment for the UpperCaseFirst | |
| U = self.upperCase1 | |
| return eval(self.actionList[action], locals()) | |
| class Layout: | |
| """Class to layout the output. | |
| """ | |
| #display width of hexdata+bitdata | |
| width = 25 | |
| #general | |
| def __init__(self, stream): | |
| self.stream = stream | |
| self.bitPtr = self.width | |
| def makeHexData(self, pos): | |
| """Produce hex dump of all data containing the bits | |
| from pos to stream.pos | |
| """ | |
| firstAddress = pos+7>>3 | |
| lastAddress = self.stream.pos+7>>3 | |
| return ''.join(map('{:02x} '.format, | |
| self.stream.data[firstAddress:lastAddress])) | |
| def formatBitData(self, pos, width1, width2=0): | |
| """Show formatted bit data: | |
| Bytes are separated by commas | |
| whole bytes are displayed in hex | |
| >>> Layout(olleke).formatBitData(6, 2, 16) | |
| '|00h|2Eh,|00' | |
| >>> Layout(olleke).formatBitData(4, 1, 0) | |
| '1' | |
| """ | |
| result = [] | |
| #make empty prefix code explicit | |
| if width1==0: result = ['()', ','] | |
| for width in width1, width2: | |
| #skip empty width2 | |
| if width==0: continue | |
| #build result backwards in a list | |
| while width>0: | |
| availableBits = 8-(pos&7) | |
| if width<availableBits: | |
| #read partial byte, beginning nor ending at boundary | |
| data = self.stream.data[pos>>3] >> (pos&7) & (1<<width)-1 | |
| result.append('{:0{}b}'.format(data, width)) | |
| elif availableBits<8: | |
| #read rest of byte, ending at boundary | |
| data = self.stream.data[pos>>3] >> (pos&7) | |
| result.append('|{:0{}b}'.format(data, availableBits)) | |
| else: | |
| #read whole byte (in hex), beginning and ending at boundary | |
| data = self.stream.data[pos>>3] | |
| result.append('|{:02X}h'.format(data)) | |
| width -= availableBits | |
| pos += availableBits | |
| #if width overshot from the availableBits subtraction, fix it | |
| pos += width | |
| #add comma to separate fields | |
| result.append(',') | |
| #concatenate pieces, reversed, skipping the last space | |
| return ''.join(result[-2::-1]) | |
| def readPrefixCode(self, alphabet): | |
| """give alphabet the prefix code that is read from the stream | |
| Called for the following alphabets, in this order: | |
| The alphabet in question must have a "logical" order, | |
| otherwise the assignment of symbols doesn't work. | |
| """ | |
| mode, numberOfSymbols = self.verboseRead(PrefixCodeHeader(alphabet.name)) | |
| if mode=='Complex': | |
| #for a complex code, numberOfSymbols means hskip | |
| self.readComplexCode(numberOfSymbols, alphabet) | |
| return alphabet | |
| else: | |
| table = [] | |
| #Set table of lengths for mnemonic function | |
| lengths = [[0], [1,1], [1,2,2], '????'][numberOfSymbols-1] | |
| #adjust mnemonic function of alphabet class | |
| def myMnemonic(index): | |
| return '{} bit{}: {}'.format( | |
| lengths[i], | |
| '' if lengths[i]==1 else 's', | |
| alphabet.__class__.mnemonic(alphabet, index) | |
| ) | |
| alphabet.mnemonic = myMnemonic | |
| for i in range(numberOfSymbols): | |
| table.append(self.verboseRead(alphabet, skipExtra=True).index) | |
| #restore mnemonic | |
| del alphabet.mnemonic | |
| if numberOfSymbols==4: | |
| #read tree shape to redefine lengths | |
| lengths = self.verboseRead(TreeShapeAlphabet()) | |
| #construct the alphabet prefix code | |
| alphabet.setLength(dict(zip(table, lengths))) | |
| return alphabet | |
| def readComplexCode(self, hskip, alphabet): | |
| """Read complex code""" | |
| stream = self.stream | |
| #read the lengths for the length code | |
| lengths = [1,2,3,4,0,5,17,6,16,7,8,9,10,11,12,13,14,15][hskip:] | |
| codeLengths = {} | |
| total = 0 | |
| lol = LengthOfLengthAlphabet('##'+alphabet.name) | |
| #lengthCode will be used for coding the lengths of the new code | |
| #we use it for display until now; definition comes below | |
| lengthCode = LengthAlphabet('#'+alphabet.name) | |
| lengthIter = iter(lengths) | |
| lengthsLeft = len(lengths) | |
| while total<32 and lengthsLeft>0: | |
| lengthsLeft -= 1 | |
| newSymbol = next(lengthIter) | |
| lol.description = str(lengthCode[newSymbol]) | |
| length = self.verboseRead(lol) | |
| if length: | |
| codeLengths[newSymbol] = length | |
| total += 32>>length | |
| if total>32: raise ValueError("Stream format") | |
| if len(codeLengths)==1: codeLengths[list(codeLengths.keys())[0]] = 0 | |
| #Now set the encoding of the lengthCode | |
| lengthCode.setLength(codeLengths) | |
| print("***** Lengths for {} will be coded as:".format(alphabet.name)) | |
| lengthCode.showCode() | |
| #Now determine the symbol lengths with the lengthCode | |
| symbolLengths = {} | |
| total = 0 | |
| lastLength = 8 | |
| alphabetIter = iter(alphabet) | |
| while total<32768: | |
| #look ahead to see what is going to happen | |
| length = lengthCode.decodePeek( | |
| self.stream.peek(lengthCode.maxLength))[1].index | |
| #in every branch, set lengthCode.description to explanatory text | |
| #lengthCode calls format(symbol, extra) with this string | |
| if length==0: | |
| symbol = next(alphabetIter) | |
| lengthCode.description = 'symbol {} unused'.format(symbol) | |
| self.verboseRead(lengthCode) | |
| #unused symbol | |
| continue | |
| if length==16: | |
| lengthCode.description = \ | |
| '{1}+3 symbols of length '+str(lastLength) | |
| extra = self.verboseRead(lengthCode) | |
| #scan series of 16s (repeat counts) | |
| #start with repeat count 2 | |
| repeat = 2 | |
| startSymbol = next(alphabetIter) | |
| endSymbol = next(alphabetIter) | |
| symbolLengths[startSymbol.index] = \ | |
| symbolLengths[endSymbol.index] = lastLength | |
| #count the two just defined symbols | |
| total += 2*32768>>lastLength | |
| #note: loop may end because we're there | |
| #even if a 16 _appears_ to follow | |
| while True: | |
| #determine last symbol | |
| oldRepeat = repeat | |
| repeat = (repeat-2<<2)+extra+3 | |
| #read as many symbols as repeat increased | |
| for i in range(oldRepeat, repeat): | |
| endSymbol = next(alphabetIter) | |
| symbolLengths[endSymbol.index] = lastLength | |
| #compute new total; it may be end of loop | |
| total += (repeat-oldRepeat)*32768>>lastLength | |
| if total>=32768: break | |
| #see if there is more to do | |
| length = lengthCode.decodePeek( | |
| self.stream.peek(lengthCode.maxLength))[1].index | |
| if length!=16: break | |
| lengthCode.description = 'total {}+{{1}} symbols'.format( | |
| (repeat-2<<2)+3) | |
| extra = self.verboseRead(lengthCode) | |
| elif length==17: | |
| #read, and show explanation | |
| lengthCode.description = '{1}+3 unused' | |
| extra = self.verboseRead(lengthCode) | |
| #scan series of 17s (groups of zero counts) | |
| #start with repeat count 2 | |
| repeat = 2 | |
| startSymbol = next(alphabetIter) | |
| endSymbol = next(alphabetIter) | |
| #note: loop will not end with total==32768, | |
| #since total doesn't change here | |
| while True: | |
| #determine last symbol | |
| oldRepeat = repeat | |
| repeat = (repeat-2<<3)+extra+3 | |
| #read as many symbols as repeat increases | |
| for i in range(repeat-oldRepeat): | |
| endSymbol = next(alphabetIter) | |
| #see if there is more to do | |
| length = lengthCode.decodePeek( | |
| self.stream.peek(lengthCode.maxLength))[1].index | |
| if length!=17: break | |
| lengthCode.description = 'total {}+{{1}} unused'.format( | |
| (repeat-2<<3)+3) | |
| extra = self.verboseRead(lengthCode) | |
| else: | |
| symbol = next(alphabetIter) | |
| #double braces for format | |
| char = str(symbol) | |
| if char in '{}': char *= 2 | |
| lengthCode.description = \ | |
| 'Length for {} is {{0.index}} bits'.format(char) | |
| #output is not needed (will be 0) | |
| self.verboseRead(lengthCode) | |
| symbolLengths[symbol.index] = length | |
| total += 32768>>length | |
| lastLength = length | |
| assert total==32768 | |
| alphabet.setLength(symbolLengths) | |
| print('End of table. Prefix code '+alphabet.name+':') | |
| alphabet.showCode() | |
| #stream | |
| def processStream(self): | |
| """Process a brotli stream. | |
| """ | |
| print('addr hex{:{}s}binary context explanation'.format( | |
| '', self.width-10)) | |
| print('Stream header'.center(60, '-')) | |
| self.windowSize = self.verboseRead(WindowSizeAlphabet()) | |
| print('Metablock header'.center(60, '=')) | |
| self.ISLAST = False | |
| self.output = bytearray() | |
| while not self.ISLAST: | |
| self.ISLAST = self.verboseRead( | |
| BoolCode('LAST', description="Last block")) | |
| if self.ISLAST: | |
| if self.verboseRead( | |
| BoolCode('EMPTY', description="Empty block")): break | |
| if self.metablockLength(): continue | |
| if not self.ISLAST and self.uncompressed(): continue | |
| print('Block type descriptors'.center(60, '-')) | |
| self.numberOfBlockTypes = {} | |
| self.currentBlockCounts = {} | |
| self.blockTypeCodes = {} | |
| self.blockCountCodes = {} | |
| for blockType in (L,I,D): self.blockType(blockType) | |
| print('Distance code parameters'.center(60, '-')) | |
| self.NPOSTFIX, self.NDIRECT = self.verboseRead(DistanceParamAlphabet()) | |
| self.readLiteralContextModes() | |
| print('Context maps'.center(60, '-')) | |
| self.cmaps = {} | |
| #keep the number of each kind of prefix tree for the last loop | |
| numberOfTrees = {I: self.numberOfBlockTypes[I]} | |
| for blockType in (L,D): | |
| numberOfTrees[blockType] = self.contextMap(blockType) | |
| print('Prefix code lists'.center(60, '-')) | |
| self.prefixCodes = {} | |
| for blockType in (L,I,D): | |
| self.readPrefixArray(blockType, numberOfTrees[blockType]) | |
| self.metablock() | |
| #metablock header | |
| def verboseRead(self, alphabet, context='', skipExtra=False): | |
| """Read symbol and extra from stream and explain what happens. | |
| Returns the value of the symbol | |
| >>> olleke.pos = 0 | |
| >>> l = Layout(olleke) | |
| >>> l.verboseRead(WindowSizeAlphabet()) | |
| 0000 1b 1011 WSIZE windowsize=(1<<22)-16=4194288 | |
| 4194288 | |
| """ | |
| #TODO 2: verbosity level, e.g. show only codes and maps in header | |
| stream = self.stream | |
| pos = stream.pos | |
| if skipExtra: | |
| length, symbol = alphabet.readTuple(stream) | |
| extraBits, extra = 0, None | |
| else: | |
| length, symbol, extraBits, extra = alphabet.readTupleAndExtra( | |
| stream) | |
| #fields: address, hex data, binary data, name of alphabet, explanation | |
| hexdata = self.makeHexData(pos) | |
| addressField = '{:04x}'.format(pos+7>>3) if hexdata else '' | |
| bitdata = self.formatBitData(pos, length, extraBits) | |
| #bitPtr moves bitdata so that the bytes are easier to read | |
| #jump back to right if a new byte starts | |
| if '|' in bitdata[1:]: | |
| #start over on the right side | |
| self.bitPtr = self.width | |
| fillWidth = self.bitPtr-(len(hexdata)+len(bitdata)) | |
| if fillWidth<0: fillWidth = 0 | |
| print('{:<5s} {:<{}s} {:7s} {}'.format( | |
| addressField, | |
| hexdata+' '*fillWidth+bitdata, self.width, | |
| context+alphabet.name, | |
| symbol if skipExtra else symbol.explanation(extra), | |
| )) | |
| #jump to the right if we started with a '|' | |
| #because we didn't jump before printing | |
| if bitdata.startswith('|'): self.bitPtr = self.width | |
| else: self.bitPtr -= len(bitdata) | |
| return symbol if skipExtra else symbol.value(extra) | |
| def metablockLength(self): | |
| """Read MNIBBLES and meta block length; | |
| if empty block, skip block and return true. | |
| """ | |
| self.MLEN = self.verboseRead(MetablockLengthAlphabet()) | |
| if self.MLEN: | |
| return False | |
| #empty block; skip and return False | |
| self.verboseRead(ReservedAlphabet()) | |
| MSKIP = self.verboseRead(SkipLengthAlphabet()) | |
| self.verboseRead(FillerAlphabet(streamPos=self.stream.pos)) | |
| self.stream.pos += 8*MSKIP | |
| print("Skipping to {:x}".format(self.stream.pos>>3)) | |
| return True | |
| def uncompressed(self): | |
| """If true, handle uncompressed data | |
| """ | |
| ISUNCOMPRESSED = self.verboseRead( | |
| BoolCode('UNCMPR', description='Is uncompressed?')) | |
| if ISUNCOMPRESSED: | |
| self.verboseRead(FillerAlphabet(streamPos=self.stream.pos)) | |
| print('Uncompressed data:') | |
| self.output += self.stream.readBytes(self.MLEN) | |
| print(outputFormatter(self.output[-self.MLEN:])) | |
| return ISUNCOMPRESSED | |
| def blockType(self, kind): | |
| """Read block type switch descriptor for given kind of blockType.""" | |
| NBLTYPES = self.verboseRead(TypeCountAlphabet( | |
| 'BT#'+kind[0].upper(), | |
| description='{} block types'.format(kind), | |
| )) | |
| self.numberOfBlockTypes[kind] = NBLTYPES | |
| if NBLTYPES>=2: | |
| self.blockTypeCodes[kind] = self.readPrefixCode( | |
| BlockTypeAlphabet('BT'+kind[0].upper(), NBLTYPES)) | |
| self.blockCountCodes[kind] = self.readPrefixCode( | |
| BlockCountAlphabet('BC'+kind[0].upper())) | |
| blockCount = self.verboseRead(self.blockCountCodes[kind]) | |
| else: | |
| blockCount = 1<<24 | |
| self.currentBlockCounts[kind] = blockCount | |
| def readLiteralContextModes(self): | |
| """Read literal context modes. | |
| LSB6: lower 6 bits of last char | |
| MSB6: upper 6 bits of last char | |
| UTF8: roughly dependent on categories: | |
| upper 4 bits depend on category of last char: | |
| control/whitespace/space/ punctuation/quote/%/open/close/ | |
| comma/period/=/digits/ VOWEL/CONSONANT/vowel/consonant | |
| lower 2 bits depend on category of 2nd last char: | |
| space/punctuation/digit or upper/lowercase | |
| signed: hamming weight of last 2 chars | |
| """ | |
| print('Context modes'.center(60, '-')) | |
| self.literalContextModes = [] | |
| for i in range(self.numberOfBlockTypes[L]): | |
| self.literalContextModes.append( | |
| self.verboseRead(LiteralContextMode(number=i))) | |
| def contextMap(self, kind): | |
| """Read context maps | |
| Returns the number of different values on the context map | |
| (In other words, the number of prefix trees) | |
| """ | |
| NTREES = self.verboseRead(TypeCountAlphabet( | |
| kind[0].upper()+'T#', | |
| description='{} prefix trees'.format(kind))) | |
| mapSize = {L:64, D:4}[kind] | |
| if NTREES<2: | |
| self.cmaps[kind] = [0]*mapSize | |
| else: | |
| #read CMAPkind | |
| RLEMAX = self.verboseRead(RLEmaxAlphabet( | |
| 'RLE#'+kind[0].upper(), | |
| description=kind+' context map')) | |
| alphabet = TreeAlphabet('CM'+kind[0].upper(), NTREES=NTREES, RLEMAX=RLEMAX) | |
| cmapCode = self.readPrefixCode(alphabet) | |
| tableSize = mapSize*self.numberOfBlockTypes[kind] | |
| cmap = [] | |
| while len(cmap)<tableSize: | |
| cmapCode.description = 'map {}, entry {}'.format( | |
| *divmod(len(cmap), mapSize)) | |
| count, value = self.verboseRead(cmapCode) | |
| cmap.extend([value]*count) | |
| assert len(cmap)==tableSize | |
| IMTF = self.verboseRead(BoolCode('IMTF', description='Apply inverse MTF')) | |
| if IMTF: | |
| self.IMTF(cmap) | |
| if kind==L: | |
| print('Context maps for literal data:') | |
| for i in range(0, len(cmap), 64): | |
| print(*( | |
| ''.join(map(str, cmap[j:j+8])) | |
| for j in range(i, i+64, 8) | |
| )) | |
| else: | |
| print('Context map for distances:') | |
| print(*( | |
| ''.join(map('{:x}'.format, cmap[i:i+4])) | |
| for i in range(0, len(cmap), 4) | |
| )) | |
| self.cmaps[kind] = cmap | |
| return NTREES | |
| @staticmethod | |
| def IMTF(v): | |
| """In place inverse move to front transform. | |
| """ | |
| #mtf is initialized virtually with range(infinity) | |
| mtf = [] | |
| for i, vi in enumerate(v): | |
| #get old value from mtf. If never seen, take virtual value | |
| try: value = mtf.pop(vi) | |
| except IndexError: value = vi | |
| #put value at front | |
| mtf.insert(0, value) | |
| #replace transformed value | |
| v[i] = value | |
| def readPrefixArray(self, kind, numberOfTrees): | |
| """Read prefix code array""" | |
| prefixes = [] | |
| for i in range(numberOfTrees): | |
| if kind==L: alphabet = LiteralAlphabet(i) | |
| elif kind==I: alphabet = InsertAndCopyAlphabet(i) | |
| elif kind==D: alphabet = DistanceAlphabet( | |
| i, NPOSTFIX=self.NPOSTFIX, NDIRECT=self.NDIRECT) | |
| self.readPrefixCode(alphabet) | |
| prefixes.append(alphabet) | |
| self.prefixCodes[kind] = prefixes | |
| #metablock data | |
| def metablock(self): | |
| """Process the data. | |
| Relevant variables of self: | |
| numberOfBlockTypes[kind]: number of block types | |
| currentBlockTypes[kind]: current block types (=0) | |
| literalContextModes: the context modes for the literal block types | |
| currentBlockCounts[kind]: counters for block types | |
| blockTypeCodes[kind]: code for block type | |
| blockCountCodes[kind]: code for block count | |
| cmaps[kind]: the context maps (not for I) | |
| prefixCodes[kind][#]: the prefix codes | |
| lastDistances: the last four distances | |
| lastChars: the last two chars | |
| output: the result | |
| """ | |
| print('Meta block contents'.center(60, '=')) | |
| self.currentBlockTypes = {L:0, I:0, D:0, pL:1, pI:1, pD:1} | |
| self.lastDistances = deque([17,16,11,4], maxlen=4) | |
| #the current context mode is for block type 0 | |
| self.contextMode = ContextModeKeeper(self.literalContextModes[0]) | |
| wordList = WordList() | |
| #setup distance callback function | |
| def distanceCallback(symbol, extra): | |
| "callback function for displaying decoded distance" | |
| index, offset = symbol.value(extra) | |
| if index: | |
| #recent distance | |
| distance = self.lastDistances[-index]+offset | |
| return 'Distance: {}last{:+d}={}'.format(index, offset, distance) | |
| #absolute value | |
| if offset<=maxDistance: | |
| return 'Absolute value: {} (pos {})'.format(offset, maxDistance-offset) | |
| #word list value | |
| action, word = divmod(offset-maxDistance, 1<<wordList.NDBITS[copyLen]) | |
| return '{}-{} gives word {},{} action {}'.format( | |
| offset, maxDistance, copyLen, word, action) | |
| for dpc in self.prefixCodes[D]: dpc.callback = distanceCallback | |
| blockLen = 0 | |
| #there we go | |
| while blockLen<self.MLEN: | |
| #get insert© command | |
| litLen, copyLen, dist0Flag = self.verboseRead( | |
| self.prefixCodes[I][ | |
| self.figureBlockType(I)]) | |
| #literal data | |
| for i in range(litLen): | |
| bt = self.figureBlockType(L) | |
| cm = self.contextMode.getIndex() | |
| ct = self.cmaps[L][bt<<6|cm] | |
| char = self.verboseRead( | |
| self.prefixCodes[L][ct], | |
| context='{},{}='.format(bt,cm)) | |
| self.contextMode.add(char) | |
| self.output.append(char) | |
| blockLen += litLen | |
| #check if we're done | |
| if blockLen>=self.MLEN: return | |
| #distance | |
| #distances are computed relative to output length, at most window size | |
| maxDistance = min(len(self.output), self.windowSize) | |
| if dist0Flag: | |
| distance = self.lastDistances[-1] | |
| else: | |
| bt = self.figureBlockType(D) | |
| cm = {2:0, 3:1, 4:2}.get(copyLen, 3) | |
| ct = self.cmaps[D][bt<<2|cm] | |
| index, offset = self.verboseRead( | |
| self.prefixCodes[D][ct], | |
| context='{},{}='.format(bt,cm)) | |
| distance = self.lastDistances[-index]+offset if index else offset | |
| if index==1 and offset==0: | |
| #to make sure distance is not put in last distance list | |
| dist0Flag = True | |
| if distance<=maxDistance: | |
| #copy from output | |
| for i in range( | |
| maxDistance-distance, | |
| maxDistance-distance+copyLen): | |
| self.output.append(self.output[i]) | |
| if not dist0Flag: self.lastDistances.append(distance) | |
| comment = 'Seen before' | |
| else: | |
| #fetch from wordlist | |
| newWord = wordList.word(copyLen, distance-maxDistance-1) | |
| self.output.extend(newWord) | |
| #adjust copyLen to reflect actual new data | |
| copyLen = len(newWord) | |
| comment = 'From wordlist' | |
| blockLen += copyLen | |
| print(' '*40, | |
| comment, | |
| ': "', | |
| outputFormatter(self.output[-copyLen:]), | |
| '"', | |
| sep='') | |
| self.contextMode.add(self.output[-2]) | |
| self.contextMode.add(self.output[-1]) | |
| def figureBlockType(self, kind): | |
| counts, types = self.currentBlockCounts, self.currentBlockTypes | |
| if counts[kind]==0: | |
| newType = self.verboseRead(self.blockTypeCodes[kind]) | |
| if newType==-2: newType = types['P'+kind] | |
| elif newType==-1: | |
| newType = (types[kind]+1)%self.numberOfBlockTypes[kind] | |
| types['P'+kind] = types[kind] | |
| types[kind] = newType | |
| counts[kind] = self.verboseRead(self.blockCountCodes[kind]) | |
| counts[kind] -=1 | |
| return types[kind] | |
| __test__ = { | |
| 'BitStream': """ | |
| >>> bs = BitStream(b'Jurjen') | |
| >>> bs.readBytes(2) | |
| b'Ju' | |
| >>> bs.read(6) #r=01110010 | |
| 50 | |
| >>> bs | |
| BitStream(pos=2:6) | |
| >>> bs.peek(5) #j=01101010 | |
| 9 | |
| >>> bs.readBytes(2) | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: readBytes: need byte boundary | |
| """, | |
| 'Symbol': """ | |
| >>> a=Symbol(MetablockLengthAlphabet(),5) | |
| >>> len(a) | |
| 2 | |
| >>> int(a) | |
| 5 | |
| >>> a.bitPattern() | |
| '01' | |
| >>> a.value(200000) | |
| 200001 | |
| >>> a.explanation(300000) | |
| 'data length: 493e0h+1=300001' | |
| """, | |
| 'RangeDecoder': """ | |
| >>> a=RangeDecoder(bitLength=3) | |
| >>> len(a) | |
| 8 | |
| >>> a.name='t' | |
| >>> list(a) | |
| [Symbol(t, 0), Symbol(t, 1), Symbol(t, 2), Symbol(t, 3), Symbol(t, 4), Symbol(t, 5), Symbol(t, 6), Symbol(t, 7)] | |
| >>> a[2] | |
| Symbol(t, 2) | |
| >>> a.bitPattern(4) | |
| '100' | |
| >>> a.length(2) | |
| 3 | |
| >>> a.decodePeek(15) | |
| (3, Symbol(t, 7)) | |
| >>> | |
| """, | |
| 'PrefixDecoder': """ | |
| >>> a=PrefixDecoder(decodeTable={0:1,1:2,3:3,7:4}) | |
| >>> len(a) | |
| 4 | |
| >>> a.name='t' | |
| >>> list(a) | |
| [Symbol(t, 1), Symbol(t, 2), Symbol(t, 3), Symbol(t, 4)] | |
| >>> a.decodePeek(22) | |
| (1, Symbol(t, 1)) | |
| >>> a.decodePeek(27) | |
| (3, Symbol(t, 3)) | |
| >>> a.length(1) | |
| 1 | |
| >>> a.length(4) | |
| 3 | |
| """, | |
| 'Code': """ | |
| >>> a=Code('t',alphabetSize=10) | |
| >>> len(a) | |
| 10 | |
| >>> a.showCode() | |
| 0000:0 0001:1 0010:2 0011:3 0100:4 0101:5 0110:6 0111:7 1000:8 1001:9 | |
| >>> a.setLength({2:1,3:2,5:3,6:3}) | |
| >>> a.showCode() | |
| 0:2 01:3 011:5 111:6 | |
| >>> len(a) | |
| 4 | |
| >>> def callback(i): return 'call{}back'.format(i) | |
| >>> a=Code('t',callback=callback,bitLength=3) | |
| >>> a[6].explanation() | |
| 'call6back' | |
| """, | |
| 'WithExtra': """ | |
| >>> class A(WithExtra): | |
| ... extraTable = [0,1,1,2,2] | |
| >>> a=A('t',alphabetSize=5) | |
| >>> a[1] | |
| Symbol(t, 1) | |
| >>> a.extraBits(2) | |
| 1 | |
| >>> a.mnemonic(4) | |
| '4' | |
| >>> a.readTupleAndExtra(BitStream(b'\x5b')) | |
| (3, Symbol(t, 3), 2, 3) | |
| """, | |
| 'BoolCode': """ | |
| >>> BoolCode('test')[0].explanation() | |
| '0: False' | |
| """, | |
| 'Enumerator': """ | |
| >>> class A(Enumerator): | |
| ... extraTable = [0,1,1,2,2] | |
| ... value0=3 | |
| >>> a=A(alphabetLength=5) | |
| >>> a.value(3) | |
| Traceback (most recent call last): | |
| ... | |
| TypeError: value() missing 1 required positional argument: 'extra' | |
| >>> a.explanation(3,4) | |
| 'xx 011: 8-11; 8+4=12' | |
| """, | |
| 'WindowSizeAlphabet': """ | |
| >>> windowSizeAlphabet = WindowSizeAlphabet() | |
| >>> windowSizeAlphabet[0] | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: No symbol WindowSizeAlphabet[0] | |
| >>> len(windowSizeAlphabet) | |
| 16 | |
| >>> windowSizeAlphabet[21] | |
| Symbol(WSIZE, 21) | |
| >>> windowSizeAlphabet[21].bitPattern() | |
| '1001' | |
| >>> windowSizeAlphabet[21].extraBits() | |
| 0 | |
| >>> windowSizeAlphabet[21].index | |
| 21 | |
| >>> windowSizeAlphabet[10].value() | |
| 1008 | |
| >>> windowSizeAlphabet[10].explanation() | |
| 'windowsize=(1<<10)-16=1008' | |
| >>> windowSizeAlphabet.showCode() | |
| 0:65520 1100001:16368 1110001:32752 0011:262128 | |
| 0000001:131056 0010001:None 1001:2097136 1011:4194288 | |
| 1000001:4080 1010001:8176 0101:524272 0111:1048560 | |
| 0100001:1008 0110001:2032 1101:8388592 1111:16777200 | |
| """, | |
| 'TypeCountAlphabet': """ | |
| >>> typeCountAlphabet = TypeCountAlphabet(description='bananas') | |
| >>> len(typeCountAlphabet) | |
| 9 | |
| >>> typeCountAlphabet[3] | |
| Symbol(BT#, 3) | |
| >>> typeCountAlphabet[9] | |
| Traceback (most recent call last): | |
| ... | |
| ValueError: No symbol TypeCountAlphabet[9] | |
| >>> print(typeCountAlphabet[3]) | |
| xx,0101 | |
| >>> typeCountAlphabet[8].value(127) | |
| 256 | |
| >>> typeCountAlphabet[4].explanation(2) | |
| 'xxx,0111: 11 bananas' | |
| >>> typeCountAlphabet[0].explanation() | |
| '0: 1 banana' | |
| """, | |
| 'DistanceParamAlphabet': """ | |
| >>> dpa = DistanceParamAlphabet() | |
| >>> dpa.showCode() | |
| 00:PF0 01:PF1 10:PF2 11:PF3 | |
| >>> dpa.readTupleAndExtra(BitStream(b'\\x29')) | |
| (2, Symbol(DIST, 1), 4, 10) | |
| >>> dpa.explanation(2, 5) | |
| '2 postfix bits and 0101<<2=20 direct codes' | |
| """, | |
| 'LiteralAlphabet': """ | |
| >>> LiteralAlphabet(-1).showCode() #doctest: +ELLIPSIS | |
| 00000000:\\x00 00110100:4 01101000:h 10011100:\\x9c 11010000:\\xd0 | |
| 00000001:\\x01 00110101:5 01101001:i 10011101:\\x9d 11010001:\\xd1 | |
| 00000010:\\x02 00110110:6 01101010:j 10011110:\\x9e 11010010:\\xd2 | |
| ... | |
| 00101111:/ 01100011:c 10010111:\\x97 11001011:\\xcb 11111111:\\xff | |
| 00110000:0 01100100:d 10011000:\\x98 11001100:\\xcc | |
| 00110001:1 01100101:e 10011001:\\x99 11001101:\\xcd | |
| 00110010:2 01100110:f 10011010:\\x9a 11001110:\\xce | |
| 00110011:3 01100111:g 10011011:\\x9b 11001111:\\xcf | |
| """, | |
| 'BlockCountAlphabet': """ | |
| >>> bc=BlockCountAlphabet('BCL') | |
| >>> len(bc) | |
| 26 | |
| >>> bs=BitStream(b'\\x40\\x83\\xc8\\x59\\12\\x02') | |
| >>> x = bc.readTupleAndExtra(bs); x[1].explanation(x[3]) | |
| 'Block count: xx 00000: 1-4; 1+2=3' | |
| >>> x = bc.readTupleAndExtra(bs); x[1].explanation(x[3]) | |
| 'Block count: xxx 00110: 33-40; 33+0=33' | |
| >>> x = bc.readTupleAndExtra(bs); x[1].explanation(x[3]) | |
| 'Block count: xxxxxx 10001: 305-368; 305+28=333' | |
| >>> x = bc.readTupleAndExtra(bs); x[1].explanation(x[3]) | |
| 'Block count: xxxxxxxxxxx 10110: 2289-4336; 2289+1044=3333' | |
| """, | |
| 'Layout': """ | |
| >>> olleke.pos = 0 | |
| >>> l = Layout(olleke) | |
| >>> l.verboseRead(WindowSizeAlphabet()) | |
| 0000 1b 1011 WSIZE windowsize=(1<<22)-16=4194288 | |
| 4194288 | |
| >>> l.verboseRead(BoolCode('LAST', description="Last block")) | |
| 1 LAST Last block: 1: True | |
| True | |
| >>> l.verboseRead(BoolCode('EMPTY', description="Empty block")) | |
| 0 EMPTY Empty block: 0: False | |
| False | |
| >>> l.verboseRead(MetablockLengthAlphabet()) | |
| 0001 2e 00 |00h|2Eh,|00 MLEN data length: 002eh+1=47 | |
| 47 | |
| >>> olleke.pos = 76 | |
| >>> l = Layout(olleke) | |
| >>> x = l.verboseRead(DistanceAlphabet(0,NPOSTFIX=0,NDIRECT=0), skipExtra=True) | |
| 000a 82 10|1100 D0 10[15*x]-3 | |
| >>> x.explanation(0x86a3) | |
| '10[1000011010100011]-3: [0]+100000' | |
| """, | |
| 'olleke': """ | |
| >>> olleke.pos = 0 | |
| >>> try: Layout(olleke).processStream() | |
| ... except NotImplementedError: pass | |
| ... #doctest: +REPORT_NDIFF | |
| addr hex binary context explanation | |
| -----------------------Stream header------------------------ | |
| 0000 1b 1011 WSIZE windowsize=(1<<22)-16=4194288 | |
| ======================Metablock header====================== | |
| 1 LAST Last block: 1: True | |
| 0 EMPTY Empty block: 0: False | |
| 0001 2e 00 |00h|2Eh,|00 MLEN data length: 002eh+1=47 | |
| -------------------Block type descriptors------------------- | |
| 0003 00 0 BT#L 0: 1 literal block type | |
| 0 BT#I 0: 1 insert© block type | |
| 0 BT#D 0: 1 distance block type | |
| ------------------Distance code parameters------------------ | |
| 0004 44 0|000,00 DIST 0 postfix bits and 0000<<0=0 direct codes | |
| -----------------------Context modes------------------------ | |
| 10 LC0 Context mode for type 0: 2(UTF8) | |
| ------------------------Context maps------------------------ | |
| 0 LT# 0: 1 literal prefix tree | |
| 0 DT# 0: 1 distance prefix tree | |
| ---------------------Prefix code lists---------------------- | |
| 10 PFX L0 is complex with lengths 3,4,0,5,17... | |
| 0005 4f 1|0 ##L0 len 3: coded with 3 bits | |
| 0111 ##L0 len 4: coded with 1 bits | |
| 10 ##L0 unused: coded with 3 bits | |
| 0006 d6 0|0 ##L0 len 5: skipped | |
| 011 ##L0 zero xxx: coded with 2 bits | |
| ***** Lengths for L0 will be coded as: | |
| 0:len 4 01:zero xxx 011:unused 111:len 3 | |
| 0007 95 1|11,01 #L0 7+3 unused | |
| 0 #L0 Length for \\n is 4 bits | |
| 001,01 #L0 1+3 unused | |
| 0008 44 010,0|1 #L0 total 19+2 unused | |
| 0 #L0 Length for " " is 4 bits | |
| 0 #L0 Length for ! is 4 bits | |
| 0009 cb 011,|01 #L0 3+3 unused | |
| |110,01 #L0 total 35+6 unused | |
| 000a 82 0 #L0 Length for K is 4 bits | |
| 000,01 #L0 0+3 unused | |
| 0 #L0 Length for O is 4 bits | |
| 000b 4d 01|1 #L0 symbol P unused | |
| 011 #L0 symbol Q unused | |
| 0 #L0 Length for R is 4 bits | |
| 000c 88 000,|01 #L0 0+3 unused | |
| |100,01 #L0 total 11+4 unused | |
| 000d b6 0 #L0 Length for b is 4 bits | |
| 011 #L0 symbol c unused | |
| 011 #L0 symbol d unused | |
| 000e 27 11|1 #L0 Length for e is 3 bits | |
| 010,01 #L0 2+3 unused | |
| |0 #L0 Length for k is 4 bits | |
| 000f 1f 111 #L0 Length for l is 3 bits | |
| 011 #L0 symbol m unused | |
| 0 #L0 Length for n is 4 bits | |
| |0 #L0 Length for o is 4 bits | |
| 0010 c1 000,01 #L0 0+3 unused | |
| 0 #L0 Length for s is 4 bits | |
| 0011 b4 0|11 #L0 symbol t unused | |
| 0 #L0 Length for u is 4 bits | |
| End of table. Prefix code L0: | |
| 000:e 0010:\\n 0110:! 0001:O 0101:b 0011:n 0111:s | |
| 100:l 1010:" " 1110:K 1001:R 1101:k 1011:o 1111:u | |
| 11,01 PFX IC0 is simple with 4 code words | |
| 0012 2a |2Ah|10 IC0 ? bits: I5C4 | |
| 0013 b5 ec 00|B5h IC0 ? bits: I6+xC7 | |
| 0015 22 0010|111011 IC0 ? bits: I8+xC5 | |
| 0016 8c 001100|0010 IC0 ? bits: I0C14+xx | |
| 0 SHAPE False: lengths 2,2,2,2 | |
| 0017 74 10,0|1 PFX D0 is simple with 3 code words | |
| 0018 a6 0|01110 D0 1 bit: 2last-3 | |
| 010011 D0 2 bits: 11xx-3 | |
| 0019 aa 01010|1 D0 2 bits: 11xxx-3 | |
| ====================Meta block contents===================== | |
| |1,01 IC0 Literal: 9, copy: 5 | |
| 001a 41 0001 0,0=L0 O | |
| 100 0,48=L0 l | |
| 001b a2 10|0 0,62=L0 l | |
| 000 0,63=L0 e | |
| 001c a1 1|101 0,59=L0 k | |
| 000 0,63=L0 e | |
| |1010 0,59=L0 " " | |
| 001d b5 0101 0,11=L0 b | |
| |1011 0,60=L0 o | |
| 001e 24 0 0,3=D0 Distance: 2last-3=8 | |
| Seen before: "lleke" | |
| 0,10 IC0 Literal: 6, copy: 7 | |
| |0010 0,59=L0 \\n | |
| 001f 89 1001 0,7=L0 R | |
| 000 0,52=L0 e | |
| 0020 fa 010|1 0,58=L0 b | |
| 1111 0,63=L0 u | |
| 0021 eb 011|1 0,59=L0 s | |
| 11,01 0,3=D0 Absolute value: 12 (pos 8) | |
| Seen before: "olleke\\n" | |
| 0022 db 01,1|1 IC0 Literal: 0, copy: 15 | |
| |110,11 0,3=D0 Absolute value: 27 (pos 0) | |
| Seen before: "Olleke bolleke\\n" | |
| 0023 f8 00 IC0 Literal: 5, copy: 4 | |
| 1110 0,7=L0 K | |
| 0024 2c 00|11 0,52=L0 n | |
| 1011 0,62=L0 o | |
| 0025 0d 1|00 0,59=L0 l | |
| 0110 0,63=L0 ! | |
| """, | |
| 'file': """ | |
| >>> try: Layout(BitStream( | |
| ... open("./tests/testdata/10x10y.compressed",'rb') | |
| ... .read())).processStream() | |
| ... except NotImplementedError: pass | |
| addr hex binary context explanation | |
| -----------------------Stream header------------------------ | |
| 0000 1b 1011 WSIZE windowsize=(1<<22)-16=4194288 | |
| ======================Metablock header====================== | |
| 1 LAST Last block: 1: True | |
| 0 EMPTY Empty block: 0: False | |
| 0001 13 00 |00h|13h,|00 MLEN data length: 0013h+1=20 | |
| -------------------Block type descriptors------------------- | |
| 0003 00 0 BT#L 0: 1 literal block type | |
| 0 BT#I 0: 1 insert© block type | |
| 0 BT#D 0: 1 distance block type | |
| ------------------Distance code parameters------------------ | |
| 0004 a4 0|000,00 DIST 0 postfix bits and 0000<<0=0 direct codes | |
| -----------------------Context modes------------------------ | |
| 10 LC0 Context mode for type 0: 2(UTF8) | |
| ------------------------Context maps------------------------ | |
| 0 LT# 0: 1 literal prefix tree | |
| 0 DT# 0: 1 distance prefix tree | |
| ---------------------Prefix code lists---------------------- | |
| 0005 b0 0|1,01 PFX L0 is simple with 2 code words | |
| 0006 b2 0|1011000 L0 1 bit: X | |
| 0007 ea 0|1011001 L0 1 bit: Y | |
| 01,01 PFX IC0 is simple with 2 code words | |
| 0008 81 0000001|111 IC0 1 bit: I1C9&D=0 | |
| 0009 47 02 0|47h|1 IC0 1 bit: I1C9 | |
| 00,01 PFX D0 is simple with 1 code word | |
| 000b 8a 010|000 D0 0 bits: 10x-3 | |
| ====================Meta block contents===================== | |
| 1 IC0 Literal: 1, copy: 9 | |
| 0 0,0=L0 X | |
| 0,() 0,3=D0 Absolute value: 1 (pos 0) | |
| Seen before: "XXXXXXXXX" | |
| 0 IC0 Literal: 1, copy: 9, same distance | |
| |1 0,54=L0 Y | |
| Seen before: "YYYYYYYYY" | |
| """, | |
| 'XY': """ | |
| >>> try: Layout(BitStream(brotli.compress('X'*10+'Y'*10))).processStream() | |
| ... except NotImplementedError: pass | |
| addr hex binary context explanation | |
| -----------------------Stream header------------------------ | |
| 0000 1b 1011 WSIZE windowsize=(1<<22)-16=4194288 | |
| ======================Metablock header====================== | |
| 1 LAST Last block: 1: True | |
| 0 EMPTY Empty block: 0: False | |
| 0001 13 00 |00h|13h,|00 MLEN data length: 0013h+1=20 | |
| -------------------Block type descriptors------------------- | |
| 0003 00 0 BT#L 0: 1 literal block type | |
| 0 BT#I 0: 1 insert© block type | |
| 0 BT#D 0: 1 distance block type | |
| ------------------Distance code parameters------------------ | |
| 0004 a4 0|000,00 DIST 0 postfix bits and 0000<<0=0 direct codes | |
| -----------------------Context modes------------------------ | |
| 10 LC0 Context mode for type 0: 2(UTF8) | |
| ------------------------Context maps------------------------ | |
| 0 LT# 0: 1 literal prefix tree | |
| 0 DT# 0: 1 distance prefix tree | |
| ---------------------Prefix code lists---------------------- | |
| 0005 b0 0|1,01 PFX L0 is simple with 2 code words | |
| 0006 b2 0|1011000 L0 1 bit: X | |
| 0007 82 0|1011001 L0 1 bit: Y | |
| 00,01 PFX IC0 is simple with 1 code word | |
| 0008 84 0000100|100 IC0 0 bits: I4C6&D=0 | |
| 0009 00 00,0|1 PFX D0 is simple with 1 code word | |
| 000a e0 0|00000 D0 0 bits: last | |
| ====================Meta block contents===================== | |
| () IC0 Literal: 4, copy: 6, same distance | |
| 0 0,0=L0 X | |
| 0 0,52=L0 X | |
| 0 0,54=L0 X | |
| 0 0,54=L0 X | |
| Seen before: "XXXXXX" | |
| () IC0 Literal: 4, copy: 6, same distance | |
| 1 0,54=L0 Y | |
| 1 0,54=L0 Y | |
| |1 0,54=L0 Y | |
| 000b 01 1 0,54=L0 Y | |
| Seen before: "YYYYYY" | |
| """, | |
| 'empty': """ | |
| >>> try: Layout(BitStream(b'\\x81\\x16\\x00\\x58')).processStream() | |
| ... except NotImplementedError: pass | |
| addr hex binary context explanation | |
| -----------------------Stream header------------------------ | |
| 0000 81 0000001 WSIZE windowsize=(1<<17)-16=131056 | |
| ======================Metablock header====================== | |
| |1 LAST Last block: 1: True | |
| 0001 16 0 EMPTY Empty block: 0: False | |
| 11 MLEN 11: empty block | |
| 0 RSVD Reserved (must be zero) | |
| 0002 00 000000|00,01 SKIP skip length: 0h+1=1 | |
| |00 SKIP 2 bits ignored | |
| Skipping to 4 | |
| """, | |
| } | |
| if __name__ == '__main__': | |
| import os | |
| import sys | |
| here = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) | |
| DICTIONARY_PATH = os.path.realpath(os.path.join(here, DICTIONARY_PATH)) | |
| if len(sys.argv) > 1: | |
| l = Layout(BitStream(open(sys.argv[1],'rb').read())) | |
| l.processStream() | |
| else: | |
| sys.path.append("h:/Persoonlijk/bin") | |
| try: | |
| import brotli | |
| open('brotlidump.br', 'wb').write( | |
| brotli.compress( | |
| open('brotlidump.py', 'r').read() | |
| )) | |
| olleke = BitStream(brotli.compress( | |
| 'Olleke bolleke\nRebusolleke\nOlleke bolleke\nKnol!')) | |
| except ImportError: pass | |
| import doctest | |
| doctest.testmod(optionflags=doctest.REPORT_NDIFF | |
| #|doctest.FAIL_FAST | |
| ) |