Browse Source

- separated subtasks into different Modules
- enhanced readability and efficiency

git-svn-id: https://svn.inf.ethz.ch/svn/lecturers/a2/trunk@8667 8c9fc860-2736-0410-a75d-ab315db34111

eth.guenter 6 years ago
parent
commit
40bea38d3c
3 changed files with 715 additions and 0 deletions
  1. 265 0
      source/BorrowsWheeler.Mod
  2. 293 0
      source/Huffman.Mod
  3. 157 0
      source/OZip.Mod

+ 265 - 0
source/BorrowsWheeler.Mod

@@ -0,0 +1,265 @@
+MODULE BorrowsWheeler;  (** AUTHOR GF; PURPOSE  "Borrows Wheeler Transformation"; *)
+	
+CONST
+	BlockSize* = 8*1024;
+	
+TYPE 	
+	MTF = OBJECT (* move to front *)
+		TYPE 
+			Node = POINTER TO RECORD
+				byte: CHAR;  next: Node
+			END;
+		VAR
+			alpha: Node;
+			
+			
+			PROCEDURE Initialize;
+			VAR n: Node;  i: LONGINT;
+			BEGIN
+				alpha := NIL;
+				FOR i := 0 TO 255 DO
+					NEW( n );  n.next :=alpha;  n.byte := CHR( 255 - i );  alpha := n
+				END
+			END Initialize;
+
+			PROCEDURE Encode( VAR buf: ARRAY OF CHAR; len: LONGINT );
+			VAR l, m: Node;  i, k: LONGINT; ch: CHAR;
+			BEGIN
+				Initialize;
+				FOR i := 0 TO len - 1 DO
+					ch := buf[i];
+					IF alpha.byte = ch THEN  k := 0
+					ELSE
+						l := alpha;  m := alpha.next;  k := 1;
+						WHILE m.byte # ch DO  
+							INC( k );  l := m;  m := m.next  
+						END;
+						l.next := m.next;  m.next := alpha;  alpha := m
+					END;
+					buf[i] := CHR( k )
+				END
+			END Encode;
+			
+			PROCEDURE Decode( VAR buf: ARRAY OF CHAR; len: LONGINT );
+			VAR l, m: Node;  i, c: LONGINT;  ch: CHAR;
+			BEGIN
+				Initialize;
+				FOR i := 0 TO len - 1 DO
+					ch := buf[i];
+					IF ch # 0X THEN 
+						c := ORD( ch );  l := alpha;
+						WHILE c > 1 DO  l := l.next;  DEC( c )  END;
+						m := l.next;  l.next := m.next;  m.next := alpha;  
+						alpha := m
+					END;
+					buf[i] := alpha.byte;
+				END
+			END Decode;
+	
+	END MTF;
+	
+TYPE
+	Encoder* = OBJECT
+		TYPE
+			Index = LONGINT;
+		VAR
+			mtf: MTF;  length: LONGINT;
+			sbuf: ARRAY 2*BlockSize OF CHAR;
+			rotation: ARRAY BlockSize OF Index;
+			
+			PROCEDURE &New*;
+			BEGIN
+				NEW( mtf );
+			END New;
+			
+	
+			PROCEDURE Less( a, b: Index ): BOOLEAN;
+			VAR i1, i2: Index;  n, diff: LONGINT;  
+			BEGIN
+				n := 0;  i1 := rotation[a];  i2 := rotation[b];
+				REPEAT
+					diff := ORD( sbuf[i1]) - ORD( sbuf[i2] );
+					INC( i1 );  INC( i2 );  INC( n ); 
+				UNTIL (diff # 0) OR (n = length);
+				RETURN diff < 0
+			END Less;
+			
+			PROCEDURE Swap( a, b: Index );
+			VAR  tmp: Index;
+			BEGIN
+				tmp := rotation[a];  rotation[a] := rotation[b];  rotation[b] := tmp
+			END Swap;
+			
+			PROCEDURE InsertSort( lo, hi: Index );
+			VAR x, i, l, h, m, ip, tmp: Index; 
+			BEGIN
+				x := lo + 1; 
+				WHILE x <= hi DO
+					IF Less( x, x - 1 )  THEN
+						(* find insert position ip *)
+						ip := x - 1;  l := lo;  h := ip - 1;
+						WHILE l <= h DO
+							m := (l + h) DIV 2; 
+							IF Less( x, m ) THEN  ip := m;  h := m - 1  ELSE  l := m + 1  END
+						END;
+						(* insert rotation[x] at position ip*)
+						tmp := rotation[x];  i := x;
+						REPEAT  rotation[i] := rotation[i - 1];  DEC( i )  UNTIL i = ip;
+						rotation[ip] := tmp;
+					END;
+					INC( x )
+				END
+			END InsertSort;
+			
+			PROCEDURE SortR( lo, hi: LONGINT );
+			VAR i, j, m, n: LONGINT;
+			BEGIN
+				IF lo < hi THEN
+					i := lo;  j := hi;  m := (lo + hi) DIV 2;  n := hi - lo + 1;
+					IF n = 2 THEN 
+					IF Less( hi, lo ) THEN  
+						Swap( lo, hi )  END;
+					ELSIF n = 3 THEN 
+						IF Less( m, lo ) THEN  Swap( lo, m )  END;
+						IF Less( hi, m ) THEN  
+							Swap( m, hi );
+							IF Less( m, lo ) THEN  Swap( lo, m )  END	
+						END
+					ELSIF n < 16 THEN  
+						InsertSort( lo, hi )
+					ELSE
+						(* QuickSort *)
+						REPEAT
+							WHILE Less( i, m ) DO  INC( i )  END;  
+							WHILE Less( m, j ) DO  DEC( j )  END;
+							IF i <= j THEN
+								IF m = i THEN  m := j  ELSIF  m = j THEN  m := i  END;
+								Swap( i, j );  INC( i );  DEC( j )
+							END
+						UNTIL i > j;
+						SortR( lo, j );  SortR( i, hi )
+					END
+				END
+			END SortR;
+	
+			PROCEDURE EncodeBlock*( VAR buf: ARRAY OF CHAR; len: LONGINT ): LONGINT;
+			VAR  i, index: LONGINT;  
+			BEGIN
+				ASSERT( len <= BlockSize );  length := len;
+				FOR i := 0 TO length - 1 DO  sbuf[i] := buf[i];  sbuf[i+length] := buf[i]  END; 
+				FOR i := 0 TO length - 1 DO  rotation[i] := INTEGER( i )  END;
+				SortR( 0, length - 1 );
+				(* find index of the original row *)
+				index := 0;  WHILE rotation[index] # 0 DO  INC( index )  END;
+				(* replace buf by column L *)
+				FOR i := 0 TO length -1 DO  buf[i] := sbuf[rotation[i] + length - 1]  END;
+				mtf.Encode( buf, length );
+				RETURN index
+			END EncodeBlock;
+			
+	END Encoder;
+			
+				
+TYPE
+	Decoder* = OBJECT
+		TYPE
+			Index = LONGINT;
+		VAR
+			length, index: LONGINT;
+			mtf: MTF;
+			f: ARRAY BlockSize OF CHAR; 
+			
+			PROCEDURE &New*;
+			BEGIN
+				NEW( mtf );
+			END New;
+			
+			
+			PROCEDURE Swap( a, b: Index );
+			VAR  tmp: CHAR;
+			BEGIN
+				tmp := f[a];  f[a] := f[b];  f[b] := tmp
+			END Swap;
+			
+			PROCEDURE InsertSort( lo, hi: Index );
+			VAR x, i, l, h, m, ip: Index;  tmp: CHAR; 
+			BEGIN
+				x := lo + 1; 
+				WHILE x <= hi DO
+					IF f[x] < f[x - 1]  THEN
+						(* find insert position ip *)
+						ip := x - 1;  l := lo;  h := ip - 1;
+						WHILE l <= h DO
+							m := (l + h) DIV 2; 
+							IF f[x] < f[m] THEN  ip := m;  h := m - 1  ELSE  l := m + 1  END
+						END;
+						(* insert f[x] at position ip*)
+						tmp := f[x];  i := x;
+						REPEAT  f[i] := f[i - 1];  DEC( i )  UNTIL i = ip;
+						f[ip] := tmp;
+					END;
+					INC( x )
+				END
+			END InsertSort;
+			
+			PROCEDURE SortF( lo, hi: Index );
+			VAR i, j, m: Index;  n: LONGINT;
+			BEGIN
+				IF lo < hi THEN
+					i := lo;  j := hi;  m := (lo + hi) DIV 2;  n := hi - lo + 1;
+					IF n = 2 THEN 
+						IF f[hi] < f[lo] THEN  Swap( lo, hi )  END;
+					ELSIF n = 3 THEN 
+						IF f[m] < f[lo] THEN  Swap( lo, m )  END;
+						IF f[hi] < f[m] THEN  
+							Swap( m, hi );
+							IF f[m] < f[lo] THEN  Swap( lo, m )  END	
+						END
+					ELSIF n < 16 THEN  
+						InsertSort( lo, hi )
+					ELSE
+						(* QuickSort *)
+						REPEAT
+							WHILE f[i] < f[m] DO  INC( i )  END;  
+							WHILE f[m] < f[j] DO  DEC( j )  END;
+							IF i <= j THEN
+								IF m = i THEN  m := j  ELSIF m = j THEN  m := i  END;
+								Swap( i, j );  INC( i );  DEC( j )
+							END
+						UNTIL i > j;
+						SortF( lo, j );  SortF( i, hi )
+					END
+				END
+			END SortF;
+			
+			
+			PROCEDURE DecodeBlock*( VAR buf: ARRAY OF CHAR; len, ind: LONGINT );
+			VAR 
+				i, j, n: LONGINT;  ch: CHAR;
+				l: POINTER TO ARRAY OF CHAR;
+				lc, fc: POINTER TO ARRAY OF LONGINT;
+				xn: ARRAY 256 OF LONGINT;  
+			BEGIN
+				ASSERT( len <= BlockSize );  length := len;  index := ind;
+				mtf.Decode( buf, len );
+				NEW( l, length );  NEW( lc, length );  NEW( fc, length );
+				FOR i := 0 TO 255 DO  xn[i] := 0  END;
+				FOR i := 0 TO length - 1 DO 
+					l[i] := buf[i];  f[i] := buf[i];
+					j := ORD( l[i] );  lc[i] := xn[j];  INC( xn[j] )
+				END;
+				SortF( 0, length - 1 );
+				FOR i := 0 TO 255 DO  xn[i] := 0  END;
+				FOR i := 0 TO length - 1 DO 
+					j := ORD( f[i] );  fc[i] := xn[j];  INC( xn[j] )
+				END;
+				FOR i := 0 TO length - 1 DO
+					ch := f[index];  n := fc[index];  buf[i] := ch;  index := 0;
+					WHILE (l[index] # ch) OR (lc[index] # n) DO  INC( index )  END
+				END;
+			END DecodeBlock;
+			
+	END Decoder;
+	
+	
+END BorrowsWheeler.

+ 293 - 0
source/Huffman.Mod

@@ -0,0 +1,293 @@
+MODULE Huffman;  (** AUTHOR GF;  PURPOSE "Huffman compression";  *)
+
+IMPORT Streams;
+
+CONST
+	BlockSize = 8*1024;
+	
+TYPE 
+	
+	Pattern = RECORD  pattern, freq: LONGINT  END;
+	
+	PatternCounts = ARRAY 256 OF Pattern;
+	PatternFrequencies = POINTER TO ARRAY OF Pattern;		(* ordered by frequency *)
+	
+	
+	
+	Node = OBJECT 
+		VAR 
+			freq: LONGINT;
+			left, right: Node;		(* both NIL in case of leaf *)
+			pattern: LONGINT;						
+		
+		PROCEDURE & Init( patt, f: LONGINT );
+		BEGIN
+			pattern := patt;  freq := f;  left := NIL;  right := NIL
+		END Init;
+		
+		PROCEDURE AddChildren( l, r: Node );
+		BEGIN
+			left := l;  right := r;  freq := l.freq + r.freq
+		END AddChildren;
+			
+	END Node;
+	
+	
+	
+	Encoder* = OBJECT
+		TYPE
+			HCode = RECORD  len, val: LONGINT  END;
+		VAR
+			w: Streams.Writer;
+			codeTable: ARRAY 256 OF HCode;
+			buffer: ARRAY 2*BlockSize OF CHAR;
+			byte, curBit, bpos: LONGINT;
+			
+			
+			PROCEDURE &New*( output: Streams.Writer );
+			BEGIN
+				w := output;
+			END New;
+			
+			PROCEDURE Initialize( CONST source: ARRAY OF CHAR; len: LONGINT );
+			VAR pf: PatternFrequencies;
+			BEGIN
+				pf := CountPatterns( source, len );
+				WriteFrequencies( pf );
+				BuildCodeTable( BuildTree( pf ) );				
+				byte := 0;  bpos := 0;  curBit := 7;
+			END Initialize;
+			
+			
+			PROCEDURE WriteFrequencies( pf: PatternFrequencies );
+			VAR i, n: LONGINT;
+				a: ARRAY 256 OF LONGINT;
+			BEGIN
+				n := LEN( pf^ );
+				IF n < 128 THEN
+					w.Char( CHR( n ) );
+					FOR i := 0 TO n - 1 DO  
+						w.RawNum( pf[i].freq );  w.Char( CHR( pf[i].pattern ) )  
+					END
+				ELSE
+					w.Char( 0X );
+					FOR i := 0 TO 255 DO  a[i] := 0  END;
+					FOR i := 0 TO n -1 DO  a[pf[i].pattern] := pf[i].freq  END;
+					FOR i := 0 TO 255 DO  w.RawNum( a[i] )  END
+				END
+			END WriteFrequencies;
+			
+			
+			PROCEDURE CountPatterns( CONST source: ARRAY OF CHAR; len: LONGINT ): PatternFrequencies;
+			VAR 
+				i: LONGINT;  a: PatternCounts;
+			BEGIN
+				FOR i := 0 TO 255 DO  a[i].pattern := i;  a[i].freq := 0  END;
+				FOR i := 0 TO len - 1 DO  INC( a[ORD( source[i] )].freq )  END;
+				FOR i := 0 TO 255 DO  
+					IF a[i].freq > 0 THEN (* scale => [1..101H] *)
+						a[i].freq := 100H * a[i].freq DIV len + 1;
+					END
+				END;
+				RETURN SortPatterns( a )
+			END CountPatterns;
+			
+			
+			PROCEDURE BuildCodeTable( tree: Node );
+			VAR 
+				initval: HCode; i: LONGINT;
+			
+				PROCEDURE Traverse( node: Node;  code: HCode );
+				BEGIN
+					IF node.left = NIL THEN  (* leaf *)
+						codeTable[node.pattern] := code;
+					ELSE
+						INC( code.len );  
+						code.val := 2*code.val;  Traverse( node.right, code );	(* ..xx0 *)
+						code.val := code.val + 1;  Traverse( node.left, code );	(* ..xx1 *)
+					END;
+				END Traverse;
+			
+			BEGIN
+				FOR i := 0 TO 255 DO  codeTable[i].len := 0;  codeTable[i].val := 0  END;
+				initval.len := 0;  initval.val := 0;
+				Traverse( tree, initval );
+			END BuildCodeTable;	
+			
+			
+			PROCEDURE AppendBit( bit: LONGINT );
+			BEGIN
+				IF bit # 0 THEN  byte := byte + ASH( 1, curBit )  END;
+				DEC( curBit );
+				IF curBit < 0 THEN
+					buffer[bpos] := CHR( byte );  INC( bpos );
+					byte := 0; curBit := 7
+				END
+			END AppendBit;
+			
+			
+			PROCEDURE Append( code: HCode );
+			VAR len, val: LONGINT;
+			BEGIN
+				len := code.len;  val := code.val;
+				WHILE len > 0 DO
+					DEC( len );  AppendBit( ASH( val, -len ) MOD 2 )
+				END
+			END Append;
+			
+			
+			PROCEDURE CompressBlock*( CONST source: ARRAY OF CHAR; len: LONGINT );
+			VAR i, codesize: LONGINT;
+			BEGIN
+				Initialize( source, len );
+				FOR i := 0 TO len - 1 DO  Append( codeTable[ORD( source[i] )] )  END;
+				
+				codesize := 8*bpos;
+				IF curBit # 7 THEN  
+					INC( codesize, (7 - curBit) );
+					buffer[bpos] := CHR( byte );  INC( bpos ); 
+				END;
+				
+				w.RawNum( codesize );
+				FOR i := 0 TO bpos - 1 DO  w.Char( buffer[i] )  END;
+				w.Update
+			END CompressBlock;
+				
+	END Encoder;
+	
+	
+	
+	Decoder* = OBJECT
+		VAR
+			codesize: LONGINT;	(* bits! *)
+			r: Streams.Reader;
+			tree: Node;
+			byte, curBit: LONGINT;
+		
+			PROCEDURE &New*( input: Streams.Reader );
+			BEGIN
+				r := input;  
+			END New;
+			
+			PROCEDURE Initialize;
+			VAR  pf: PatternFrequencies;
+			BEGIN
+				pf := ReadFrequencies( r );
+				tree := BuildTree( pf );
+				r.RawNum( codesize );
+				curBit := -1
+			END Initialize;
+			
+			
+			PROCEDURE ReadFrequencies( r: Streams.Reader ): PatternFrequencies;
+			VAR i, n: LONGINT;  c: CHAR;
+				pf: PatternFrequencies;
+				a: PatternCounts;
+			BEGIN
+				r.Char( c );  n := ORD( c );
+				IF n > 0 THEN
+					NEW( pf, n );
+					FOR i := 0 TO n - 1 DO  r.RawNum( pf[i].freq );  r.Char( c );  pf[i].pattern := ORD( c )  END
+				ELSE
+					FOR i := 0 TO 255 DO  a[i].pattern := i;  r.RawNum( a[i].freq )  END;
+					pf := SortPatterns( a )
+				END;
+				RETURN pf
+			END ReadFrequencies;
+		
+		
+			PROCEDURE GetBit( ): LONGINT;
+			VAR bit: LONGINT;  c: CHAR;
+			BEGIN
+				IF curBit < 0 THEN
+					r.Char( c );  byte := ORD( c );  curBit := 7
+				END;
+				bit := ASH( byte, -curBit ) MOD 2;  DEC( curBit );
+				RETURN bit
+			END GetBit;	
+			
+			
+			PROCEDURE ExtractBlock*( VAR buf: ARRAY OF CHAR; VAR len: LONGINT );
+			VAR 
+				i: LONGINT;  n: Node;
+			BEGIN
+				Initialize;  i := 0;  len := 0;
+				REPEAT
+					n := tree; 
+					REPEAT
+						IF GetBit() # 0 THEN  n := n.left  ELSE  n := n.right  END;
+						INC( i )
+					UNTIL n.left = NIL;	(* leaf *)
+					buf[len] := CHR( n.pattern );  INC( len )
+				UNTIL i >= codesize;
+			END ExtractBlock;
+				
+	END Decoder;
+	
+	
+	
+	
+	
+	(* sort patterns by frequency, omit unused patterns *)
+	PROCEDURE SortPatterns( VAR a: PatternCounts ): PatternFrequencies;
+	VAR 
+		i, n, start: LONGINT;  pf: PatternFrequencies;
+		
+		PROCEDURE Sort( low, high: LONGINT );  
+		VAR 
+			i, j, m: LONGINT;  tmp: Pattern;
+		BEGIN
+			IF low < high THEN
+				i := low;  j := high;  m := (i + j) DIV 2;
+				REPEAT
+					WHILE a[i].freq < a[m].freq DO  INC( i )  END;
+					WHILE a[j].freq > a[m].freq DO  DEC( j )  END;
+					IF i <= j THEN
+						IF i = m THEN  m := j  ELSIF j = m THEN  m := i  END;
+						tmp := a[i];  a[i] := a[j];  a[j] := tmp;
+						INC( i );  DEC( j )
+					END;
+				UNTIL i > j;
+				Sort( low, j );  Sort( i, high )
+			END
+		END Sort;
+		
+	BEGIN
+		Sort( 0, 255 );	(* sort patterns by frequency *)
+		i := 0;
+		WHILE a[i].freq = 0 DO  INC( i )  END; 	(* skip unused patterns *)
+		n := 256 - i;  start := i;
+		NEW( pf, n );
+		FOR i := 0 TO n - 1 DO  pf[i] := a[start + i]  END;
+		RETURN pf
+	END SortPatterns;
+
+	
+	PROCEDURE BuildTree( pf: PatternFrequencies ): Node;
+	VAR 
+		i, start, top: LONGINT;  node, n2: Node;
+		a: POINTER TO ARRAY OF Node;
+		patt: LONGINT;
+	BEGIN
+		NEW( a, LEN( pf^ ) );  top := LEN( pf^ ) - 1;
+		FOR i := 0 TO top DO  NEW( a[i], pf[i].pattern, pf[i].freq )  END;
+		IF top = 0 THEN  
+			(* the whole, probably last small block contains only a single pattern *)
+			patt := (a[0].pattern + 1) MOD 256;	(* some different pattern *)
+			NEW( node, 0, 0 );  NEW( n2, patt, 0 );  node.AddChildren( n2, a[0] );
+		ELSE
+			start := 0;  
+			WHILE start < top DO  
+				NEW( node, 0, 0 );  node.AddChildren( a[start], a[start+1] ); 
+				i := start + 1;  
+				WHILE (i < top) & (a[i+1].freq < node.freq) DO  a[i] := a[i+1];  INC( i )  END;
+				a[i] := node;  
+				INC( start );
+			END
+		END;
+		RETURN node
+	END BuildTree;	
+	
+
+END Huffman.
+

+ 157 - 0
source/OZip.Mod

@@ -0,0 +1,157 @@
+MODULE OZip; (** AUTHOR GF; PURPOSE "files and streams compression tool"; *)
+
+IMPORT Streams, Commands, Files, Strings, BW :=  BorrowsWheeler, Huffman, Log := KernelLog;
+
+(*
+	Format	=	ComprTag { Block }
+	Block		=	Index  Freqs  Code 
+	Freqs		= 	num { freq } 			(num = 0,  (256 * freq))
+				|	num { freq patt }		(num = 1..127)
+	Code		=	size { CHAR }		(size in bits ! )
+	num	=	CHAR			
+	patt	=	CHAR
+	freq	=	RawNum
+	Index	=	RawNum
+	size	=	RawNum
+*)
+
+CONST
+	BlockSize = 8*1024;
+	ComprTag = LONGINT(0FEFD1F2FH);
+	Suffix = ".oz";
+		
+
+	PROCEDURE Compress*( r: Streams.Reader;  w: Streams.Writer );
+	VAR 
+		huff: Huffman.Encoder; 
+		bw: BW.Encoder;  index: LONGINT; 
+		buffer: ARRAY BlockSize OF CHAR;  len: LONGINT;
+	BEGIN 
+		NEW( huff, w );  NEW( bw );
+		w.RawLInt( ComprTag );
+		LOOP
+			r.Bytes( buffer, 0, BlockSize, len );
+			IF len < 1 THEN  EXIT  END;
+			index := bw.EncodeBlock( buffer, len );
+			w.RawNum( index );
+			huff.CompressBlock( buffer, len );
+			IF r IS Files.Reader THEN  Log.Char( '.' )  END
+		END
+	END Compress;
+	
+	
+		
+	PROCEDURE Uncompress*( r: Streams.Reader;  w: Streams.Writer;  VAR msg: ARRAY OF CHAR ): BOOLEAN;
+	VAR 
+		huff: Huffman.Decoder;
+		tag, len, i, bwIndex: LONGINT; 
+		bw: BW.Decoder;
+		buffer: ARRAY BlockSize OF CHAR;
+	BEGIN 
+		r.RawLInt( tag );
+		IF tag # ComprTag  THEN
+			msg := "OZip.Uncompress: bad input (compressed stream expected)"; 
+			RETURN FALSE
+		END;
+		NEW( huff, r );  NEW( bw );
+		WHILE r.Available( ) > 0 DO	
+			r.RawNum( bwIndex );
+			huff.ExtractBlock( buffer, len );	
+			bw.DecodeBlock( buffer, len, bwIndex );
+			FOR i := 0 TO len - 1 DO  w.Char( buffer[i] )  END;
+			IF w IS Files.Writer THEN  Log.Char( '.' )  END
+		END;
+		w.Update;
+		RETURN TRUE
+	END Uncompress;
+
+	
+
+	
+	PROCEDURE NewFile( CONST name: ARRAY OF CHAR ): Files.File;
+	VAR
+		name2: ARRAY 128 OF CHAR;  res: LONGINT;
+	BEGIN
+		IF Files.Old( name ) # NIL THEN
+			COPY( name, name2);  Strings.Append( name2, ".Bak" );
+			Files.Rename( name, name2, res );
+		END;
+		RETURN Files.New( name )
+	END NewFile;
+	
+	
+	
+	(** OZip.CompressFile  infile [outfile] ~ *)
+	PROCEDURE CompressFile*( c: Commands.Context );
+	VAR
+		f1, f2: Files.File;
+		r: Files.Reader;  w: Files.Writer;
+		name1, name2: ARRAY 128 OF CHAR;
+	BEGIN
+		IF c.arg.GetString( name1 ) THEN
+			IF ~c.arg.GetString( name2 ) THEN
+				name2 := name1;  Strings.Append( name2, Suffix )
+			END;
+			f1 := Files.Old( name1 );
+			IF f1 # NIL THEN
+				Log.String( "OZip.Compress " ); Log.String( name1 ); Log.Char( ' ' );
+				Files.OpenReader( r, f1, 0 ); 
+				f2 := NewFile( name2 );  Files.OpenWriter( w, f2, 0 );
+				Compress( r, w );  w.Update;  Files.Register( f2 );
+				Log.String( " => " );  Log.String( name2 );  Log.Ln; 
+			ELSE
+				c.error.String( "could not open file  " );  c.error.String( name1 );  c.error.Ln
+			END
+		ELSE
+			c.error.String( "usage: OZip.CompressFile infile [outfile] ~ " );  c.error.Ln;
+		END;
+		c.error.Update
+	END CompressFile;
+	
+	
+	(** OZip.UncompressFile  infile [outfile] ~ *)
+	PROCEDURE UncompressFile*( c: Commands.Context );
+	VAR
+		f1, f2: Files.File;
+		r: Files.Reader;  w: Files.Writer;
+		name1, name2, msg: ARRAY 128 OF CHAR;
+	BEGIN
+		IF c.arg.GetString( name1 ) THEN
+			IF ~c.arg.GetString( name2 ) THEN
+				name2 := name1;
+				IF Strings.EndsWith( Suffix, name2 ) THEN  name2[Strings.Length( name2 ) - 3] := 0X
+				ELSE  Strings.Append( name2, ".uncomp" )
+				END
+			END;
+			f1 := Files.Old( name1 );
+			IF f1 # NIL THEN
+				Files.OpenReader( r, f1, 0 );	 
+				f2 := NewFile( name2 );  Files.OpenWriter( w, f2, 0 );
+				Log.String( "OZip.Uncompress " );  Log.String( name1 );  Log.Char( ' ' );
+				IF Uncompress( r, w, msg ) THEN
+					w.Update;  Files.Register( f2 );
+					Log.String( " => " );  Log.String( name2 );  Log.Ln; 
+				ELSE
+					c.error.String( msg );  c.error.Ln
+				END
+			ELSE
+				c.error.String( "could not open file  " );  c.error.String( name1 );  c.error.Ln
+			END
+		ELSE
+			c.error.String( "usage: OZip.UncompressFile infile [outfile] ~ " );  c.error.Ln;
+		END;
+		c.error.Update
+	END UncompressFile;
+	
+END OZip.
+
+
+	OZip.CompressFile   OZip.Mod ~
+	OZip.CompressFile   OZip.GofU ~
+	OZip.CompressFile   guide.pdf ~
+		
+	OZip.UncompressFile   OZip.Mod.oz  TTT.Mod ~
+	OZip.UncompressFile   OZip.GofU.oz  TTT.GofU ~
+	OZip.UncompressFile   guide.pdf.oz  TTT.pdf ~
+	
+	System.Free  OZip Huffman  BorrowsWheeler ~