home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / modula2 / library / modula1 / huffman.mod < prev    next >
Text File  |  1988-11-18  |  26KB  |  1,066 lines

  1. ======
  2. Editor's note: Remember to break the folowing modules into their own files
  3. before attempting to compile them.  Delete "======" and comments inside them
  4. ======
  5. Start BitStream.DEF
  6. ======
  7.  
  8. DEFINITION MODULE BitStream;
  9.  
  10. (* Used for bit-oriented I/O.  Minimal facilities. *)
  11.  
  12.  
  13. EXPORT QUALIFIED connect, disconnect, EOS, read, write, bitStream,
  14.         readChar, writeChar, readCard, writeCard;
  15.  
  16. TYPE bitStream;   
  17.  
  18.  
  19. PROCEDURE connect(fileName:ARRAY OF CHAR; read:BOOLEAN):bitStream;
  20. (* Associates a file with a bitStream.  A given stream can be
  21.    read from or written to, but not both.  On a Mac, this procedure
  22.    uses the default drive. *)
  23.  
  24. PROCEDURE disconnect(bs:bitStream);
  25. (* Disconnects stream from file. *)
  26.  
  27. PROCEDURE EOS(bs:bitStream):BOOLEAN;
  28. (* TRUE at end of stream; for read streams only! *)
  29.  
  30. PROCEDURE read(bs:bitStream):BOOLEAN;
  31. (* Reads a bit from the stream.  TRUE = 1, FALSE = 0 *)
  32.  
  33. PROCEDURE write(bs:bitStream; b:BOOLEAN);
  34. (* Writes a bit to the stream. *)
  35.  
  36. PROCEDURE readChar(bs:bitStream):CHAR;
  37. (* Reads eight consecutive bits and translates them into a CHAR.  This is
  38.    somewhat implementation-dependent. *)
  39.  
  40. PROCEDURE writeChar(bs:bitStream; c:CHAR);
  41. (* Writes the character as eight consecutive bits.  This is somewhat
  42.    implementation-dependent. *)
  43.  
  44. PROCEDURE readCard(bs:bitStream):CARDINAL;
  45. (* Reads 16 consecutive bits and translates them into a CARDINAL. *)
  46.  
  47. PROCEDURE writeCard(bs:bitStream; c:CARDINAL);
  48. (* Writes the cardinal as 16 consecutive bits *)
  49.  
  50. END BitStream.
  51.  
  52. ======
  53. Start BitStream.MOD
  54. ======
  55.  
  56. IMPLEMENTATION MODULE BitStream;
  57.  
  58.  
  59. (* Note: because Streams.WriteWord and Streams.ReadWord don't appear to
  60.    work in MacModula-2, I do I/O with the character operations. A character
  61.    code occupies bits 8-15 of a word. *)
  62.  
  63. FROM Streams IMPORT STREAM, StreamType, Connect, Disconnect, ReadChar,
  64.         WriteChar;
  65. IMPORT Streams;
  66. FROM Storage IMPORT ALLOCATE, DEALLOCATE;
  67. FROM MyTerminal IMPORT fatal;
  68.  
  69. CONST maxBit = 15;  (* highest numbered bit in a BITSET *)
  70.       lowCharBit = 8; (* first bit of a character *)
  71.  
  72. TYPE wordRange = [0..maxBit];
  73.      charRange = [lowCharBit..maxBit];
  74.      bitStream = POINTER TO bsRec;
  75.      bsRec = RECORD
  76.                 stream:STREAM;
  77.                 read:BOOLEAN;
  78.                 curWord:BITSET;
  79.                 curBit:charRange;
  80.              END;
  81.  
  82. PROCEDURE connect(fileName:ARRAY OF CHAR; read:BOOLEAN):bitStream;
  83. VAR bs:bitStream;
  84.     nullVol:ARRAY[0..0] OF CHAR;
  85.     done:BOOLEAN;
  86. BEGIN
  87.     nullVol[0] := 0C;
  88.     NEW(bs);
  89.     bs^.read := read;
  90.     IF read THEN
  91.         Connect(bs^.stream, streamread,      (* open stream for reading *)
  92.                 fileName, nullVol, 1, (* use drive #1 *)
  93.                 FALSE,                (* don't create if nonexistent *)
  94.                 done);
  95.         IF NOT done THEN
  96.             fatal('cannot open file');
  97.         END;
  98.         bs^.curBit := maxBit;
  99.     ELSE
  100.         Connect(bs^.stream, streamwrite, fileName, nullVol, 1, TRUE, done);
  101.         IF NOT done THEN
  102.             fatal('cannot open file');
  103.         END;
  104.         bs^.curBit := lowCharBit;
  105.         bs^.curWord := {};
  106.     END;
  107.     RETURN bs;
  108. END connect;
  109.  
  110. PROCEDURE disconnect(bs:bitStream);
  111. BEGIN
  112.   WITH bs^ DO
  113.     IF (NOT read) AND (curBit <> lowCharBit) THEN  (* flush the last word *)
  114.         WriteChar(stream, CHAR(curWord));
  115.     END;   
  116.     Disconnect(stream);
  117.     DISPOSE(bs);
  118.   END;
  119. END disconnect;
  120.  
  121. PROCEDURE EOS(bs:bitStream):BOOLEAN;
  122. BEGIN
  123.   WITH bs^ DO
  124.     IF read THEN
  125.         RETURN (curBit = maxBit) AND Streams.EOS(stream);
  126.     ELSE
  127.         fatal('EOS called on write bit stream');
  128.     END;
  129.   END;
  130. END EOS;
  131.  
  132. PROCEDURE read(bs:bitStream):BOOLEAN;
  133. (* Init: curBit := maxBit.  curBit = "all bits to curBit have been read" *)
  134. VAR c:CHAR;
  135. BEGIN
  136.     IF NOT bs^.read  THEN
  137.         fatal('attempt to read a write bit stream');
  138.     ELSE WITH bs^ DO
  139.         IF curBit = maxBit THEN
  140.             IF NOT Streams.EOS(stream) THEN
  141.                 ReadChar(stream, c);
  142.                 curWord := BITSET(c);
  143.                 curBit := lowCharBit;
  144.             END;
  145.         ELSE
  146.             INC(curBit);
  147.         END;
  148.         RETURN curBit IN curWord;
  149.     END; END;
  150. END read;
  151.         
  152.  
  153. PROCEDURE write(bs:bitStream; b:BOOLEAN);
  154. (* init: curBit := lowCharBit, curWord := {}. 
  155.    curBit = "bit curBit is next to be written" *)
  156. BEGIN
  157.   WITH bs^ DO
  158.     IF read THEN
  159.         fatal('attempt to write a read bit stream');
  160.     END;
  161.     IF b THEN
  162.         INCL(curWord, curBit);
  163.     END;
  164.     IF curBit = maxBit THEN
  165.         WriteChar(stream, CHAR(curWord));
  166.         curWord := {};
  167.         curBit := lowCharBit;
  168.     ELSE
  169.         INC(curBit);
  170.     END;
  171.   END;
  172. END write;
  173.  
  174. PROCEDURE readChar(bs:bitStream):CHAR;
  175. (* Read 8 bits and make them into a character.  In MacModula-2,
  176.    a CHAR variable is a word with bits 8-15 containing the ASCII code. *)
  177. VAR i:charRange;
  178.     char:BITSET;
  179. BEGIN
  180.     char := {};
  181.     FOR i := lowCharBit TO maxBit DO
  182.         IF read(bs) THEN
  183.             INCL(char, i);
  184.         END;
  185.     END;
  186.     RETURN CHAR(char);
  187. END readChar;
  188.  
  189. PROCEDURE writeChar(bs:bitStream; c:CHAR);
  190. (* see readChar for implementation details *)
  191. VAR i:charRange;
  192. BEGIN
  193.     FOR i := lowCharBit TO maxBit DO
  194.         write(bs, i IN BITSET(c));
  195.     END;
  196. END writeChar;
  197.  
  198. PROCEDURE readCard(bs:bitStream):CARDINAL;
  199. VAR i:wordRange;
  200.     card:BITSET;
  201. BEGIN
  202.     FOR i := 0 TO maxBit DO
  203.         IF read(bs) THEN
  204.             INCL(card, i);
  205.         ELSE
  206.             EXCL(card, i);
  207.         END;
  208.     END;
  209.     RETURN CARDINAL(card);
  210. END readCard;
  211.  
  212. PROCEDURE writeCard(bs:bitStream; c:CARDINAL);
  213. VAR i:wordRange;
  214. BEGIN
  215.     FOR i := 0 TO maxBit DO
  216.         write(bs, i IN BITSET(c));
  217.     END;
  218. END writeCard;
  219.  
  220. BEGIN
  221. END BitStream.
  222.  
  223. ======
  224. Start CharStream.DEF
  225. ======
  226.  
  227. DEFINITION MODULE CharStream;
  228.  
  229. EXPORT QUALIFIED charStream, connect, disconnect, read, write, EOS;
  230.  
  231. TYPE 
  232.     charStream;
  233.     
  234.  
  235. PROCEDURE connect(fileName:ARRAY OF CHAR; read:BOOLEAN):charStream;
  236.  
  237. PROCEDURE disconnect(cs:charStream);
  238.  
  239. PROCEDURE read(cs:charStream):CHAR;
  240.  
  241. PROCEDURE write(cs:charStream; c:CHAR);
  242.  
  243. PROCEDURE EOS(cs:charStream):BOOLEAN;
  244.  
  245. END CharStream.
  246.  
  247. ======
  248. Start CharStream.MOD
  249. ======
  250.  
  251. IMPLEMENTATION MODULE CharStream;
  252.  
  253. (* This module supports character I/O from files.  Its facilities are minimal.
  254.    I wrote it using MacModula-2's Streams module; it should be easy to
  255.    duplicate its behavior with whatever file system you have. *)
  256.  
  257. FROM Streams IMPORT STREAM, StreamType, Connect, Disconnect,
  258.         ReadChar, WriteChar;
  259. IMPORT Streams;
  260. FROM MyTerminal IMPORT fatal;
  261. FROM Storage IMPORT ALLOCATE, DEALLOCATE;
  262.  
  263. TYPE 
  264.     charStream = POINTER TO STREAM;
  265.     
  266.  
  267. PROCEDURE connect(fileName:ARRAY OF CHAR; read:BOOLEAN):charStream;
  268. VAR cs:charStream;
  269.     nullVol: ARRAY[0..0] OF CHAR;
  270.     done:BOOLEAN;
  271. BEGIN
  272.     nullVol[0] := 0C;
  273.     NEW(cs);
  274.     IF read THEN
  275.         Connect(cs^, streamread,      (* open stream for reading *)
  276.                 fileName, nullVol, 1, (* use drive #1 *)
  277.                 FALSE,                (* don't create if nonexistent *)
  278.                 done);
  279.         IF NOT done THEN
  280.             fatal('cannot open file');
  281.         END;
  282.     ELSE
  283.         Connect(cs^, streamwrite, fileName, nullVol, 1, TRUE, done);
  284.         IF NOT done THEN
  285.             fatal('cannot open file');
  286.         END;
  287.     END;
  288.     RETURN cs;
  289. END connect;
  290.  
  291. PROCEDURE disconnect(cs:charStream);
  292. BEGIN
  293.     Disconnect(cs^);
  294.     DISPOSE(cs);
  295. END disconnect;
  296.  
  297. PROCEDURE read(cs:charStream):CHAR;
  298. VAR c:CHAR;
  299. BEGIN
  300.     ReadChar(cs^, c);
  301.     RETURN c;
  302. END read;
  303.  
  304. PROCEDURE write(cs:charStream; c:CHAR);
  305. BEGIN
  306.     WriteChar(cs^, c);
  307. END write;
  308.  
  309. PROCEDURE EOS(cs:charStream):BOOLEAN;
  310. BEGIN
  311.     RETURN Streams.EOS(cs^);
  312. END EOS;
  313.  
  314. BEGIN
  315. END CharStream.
  316.  
  317. ======
  318. Start Compress.MOD
  319. ======
  320.  
  321. MODULE Compress;
  322.  
  323. (* File compression algorithm using Huffman coding.
  324.    Based on "Data Compression with Huffman Coding," BYTE March 1986.
  325.    Copyright 1986 by Jonathan Amsterdam.  All Rights Reserved.
  326. *)
  327.  
  328. FROM CharStream IMPORT charStream;
  329. IMPORT CharStream;
  330. FROM BitStream IMPORT bitStream;
  331. IMPORT BitStream;
  332. FROM MyTerminal IMPORT ClearScreen, pause, WriteCard, WriteLn, WriteString,
  333.     WriteLnString, Write;
  334. FROM InOut IMPORT ReadString;
  335. FROM Huffman IMPORT huffTree, huffman, readCode, writeCode, readTree,
  336.         writeTree, codeSize;
  337. FROM StringStuff IMPORT stringLen, stringCopy;
  338. FROM RealInOut IMPORT FWriteReal;  
  339. (* FWriteReal writes real numbers in decimal format.  If your implementation
  340. doesn't have it, substitute WriteReal. *)
  341.  
  342. CONST stringlen = 60;
  343.  
  344. VAR frequency:ARRAY CHAR OF CARDINAL;
  345.     fileSize:CARDINAL;
  346.     inFileName, outFileName: ARRAY[0..stringlen] OF CHAR;
  347.     hTree:huffTree;
  348.  
  349.  
  350. PROCEDURE doFreq;
  351. (* Obtain frequency count from file *)
  352. VAR cs:charStream;
  353. BEGIN
  354.     cs := CharStream.connect(inFileName, TRUE);        (* read file *)
  355.     freqCount(cs);
  356.     CharStream.disconnect(cs);
  357. END doFreq;
  358.  
  359. PROCEDURE freqCount(cs:charStream);
  360. VAR c:CHAR;
  361. BEGIN
  362.     FOR c := 0C TO CHR(HIGH(frequency)) DO
  363.         frequency[c] := 0;
  364.     END;
  365.     c := CharStream.read(cs);
  366.     WHILE NOT CharStream.EOS(cs) DO
  367.         INC(frequency[c]);
  368.         INC(fileSize);
  369.         c := CharStream.read(cs);
  370.     END;
  371. END freqCount;
  372.  
  373.  
  374. PROCEDURE doOutput;
  375. (* Output encoded file *)
  376. VAR inStream:charStream;
  377.     outStream:bitStream;
  378.     c:CHAR;
  379. BEGIN
  380.     inStream := CharStream.connect(inFileName, TRUE);
  381.     outStream := BitStream.connect(outFileName, FALSE);
  382.     BitStream.writeCard(outStream, fileSize);
  383.     writeTree(outStream, hTree);
  384.     c := CharStream.read(inStream);
  385.     WHILE NOT CharStream.EOS(inStream) DO
  386.         writeCode(outStream, hTree, c);
  387.         c := CharStream.read(inStream);
  388.     END;
  389.     CharStream.disconnect(inStream);
  390.     BitStream.disconnect(outStream);
  391. END doOutput;
  392.  
  393.  
  394.  
  395. PROCEDURE computeStats;
  396. (* Compute statistics on how much space was saved *)
  397. VAR c:CHAR;
  398.     origBits, compBits, nChars:CARDINAL;
  399.     savings:REAL;
  400. BEGIN
  401.     origBits := fileSize * 8;
  402.     compBits := 0;
  403.     nChars := 0;
  404.     FOR c := 0C TO CHR(HIGH(frequency)) DO
  405.         IF frequency[c] <> 0 THEN
  406.             INC(nChars);
  407.             compBits := compBits + codeSize(hTree, c) * frequency[c];
  408.         END;
  409.     END;
  410.     WriteString("number of different characters: ");
  411.     WriteCard(nChars, 0); WriteLn;
  412.     WriteString("original file size (bits): ");
  413.     WriteCard(origBits, 0); WriteLn;
  414.     WriteString("compressed f. size (bits): ");
  415.     WriteCard(compBits, 0); WriteLn;
  416.     WriteString("percent savings: ");
  417.     savings := 1.0 - (FLOAT(compBits) / FLOAT(origBits));
  418.     FWriteReal(savings * 100.0, 5); WriteLn;
  419.     WriteString("compressed size, including bookkeeping: ");
  420.     (* add 16 bits for character, count, 10n-1 bits for tree *)
  421.     INC(compBits, 16 + 10*nChars -1);
  422.     WriteCard(compBits, 0); WriteLn;
  423.     WriteString("true percent savings: ");
  424.     savings := 1.0 - (FLOAT(compBits) / FLOAT(origBits));
  425.     FWriteReal(savings * 100.0, 5); WriteLn;
  426. END computeStats;
  427.  
  428. PROCEDURE doOutfileName;
  429. (* Make the name of the output file by appending ".P" to the input file's
  430.    name *)
  431. VAR len:CARDINAL;
  432. BEGIN
  433.     len := stringLen(inFileName);
  434.     stringCopy(outFileName, inFileName);
  435.     outFileName[len] := '.';
  436.     outFileName[len+1] := 'P';
  437.     outFileName[len+2] := 0C;
  438. END doOutfileName;
  439.  
  440. BEGIN
  441.     ClearScreen;
  442.     WriteLnString("File Compression using Huffman Coding");
  443.     WriteString("Input file: ");
  444.     ReadString(inFileName);
  445.     doOutfileName;
  446.     doFreq;
  447.     hTree := huffman(frequency);
  448.     doOutput;
  449.     computeStats;
  450.     pause('done--');
  451. END Compress.
  452.  
  453. ======
  454. Start Huffman.DEF
  455. ======
  456.  
  457. DEFINITION MODULE Huffman;
  458.  
  459. (* Implements the Huffman coding scheme and procedures for manipulating
  460.    the code tree. *)
  461.  
  462. FROM BitStream IMPORT bitStream;
  463.  
  464. EXPORT QUALIFIED huffTree, huffman, writeCode, readCode, writeTree, readTree,
  465.         codeSize;
  466.  
  467. TYPE huffTree;
  468.  
  469. PROCEDURE huffman(VAR frequency:ARRAY OF CARDINAL):huffTree;
  470. (* construct a Huffman coding tree from the given character frequencies *)
  471.  
  472. PROCEDURE writeCode(bs:bitStream; ht:huffTree; c:CHAR);
  473. (* Write the code for c onto bs, using ht. *)
  474.  
  475. PROCEDURE readCode(bs:bitStream; ht:huffTree):CHAR;
  476. (* Read bits from bs until a full code is read; return the character *)
  477.  
  478. PROCEDURE writeTree(bs:bitStream; ht:huffTree);
  479. (* Write the tree onto the stream *)
  480.  
  481. PROCEDURE readTree(bs:bitStream):huffTree;
  482. (* Read a huffTree from the stream *)
  483.  
  484. PROCEDURE codeSize(ht:huffTree; c:CHAR):CARDINAL;
  485. (* returns the length of the code for c *)
  486.  
  487. END Huffman.
  488.  
  489. ======
  490. Start Huffman.MOD
  491. ======
  492.  
  493. IMPLEMENTATION MODULE Huffman;
  494.  
  495. (* Huffman coding algorithm, as described in "Data Compression With Huffman
  496.    Coding," BYTE, March 1986.
  497.    Copyright Jonathan Amsterdam 1986, All Rights Reserved. *)
  498.  
  499. FROM Storage IMPORT ALLOCATE, DEALLOCATE;
  500. FROM BitStream IMPORT bitStream;
  501. IMPORT BitStream;
  502. FROM MyTerminal IMPORT WriteString, WriteCard, WriteLn, fatal;
  503. CONST maxChars = 256;
  504.  
  505. TYPE
  506.     node = POINTER TO nodeRec;
  507.     nodeRec = RECORD
  508.                 char:CHAR;
  509.                 freq:CARDINAL;
  510.                 child:ARRAY BOOLEAN OF node;
  511.                 parent:node;        (* used for encoding *)
  512.               END;
  513.  
  514.     huffTree = POINTER TO htRec;
  515.     htRec = RECORD
  516.                 tree:node;      (* the tree itself *)
  517.                 leaf:ARRAY CHAR OF node; (* index by character, for encoding *)
  518.             END;
  519.  
  520.  
  521. VAR tree:ARRAY[1..maxChars] OF node;    (* temporary list of trees *)
  522.     nTrees:CARDINAL;
  523.  
  524.         (*** constructing the tree ***)
  525.  
  526. PROCEDURE huffman(VAR frequency:ARRAY OF CARDINAL):huffTree;
  527. VAR ht:huffTree;
  528. BEGIN
  529.     ht := initHuffTree(frequency);
  530.     initTrees(ht^.leaf);
  531.     WHILE nTrees > 1 DO
  532.         insert(combineNodes(removeSmallest(), removeSmallest()));
  533.     END;
  534.     ht^.tree := tree[1];
  535.     RETURN ht;
  536. END huffman;
  537.  
  538. PROCEDURE initHuffTree(VAR freq:ARRAY OF CARDINAL):huffTree;
  539. VAR i:CARDINAL;
  540.     ht:huffTree;
  541. BEGIN
  542.     ht := newHuffTree();
  543.     FOR i := 0 TO HIGH(freq) DO
  544.         IF freq[i] <> 0 THEN
  545.             ht^.leaf[CHR(i)] := newNode(CHR(i), freq[i], NIL, NIL);
  546.         END;
  547.     END;
  548.     RETURN ht;
  549. END initHuffTree;
  550.  
  551.  
  552. PROCEDURE initTrees(VAR leaf:ARRAY OF node);
  553. VAR i:CARDINAL;
  554. BEGIN
  555.     nTrees := 0;
  556.     FOR i := 0 TO HIGH(leaf) DO
  557.         IF leaf[i] <> NIL THEN
  558.             insert(leaf[i]);
  559.         END;
  560.     END;
  561. END initTrees;
  562.  
  563. PROCEDURE removeSmallest():node;
  564. VAR i, smallest:CARDINAL;
  565.     smallestNode:node;
  566. BEGIN
  567.     smallest := 1;
  568.     FOR i := 2 TO nTrees DO
  569.         IF tree[i]^.freq < tree[smallest]^.freq THEN
  570.             smallest := i;
  571.         END;
  572.     END;
  573.     smallestNode := tree[smallest];
  574.     tree[smallest] := tree[nTrees];
  575.     DEC(nTrees);
  576.     RETURN smallestNode;
  577. END removeSmallest;
  578.  
  579. PROCEDURE insert(n:node);
  580. BEGIN
  581.     INC(nTrees);
  582.     tree[nTrees] := n;
  583. END insert;
  584.  
  585.             (*** code I/O ***)
  586.  
  587. PROCEDURE writeCode(bs:bitStream; ht:huffTree; c:CHAR);
  588. (* Write the code for c onto bs, using ht. By using recursion, we can
  589.    avoid explicitly retracing the path from the root to the leaf. *)
  590.  
  591.     PROCEDURE wrCode(n:node);
  592.     BEGIN
  593.         IF n^.parent <> NIL THEN
  594.             wrCode(n^.parent);
  595.             BitStream.write(bs, n = n^.parent^.child[TRUE]);
  596.         END;
  597.     END wrCode;
  598.  
  599. BEGIN
  600.     IF ht^.leaf[c] = NIL THEN
  601.         WriteString("no code for "); WriteCard(CARDINAL(c), 0); WriteLn;
  602.         fatal('dying');
  603.     END;
  604.     wrCode(ht^.leaf[c]);
  605. END writeCode;
  606.  
  607. PROCEDURE readCode(bs:bitStream; ht:huffTree):CHAR;
  608. (* Read bits from bs until a full code is read; return the character *)
  609.  
  610.     PROCEDURE rdCode(n:node):CHAR;
  611.     BEGIN
  612.         IF leaf(n) THEN
  613.             RETURN n^.char;
  614.         ELSE
  615.             RETURN rdCode(n^.child[BitStream.read(bs)]);
  616.         END;
  617.     END rdCode;
  618.  
  619. BEGIN
  620.     RETURN rdCode(ht^.tree);
  621. END readCode;         
  622.             
  623. PROCEDURE writeTree(bs:bitStream; ht:huffTree);
  624. (* Write the tree onto the stream.  It is encoded as follows:
  625.    A 1 bit indicates an internal node.
  626.    A 0 bit indicates a leaf; the next 8 bits are the character code. 
  627.    The tree is traversed by preorder traversal: first the root, then
  628.    the left (FALSE) subtree, then the right (TRUE). *)
  629.  
  630.     PROCEDURE wrTree(n:node);
  631.     BEGIN
  632.         IF leaf(n) THEN
  633.             BitStream.write(bs, FALSE);
  634.             BitStream.writeChar(bs, n^.char);
  635.         ELSE
  636.             BitStream.write(bs, TRUE);
  637.             wrTree(n^.child[FALSE]);
  638.             wrTree(n^.child[TRUE]);
  639.         END;
  640.     END wrTree;
  641.  
  642. BEGIN
  643.     wrTree(ht^.tree);
  644. END writeTree;
  645.  
  646. PROCEDURE readTree(bs:bitStream):huffTree;
  647. (* Read a huffTree from the stream. See writeTree for the encoding used. 
  648.    Frequency information is NOT preserved. *)
  649. VAR ht:huffTree;
  650.  
  651.     PROCEDURE rdTree():node;
  652.     VAR false, true, n:node;
  653.     BEGIN
  654.         IF BitStream.read(bs) THEN (* an internal node *)
  655.             false := rdTree();
  656.             true := rdTree();
  657.             n := newNode(0C, 0, false, true);
  658.             false^.parent := n;
  659.             true^.parent := n;
  660.             RETURN n;
  661.         ELSE (* a leaf *)
  662.             n := newNode(BitStream.readChar(bs), 0, NIL, NIL);
  663.             ht^.leaf[n^.char] := n;
  664.             RETURN n;
  665.         END;
  666.     END rdTree;
  667.  
  668. BEGIN
  669.     ht := newHuffTree();
  670.     ht^.tree := rdTree();
  671.     RETURN ht;
  672. END readTree;
  673.  
  674.  
  675.             (*** huffTree allocation ***)
  676.  
  677. PROCEDURE newHuffTree():huffTree;
  678. VAR c:CHAR;
  679.     ht:huffTree;
  680. BEGIN
  681.     NEW(ht);
  682.     FOR c := 0C TO CHR(HIGH(ht^.leaf)) DO
  683.         ht^.leaf[c] := NIL;
  684.     END;
  685.     RETURN ht;
  686. END newHuffTree;
  687.     
  688.             (*** node stuff ***)
  689.  
  690. PROCEDURE combineNodes(n1, n2:node):node;
  691. (* used to combine nodes when constructing the coding tree *)
  692. VAR n:node;
  693. BEGIN
  694.     n := newNode(0C, n1^.freq + n2^.freq, n1, n2);
  695.     n1^.parent := n;
  696.     n2^.parent := n;
  697.     RETURN n;
  698. END combineNodes;
  699.  
  700. PROCEDURE newNode(c:CHAR; f:CARDINAL; false, true:node):node;
  701. VAR n:node;
  702. BEGIN
  703.     NEW(n);
  704.     WITH n^ DO
  705.         char := c;
  706.         freq := f;
  707.         child[FALSE] := false;
  708.         child[TRUE] := true;
  709.         parent := NIL;
  710.     END;
  711.     RETURN n;
  712. END newNode;
  713.  
  714. PROCEDURE freeNode(n:node);
  715. (* In the current implementation, this is never used *)
  716. BEGIN
  717.     IF n <> NIL THEN
  718.         freeNode(n^.child[FALSE]);
  719.         freeNode(n^.child[TRUE]);
  720.         DISPOSE(n);
  721.     END;
  722. END freeNode;
  723.  
  724. PROCEDURE leaf(n:node):BOOLEAN;
  725. BEGIN
  726.     IF n = NIL THEN
  727.         fatal('leaf: n NIL');
  728.     END;
  729.     RETURN n^.child[FALSE] = NIL;
  730. END leaf;
  731.  
  732. PROCEDURE codeSize(ht:huffTree; c:CHAR):CARDINAL;
  733. (* returns the length of the code for c *)
  734. VAR i:CARDINAL;
  735.     n:node;
  736. BEGIN
  737.     i := 0;
  738.     n := ht^.leaf[c];
  739.     WHILE n <> NIL DO
  740.         INC(i);
  741.         n := n^.parent;
  742.     END;
  743.     RETURN i-1;
  744. END codeSize;
  745.  
  746. BEGIN
  747. END Huffman.
  748.  
  749.  
  750. ======
  751. Start MyTerminal.DEF
  752. ======
  753.  
  754. DEFINITION MODULE MyTerminal;
  755.  
  756. (* Some small but useful additions to the Terminal module. *)
  757.  
  758. EXPORT QUALIFIED WriteString, WriteLn, Write, Read, ClearScreen, Beep,
  759.         WriteLnString, WriteInt, WriteCard, pause, fatal;
  760.  
  761. PROCEDURE WriteString(s:ARRAY OF CHAR);
  762. PROCEDURE WriteLn;
  763. PROCEDURE Write(c:CHAR);
  764. PROCEDURE Read(VAR c:CHAR);
  765. PROCEDURE ClearScreen;
  766. PROCEDURE Beep;
  767.  
  768. PROCEDURE WriteLnString(s:ARRAY OF CHAR);
  769. PROCEDURE WriteInt(i:INTEGER; spaces:CARDINAL);
  770. PROCEDURE WriteCard(c, spaces:CARDINAL);
  771.  
  772. PROCEDURE pause(msg:ARRAY OF CHAR);
  773. (* Prevents the screen from blanking and returning to the Finder until the
  774.    user hits a key.  msg is typed out. *)
  775.  
  776. PROCEDURE fatal(msg:ARRAY OF CHAR);
  777. (* Prints the message, does a pause, and HALTs. *)
  778.  
  779. END MyTerminal.
  780.  
  781.  
  782. ======
  783. Start MyTerminal.MOD
  784. ======
  785.  
  786. IMPLEMENTATION MODULE MyTerminal;
  787.  
  788. (* Some small but useful additions to the Terminal module. *)
  789.  
  790. IMPORT Terminal;
  791.  
  792. VAR powerOfTen: ARRAY[0..4] OF CARDINAL;
  793.  
  794.  
  795. PROCEDURE WriteLnString(s:ARRAY OF CHAR);
  796. BEGIN
  797.     Terminal.WriteString(s);
  798.     Terminal.WriteLn;
  799. END WriteLnString;
  800.  
  801. PROCEDURE WriteInt(i:INTEGER; spaces:CARDINAL);
  802. BEGIN
  803.     IF i < 0 THEN
  804.         writeNum(CARDINAL(-i), spaces-1, TRUE);
  805.     ELSE
  806.         writeNum(CARDINAL(i), spaces, FALSE);
  807.     END;
  808. END WriteInt;
  809.  
  810. PROCEDURE WriteCard(c, spaces:CARDINAL);
  811. BEGIN
  812.     writeNum(c, spaces, FALSE);
  813. END WriteCard;
  814.  
  815. PROCEDURE writeNum(c, spaces:CARDINAL; neg:BOOLEAN);
  816. VAR p:CARDINAL;
  817.     i:INTEGER;
  818. BEGIN
  819.     p := places(c);
  820.     FOR i := 1 TO INTEGER(spaces) - INTEGER(p) DO
  821.         Terminal.Write(' ');
  822.     END;
  823.     IF neg THEN 
  824.         Terminal.Write('-'); 
  825.     END;
  826.     FOR i := p-1 TO 0 BY -1 DO
  827.         Terminal.Write(CHR((c DIV powerOfTen[i]) + ORD('0')));
  828.         c := c MOD powerOfTen[i];
  829.     END;
  830. END writeNum;
  831.  
  832. PROCEDURE places(c:CARDINAL):CARDINAL;
  833. (* Returns the number of places c takes to print; i.e. trunc(1+log10(c)). *)
  834. VAR i:CARDINAL;
  835. BEGIN
  836.     FOR i := 4 TO 0 BY -1 DO
  837.         IF (c DIV powerOfTen[i]) > 0 THEN
  838.             RETURN i+1;
  839.         END;
  840.     END;
  841.     RETURN 1;
  842. END places;
  843.         
  844.  
  845. PROCEDURE pause(msg:ARRAY OF CHAR);
  846. (* Prevents the screen from blanking and returning to the Finder until the
  847.    user hits a key.  msg is typed out. *)
  848. VAR ch:CHAR;
  849. BEGIN
  850.     Terminal.WriteString(msg);
  851.     Terminal.Read(ch);
  852. END pause;
  853.  
  854. PROCEDURE fatal(msg:ARRAY OF CHAR);
  855. BEGIN
  856.     WriteLnString(msg);
  857.     pause('Hit any key to die--');
  858.     HALT;
  859. END fatal;
  860.  
  861.  
  862.             (*** Copies of Terminal procedures ***)
  863.  
  864. PROCEDURE WriteString(s:ARRAY OF CHAR);
  865. BEGIN
  866.     Terminal.WriteString(s);
  867. END WriteString;
  868.  
  869. PROCEDURE WriteLn;
  870. BEGIN
  871.     Terminal.WriteLn;
  872. END WriteLn;
  873.  
  874. PROCEDURE Write(c:CHAR);
  875. BEGIN
  876.     Terminal.Write(c);
  877. END Write;
  878.  
  879. PROCEDURE Read(VAR c:CHAR);
  880. BEGIN
  881.     Terminal.Read(c);
  882. END Read;
  883.  
  884. PROCEDURE ClearScreen;
  885. BEGIN
  886.     Terminal.ClearScreen;
  887. END ClearScreen;
  888.  
  889. PROCEDURE Beep;
  890. BEGIN
  891.     Terminal.Beep;
  892. END Beep;
  893.  
  894. BEGIN
  895.     powerOfTen[0] := 1;
  896.     powerOfTen[1] := 10;
  897.     powerOfTen[2] := 100;
  898.     powerOfTen[3] := 1000;
  899.     powerOfTen[4] := 10000;
  900. END MyTerminal.
  901.  
  902.  
  903. ======
  904. Start StringStuff.DEF
  905. ======
  906.  
  907. DEFINITION MODULE StringStuff;
  908.  
  909. EXPORT QUALIFIED stringCap, charCap, stringLen, stringCopy, stringEqual;
  910.  
  911. PROCEDURE charCap(ch:CHAR):CHAR;
  912.  
  913. PROCEDURE stringCap(VAR s:ARRAY OF CHAR);
  914.  
  915. PROCEDURE stringLen(VAR s:ARRAY OF CHAR):CARDINAL;
  916.  
  917. PROCEDURE stringCopy(VAR s1:ARRAY OF CHAR; s2:ARRAY OF CHAR);
  918.  
  919. PROCEDURE stringEqual(s1, s2:ARRAY OF CHAR):BOOLEAN;
  920.  
  921. END StringStuff.
  922.  
  923.  
  924. ======
  925. Start StringStuff.MOD
  926. ======
  927.  
  928.  
  929. IMPLEMENTATION MODULE StringStuff;
  930.  
  931.  
  932. PROCEDURE charCap(ch:CHAR):CHAR;
  933. BEGIN
  934.     IF (ch >= 'a') AND (ch <= 'z') THEN
  935.         RETURN CAP(ch);
  936.     ELSE
  937.         RETURN ch;
  938.     END;
  939. END charCap;
  940.  
  941. PROCEDURE stringCap(VAR s:ARRAY OF CHAR);
  942. VAR i:CARDINAL;
  943. BEGIN
  944.     FOR i := 0 TO stringLen(s) DO
  945.         s[i] := charCap(s[i]);
  946.     END;
  947. END stringCap;
  948.  
  949. PROCEDURE stringLen(VAR s:ARRAY OF CHAR):CARDINAL;
  950. VAR i:CARDINAL;
  951. BEGIN
  952.     FOR i := 0 TO HIGH(s) DO
  953.         IF s[i] = 0C THEN
  954.             RETURN i;
  955.         END;
  956.     END;
  957.     RETURN HIGH(s)+1;
  958. END stringLen;
  959.  
  960. PROCEDURE stringCopy(VAR s1:ARRAY OF CHAR; s2:ARRAY OF CHAR);
  961. VAR i:CARDINAL;
  962. BEGIN
  963.     i := 0;
  964.     LOOP
  965.         IF i > HIGH(s1) THEN
  966.             EXIT;
  967.         ELSIF i > HIGH(s2) THEN
  968.             s1[i] := 0C;
  969.             EXIT;
  970.         ELSE
  971.             s1[i] := s2[i];
  972.         END;
  973.         INC(i);
  974.     END;
  975. END stringCopy;
  976.  
  977. PROCEDURE stringEqual(s1, s2:ARRAY OF CHAR):BOOLEAN;
  978. VAR i:CARDINAL;
  979. BEGIN
  980.     FOR i := 0 TO HIGH(s1) DO
  981.         IF i > HIGH(s2) THEN
  982.             RETURN s1[i] = 0C;
  983.         ELSIF s1[i] <> s2[i] THEN
  984.             RETURN FALSE;
  985.         ELSIF s1[i] = 0C THEN
  986.             RETURN TRUE;
  987.         END;
  988.     END;
  989.     RETURN TRUE;
  990. END stringEqual;
  991.  
  992.  
  993. BEGIN
  994. END StringStuff.
  995.  
  996.  
  997. ======
  998. Start Uncompress.MOD
  999. ======
  1000.  
  1001. MODULE Uncompress;
  1002.  
  1003. (* Takes files encoded by Compress and restores them to their original
  1004.    state.
  1005.    Copyright 1986 by Jonathan Amsterdam.  All Rights Reserved. *)
  1006.  
  1007. FROM CharStream IMPORT charStream;
  1008. IMPORT CharStream;
  1009. FROM BitStream IMPORT bitStream;
  1010. IMPORT BitStream;
  1011. FROM MyTerminal IMPORT ClearScreen, pause, WriteCard, WriteLn, WriteString,
  1012.     WriteLnString, Write;
  1013. FROM InOut IMPORT ReadString;
  1014. FROM Huffman IMPORT huffTree, huffman, readCode, readTree;
  1015. FROM StringStuff IMPORT stringLen, stringCopy;
  1016.  
  1017. CONST stringlen = 60;
  1018.  
  1019. VAR inFileName, outFileName: ARRAY[0..stringlen] OF CHAR;
  1020.  
  1021. PROCEDURE doUncompress;
  1022. VAR inStream:bitStream;
  1023.     outStream:charStream;
  1024.     fileSize, i:CARDINAL;  (* number of characters in file *)
  1025.     hTree:huffTree;
  1026. BEGIN
  1027.     inStream := BitStream.connect(inFileName, TRUE);
  1028.     outStream := CharStream.connect(outFileName, FALSE);
  1029.     fileSize := BitStream.readCard(inStream);
  1030.     hTree := readTree(inStream);
  1031.     FOR i := 1 TO fileSize DO
  1032.         CharStream.write(outStream, readCode(inStream, hTree));
  1033.     END;
  1034.     CharStream.disconnect(outStream);
  1035.     BitStream.disconnect(inStream);
  1036. END doUncompress;
  1037.  
  1038.  
  1039. PROCEDURE doFileNames;
  1040. VAR len:CARDINAL;
  1041. BEGIN
  1042.     len := stringLen(inFileName);
  1043.     stringCopy(outFileName, inFileName);
  1044.     inFileName[len] := '.';
  1045.     inFileName[len+1] := 'P';
  1046.     inFileName[len+2] := 0C;
  1047.     outFileName[len] := '.';
  1048.     outFileName[len+1] := 'U';
  1049.     outFileName[len+2] := 0C;
  1050. END doFileNames;
  1051.  
  1052. BEGIN
  1053.     ClearScreen;
  1054.     WriteLnString("Uncompression program");
  1055.     WriteString('Input file (omit ".P"): ');
  1056.     ReadString(inFileName);
  1057.     doFileNames;
  1058.     doUncompress;
  1059.     pause('done--');
  1060. END Uncompress.
  1061. ile (omit ".P"): ');
  1062.     ReadString(inFileName);
  1063.     doFileNames;
  1064.     doUncompress;
  1065.     pause('done--');
  1066. END Unc