On the design of the Delphi TStream classes – why aren’t they decomposed better?
Posted by jpluimers on 2017/05/16
Ever since I started using Delphi more than 2 decades ago, I wondered about the design of the TStream classes, especially as “stream16.zip” by Duncan Murdoch from the DOS Turbo Pascal era (which I copied in the below gist) showed how to perform composition using streams.
Even though by now there is TStreamReader/TStreamWriter allowing some level of composition, it always bugged me that large parts of the resource handling and component (de)serialisation is in it (centred around ReadComponentRes/WriteComponentRes in stead of fully being in TFiler/TReader/TWriter or even further decomposed), that you cannot interrogate capabilities (like supporting seeking, length, directionality and such) and that a lot of overloads (for instance Read/ReadBuffer/ReadBufferData/Write/WriteBuffer/WriteBufferData) are still based on signed or 32-bit types (though it has improved back in the early days they were even signed 16-bit types).
I’m not the only one who wonders about this: Faster FileStream with TBufferedFileStream • DelphiABall mentioned a new Berlin piece the TStream hierarchy and – being in the field a lot longer – Stefan Glienke rightly asked why the buffering isn’t done with the decorator pattern like JclStreams does, and Asbjørn Heid chimed in with a very dense version of more gripes.
Even TZCompressionStream/TZDecompressionStream (though relatively new) aren’t doing composition really well (by not abstracting the compression/decompression from the write-only/read-only behaviour).
Now that all key players from the early TStream design day and age have left the core Delphi R&D team, maybe one of them can step in and explain why.
–jeroen
via: [WayBack] I was wondering if the TBufferedFileStream (see https://delphiaball.co.uk/2016/04/29/faster-filestream-tbufferedfilestream/) would not have been implemented using the decorator pattern… – Stefan Glienke – Google+ -> https://github.com/project-jedi/jcl/blob/master/jcl/source/common/JclStreams.pas#L207
TJclBufferedStream = class(TJclStreamDecorator)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$B-} { Use fast boolean evaluation. } | |
program Compress; | |
{ Program to demonstrate use of TLZWFilter } | |
{$i stdefine.inc} | |
uses | |
{$ifdef windows} wincrt, {$endif} | |
{$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
streams; | |
procedure SyntaxExit(s:string); | |
begin | |
writeln; | |
writeln(s); | |
writeln; | |
writeln('Usage: COMPRESS Sourcefile Destfile [/X]'); | |
writeln(' will compress the source file to the destination'); | |
writeln(' file, or if /X flag is used, will expand source to destination.'); | |
halt(99); | |
end; | |
var | |
Source : PStream; { We don't know in advance which will be compressed } | |
Dest : PStream; | |
filename : string; | |
begin | |
Case ParamCount of | |
2 : begin | |
{$ifdef windows} | |
Filename := Paramstr(1); | |
Filename[length(filename)+1] := #0; | |
Source := New(PBufStream, init(@filename[1], stOpenRead, 2048)); | |
Filename := Paramstr(2); | |
Filename[length(filename)+1] := #0; | |
Dest := New(PLZWFilter, init(New(PBufStream, | |
init(@filename[1], | |
stCreate, 2048)), | |
stOpenWrite)); | |
{$else} | |
Source := New(PBufStream, init(Paramstr(1), stOpenRead, 2048)); | |
Dest := New(PLZWFilter, init(New(PBufStream, | |
init(Paramstr(2), | |
stCreate, 2048)), | |
stOpenWrite)); | |
{$endif windows} | |
Write('Compressing ',Paramstr(1),' (',Source^.GetSize, | |
' bytes) to ',Paramstr(2)); | |
end; | |
3 : begin | |
if (Paramstr(3) <> '/X') and (Paramstr(3) <> '/x') then | |
SyntaxExit('Unrecognized option '+Paramstr(3)); | |
{$ifdef windows} | |
Filename := Paramstr(2); | |
Filename[length(filename)+1] := #0; | |
Source := New(PLZWFilter, init(New(PBufStream, | |
init(@filename[1], | |
stOpenRead, 2048)), | |
stOpenRead)); | |
Filename := Paramstr(2); | |
Filename[length(filename)+1] := #0; | |
Dest := New(PBufStream, init(@filename[1], stCreate, 2048)); | |
{$else} | |
Source := New(PLZWFilter, init(New(PBufStream, | |
init(Paramstr(1), | |
stOpenRead, 2048)), | |
stOpenRead)); | |
Dest := New(PBufStream, init(Paramstr(2), stCreate, 2048)); | |
{$endif windows} | |
Write('Expanding ',Paramstr(1),' (', | |
PLZWFilter(Source)^.Base^.GetSize,' bytes) to ', | |
Paramstr(2)); | |
end; | |
else | |
SyntaxExit('Two or three parameters required.'); | |
end; | |
if (Source = nil) or (Source^.status <> stOk) then | |
SyntaxExit('Unable to open file '+ParamStr(1)+' for reading.'); | |
if (Dest = nil) or (Dest^.status <> stOk) then | |
SyntaxExit('Unable to create file '+Paramstr(2)+'.'); | |
FastCopy(Source^,Dest^, Source^.GetSize); | |
if Dest^.status <> stOK then | |
SyntaxExit('File error during compression/expansion.'); | |
Case ParamCount of | |
2 : begin | |
Dest^.Flush; | |
Writeln(' (',PLZWFilter(Dest)^.Base^.GetSize,' bytes).'); | |
end; | |
3 : Writeln(' (',Dest^.GetSize,' bytes).'); | |
end; | |
Dispose(Source, done); | |
Dispose(Dest, done); | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
IDEAL | |
; This CRC-16 routine and tables were converted from C code discovered | |
; in rzsz.arc by Chuck Forsberg. The comments there are: | |
; | |
; crctab calculated by Mark G. Mendel, Network Systems Corporation | |
; | |
; updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. | |
; NOTE: First srgument must be in range 0 to 255. | |
; Second argument is referenced twice. | |
; | |
; Programmers may incorporate any or all code into their programs, | |
; giving proper credit within the source. Publication of the | |
; source routines is permitted so long as proper credit is given | |
; to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, | |
; Omen Technology. | |
; | |
; <End of rzsz comments> | |
; | |
; The C macro is: | |
; | |
; #define updcrc(cp, crc) ( crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp) | |
; | |
; This routine appears to compute the 16 bit CRC used by XModem and its | |
; derivatives. For transmission, the CRC value should start with 0 and, | |
; after the block crc is computed, the value should be updated with two | |
; nulls and transmitted high-order byte first. The computed CRC value | |
; on the received block + 2 byte CRC should be zero. | |
c:\dosauto | |
; | |
; This TASM conversion done by: | |
; | |
; Edwin T. Floyd [76067h747] | |
; #9 Adams Park Ct. | |
; Columbush GA 31909 | |
; 404-576-3305 (work) | |
; 404-322-0076 (home) | |
; | |
; Borland's Turbo Assembler – TASM is required to assemble this program. | |
; | |
SEGMENT code BYTE PUBLIC | |
ASSUME cs:code | |
; 0 | |
crctab dw 00000h, 01021h, 02042h, 03063h, 04084h, 050a5h, 060c6h, 070e7h | |
dw 08108h, 09129h, 0a14ah, 0b16bh, 0c18ch, 0d1adh, 0e1ceh, 0f1efh | |
; 1 | |
dw 01231h, 00210h, 03273h, 02252h, 052b5h, 04294h, 072f7h, 062d6h | |
dw 09339h, 08318h, 0b37bh, 0a35ah, 0d3bdh, 0c39ch, 0f3ffh, 0e3deh | |
; 2 | |
dw 02462h, 03443h, 00420h, 01401h, 064e6h, 074c7h, 044a4h, 05485h | |
dw 0a56ah, 0b54bh, 08528h, 09509h, 0e5eeh, 0f5cfh, 0c5ach, 0d58dh | |
; 3 | |
dw 03653h, 02672h, 01611h, 00630h, 076d7h, 066f6h, 05695h, 046b4h | |
dw 0b75bh, 0a77ah, 09719h, 08738h, 0f7dfh, 0e7feh, 0d79dh, 0c7bch | |
; 4 | |
dw 048c4h, 058e5h, 06886h, 078a7h, 00840h, 01861h, 02802h, 03823h | |
dw 0c9cch, 0d9edh, 0e98eh, 0f9afh, 08948h, 09969h, 0a90ah, 0b92bh | |
; 5 | |
dw 05af5h, 04ad4h, 07ab7h, 06a96h, 01a71h, 00a50h, 03a33h, 02a12h | |
dw 0dbfdh, 0cbdch, 0fbbfh, 0eb9eh, 09b79h, 08b58h, 0bb3bh, 0ab1ah | |
; 6 | |
dw 06ca6h, 07c87h, 04ce4h, 05cc5h, 02c22h, 03c03h, 00c60h, 01c41h | |
dw 0edaeh, 0fd8fh, 0cdech, 0ddcdh, 0ad2ah, 0bd0bh, 08d68h, 09d49h | |
; 7 | |
dw 07e97h, 06eb6h, 05ed5h, 04ef4h, 03e13h, 02e32h, 01e51h, 00e70h | |
dw 0ff9fh, 0efbeh, 0dfddh, 0cffch, 0bf1bh, 0af3ah, 09f59h, 08f78h | |
; 8 | |
dw 09188h, 081a9h, 0b1cah, 0a1ebh, 0d10ch, 0c12dh, 0f14eh, 0e16fh | |
dw 01080h, 000a1h, 030c2h, 020e3h, 05004h, 04025h, 07046h, 06067h | |
; 9 | |
dw 083b9h, 09398h, 0a3fbh, 0b3dah, 0c33dh, 0d31ch, 0e37fh, 0f35eh | |
dw 002b1h, 01290h, 022f3h, 032d2h, 04235h, 05214h, 06277h, 07256h | |
; A | |
dw 0b5eah, 0a5cbh, 095a8h, 08589h, 0f56eh, 0e54fh, 0d52ch, 0c50dh | |
dw 034e2h, 024c3h, 014a0h, 00481h, 07466h, 06447h, 05424h, 04405h | |
; B | |
dw 0a7dbh, 0b7fah, 08799h, 097b8h, 0e75fh, 0f77eh, 0c71dh, 0d73ch | |
dw 026d3h, 036f2h, 00691h, 016b0h, 06657h, 07676h, 04615h, 05634h | |
; C | |
dw 0d94ch, 0c96dh, 0f90eh, 0e92fh, 099c8h, 089e9h, 0b98ah, 0a9abh | |
dw 05844h, 04865h, 07806h, 06827h, 018c0h, 008e1h, 03882h, 028a3h | |
; D | |
dw 0cb7dh, 0db5ch, 0eb3fh, 0fb1eh, 08bf9h, 09bd8h, 0abbbh, 0bb9ah | |
dw 04a75h, 05a54h, 06a37h, 07a16h, 00af1h, 01ad0h, 02ab3h, 03a92h | |
; E | |
dw 0fd2eh, 0ed0fh, 0dd6ch, 0cd4dh, 0bdaah, 0ad8bh, 09de8h, 08dc9h | |
dw 07c26h, 06c07h, 05c64h, 04c45h, 03ca2h, 02c83h, 01ce0h, 00cc1h | |
; F | |
dw 0ef1fh, 0ff3eh, 0cf5dh, 0df7ch, 0af9bh, 0bfbah, 08fd9h, 09ff8h | |
dw 06e17h, 07e36h, 04e55h, 05e74h, 02e93h, 03eb2h, 00ed1h, 01ef0h | |
MODEL TPASCAL | |
PUBLIC UpdateCRC16 | |
PROC UpdateCRC16 FAR initcrc:WORD,inbuf:DWORD,inlen:WORD | |
; UpdateCRC16 takes an initial CRC value and updates it with inlen bytes from | |
; inbuf. The updated CRC is returned in AX. The Pascal declaration is: | |
; Function UpdateCRC16(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
; Stomps registers: AX,BX,CX,SI | |
push ds | |
lds si,[inbuf] ; ds:si := ^inbuf | |
mov ax,[initcrc] ; ax := initcrc | |
mov cx,[inlen] ; cx := inlen | |
or cx,cx | |
jz @@done | |
@@loop: | |
xor bh,bh | |
mov bl,ah | |
mov ah,al | |
lodsb | |
shl bx,1 | |
xor ax,[crctab+bx] | |
loop @@loop | |
@@done: | |
pop ds | |
ret | |
ENDP | |
ENDS | |
END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
IDEAL | |
; This CRC-32 routine and tables were converted from code discovered | |
; in the DEZIP.PAS V2.0 by R. P. Byrne. The comments there are: | |
; | |
; Converted to Turbo Pascal ™ V4.0 March, 1988 by J.R.Louvau | |
; COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or | |
; code or tables extracted from it, as desired without restriction. | |
; | |
; First, the polynomial itself and its table of feedback terms. The | |
; polynomial is | |
; X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 | |
; | |
; Note that we take it "backwards" and put the highest-order term in | |
; the lowest-order bit. The X^32 term is "implied"; the LSB is the | |
; X^31 term, etc. The X^0 term (usually shown as "+1") results in | |
; the MSB being 1. | |
; | |
; Note that the usual hardware shift register implementation, which | |
; is what we're using (we're merely optimizing it by doing eight-bit | |
; chunks at a time) shifts bits into the lowest-order term. In our | |
; implementation, that means shifting towards the right. Why do we | |
; do it this way? Because the calculated CRC must be transmitted in | |
; order from highest-order term to lowest-order term. UARTs transmit | |
; characters in order from LSB to MSB. By storing the CRC this way, | |
; we hand it to the UART in the order low-byte to high-byte; the UART | |
; sends each low-bit to high-bit; and the result is transmission bit | |
; by bit from highest- to lowest-order term without requiring any bit | |
; shuffling on our part. Reception works similarly. | |
; | |
; The feedback terms table consists of 256, 32-bit entries. Notes: | |
; | |
; The table can be generated at runtime if desired; code to do so | |
; is shown later. It might not be obvious, but the feedback | |
; terms simply represent the results of eight shift/xor opera- | |
; tions for all combinations of data and CRC register values. | |
; | |
; The values must be right-shifted by eight bits by the "updcrc" | |
; logic; the shift must be unsigned (bring in zeroes). On some | |
; hardware you could probably optimize the shift in assembler by | |
; using byte-swap instructions. | |
; polynomial $edb88320 | |
; | |
; <End of Pascal version comments> | |
; | |
; The Pascal logic is: | |
; | |
; Function UpdC32(Octet: Byte; Crc: LongInt) : LongInt; | |
; Begin | |
; | |
; UpdC32 := CRC_32_TAB[Byte(Crc XOR LongInt(Octet))] XOR ((Crc SHR 8) | |
; AND $00FFFFFF); | |
; | |
; End {UpdC32}; | |
; | |
; This routine computes the 32 bit CRC used by PKZIP and its derivatives, | |
; and by Chuck Forsberg's "ZMODEM" protocol. The block CRC computation | |
; should start with high-values (0ffffffffh), and finish by inverting all | |
; bits. | |
; | |
; This TASM conversion done by: | |
; | |
; Edwin T. Floyd [76067,747] | |
; #9 Adams Park Ct. | |
; Columbus, GA 31909 | |
; 404-576-3305 (work) | |
; 404-322-0076 (home) | |
; | |
; Borland's Turbo Assembler – TASM is required to assemble this program. | |
; | |
SEGMENT code BYTE PUBLIC | |
ASSUME cs:code | |
; 0 | |
crc32tab dd 000000000h, 077073096h, 0ee0e612ch, 0990951bah | |
dd 0076dc419h, 0706af48fh, 0e963a535h, 09e6495a3h | |
dd 00edb8832h, 079dcb8a4h, 0e0d5e91eh, 097d2d988h | |
dd 009b64c2bh, 07eb17cbdh, 0e7b82d07h, 090bf1d91h | |
; 1 | |
dd 01db71064h, 06ab020f2h, 0f3b97148h, 084be41deh | |
dd 01adad47dh, 06ddde4ebh, 0f4d4b551h, 083d385c7h | |
dd 0136c9856h, 0646ba8c0h, 0fd62f97ah, 08a65c9ech | |
dd 014015c4fh, 063066cd9h, 0fa0f3d63h, 08d080df5h | |
; 2 | |
dd 03b6e20c8h, 04c69105eh, 0d56041e4h, 0a2677172h | |
dd 03c03e4d1h, 04b04d447h, 0d20d85fdh, 0a50ab56bh | |
dd 035b5a8fah, 042b2986ch, 0dbbbc9d6h, 0acbcf940h | |
dd 032d86ce3h, 045df5c75h, 0dcd60dcfh, 0abd13d59h | |
; 3 | |
dd 026d930ach, 051de003ah, 0c8d75180h, 0bfd06116h | |
dd 021b4f4b5h, 056b3c423h, 0cfba9599h, 0b8bda50fh | |
dd 02802b89eh, 05f058808h, 0c60cd9b2h, 0b10be924h | |
dd 02f6f7c87h, 058684c11h, 0c1611dabh, 0b6662d3dh | |
; 4 | |
dd 076dc4190h, 001db7106h, 098d220bch, 0efd5102ah | |
dd 071b18589h, 006b6b51fh, 09fbfe4a5h, 0e8b8d433h | |
dd 07807c9a2h, 00f00f934h, 09609a88eh, 0e10e9818h | |
dd 07f6a0dbbh, 0086d3d2dh, 091646c97h, 0e6635c01h | |
; 5 | |
dd 06b6b51f4h, 01c6c6162h, 0856530d8h, 0f262004eh | |
dd 06c0695edh, 01b01a57bh, 08208f4c1h, 0f50fc457h | |
dd 065b0d9c6h, 012b7e950h, 08bbeb8eah, 0fcb9887ch | |
dd 062dd1ddfh, 015da2d49h, 08cd37cf3h, 0fbd44c65h | |
; 6 | |
dd 04db26158h, 03ab551ceh, 0a3bc0074h, 0d4bb30e2h | |
dd 04adfa541h, 03dd895d7h, 0a4d1c46dh, 0d3d6f4fbh | |
dd 04369e96ah, 0346ed9fch, 0ad678846h, 0da60b8d0h | |
dd 044042d73h, 033031de5h, 0aa0a4c5fh, 0dd0d7cc9h | |
; 7 | |
dd 05005713ch, 0270241aah, 0be0b1010h, 0c90c2086h | |
dd 05768b525h, 0206f85b3h, 0b966d409h, 0ce61e49fh | |
dd 05edef90eh, 029d9c998h, 0b0d09822h, 0c7d7a8b4h | |
dd 059b33d17h, 02eb40d81h, 0b7bd5c3bh, 0c0ba6cadh | |
; 8 | |
dd 0edb88320h, 09abfb3b6h, 003b6e20ch, 074b1d29ah | |
dd 0ead54739h, 09dd277afh, 004db2615h, 073dc1683h | |
dd 0e3630b12h, 094643b84h, 00d6d6a3eh, 07a6a5aa8h | |
dd 0e40ecf0bh, 09309ff9dh, 00a00ae27h, 07d079eb1h | |
; 9 | |
dd 0f00f9344h, 08708a3d2h, 01e01f268h, 06906c2feh | |
dd 0f762575dh, 0806567cbh, 0196c3671h, 06e6b06e7h | |
dd 0fed41b76h, 089d32be0h, 010da7a5ah, 067dd4acch | |
dd 0f9b9df6fh, 08ebeeff9h, 017b7be43h, 060b08ed5h | |
; A | |
dd 0d6d6a3e8h, 0a1d1937eh, 038d8c2c4h, 04fdff252h | |
dd 0d1bb67f1h, 0a6bc5767h, 03fb506ddh, 048b2364bh | |
dd 0d80d2bdah, 0af0a1b4ch, 036034af6h, 041047a60h | |
dd 0df60efc3h, 0a867df55h, 0316e8eefh, 04669be79h | |
; B | |
dd 0cb61b38ch, 0bc66831ah, 0256fd2a0h, 05268e236h | |
dd 0cc0c7795h, 0bb0b4703h, 0220216b9h, 05505262fh | |
dd 0c5ba3bbeh, 0b2bd0b28h, 02bb45a92h, 05cb36a04h | |
dd 0c2d7ffa7h, 0b5d0cf31h, 02cd99e8bh, 05bdeae1dh | |
; C | |
dd 09b64c2b0h, 0ec63f226h, 0756aa39ch, 0026d930ah | |
dd 09c0906a9h, 0eb0e363fh, 072076785h, 005005713h | |
dd 095bf4a82h, 0e2b87a14h, 07bb12baeh, 00cb61b38h | |
dd 092d28e9bh, 0e5d5be0dh, 07cdcefb7h, 00bdbdf21h | |
; D | |
dd 086d3d2d4h, 0f1d4e242h, 068ddb3f8h, 01fda836eh | |
dd 081be16cdh, 0f6b9265bh, 06fb077e1h, 018b74777h | |
dd 088085ae6h, 0ff0f6a70h, 066063bcah, 011010b5ch | |
dd 08f659effh, 0f862ae69h, 0616bffd3h, 0166ccf45h | |
; E | |
dd 0a00ae278h, 0d70dd2eeh, 04e048354h, 03903b3c2h | |
dd 0a7672661h, 0d06016f7h, 04969474dh, 03e6e77dbh | |
dd 0aed16a4ah, 0d9d65adch, 040df0b66h, 037d83bf0h | |
dd 0a9bcae53h, 0debb9ec5h, 047b2cf7fh, 030b5ffe9h | |
; F | |
dd 0bdbdf21ch, 0cabac28ah, 053b39330h, 024b4a3a6h | |
dd 0bad03605h, 0cdd70693h, 054de5729h, 023d967bfh | |
dd 0b3667a2eh, 0c4614ab8h, 05d681b02h, 02a6f2b94h | |
dd 0b40bbe37h, 0c30c8ea1h, 05a05df1bh, 02d02ef8dh | |
MODEL TPASCAL | |
PUBLIC UpdateCRC32 | |
PROC UpdateCRC32 FAR initcrc:DWORD,inbuf:DWORD,inlen:WORD | |
; UpdateCRC32 takes an initial CRC value and updates it with inlen bytes from | |
; inbuf. The updated CRC is returned in DX:AX. The Pascal declaration is: | |
; Function UpdateCRC32(InitCRC : LongInt; Var InBuf; InLen : Word) : LongInt; | |
; Stomps registers: AX,BX,CX,DX,ES,SI | |
push ds | |
lds si,[inbuf] ; ds:si := ^inbuf | |
les ax,[initcrc] ; dx:ax := initcrc | |
mov dx,es | |
mov cx,[inlen] ; cx := inlen | |
or cx,cx | |
jz @@done | |
@@loop: | |
xor bh,bh | |
mov bl,al | |
lodsb | |
xor bl,al | |
mov al,ah | |
mov ah,dl | |
mov dl,dh | |
xor dh,dh | |
shl bx,1 | |
shl bx,1 | |
les bx,[crc32tab+bx] | |
xor ax,bx | |
mov bx,es | |
xor dx,bx | |
loop @@loop | |
@@done: | |
pop ds | |
ret | |
ENDP | |
ENDS | |
END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
IDEAL | |
; This CRC-16 routine and tables were converted from pascal code discovered | |
; in DEARC.PAS (author unknown). The result matches the 16 bit CRC computed | |
; by SEA's "ARC" utility. This CRC routine is contributed to the public | |
; domain. | |
; | |
; The Pascal logic is: | |
; | |
; crc := ((crc shr 8) and $00FF) xor crctab[(crc xor c) and $00FF]; | |
; | |
; This TASM conversion done by: | |
; | |
; Edwin T. Floyd [76067h747] | |
; #9 Adams Park Ct. | |
; Columbus, GA 31909 | |
; 404-576-3305 (work) | |
; 404-322-0076 (home) | |
; | |
; Borland's Turbo Assembler – TASM is required to assemble this program. | |
; | |
SEGMENT code BYTE PUBLIC | |
ASSUME cs:code | |
; 0 | |
crctab dw 00000h, 0C0C1h, 0C181h, 00140h, 0C301h, 003C0h, 00280h, 0C241h | |
dw 0C601h, 006C0h, 00780h, 0C741h, 00500h, 0C5C1h, 0C481h, 00440h | |
; 1 | |
dw 0CC01h, 00CC0h, 00D80h, 0CD41h, 00F00h, 0CFC1h, 0CE81h, 00E40h | |
dw 00A00h, 0CAC1h, 0CB81h, 00B40h, 0C901h, 009C0h, 00880h, 0C841h | |
; 2 | |
dw 0D801h, 018C0h, 01980h, 0D941h, 01B00h, 0DBC1h, 0DA81h, 01A40h | |
dw 01E00h, 0DEC1h, 0DF81h, 01F40h, 0DD01h, 01DC0h, 01C80h, 0DC41h | |
; 3 | |
dw 01400h, 0D4C1h, 0D581h, 01540h, 0D701h, 017C0h, 01680h, 0D641h | |
dw 0D201h, 012C0h, 01380h, 0D341h, 01100h, 0D1C1h, 0D081h, 01040h | |
; 4 | |
dw 0F001h, 030C0h, 03180h, 0F141h, 03300h, 0F3C1h, 0F281h, 03240h | |
dw 03600h, 0F6C1h, 0F781h, 03740h, 0F501h, 035C0h, 03480h, 0F441h | |
; 5 | |
dw 03C00h, 0FCC1h, 0FD81h, 03D40h, 0FF01h, 03FC0h, 03E80h, 0FE41h | |
dw 0FA01h, 03AC0h, 03B80h, 0FB41h, 03900h, 0F9C1h, 0F881h, 03840h | |
; 6 | |
dw 02800h, 0E8C1h, 0E981h, 02940h, 0EB01h, 02BC0h, 02A80h, 0EA41h | |
dw 0EE01h, 02EC0h, 02F80h, 0EF41h, 02D00h, 0EDC1h, 0EC81h, 02C40h | |
; 7 | |
dw 0E401h, 024C0h, 02580h, 0E541h, 02700h, 0E7C1h, 0E681h, 02640h | |
dw 02200h, 0E2C1h, 0E381h, 02340h, 0E101h, 021C0h, 02080h, 0E041h | |
; 8 | |
dw 0A001h, 060C0h, 06180h, 0A141h, 06300h, 0A3C1h, 0A281h, 06240h | |
dw 06600h, 0A6C1h, 0A781h, 06740h, 0A501h, 065C0h, 06480h, 0A441h | |
; 9 | |
dw 06C00h, 0ACC1h, 0AD81h, 06D40h, 0AF01h, 06FC0h, 06E80h, 0AE41h | |
dw 0AA01h, 06AC0h, 06B80h, 0AB41h, 06900h, 0A9C1h, 0A881h, 06840h | |
; A | |
dw 07800h, 0B8C1h, 0B981h, 07940h, 0BB01h, 07BC0h, 07A80h, 0BA41h | |
dw 0BE01h, 07EC0h, 07F80h, 0BF41h, 07D00h, 0BDC1h, 0BC81h, 07C40h | |
; B | |
dw 0B401h, 074C0h, 07580h, 0B541h, 07700h, 0B7C1h, 0B681h, 07640h | |
dw 07200h, 0B2C1h, 0B381h, 07340h, 0B101h, 071C0h, 07080h, 0B041h | |
; C | |
dw 05000h, 090C1h, 09181h, 05140h, 09301h, 053C0h, 05280h, 09241h | |
dw 09601h, 056C0h, 05780h, 09741h, 05500h, 095C1h, 09481h, 05440h | |
; D | |
dw 09C01h, 05CC0h, 05D80h, 09D41h, 05F00h, 09FC1h, 09E81h, 05E40h | |
dw 05A00h, 09AC1h, 09B81h, 05B40h, 09901h, 059C0h, 05880h, 09841h | |
; E | |
dw 08801h, 048C0h, 04980h, 08941h, 04B00h, 08BC1h, 08A81h, 04A40h | |
dw 04E00h, 08EC1h, 08F81h, 04F40h, 08D01h, 04DC0h, 04C80h, 08C41h | |
; F | |
dw 04400h, 084C1h, 08581h, 04540h, 08701h, 047C0h, 04680h, 08641h | |
dw 08201h, 042C0h, 04380h, 08341h, 04100h, 081C1h, 08081h, 04040h | |
MODEL TPASCAL | |
PUBLIC UpdateCRCArc | |
PROC UpdateCRCArc FAR initcrc:WORD,inbuf:DWORD,inlen:WORD | |
; UpdateCRCArc takes an initial CRC value and updates it with inlen bytes from | |
; inbuf. The updated CRC is returned in AX. The Pascal declaration is: | |
; Function UpdateCRCArc(InitCRC : Word; Var InBuf; InLen : Word) : Word; | |
; Stomps registers: AX,BX,CX,SI | |
push ds | |
lds si,[inbuf] ; ds:si := ^inbuf | |
mov ax,[initcrc] ; ax := initcrc | |
mov cx,[inlen] ; cx := inlen | |
or cx,cx | |
jz @@done | |
@@loop: | |
xor bh,bh | |
mov bl,al | |
lodsb | |
xor bl,al | |
shl bx,1 | |
mov bx,[crctab+bx] | |
xor bl,ah | |
mov ax,bx | |
loop @@loop | |
@@done: | |
pop ds | |
ret | |
ENDP | |
ENDS | |
END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$B-} { Use fast boolean evaluation. } | |
program Encrypt; | |
{ Program to demonstrate use of TEncryptFilter } | |
{$i StDefine.inc} | |
uses | |
{$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
{$ifdef windows} wincrt, {$endif} | |
streams; | |
procedure SyntaxExit(s:string); | |
begin | |
writeln; | |
writeln(s); | |
writeln; | |
writeln('Usage: ENCRYPT Sourcefile Destfile'); | |
writeln(' will encrypt sourcefile using key $12345678.'); | |
writeln(' Run ENCRYPT on the encrypted file to decrypt it.'); | |
halt(99); | |
end; | |
var | |
Source : PBufStream; | |
Dest : PEncryptFilter; | |
filename : string; | |
begin | |
if paramcount <> 2 then | |
SyntaxExit('Two parameters required.'); | |
{ Open the source file with a buffer size of 2048. } | |
{$ifdef windows} | |
Filename := Paramstr(1); | |
Filename[length(filename)+1] := #0; | |
New(Source, Init( @filename[1], stOpenRead, 2048) ); | |
{$else} | |
New(Source, Init( Paramstr(1), stOpenRead, 2048) ); | |
{$endif windows} | |
if (Source = nil) or (Source^.status <> stOk) then | |
SyntaxExit('Unable to open file '+ParamStr(1)+' for reading.'); | |
{ Open the destination file with a buffer size of 2048, and insert it | |
into the encrypting filter. } | |
{$ifdef windows} | |
Filename := Paramstr(2); | |
Filename[length(filename)+1] := #0; | |
New(Dest, Init($12345678, New(PBufStream, | |
Init( @filename[1], stCreate, 2048)))); | |
{$else} | |
New(Dest, Init($12345678, New(PBufStream, | |
Init( Paramstr(2), stCreate, 2048)))); | |
{$endif windows} | |
if (Dest = nil) or (Dest^.status <> stOk) then | |
SyntaxExit('Unable to create file '+Paramstr(2)+'.'); | |
{ Encrypt the source file by copying it to the filter.} | |
Write('Encrypting ',Paramstr(1),' to ',Paramstr(2),'…'); | |
FastCopy(Source^, Dest^, Source^.GetSize); | |
if Dest^.status <> stOK then | |
SyntaxExit('File error during encryption.'); | |
{ Dispose of stream variables to close the files.} | |
Dispose(Source, done); | |
Dispose(Dest, done); | |
Writeln('Done.'); | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$B-} { Use fast boolean evaluation. } | |
Program HuffComp; | |
{ Simple compression program using Huffman compression. Much like | |
COMPRESS.PAS. } | |
{$i StDefine.inc} | |
uses | |
{$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
{$ifdef windows} wincrt, {$endif} | |
streams, huffman; | |
procedure SyntaxExit(s:string); | |
begin | |
writeln; | |
writeln(s); | |
writeln; | |
writeln('Usage: HUFFMAN Sourcefile Destfile [/X]'); | |
writeln(' will compress the source file to the destination'); | |
writeln(' file, or if /X flag is used, will expand source to destination.'); | |
halt(99); | |
end; | |
var | |
Source : PStream; { We don't know in advance which will be compressed } | |
Dest : PStream; | |
Fullsize:longint; | |
Filename : string; | |
begin | |
Case ParamCount of | |
2 : begin | |
{$ifdef windows} | |
Filename := Paramstr(1); | |
Filename[length(filename)+1] := #0; | |
Source := New(PBufStream, init(@filename[1], stOpenRead, 2048)); | |
Filename := Paramstr(2); | |
Filename[length(filename)+1] := #0; | |
Dest := New(PHuffmanFilter, init(New(PBufStream, | |
init(@filename[1], | |
stCreate, 2048)))); | |
{$else} | |
Source := New(PBufStream, init(Paramstr(1), stOpenRead, 2048)); | |
Dest := New(PHuffmanFilter, init(New(PBufStream, | |
init(Paramstr(2), | |
stCreate, 2048)))); | |
{$endif windows} | |
Write('Compressing ',Paramstr(1),' (',Source^.GetSize, | |
' bytes) to ',Paramstr(2)); | |
{ Count characters in source. } | |
FullSize := Source^.GetSize; | |
Dest^.Write(FullSize,sizeof(FullSize)); | |
FastCopy(Source^,Dest^,Source^.GetSize); | |
Source^.Seek(0); | |
With PHuffmanFilter(Dest)^ do | |
begin | |
Seek(0); | |
BuildCode; | |
StoreCode; | |
Learning := false; | |
Write(Fullsize,sizeof(Fullsize)); | |
end; | |
end; | |
3 : begin | |
if (Paramstr(3) <> '/X') and (Paramstr(3) <> '/x') then | |
SyntaxExit('Unrecognized option '+Paramstr(3)); | |
{$ifdef windows} | |
Filename := Paramstr(1); | |
Filename[length(filename)+1] := #0; | |
Source := New(PHuffmanFilter, init(New(PBufStream, | |
init(@filename[1], | |
stOpenRead, 2048)))); | |
Filename := Paramstr(2); | |
Filename[length(filename)+1] := #0; | |
Dest := New(PBufStream, init(@filename[1], stCreate, 2048)); | |
{$else} | |
Source := New(PHuffmanFilter, init(New(PBufStream, | |
init(Paramstr(1), | |
stOpenRead, 2048)))); | |
Dest := New(PBufStream, init(Paramstr(2), stCreate, 2048)); | |
{$endif} | |
Write('Expanding ',Paramstr(1),' (', | |
PHuffmanFilter(Source)^.Base^.GetSize,' bytes) to ', | |
Paramstr(2)); | |
with PHuffmanFilter(Source)^ do | |
begin | |
LoadCode; | |
Learning := false; | |
Read(Fullsize,Sizeof(Fullsize)); | |
end; | |
end; | |
else | |
SyntaxExit('Two or three parameters required.'); | |
end; | |
if (Source = nil) or (Source^.status <> stOk) then | |
SyntaxExit('Unable to open file '+ParamStr(1)+' for reading.'); | |
if (Dest = nil) or (Dest^.status <> stOk) then | |
SyntaxExit('Unable to create file '+Paramstr(2)+'.'); | |
FastCopy(Source^, Dest^, FullSize); | |
if Dest^.status <> stOK then | |
SyntaxExit('File error during compression/expansion.'); | |
Case ParamCount of | |
2 : begin | |
Dest^.Flush; | |
Writeln(' (',PHuffmanFilter(Dest)^.Base^.GetSize,' bytes).'); | |
end; | |
3 : Writeln(' (',FullSize,' bytes).'); | |
end; | |
Dispose(Source, done); | |
Dispose(Dest, done); | |
end. | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$B-} { Use fast boolean evaluation. } | |
unit Huffman; { Copyright D.J. Murdoch, (1992) } | |
{ Defines a Huffman compression filter to illustrate use of the TBitFilter. } | |
{ The THuffmanFilter object defined in this file isn't optimized as much as | |
I'd like, so I haven't put it into the main Streams unit. It's also a | |
little rough – be careful if you use it. If you make any substantial | |
improvements, I'd like to see them! – djm} | |
interface | |
{$i StDefine.inc} | |
uses | |
{$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
streams; | |
const | |
MaxNode = 510; | |
StoreSize = ((MaxNode-255)*18+7) div 8; { Bytes required to store the code | |
table } | |
type | |
PHuffmanfilter = ^THuffmanfilter; | |
THuffmanfilter = object(TBitfilter) | |
{ This object defines a Huffman encoder/decoder which encodes the 256 | |
letter alphabet of bytes using variable length codes in the 2 letter | |
alphabet of bits. } | |
Size, { The size of the expanded stream. } | |
Position : LongInt; { The current position in the expanded stream } | |
Counts : array[0..MaxNode] of longint; { Counts uncompressed characters; | |
second half used as workspace } | |
Decoder : array[256..MaxNode,TBit] of integer; { Array holding decoder } | |
EncodeStates : array[0..MaxNode] of integer; { The state change array } | |
EncodeBits : array[0..MaxNode] of TBit; { The encoding bit for each | |
state } | |
Learning : boolean; { Signals whether writes are enabled, and whether | |
to attempt to decode reads. } | |
constructor init(ABase:PStream); | |
{ Inits the Counts to 0, but doesn't set up a code. Puts filter | |
in "learning" mode. Before setting Learning to false, be sure to | |
call LoadCode or BuildCode. } | |
procedure LoadCode; | |
{ Reads an encoding from the base stream. } | |
procedure StoreCode; | |
{ Writes an encoding to the base stream. } | |
procedure BuildCode; | |
{ Builds the optimal encoding based on the values in the Counts array } | |
procedure BuildEncoder(Verify:boolean); | |
{ Initializes the Encode arrays based on the Decoder array. Called | |
automatically by LoadCode and BuildCode; use this routine only | |
if you've loaded the Decoder in some other way. If Verify is true, | |
it will check that the Decoder array is valid. } | |
function CodeBits(b:byte):word; | |
{ Returns the number of bits that will be used in the current code | |
to write b. } | |
function PredictedSize:Longint; | |
{ Returns the predicted number of bytes to write the distribution of | |
bytes given in Counts in the current encoding. } | |
procedure read(var buf; count:word); virtual; | |
procedure write(var buf; count:word); virtual; | |
function getpos:longint; virtual; | |
function getsize:longint; virtual; | |
end; | |
implementation | |
constructor THuffmanFilter.Init(ABase:PStream); | |
begin | |
if not TFilter.Init(ABase) then | |
fail; | |
Size := 0; | |
Position := 0; | |
FillChar(counts,sizeof(counts),0); | |
Learning := true; | |
end; | |
procedure THuffmanFilter.LoadCode; | |
var | |
i,code : integer; | |
begin | |
for i:=256 to MaxNode do | |
begin | |
ReadBits(code,9); | |
Decoder[i,0] := code; { Should we confirm code<=MaxNode? } | |
ReadBits(code,9); | |
Decoder[i,1] := code; | |
end; | |
BuildEncoder(true); | |
end; | |
procedure THuffmanFilter.StoreCode; | |
var | |
i : integer; | |
begin | |
for i:=256 to MaxNode do | |
begin | |
WriteBits(Decoder[i,0],9); | |
WriteBits(Decoder[i,1],9); | |
end; | |
end; | |
procedure THuffmanFilter.BuildCode; | |
var | |
letters : array[byte] of integer; { The array of symbols } | |
procedure Revsort; | |
{ Procedure to do a Quicksort on the array of letters, | |
to put Counts[letters[i]] into decreasing order. | |
Ties are broken by the letter order. | |
Based on Quicksort as given in Steal This Code, by F.D. Boswell, Watcom 1986. | |
} | |
procedure quick(first,last : integer); | |
var | |
pivot : integer; | |
temp : integer; | |
scanright, scanleft : integer; | |
begin | |
if (first < last) then | |
begin | |
pivot := letters[first]; | |
scanright := first; | |
scanleft := last; | |
while scanright < scanleft do | |
begin | |
if Counts[letters[scanright+1]] < Counts[pivot] then | |
begin | |
if Counts[letters[scanleft]] >= Counts[pivot] then | |
begin | |
temp := letters[scanleft]; | |
inc(scanright); | |
letters[scanleft] := letters[scanright]; | |
letters[scanright] := temp; | |
dec(scanleft); | |
end | |
else | |
dec(scanleft); | |
end | |
else | |
inc(scanright); | |
end; | |
temp := letters[scanright]; | |
letters[scanright] := letters[first]; | |
letters[first] := temp; | |
quick(first, scanright-1); | |
quick(scanright+1, last); | |
end; | |
end; | |
begin {quicksort} | |
quick(0, 255); | |
end; | |
var | |
i,LastEntry,LastLetter,PrevLetter,InsertAt : integer; | |
begin { BuildCode } | |
for i:=0 to 255 do | |
letters[i] := i; { Initialize to match counts } | |
RevSort; { Sort into decreasing frequency } | |
for i :=256 to MaxNode do | |
begin | |
{ Create node by combining last two entries } | |
LastEntry := 511-i; | |
LastLetter := Letters[LastEntry]; | |
PrevLetter := Letters[LastEntry-1]; | |
Decoder[i,0] := PrevLetter; | |
Decoder[i,1] := LastLetter; | |
Counts[i] := Counts[PrevLetter] + Counts[LastLetter]; | |
{ Find where to insert it } | |
InsertAt := LastEntry-1; | |
While (InsertAt > 0) and (Counts[Letters[InsertAt-1]] <= Counts[i]) do | |
dec(InsertAt); | |
{ Insert the node } | |
Move(Letters[InsertAt],Letters[InsertAt+1], | |
(LastEntry-1-InsertAt)*sizeof(Integer)); | |
Letters[InsertAt] := i; | |
end; | |
BuildEncoder(false); | |
end; | |
procedure THuffmanFilter.BuildEncoder(verify:boolean); | |
var | |
i,code : integer; | |
j : TBit; | |
begin | |
fillchar(EncodeBits,sizeof(EncodeBits),0); | |
if verify then | |
begin | |
{ First, confirm that all the Decoder values are in range } | |
for i:=256 to MaxNode do | |
for j:=0 to 1 do | |
if (Decoder[i,j] < 0) or (Decoder[i,j] > MaxNode) then | |
begin | |
Error(stIntegrity,i); | |
exit; | |
end; | |
{ Initialize the EncodeStates to illegal values to detect missing | |
codes } | |
fillchar(EncodeStates,sizeof(EncodeStates),0); | |
end; | |
for i:=256 to MaxNode do | |
begin | |
EncodeStates[Decoder[i,0]] := i; | |
code := Decoder[i,1]; | |
EncodeStates := i; | |
EncodeBits := 1; | |
end; | |
if verify then | |
for i:=0 to pred(MaxNode) do | |
if EncodeStates[i] = 0 then | |
begin | |
Error(stIntegrity,i); | |
exit; | |
end; | |
end; | |
function THuffmanFilter.CodeBits(b:byte):word; | |
var | |
state : 0..MaxNode; | |
result : word; | |
begin | |
result := 0; | |
state := b; | |
while state < MaxNode do | |
begin | |
inc(result); | |
state := EncodeStates[state]; | |
end; | |
CodeBits := result; | |
end; | |
function THuffmanFilter.PredictedSize:longint; | |
var | |
bitcount : longint; | |
b : byte; | |
begin | |
bitcount := 0; | |
for b:=0 to 255 do | |
inc(bitcount,Counts[b]*CodeBits(b)); | |
PredictedSize := (bitcount+7) div 8; | |
end; | |
procedure THuffmanFilter.Read(var buf;Count:word); | |
var | |
i : word; | |
bbuf : TByteArray absolute buf; | |
State : 0..MaxNode; | |
begin | |
if CheckStatus then | |
begin | |
if learning then | |
TBitFilter.Read(buf,Count) | |
else | |
for i:=0 to Count-1 do | |
begin | |
State := MaxNode; | |
repeat | |
State := Decoder[State,GetBit]; | |
until State < 256; | |
bbuf[i] := State; | |
end; | |
for i:=0 to Count-1 do | |
inc(Counts[bbuf[i]]); | |
inc(position,Count); | |
if Position>Size then | |
Size := Position; | |
CheckBase; | |
end; | |
end; | |
procedure THuffmanFilter.Write(var buf;Count:word); | |
var | |
bbuf : TByteArray absolute buf; | |
i : word; | |
bitstack : word; | |
bitcount : word; | |
words : word; | |
state : 0..MaxNode; | |
begin | |
if CheckStatus then | |
begin | |
for i:=0 to Count-1 do | |
inc(Counts[bbuf[i]]); | |
if not learning then | |
begin | |
for i:=0 to Count-1 do | |
begin | |
bitstack := 0; | |
bitcount := 0; | |
words := 0; | |
state := bbuf[i]; | |
{ Push all the bits onto the stack } | |
while state < MaxNode do | |
begin | |
bitstack := 2*bitstack + EncodeBits[state]; | |
inc(bitcount); | |
if bitcount = 16 then | |
begin | |
asm | |
push bitstack | |
end; | |
bitstack := 0; | |
bitcount := 0; | |
inc(words); | |
end; | |
state := EncodeStates[state]; | |
end; | |
{ Now write out all the bits } | |
WriteBits(bitstack,bitcount); | |
while words > 0 do | |
begin | |
asm | |
pop bitstack | |
end; | |
WriteBits(BitStack,16); | |
dec(words); | |
end; | |
end; | |
inc(position,count); | |
if position>size then | |
size := position; | |
CheckBase; | |
end; | |
end; | |
end; | |
function THuffmanFilter.GetPos:longint; | |
begin | |
GetPos := Position; | |
end; | |
function THuffmanFilter.GetSize:longint; | |
begin | |
GetSize := Size; | |
end; | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$B-} { Use fast boolean evaluation. } | |
program logdemo; | |
{ Demonstrates use of TLogFilter } | |
{$i stdefine.inc} | |
uses | |
{$ifdef windows} wincrt, {$endif} | |
{$ifdef wobjects} wobjects, {$else} objects, {$endif} | |
streams; | |
var | |
i : integer; | |
inlog,log : PLogFilter; | |
begin | |
{ Log both input and output to Logdemo.out } | |
new(log, init( new(PDOSStream, init('Logdemo.out',stCreate)))); | |
log^.log(input); | |
log^.log(output); | |
writeln('This is the Logdemo program, which logs input and output'); | |
writeln('to LOGDEMO.OUT'); | |
write('Enter an integer:'); | |
readln(i); | |
writeln('Logging will now be turned off.'); | |
if not log^.unlog(input) then; { This is one way to stop logging. } | |
close(output); { This is another way. } | |
{ Re-open output; input was never closed. } | |
rewrite(output); | |
writeln('This line will not be logged.'); | |
write('Enter another integer:'); | |
readln(i); | |
writeln('Logging will be turned back on now.'); | |
log^.log(input); | |
log^.log(output); | |
writeln('This line will be logged to the file.'); | |
writeln('All done now; close the log.'); | |
dispose(log,done); | |
writeln('The log has been closed, so this line won''t be logged.'); | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
PAGE 60, 132 | |
TITLE 12 bit LZW Compression Scheme | |
LOCALS @@ | |
Comment * | |
This unit is a modified version of Wilbert van Leijen's LZW unit, | |
to implement a stream type that automatically compresses data. | |
The original documentation: | |
This modules implements a 12-bit Lempel-Zev-Welch "crunch" (compress | |
data) and "uncrunch" (restore data in its original form) routines. | |
InBuffer and OutBuffer are untyped; you must pass pointers to arrays | |
(0..MaxRange) of Char or Byte to it, whereby MaxRange is limited to | |
2^16-16+1 = 65521 bytes. | |
You must also supply the size of the input buffer. | |
The LZW technique is well explained in the following reference: | |
Terry A. Welch, "A Technique for High Performance Data Compression" | |
IEEE Computer | |
Vol. 17, no. 6 (June 1984), pp. 8-19 | |
Incorporate these routines as follows in a TP unit: | |
[ deleted ] | |
(C) Copyright Wilbert van Leijen, Amsterdam 1990. | |
Released to the Public Domain under the condition that this program | |
will not be sold for a profit except with written permission from | |
the author. | |
Stream additions (c) copyright D.J. Murdoch, 1991. | |
* | |
MaxStack = 4096; ; Decompression stack size | |
TableSize = MaxStack-1; ; Upper bound of 12 bit tables | |
HalfFull = MaxStack / 2 | |
ProbeValue = 131; ; Preferably a prime number | |
True = 1 | |
False = 0 | |
EndList = -1; ; Marks end of a list | |
NoPrev = -2; ; Code for no previous character | |
Empty = -3; ; Indicates empty | |
DataSize = [BP+18] ; Number of bytes input | |
OutputSize = word ptr [BP+16] ; Max number to output | |
InputBuf = [BP+12] ; Input data buffer | |
OutputBuf = [BP+8] ; Output data buffer | |
Tables = [BP+4] ; Where the tables are | |
Table STRUC | |
Collision DB MaxStack DUP(?) ; Hash table entries | |
PrefixTable DW MaxStack DUP(?) ; Code for preceding stringf | |
SuffixTable DB MaxStack DUP(?) ; Code for current character | |
ChildTable DW MaxStack DUP(?) ; Next duplicate in collision list | |
CharStack DB MaxStack DUP(?) ; Decompression stack | |
StackPtr DW ? ; Decompression stack depth | |
Prefix DW ? ; Previous code string | |
TableUsed DW ? ; # string table entries used | |
InputPos DW ? ; Index in input buffer | |
OutputPos DW ? ; Index in output buffer | |
LastHit DW ? ; Last empty slot in collision table | |
CodeBuf DW ? ; Temporary code buffer | |
SaveIP DW ? ; Saved registers between calls | |
SaveAX DW ? | |
SaveCX DW ? | |
SaveDX DW ? | |
NotFound DB ? ; Character combination found flag | |
Table ENDS | |
; CH register is set aside for final output character (= Suffix) | |
Code SEGMENT Word Public | |
ASSUME CS:Code | |
Public Initialise | |
Public PutSignature | |
Public Crunch | |
Public FlushLZW | |
Public GetSignature | |
Public Uncrunch | |
; Initialise variables, fill tables | |
Initialise PROC near | |
PUSH BP | |
MOV BP,SP | |
PUSH DS ; save DS | |
LDS BX,Tables ; get DS:BX to point to the tables | |
XOR AX, AX | |
MOV [BX].InputPos, AX | |
MOV [BX].OutputPos, AX | |
MOV [BX].TableUsed, AX | |
MOV [BX].StackPtr, AX | |
MOV [BX].CodeBuf, Empty | |
; Loop: | |
; Clear collision table | |
; Set prefix to 'no previous character' code | |
; Set child nodes to 'end of list' code | |
MOV DX, Tablesize | |
@@1: MOV DI, DX | |
MOV [BX+DI+Collision], 0 | |
SHL DI, 1 | |
MOV [BX+DI+PrefixTable], NoPrev | |
MOV [BX+DI+ChildTable], EndList | |
DEC DX | |
JNS @@1 | |
; Loop: | |
; Enter all single characters into the hash table | |
MOV DX, 255 | |
MOV [BX].Prefix, NoPrev | |
@@2: MOV CH, DL | |
CALL MakeEntry | |
DEC DX | |
JNS @@2 | |
MOV [BX].SaveCX,CX | |
POP DS | |
POP BP | |
RETN 4 | |
Initialise ENDP | |
; Hash function: 'fold' number | |
; AX := (Prefix shl 5 xor Suffix) and TableSize | |
; uses CL,DX | |
HashNumber MACRO | |
XOR DX, DX | |
MOV DL, CH | |
MOV AX, [BX].Prefix | |
MOV CL, 5 | |
SHL AX, CL | |
XOR AX, DX | |
AND AX, TableSize | |
ENDM | |
; Store a character combination in the hash table | |
MakeEntry PROC Near | |
PUSH DX | |
HashNumber | |
; Rehash is necessary if entry in the hash table is occupied | |
MOV DI, AX | |
CMP [BX+DI+Collision], False | |
JE @@6 | |
; Loop: | |
; Advance index of ChildTable to last empty list | |
@@1: MOV DI, AX | |
SHL DI, 1 | |
CMP [BX+DI+ChildTable], EndList | |
JE @@2 | |
MOV AX, [BX+DI+ChildTable] | |
JMP Short @@1 | |
; If the hash table is less than 50% loaded | |
; Increase index with the probing value | |
@@2: CMP [BX].TableUsed, HalfFull | |
JAE @@3 | |
MOV SI, AX | |
ADD SI, ProbeValue | |
AND SI, TableSize | |
JMP Short @@4 | |
; Else deal with the clustering problem | |
; A simple yet effective solution is to start probing at the | |
; empty slot of the hash table found during the previous run | |
@@3: MOV SI, [BX].LastHit | |
; Loop: | |
; Probe hash table until an empty slot is found | |
@@4: CMP [BX+SI+Collision], False | |
JE @@5 | |
INC SI | |
AND SI, TableSize | |
JMP Short @@4 | |
; Found an empty slot, save index in ChildTable | |
; Store index in LastHit | |
@@5: MOV DI, AX | |
SHL DI, 1 | |
MOV [BX+DI+ChildTable], SI | |
MOV DI, SI | |
MOV [BX].LastHit, SI | |
; Return: | |
; Indicate hash table slot's been occupied | |
; Store character combination in PrefixTable, SuffixTable | |
; Indicate 'end of list' in ChildTable | |
; Bump table load counter | |
@@6: MOV [BX+DI+Collision], True | |
MOV [BX+DI+SuffixTable], CH | |
SHL DI, 1 | |
MOV [BX+DI+ChildTable], EndList | |
MOV AX, [BX].Prefix | |
MOV [BX+DI+PrefixTable], AX | |
INC [BX].TableUsed | |
POP DX | |
RETN | |
MakeEntry ENDP | |
; Lookup a character combination in the hash table | |
LookupStr PROC Near | |
HashNumber | |
XCHG SI, AX | |
; Search through list of hash collision entries for one that match | |
; Loop | |
; Entry is found if prefix and suffix match | |
; If no match, advance index to entry in ChildTable | |
; Until entry is found or no such list exists in ChildTable | |
@@1: MOV DI, SI | |
MOV AL, [BX+DI+SuffixTable] | |
CMP AL, CH | |
JNE @@2 | |
SHL DI, 1 | |
MOV AX, [BX+DI+PrefixTable] | |
CMP AX, [BX].Prefix | |
JE @@3 | |
@@2: MOV DI, SI | |
SHL DI, 1 | |
MOV SI, [BX+DI+ChildTable] | |
CMP SI, EndList | |
JNE @@1 | |
; Return index from ChildTable in AX | |
@@3: XCHG AX, SI | |
RETN | |
LookupStr ENDP | |
; Retrieve next character from the input buffer | |
GetChar MACRO | |
LES DI, InputBuf | |
ADD DI, [BX].InputPos | |
MOV AL, ES:[DI] | |
INC [BX].InputPos | |
ENDM | |
; Store next character in AL into the output buffer | |
PutChar MACRO | |
LES DI, OutputBuf | |
ADD DI, [BX].OutputPos | |
STOSB | |
INC [BX].OutputPos | |
ENDM | |
; Retrieve compressed code from input buffer | |
GetCode PROC Near | |
MOV SI, [BX].CodeBuf | |
; Get first character and store it in a temporary buffer | |
GetChar | |
XOR AH, AH | |
MOV DX, AX | |
; If input code is empty | |
CMP SI, Empty | |
JNE @@1 | |
; Get next character | |
; Return 8 bits from first character + 4 bits from second character | |
; Save the remaining 4 bits of the second character for next time | |
GetChar | |
MOV SI, AX | |
MOV CL, 4 | |
SHR AX, CL | |
MOV DI, AX | |
MOV AX, DX | |
SHL AX, CL | |
ADD AX, DI | |
JMP @@2 | |
; Else | |
; Get the last 4 bits from the input code + 8 bits from the second char | |
@@1: MOV AX, SI | |
XCHG AH, AL | |
AND AH, 0Fh | |
ADD AX, DX | |
MOV SI, Empty | |
@@2: | |
MOV [BX].CodeBuf,SI | |
RETN | |
GetCode ENDP | |
; Store compressed code in the output buffer | |
PutCode PROC Near | |
MOV DI, [BX].CodeBuf | |
MOV DX, [BX].Prefix | |
; If output code is empty | |
CMP DI, Empty | |
JNE @@1 | |
; Store first 8 bits in the output buffer | |
; Save last 4 bits for the next time through | |
MOV AX, DX | |
MOV CL, 4 | |
SHR AX, CL | |
PutChar | |
MOV DI, DX | |
JMP @@2 | |
; Else | |
; Put out last 4 bits of previous code + first 4 bits of this code | |
; Next, put out last 8 bits of this code | |
; Indicate output code as empty | |
@@1: MOV AX, DI | |
MOV CL, 4 | |
SHL AX, CL | |
ADD AL, DH | |
PutChar | |
MOV AL, DL | |
PutChar | |
MOV DI, Empty | |
@@2: MOV [BX].CodeBuf,DI | |
RETN | |
PutCode ENDP | |
; Start the compressor by putting 'LZ' | |
PutSignature PROC Near | |
PUSH BP | |
MOV BP,SP | |
PUSH DS ; save DS | |
LDS BX,Tables ; get DS:BX to point to the tables | |
MOV [BX].OutputPos, 0 | |
; Get first character and store it in Suffix | |
; There are no character combinations yet | |
MOV AL, 'L' | |
MOV CH, AL | |
MOV [BX].Prefix, NoPrev | |
CALL LookupStr | |
MOV [BX].Prefix, AX | |
; Get next character | |
MOV AL, 'Z' | |
MOV CH, AL | |
MOV [BX].SaveCX,CX | |
MOV AL,True ; Return success | |
POP DS | |
POP BP | |
RET 4 | |
PutSignature ENDP | |
Crunch PROC Near | |
PUSH BP | |
MOV BP,SP | |
PUSH DS ; save DS | |
LDS BX,Tables ; get DS:BX to point to the tables | |
MOV CX,[BX].SaveCX ; get saved registers | |
MOV [BX].InputPos, 0 | |
MOV [BX].OutputPos, 0 | |
DEC OutputSize ; sometimes we write 2 bytes per loop, | |
; so reduce this for safety | |
; Loop: | |
; Process all characters from input buffer | |
@@1: MOV DX, [BX].InputPos | |
MOV AX, [BX].OutputPos | |
CMP DX, DataSize | |
JAE @@5 | |
CMP AX, OutputSize | |
JAE @@5 | |
; Lookup the character combination | |
; Store if not found and if empty slots are available in the hash table | |
CALL LookupStr | |
MOV DI, AX | |
CMP DI, EndList | |
JNE @@3 | |
PUSH DI | |
CMP [BX].TableUsed, TableSize | |
JA @@2 | |
CALL MakeEntry | |
; Store code in the output buffer | |
; If string is in table, keep looking for longer strings | |
@@2: CALL PutCode | |
POP DI | |
MOV [BX].Prefix, NoPrev | |
XCHG DX, DI | |
CALL LookupStr | |
XCHG DI, DX | |
MOV [BX].Prefix, AX | |
JMP Short @@4 | |
; Get next character | |
@@3: MOV [BX].Prefix, DI | |
@@4: GetChar | |
MOV CH, AL | |
JMP Short @@1 | |
@@5: MOV [BX].SaveCX,CX | |
; return number written in AX | |
; and number used in DX | |
POP DS | |
POP BP | |
RET 14 | |
Crunch ENDP | |
; Make sure the last code will be written out | |
; If the output code <> Empty, store the last 4 pending bits | |
FlushLZW Proc Near | |
PUSH BP | |
MOV BP,SP | |
PUSH DS ; save DS | |
LDS BX,Tables ; get DS:BX to point to the tables | |
MOV CX,[BX].SaveCX ; get saved registers | |
MOV [BX].InputPos, 0 | |
MOV [BX].OutputPos, 0 | |
@@5: CALL PutCode | |
MOV SI,[BX].CodeBuf | |
CMP SI, Empty | |
JE @@7 | |
MOV AX, SI | |
MOV CL, 4 | |
SHL AX, CL | |
PutChar | |
; Return the number of bytes written to the output buffer | |
@@7: MOV AX, [BX].OutputPos | |
MOV [BX].SaveCX,CX | |
POP DS | |
POP BP | |
RET 8 | |
FlushLZW ENDP | |
; Run through code extracting single characters from code string until | |
; no more characters can be removed. Push these onto the stack. | |
; They will be entered in reverse order, and will come out in forwards order | |
; when popped off | |
PushChar PROC Near | |
@@1: MOV DI, SI | |
SHL DI, 1 | |
CMP [BX+DI+PrefixTable], NoPrev | |
JNE @@2 | |
RETN | |
@@2: MOV AL, [BX+SI+SuffixTable] | |
INC [BX].StackPtr | |
MOV DI, [BX].StackPtr | |
MOV [BX+DI+CharStack], AL | |
SHL SI, 1 | |
MOV SI, [BX+SI+PrefixTable] | |
JMP Short @@1 | |
PushChar ENDP | |
; While StackPtr > 0 | |
; Pop a character from the stack | |
PopChar PROC Near | |
PutChar | |
MOV DI, [BX].StackPtr | |
OR DI, DI | |
JE @@1 | |
MOV AL, [BX+DI+CharStack] | |
DEC [BX].StackPtr | |
RETN | |
@@1: MOV AX, Empty | |
RETN | |
PopChar ENDP | |
; Check for correct start of buffer, and initialize registers | |
GetSignature PROC Near | |
PUSH BP | |
MOV BP, SP | |
PUSH DS ; save DS | |
LDS BX,Tables ; get DS:BX to point to the tables | |
; Get first string and check the corresponding character | |
; Keep a copy of this code | |
CALL GetCode | |
MOV [BX].Prefix, AX | |
MOV SI, AX | |
MOV CH, [BX+SI+SuffixTable] | |
CMP CH, 'L' | |
JNE @@2 | |
CALL GetCode | |
MOV [BX].SaveDX, AX | |
; Do half a run through the old Uncrunch loop | |
; ( skip size check ) | |
MOV [BX].Notfound, False | |
MOV SI, AX | |
; ( skip collision test, & PushChar call ) | |
MOV CH, [BX+SI+SuffixTable] | |
CMP CH, 'Z' | |
JNE @@2 | |
; ( do PopChar's MOV AX, Empty ) | |
MOV [BX].SaveAX,Empty | |
MOV AL,True ; Success! We're ready for the loop. | |
JMP @@1 | |
@@2: MOV AL,False ; It failed! | |
@@1: MOV [BX].SaveCX,CX | |
MOV [BX].SaveIP,OFFSET MainLoop | |
POP DS | |
POP BP | |
RET 14 | |
GetSignature ENDP | |
UnCrunch PROC Near | |
PUSH BP | |
MOV BP,SP | |
PUSH DS ; save DS | |
LDS BX,Tables ; get DS:BX to point to the tables | |
MOV AX,[BX].SaveAX ; get saved registers | |
MOV CX,[BX].SaveCX | |
MOV DX,[BX].SaveDX | |
MOV [BX].InputPos, 0 ; set up string pointers & sizes | |
MOV [BX].OutputPos, 0 | |
DEC Word Ptr DataSize ; sometimes we need to read two | |
JMP [BX].SaveIP | |
; Loop: | |
; Process all characters from input buffer | |
; While the stack is not empty, remove and output all characters from | |
; stack which are rest of characters in the string | |
@@1: MOV DI,[BX].OutputPos ; Check if there's room for more characters | |
CMP DI,OutputSize | |
JB @@3 | |
CALL ExitLoop ; Exit from Uncrunch | |
Mainloop: | |
@@3: CMP AX, Empty | |
JE @@4 | |
CALL PopChar | |
JMP Short @@1 | |
; If code isn't known, store the follower character of last | |
; character of string | |
@@4: CMP [BX].NotFound, False | |
JE @@5 | |
PUSH AX | |
MOV AL, CL | |
MOV CH, AL | |
PutChar | |
POP AX | |
; Check whether there's enough space for another char | |
@@8: MOV DI,[BX].OutputPos | |
CMP DI,OutputSize | |
JB @@5 | |
; No space, so quit | |
CALL ExitLoop | |
; If the hash table is not full | |
; Enter code into table | |
; Make current code the previous code | |
; Get next code | |
@@5: CMP [BX].TableUsed, TableSize | |
JA @@6 | |
CALL MakeEntry | |
@@6: MOV [BX].Prefix, DX | |
CALL GetCode | |
XCHG DX, AX | |
; Old top of loop | |
MOV AX, [BX].InputPos | |
CMP AX, DataSize | |
JB @@10 | |
; Out of data, so exit | |
CALL ExitLoop | |
; Retrieve character from string | |
; Keep a copy of it in CL | |
@@10: MOV [BX].NotFound, False | |
MOV SI, DX | |
CMP [BX+SI+Collision], False | |
JNE @@2 | |
MOV AL, CH | |
MOV CL, AL | |
MOV SI, [BX].Prefix | |
MOV [BX].NotFound, True | |
; Get first character from string | |
@@2: CALL PushChar | |
MOV AL, [BX+SI+SuffixTable] | |
MOV CH, AL | |
CALL PopChar | |
JMP @@1 | |
UnCrunch ENDP | |
ExitLoop PROC Near ; allows exit from UnCrunch loop | |
; and resumption at several places. | |
POP [BX].SaveIP ; Get address | |
MOV [BX].SaveAX,AX ; Save registers | |
MOV [BX].SaveCX,CX | |
MOV [BX].SaveDX,DX | |
; Return the number of bytes written to the output buffer in AX | |
; and the number of bytes used in DX | |
MOV AX, [BX].OutputPos | |
MOV DX, [BX].InputPos | |
POP DS | |
POP BP | |
RETN 14 | |
ExitLoop ENDP | |
Code ENDS | |
END |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
unit ovr1; | |
{$O+,F+} | |
interface | |
procedure proc1; | |
implementation | |
procedure proc1; | |
begin | |
writeln('This line is being printed by proc1 in unit ovr1.'); | |
end; | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
unit ovr2; | |
{$O+,F+} | |
interface | |
procedure proc2; | |
implementation | |
procedure proc2; | |
begin | |
writeln('This line is being printed by proc2 in unit ovr2.'); | |
end; | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{$B-} { Use fast boolean evaluation. } | |
program ovrdemo; | |
{ Program to demonstrate use of two overlay files. } | |
{$i stdefine.inc} | |
{$ifndef overlays} | |
This program only works in modes that support overlays. | |
{$endif} | |
uses | |
overlay,objects,streams, | |
ovr1,ovr2; | |
{$O ovr1} | |
{$O ovr2} | |
type | |
PMessageStream = ^TMessageStream; | |
TMessageStream = object(TNamedBufStream) | |
{ This stream prints its name every time anything is read from it. } | |
procedure read(var buf; size:word); virtual; | |
end; | |
procedure TMessageStream.Read; | |
begin | |
writeln('Reading from ',filename^); | |
TNamedBufStream.Read(buf,size); | |
end; | |
var | |
stream1, stream2 : PMessageStream; | |
begin | |
ovrinit('ovrdemo.ovr'); | |
writeln('The overlay streams aren''t being used yet.'); | |
proc1; | |
proc2; | |
writeln('Now loading overlays to the two streams.'); | |
ovrclearbuf; { Make sure no overlay is loaded. } | |
new(stream1, init('ovrdemo.1',stCreate,2048)); | |
ovrinitstream(stream1); | |
proc1; { This loads proc1 to Stream1, but doesn't trigger a read | |
yet. } | |
new(stream2, init('ovrdemo.2',stCreate,2048)); | |
ovrinitstream(stream2); | |
proc2; { This loads proc2 to Stream2, but again, no read. } | |
writeln('Now each unit is on a different stream; let''s call them a few '); | |
writeln('times.'); | |
proc1; | |
proc2; | |
proc1; | |
proc2; | |
writeln('Now the overlay streams will be disposed of.'); | |
OvrDisposeStreams; | |
writeln('These calls will use the old overlay mechanism.'); | |
proc1; | |
proc2; | |
end. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ This file sets some conditional compilation defines } | |
{$define overlays} { Assume we'll want the overlay code, but turn | |
it off in certain cases } | |
{$ifdef windows} | |
{$undef overlays} | |
{$endif} | |
{$ifdef dpmi} | |
{$undef overlays} | |
{$endif} | |
{ Only TPW uses "WObjects"; BP 7 calls the same unit "Objects" } | |
{$ifdef windows} | |
{$ifndef ver70} | |
{$define wobjects} | |
{$endif} | |
{$endif} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
STREAMS - TP/BP/TPW unit to supplement TurboVision/ObjectWindows streams | |
Version 1.6. Copyright D.J. Murdoch (1992,1994). | |
DESCRIPTION | |
"Wierd Stream Tricks" might be a good name for this unit. It | |
contains a miscellaneous collection of objects and procedures, all | |
on a theme of adding functionality to the streams in Borland's | |
Turbo Pascal libraries TurboVision and ObjectWindows. | |
Most of my testing has been in TP 6 and BP 7 real mode; the code | |
and demos also appear to work in BP 7 protected mode, and compile | |
under Windows, but use it with care in those "foreign" | |
environments. | |
LICENSE | |
This unit is *not* public domain code. You may use it for | |
no charge if credit is granted to the author, but you may not sell | |
it, nor may you claim copyright on it. You may make copies of this | |
unit and give them to people, but may not charge more than $5 for | |
the distribution media. In particular, it may not be distributed | |
on a CD ROM costing more than $5 without prior written permission | |
from D.J. Murdoch. The Garbo and Simtel CD ROM collections have | |
permission to distribute it. | |
A lot of the code in this unit is code that's been made freely | |
available by others, some of it under their copyright, other parts | |
public domain. As far as I know, all code included here may be | |
used for free, provided you acknowledge the author. See the list of | |
credits at the end for all the (known) authors. | |
This is the fourth release of the STREAMS unit. There are probably | |
still bugs; I would really appreciate reports of any, or other | |
suggestions for improvement. Please send either one to me at one | |
of the following addresses: | |
dmurdoch@mast.queensu.ca (Internet) | |
71631,122 (Compuserve) | |
DJ Murdoch at 1:249/1.5 (Fidonet) | |
D. J. Murdoch | |
337 Willingdon Ave. | |
Kingston, Ontario, Canada | |
K7L 4J3 | |
SUMMARY | |
Hierarchy | |
TStream (from Objects) | |
TFilter Base type for filters | |
TEncryptFilter Encrypts as it writes; decrypts as it reads | |
TLZWFilter Compresses as it writes; expands as it reads | |
TTextFilter Provides text file interface to stream | |
TLogFilter Provides logging of text file activity | |
TBitFilter Allows bit-oriented I/O | |
TDupFilter Duplicates output, checks for matching input | |
TConcatFilter Concatenates two streams together | |
TLimitFilter Limits I/O to a certain byte range | |
TLoopFilter Makes stream look like a tape loop | |
TReverseFilter Reads and writes a stream in reverse order | |
TSequential Filter that doesn't allow Seek | |
TChksumFilter Calculates 8 or 16 bit checksum for reads/writes | |
TCRC16Filter Calculates XMODEM style 16 bit CRC | |
TCRCARCFilter Calculates ARC style 16 bit CRC | |
TCRC32Filter Calculates ZIP & Zmodem style 32 bit CRC | |
TNulStream Eats writes, returns constant on reads | |
TRAMStream Stream in memory | |
TEMSStream2 Workaround stream to avoid Borland bug | |
TXMSStream Stream in XMS (extended) memory | |
TDOSStream (from Objects) | |
TBufStream (from Objects) | |
TNamedBufStream Buffered file stream that knows its name | |
TTempBufStream Buffered file stream that erases itself when done | |
TWorkStream Stream that grows as needed | |
Procedures & functions: | |
TempStream allocates a temporary stream | |
StreamName returns a stream type name (for debugging) | |
OvrInitStream like OvrInitEMS, but buffers overlays on a stream | |
May be called several times to buffer different | |
segments on different streams. | |
OvrDetachStream detaches stream from overlay system | |
OvrDisposeStreams detaches all streams from overlay system and disposes of | |
them | |
OvrSizeNeeded Calculates the size needed to load the rest of the segments | |
to a stream | |
OvrLoadAll immediately copies as many overlay segments to the stream | |
as will fit | |
xms_MemAvail returns the number of bytes of XMS memory available | |
xms_MaxAvail returns size in bytes of the largest block of XMS memory | |
ems_MemAvail returns the number of bytes of EMS memory available | |
ems_MaxAvail returns size in bytes of the largest block of EMS memory | |
disk_MemAvail returns the number of bytes of temp disk space available | |
disk_MaxAvail returns size in bytes of the largest block of disk space | |
FastCopy Like TStream.CopyFrom, but uses a larger buffer | |
Reassuring Note: | |
One concern I've heard from people about using this unit: it's | |
big. Won't 110K of source add a lot of code to my program, even if I | |
only need one little part of it? The simple answer is no. I've | |
tried very hard to write Streams so that cross-links between code | |
are minimized. If all you need are the XMS routines, that's all | |
that TP's "smart linker" will link into your .EXE. Same for most | |
of the other objects and functions. One exception is the TempStream | |
function (Chapter 20). Because it makes explicit references to 4 | |
types of streams, one call to it will necessarily pull in support | |
code for all 4 types. That's only about 6K, though. StreamName is | |
much worse: it pulls in code for *every* stream type. It should | |
only be used for debugging. | |
CONTENTS | |
0. Files in archive | |
a list of the files that should accompany this one | |
1. TFilter | |
a generic object to act as a filter to a stream | |
2. TEncryptFilter | |
a filter which does simple encryption/decryption | |
3. TLZWFilter | |
a filter which does LZW compression/decompression | |
4. TTextFilter | |
a filter to provide the Read/ReadLn/Write/WriteLn | |
interface to a stream | |
5. TLogFilter | |
a filter to allow logging of activity on text files | |
6. TBitFilter | |
a filter to allow bit level I/O to a stream | |
7. TDupFilter | |
a filter to duplicate output, check for duplicate input | |
8. TConcatFilter | |
a filter to concatenate two streams | |
9. TLimitFilter | |
a filter to limit I/O to a certain byte range | |
10. TLoopFilter | |
a filter to make a stream look like a tape loop: things wrap | |
around when they reach the ends | |
11. TReverseFilter and ReverseBytes | |
a filter to reverse the apparent byte order of a stream and | |
a procedure to reverse the byte order of a buffer | |
12. TSequential | |
a generic filter that can't Seek | |
13. Checksum/CRC filters: | |
a collection of filters which calculate checksums and CRC | |
values as the data goes by | |
14. TNulStream | |
a stream which counts data written to it, and which | |
returns a constant if you read | |
15. TRAMStream | |
a stream which resides entirely in RAM | |
16. TXMSStream | |
a stream which keeps its data in XMS memory | |
17. xxx_MemAvail and xxx_MaxAvail | |
procedures to show how much memory is free | |
18. TNamedBufStream | |
a buffered file stream which knows its own name | |
19. TTempBufStream | |
a temporary buffered file stream, which deletes itself when | |
done | |
20. TempStream | |
a procedure to allocate a temporary stream, in RAM, EMS, or | |
on disk, according to a specified preference | |
21. TWorkStream | |
a temporary stream, which allocates new blocks of mixed | |
types as it grows | |
22. OvrInitStream and related procedures | |
procedures to allow overlays to be buffered on any | |
stream or combination of streams | |
23. TEMSStream2 | |
EMS stream with fix for bug in TEMSStream.done | |
24. FastCopy | |
fast copy from one stream to another | |
25. Miscellaneous constants and types | |
26. Release history and credits | |
0. FILES IN ARCHIVE | |
Streams.doc - this file | |
Streams.pas - main source file for unit | |
Lzwstrea.asm - source for LZW compression | |
StDefine.inc - a few conditional compiler defines | |
Xmsstrm.inc - source for XMS stream | |
Crc16.asm - source for 16 bit CRC | |
Crc32.asm - source for 32 bit CRC | |
Crcarc.asm - source for ARC-style 16 bit CRC | |
Lzwstrea.obj - assembled code for external linking | |
Crc16.obj - " " " " " | |
Crcarc.obj - " " " " " | |
Crc32.obj - " " " " " | |
Demo programs: | |
Encrypt.pas - encryption program, using TEncryptFilter | |
Compress.pas - file compressor, using TLZWFilter | |
Huffman.pas - defines a Huffman encoding filter, using TBitFilter | |
HuffComp.pas - file compressor using THuffmanFilter | |
Logdemo.pas - simple demo of TLogFilter | |
Ovrdemo.pas - simple demo of multiple overlay files | |
ovr1.pas - one overlaid unit | |
ovr2.pas - a second overlaid unit | |
Textdemo.pas - simple demo of TTextFilter | |
1. TFilter = object(TStream) | |
"Filters" are programs which take a file as input and produce a new | |
file as output. TFilter is an adaptation of this idea to streams. | |
Every TFilter has a base stream, which can be any kind of stream. | |
Every TFilter is itself a stream, which means you can read and | |
write to it. When you do, it just relays your request to the base | |
stream. | |
One use of this is in the fact that you can make descendants | |
of TFilter which massage the data on the way to and from the Base. | |
Just override the Read and Write (and possibly other) methods, and | |
you can make changes to the data flowing to or from *any* kind of | |
stream. No need for special coding for an TEMSStream, or for a | |
TDOSStream, or whatever. Your code can act on any of them. | |
Examples of things to do are in the Streams unit: encryption | |
(TEncryptFilter) and LZW compression (TLZWFilter). | |
The other main use is to add other kinds of functionality to a | |
stream. You can't use the formatting capabilities of ReadLn and | |
WriteLn with standard streams, but if you use a TTextFilter (see | |
below) you can. | |
FIELDS | |
Base : PStream; | |
TFilter.Base holds the pointer to the base stream. | |
StartOfs : longint; | |
TFilter.StartOfs holds the offset in the base stream of offset 0 | |
in the filter. This allows multiple filters to work on the same | |
stream; one could work at offset 0, another at offset 1000, etc. | |
Just position the base to the desired starting offset before | |
initializing the filter. | |
OwnsBase: Boolean; | |
If false, then the Base stream is assumed to be owned by some | |
other part of the program, so that when Done is called, it won't | |
be disposed of. Normally OwnsBase is true, and it's assumed | |
that all management of the Base stream will be done by the | |
TFilter. | |
METHODS | |
Constructor Init(ABase: PStream); | |
TFilter.Init sets Base to point to ABase^, and sets StartOfs to | |
the current position of the base stream. Sets ByReference to | |
false. | |
Destructor Done; virtual; | |
If Base is not nil, then TFilter.Done calls Flush, then if | |
OwnsBase is true, disposes of the base stream before calling | |
TStream.Done for itself. | |
Function CheckStatus : boolean; virtual; | |
Returns true if status is stOK. If it is, but the base status is | |
not stOK, then it assumes that someone has called a Reset for the | |
filter, so it calls Reset for the base stream. Borland should | |
have made Reset virtual, and this kludge wouldn't have been | |
necessary! | |
Procedure CheckBase; | |
Checks the base stream for an error. If Base^.status is not | |
stOK, then it calls Error with Code=stBaseError and | |
Info=Base^.status. | |
Function GetPos : longint; virtual; | |
Function GetSize : longint; virtual; | |
Procedure Read(var buf; count : word); virtual; | |
Procedure Seek(pos: longint); virtual; | |
Procedure Truncate; virtual; | |
Procedure Write(var buf; count: word); virtual; | |
Procedure Flush; virtual; | |
These methods all call the corresponding method in the base | |
stream. Offsets are translated using StartOfs. Before the call, | |
CheckStatus is called to propagate any Reset to the base; after | |
the call, CheckBase is called to propagate any errors to the | |
filter from the base. | |
2. TEncryptFilter = object(TFilter) | |
This is a filter which does simple encryption/decryption on the | |
base stream. The encryption method is to XOR each byte with a | |
Random(256) value; the starting RandSeed is the key for the | |
encryption. | |
FIELD | |
key : longint; | |
The key is used as a Randseed replacement value. Randseed itself | |
is left unmodified by Read and Write. | |
METHODS | |
Constructor Init(Akey:longint; ABase:PStream); | |
The value AKey is used as the starting key for byte 0 of the | |
filter. Anything read from ABase is decrypted using this key; | |
anything written to it is encrypted. | |
Procedure Read(var buf; count : word); virtual; | |
Procedure Seek(pos : longint); virtual; | |
Procedure Write(var buf; count: word); virtual; | |
These methods all encrypt/decrypt and update the value of Key to | |
correspond to the new position in the stream. The encryption | |
method uses the same algorithm for encryption as for decryption. | |
TEncryptFilter.Seek is fairly slow, because it updates the Key | |
once for every byte difference between the current position and | |
the new position. Moving backwards is slower, because the update | |
algorithm is written in Pascal, not assembler as is the forward | |
algorithm. | |
3. TLZWFilter = object(TFilter) | |
This is a filter which does LZW compression as it writes to and | |
decompression as it reads from the base stream. The LZW code is | |
an adaptation of Wilbert van Leijen's implementation of the 12 | |
bit LZW algorithm. It's quite fast, though not as fast as | |
Wilbert's version; it gets slowed down a lot by having to save | |
its state after every Read and Write operation. You're best off | |
writing large chunks to it to take advantage of Wilbert's | |
excellent code. (Historical note: Trying to rewrite this was | |
what inspired me to write FULLDB, a program which allows source | |
level debugging of external .ASM files. -djm) | |
Each TLZWFilter takes over 28700 bytes of memory, because it | |
keeps extensive tables to record the current state. | |
One limitation of the TLZWFilter is that it can only be opened in | |
read or write mode, and Seek cannot be used in write mode. If | |
this doesn't suit your application, you might want to flesh out | |
the demo Huffman encoder (HUFFMAN.PAS), since it does allow | |
random access. | |
FIELDS | |
Mode : word; | |
One of stOpenRead or stOpenWrite; the mode under which this | |
filter was opened. | |
Size : longint; | |
The size of the expanded stream. This is the size that users of | |
the filter will see; if compression is working, it will generally | |
be bigger than Base^.GetSize. | |
Position : longint; | |
This is the position in the expanded stream. | |
Tables : PLZWTables; | |
This is a pointer to the tables used by the compression engine. | |
They're automatically allocated on the heap. | |
METHODS | |
Constructor Init(ABase:PStream; AMode:TOpenMode); | |
Allocates Tables from the heap, and opens the compressor in | |
a mode of either stOpenRead or stOpenWrite. If reading, a | |
signature and record of the uncompressed filesize is read from | |
the base to confirm that it is compressed by LZW, and to prime | |
the Tables. If writing, the signature is written to the stream. | |
Destructor Done; virtual; | |
Flushes all data to the stream, and writes the uncompressed | |
filesize to the head of it before calling TFilter.done. | |
Procedure Flush; virtual; | |
Function GetPos: longint; virtual; | |
Function GetSize:longint; virtual; | |
Procedure Read(var buf; count:word); virtual; | |
Procedure Seek(pos:longint); virtual; | |
Procedure Truncate; virtual; | |
Procedure Write(var buf; count: word); virtual; | |
These methods all override the basic filter methods and | |
compress/decompress the data. They check whether the operation | |
requested can be performed in the current mode, and call Error | |
with Code=stBadMode and Info=Mode if the operation is not | |
supported. | |
Seek is not supported at all in Write mode. In Read mode, it is | |
slow for seeking forwards, and very slow for seeking backwards: | |
it rewinds the file to the start and seeks forward from there by | |
expanding everything. | |
Truncate is not supported in either mode, and always causes a | |
call to Error. | |
Flush may only be called once; it changes Mode to 0, so any | |
further operations will fail. | |
4. TTextFilter = object(TFilter) | |
This is a filter which provides a Read/ReadLn/Write/WriteLn | |
interface to a stream through its Textfile field or through any | |
other Text variable. Once the filter is initialized, the | |
Textfile field acts exactly like a text file in the standard I/O | |