| | private const CHUNK_SIZE = 65536; |
| | |
| | + private const array COPY_INSTRUCTION_SIZES = [ |
| | + 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4, |
| | + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, |
| | + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, |
| | + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, |
| | + 1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5, |
| | + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, |
| | + 2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6, |
| | + 3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7, |
| | + ]; |
| | + |
| | public function apply( string $base, string $delta, int $cap ): string { |
| | $pos = 0; |
 |
| | $offset = 0; |
| | $yieldBuffer = ''; |
| | + $yieldBufLen = 0; |
| | $isStream = $base instanceof StreamReader; |
| | |
| | foreach( $stream->stream( $handle ) as $data ) { |
| | if( $offset > 0 ) { |
| | $buffer = substr( $buffer, $offset ); |
| | $offset = 0; |
| | } |
| | |
| | - $buffer .= $data; |
| | - $doneBuffer = false; |
| | + $buffer .= $data; |
| | + $bufLen = strlen( $buffer ); // loop invariant: buffer unchanged inside while |
| | |
| | - while( !$doneBuffer ) { |
| | - $len = strlen( $buffer ) - $offset; |
| | + while( $offset < $bufLen ) { |
| | + $len = $bufLen - $offset; |
| | |
| | - if( $len === 0 ) { |
| | - $doneBuffer = true; |
| | - } |
| | + if( $state < 2 ) { |
| | + // Inline advanceToInstructions: scan past the variable-length source |
| | + // or target size integer (high-bit continuation bytes, then one |
| | + // terminating byte with bit-7 clear). |
| | + $pos = $offset; |
| | + $found = false; |
| | |
| | - if( !$doneBuffer ) { |
| | - if( $state < 2 ) { |
| | - $this->advanceToInstructions( |
| | - $buffer, |
| | - $offset, |
| | - $len, |
| | - $state, |
| | - $doneBuffer |
| | - ); |
| | + while( $pos < $bufLen ) { |
| | + if( !(ord( $buffer[$pos] ) & 128) ) { |
| | + $found = true; |
| | + $pos++; |
| | + break; |
| | + } |
| | + |
| | + $pos++; |
| | + } |
| | + |
| | + if( $found ) { |
| | + $offset = $pos; |
| | + $state++; |
| | } else { |
| | - yield from $this->processInstruction( |
| | - $buffer, |
| | - $offset, |
| | - $len, |
| | - $doneBuffer, |
| | - $isStream, |
| | - $base, |
| | - $yieldBuffer |
| | - ); |
| | + break; |
| | } |
| | - } |
| | - } |
| | - } |
| | + } else { |
| | + $op = ord( $buffer[$offset] ); |
| | |
| | - if( $yieldBuffer !== '' ) { |
| | - yield $yieldBuffer; |
| | - } |
| | - } |
| | + if( $op & 128 ) { |
| | + $need = self::COPY_INSTRUCTION_SIZES[$op & 0x7F]; |
| | |
| | - private function advanceToInstructions( |
| | - string $buffer, |
| | - int &$offset, |
| | - int $len, |
| | - int &$state, |
| | - bool &$doneBuffer |
| | - ): void { |
| | - $pos = $offset; |
| | - $found = false; |
| | + if( $len < 1 + $need ) { |
| | + break; |
| | + } |
| | |
| | - while( !$found && $pos < $offset + $len ) { |
| | - if( !(ord( $buffer[$pos] ) & 128) ) { |
| | - $found = true; |
| | - } |
| | + $off = 0; |
| | + $ln = 0; |
| | + $ptr = $offset + 1; |
| | |
| | - $pos++; |
| | - } |
| | + ($op & 0x01) ? $off |= ord( $buffer[$ptr++] ) : null; |
| | + ($op & 0x02) ? $off |= ord( $buffer[$ptr++] ) << 8 : null; |
| | + ($op & 0x04) ? $off |= ord( $buffer[$ptr++] ) << 16 : null; |
| | + ($op & 0x08) ? $off |= ord( $buffer[$ptr++] ) << 24 : null; |
| | + ($op & 0x10) ? $ln |= ord( $buffer[$ptr++] ) : null; |
| | + ($op & 0x20) ? $ln |= ord( $buffer[$ptr++] ) << 8 : null; |
| | + ($op & 0x40) ? $ln |= ord( $buffer[$ptr++] ) << 16 : null; |
| | |
| | - if( $found ) { |
| | - $offset = $pos; |
| | - $state++; |
| | - } else { |
| | - $doneBuffer = true; |
| | - } |
| | - } |
| | + $ln = $ln === 0 ? 0x10000 : $ln; |
| | |
| | - private function processInstruction( |
| | - string $buffer, |
| | - int &$offset, |
| | - int $len, |
| | - bool &$doneBuffer, |
| | - bool $isStream, |
| | - mixed $base, |
| | - string &$yieldBuffer |
| | - ): Generator { |
| | - $op = ord( $buffer[$offset] ); |
| | + if( $isStream ) { |
| | + $base->seek( $off ); |
| | + $rem = $ln; |
| | |
| | - if( $op & 128 ) { |
| | - yield from $this->processCopyInstruction( |
| | - $op, |
| | - $buffer, |
| | - $offset, |
| | - $len, |
| | - $doneBuffer, |
| | - $isStream, |
| | - $base, |
| | - $yieldBuffer |
| | - ); |
| | - } else { |
| | - yield from $this->processInsertInstruction( |
| | - $op, |
| | - $buffer, |
| | - $offset, |
| | - $len, |
| | - $doneBuffer, |
| | - $yieldBuffer |
| | - ); |
| | - } |
| | - } |
| | + while( $rem > 0 ) { |
| | + $slc = $base->read( min( self::CHUNK_SIZE, $rem ) ); |
| | |
| | - private function processCopyInstruction( |
| | - int $op, |
| | - string $buffer, |
| | - int &$offset, |
| | - int $len, |
| | - bool &$doneBuffer, |
| | - bool $isStream, |
| | - mixed $base, |
| | - string &$yieldBuffer |
| | - ): Generator { |
| | - $need = $this->calculateCopyInstructionSize( $op ); |
| | + if( $slc === '' ) { |
| | + $rem = 0; |
| | + } else { |
| | + $slcLen = strlen( $slc ); |
| | + $yieldBuffer .= $slc; |
| | + $yieldBufLen += $slcLen; |
| | + $rem -= $slcLen; |
| | |
| | - if( $len < 1 + $need ) { |
| | - $doneBuffer = true; |
| | - } |
| | + if( $yieldBufLen >= self::CHUNK_SIZE ) { |
| | + yield $yieldBuffer; |
| | |
| | - if( !$doneBuffer ) { |
| | - $off = 0; |
| | - $ln = 0; |
| | - $ptr = $offset + 1; |
| | + $yieldBuffer = ''; |
| | + $yieldBufLen = 0; |
| | + } |
| | + } |
| | + } |
| | + } else { |
| | + $slc = substr( $base, $off, $ln ); |
| | + $slcLen = strlen( $slc ); |
| | + $yieldBuffer .= $slc; |
| | + $yieldBufLen += $slcLen; |
| | |
| | - $this->parseCopyInstruction( $op, $buffer, $ptr, $off, $ln ); |
| | + if( $yieldBufLen >= self::CHUNK_SIZE ) { |
| | + yield $yieldBuffer; |
| | |
| | - if( $isStream ) { |
| | - $base->seek( $off ); |
| | + $yieldBuffer = ''; |
| | + $yieldBufLen = 0; |
| | + } |
| | + } |
| | |
| | - $rem = $ln; |
| | + $offset += 1 + $need; |
| | + } else { |
| | + $ln = $op & 127; |
| | |
| | - while( $rem > 0 ) { |
| | - $slc = $base->read( min( self::CHUNK_SIZE, $rem ) ); |
| | + if( $len < 1 + $ln ) { |
| | + break; |
| | + } |
| | |
| | - if( $slc === '' ) { |
| | - $rem = 0; |
| | - } else { |
| | - $yieldBuffer .= $slc; |
| | - $rem -= strlen( $slc ); |
| | + $yieldBuffer .= substr( $buffer, $offset + 1, $ln ); |
| | + $yieldBufLen += $ln; |
| | + $offset += 1 + $ln; |
| | |
| | - if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { |
| | + if( $yieldBufLen >= self::CHUNK_SIZE ) { |
| | yield $yieldBuffer; |
| | |
| | $yieldBuffer = ''; |
| | + $yieldBufLen = 0; |
| | } |
| | } |
| | - } |
| | - } else { |
| | - $yieldBuffer .= substr( $base, $off, $ln ); |
| | - |
| | - if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { |
| | - yield $yieldBuffer; |
| | - |
| | - $yieldBuffer = ''; |
| | } |
| | } |
| | - |
| | - $offset += 1 + $need; |
| | - } |
| | - } |
| | - |
| | - private function processInsertInstruction( |
| | - int $op, |
| | - string $buffer, |
| | - int &$offset, |
| | - int $len, |
| | - bool &$doneBuffer, |
| | - string &$yieldBuffer |
| | - ): Generator { |
| | - $ln = $op & 127; |
| | - |
| | - if( $len < 1 + $ln ) { |
| | - $doneBuffer = true; |
| | } |
| | - |
| | - if( !$doneBuffer ) { |
| | - $yieldBuffer .= substr( $buffer, $offset + 1, $ln ); |
| | - $offset += 1 + $ln; |
| | - |
| | - if( strlen( $yieldBuffer ) >= self::CHUNK_SIZE ) { |
| | - yield $yieldBuffer; |
| | |
| | - $yieldBuffer = ''; |
| | - } |
| | + if( $yieldBuffer !== '' ) { |
| | + yield $yieldBuffer; |
| | } |
| | } |