home *** CD-ROM | disk | FTP | other *** search
- /************************************************************************
- * Binary file squeeze for packet transmission Steve Ward W1GOH 1/86 *
- * Version 2.0 2/20/86 *
- ************************************************************************
- ** Copyright 1986 by W1GOH on bahalf of the Amateur Radio community. **
- ** Unrestricted permission to use, copy, modify, extend, improve, **
- ** and build upon this program is hereby granted. **
- ************************************************************************
- * *
- * Converts a binary file to ASCII, excluding "special" characters *
- * which might cause trouble over ASCII transmission media (viz. *
- * packet radio). Does some data compression. *
- * *
- * USAGE: *
- * bsq <options> file.ext [outname] *
- * reads binary file.ext, writes file.bsq (or outname if specified)*
- * bsq <options> file.bsq [outname] *
- * reads file.bsq, writes original file name (or outname) *
- * *
- * <options> may include: *
- * -b Bootstrap mode. Does simple bit-stuffing, no data *
- * compression. Output file suitable for simple BASIC pgm *
- * -o Old format. Writes a .BSQ file compatible with earlier *
- * versions of BSQ. *
- * *
- * A trivial BASIC program suffices to decode files converted with *
- * the -b option. Although originally intended for bootstraping *
- * the BSQ program over the air, it may be used routinely if *
- * expedient. However, the BASIC pgm is very primitive indeed. *
- * *
- * This program is written in vanilla C, and should be easy to port. *
- * Currently I have it running on VAX/UNIX and IBM PCs; I *
- * encourage interested parties to port it to CPM and other *
- * environments. *
- * *
- ************************************************************************
- * *
- * The encoding algorithm involves the following techniques: *
- * (1) Basic bit-stuffing, breaking input stream of 8-bit binary bytes*
- * into an output stream of 6-bit printable ASCII chars. Each *
- * of the latter is relocated to printable range by adding 33 dec.*
- * (2) Data compression, which uses 28 of the remaining 30 printable *
- * ASCII codes as "meta" characters abbreviating input byte *
- * sequences. My compression scheme is adapted from the TERSE *
- * algorithm; it involves 2 identical FSMs (at sender & receiver, *
- * respectively) which cause meta chars to be defined in identical*
- * way at both ends on basis of communications history. In *
- * particular, each output step -- either of a bit-stuffed byte *
- * or of a meta symbol -- causes a new meta symbol to be defined *
- * as the PREVIOUS TWO output symbols. The metasymbol to be *
- * thus defined is selected via a deterministic LRU approximation,*
- * avoiding conflicts with active subnodes via a reference count. *
- * (3) A 29th character serves as a REPEAT code. REPEAT followed by *
- * a 6-bit count (relocated to ASCII per above) causes the last *
- * INPUT symbol to be rescanned <count> times... thus if the *
- * last input symbol was a meta symbol representing a 100-byte *
- * string, the repeat sequence might expand to 6300 bytes. *
- * (4) Additional compression hacks are envisioned for the remaining *
- * character. Watch this space. *
- * *
- * The bit-stuffing yields an output file 4/3 the size of the input, *
- * inflated further by (1) a header line and (2) CR/LFs at each output *
- * line. *
- * *
- * BUGS: *
- * 1. The program has suffered from a period of evolution, and needs *
- * to be cleaned up somewhat. Volunteers welcome. *
- * 2. The algorithm is still the subject of sporadic hacking. It *
- * might change incompatibly, if I hit upon something significantly*
- * better. Incompatibities are in theory detectable, however, *
- * by virtue of a version number in .bsq file headers. *
- ************************************************************************
- * Program history: *
- * 12/24/85 SAW: First version working. *
- * 1/1/86 SAW: Added data compression (v. 1.0) *
- * 2/17/86 SAW: Cleaned up, implemented K1OJH's suggestion of output *
- * to file named in header. Also part number, nparts in header. *
- * (v. 1.2) *
- * 2/20/86 SAW: Improved data compression, by avoiding flushing the *
- * bit-stuff pipeline on meta chars (uses buffer BinBuf for latter)*
- * Also "suspicious text" messages. (v. 2.0) *
- ************************************************************************/
-
- #include <stdio.h>
- #define VERSION 2 /* Version number, for file header */
- char *ANode(); /* Here for debugging only! */
-
- /************************************************************************
- * POTENTIAL MACHINE/COMPILER DEPENDANCIES *
- ************************************************************************/
- typedef unsigned char byte;
- typedef unsigned short ushort;
-
- /************************************************************************
- * Parameters & Globals *
- ************************************************************************/
-
- #define NTS 28 /* Number of definable nonterminals */
- #define NNODES NTS /* Max nodes for data compression */
-
- #define ASCBITS 6 /* Bits per ASCII character. */
- #define ASCODES (1<<ASCBITS) /* number of codes for bitstuffing */
- #define ASCBAS 041 /* Base char for code. */
- #define METABAS (ASCBAS+ASCODES)/* Base code for meta chars */
- #define METARPT (METABAS+NTS) /* Repeat code. */
-
- int NNodes = NNODES; /* Number of nodes used */
- char *from, to[100]; /* Source, destination file names */
- char fname[100]; /* Name from .bsq file */
- FILE *in, *out; /* Input, output streams. */
-
- char BFlag=0; /* Bootstrap- mode */
- char OldFlag=0; /* Old format. */
-
- typedef ushort NodeID; /* Node identifier: */
-
- #define ATOMIC(N) ((N)&0x8000) /* Atomic node... else internal. */
- #define DATA(N) ((N)&0xFF) /* Data from atomic node. */
- #define UNUSED ((NodeID) 0xFFFF)/* Code for undefined node. */
- #define MKATOM(D) ((D)|0x8000) /* Make an atomic node, given index. */
-
- struct Node
- { NodeID Left, Right; /* Inferiors. */
- ushort Refs; /* Reference count. */
- ushort Size; /* Total number of terminals */
- ushort Next, Prev; /* LRU Chain. */
- ushort Prefix; /* List of nodes having this as Left */
- ushort Thread; /* Above thread. */
- } Nodes[NNODES]; /* The node database. */
-
- NodeID LRUHead; /* Most recently used nonterminal. */
- NodeID Prefixes[256]; /* Prefix threads for terminals */
-
- #define NodeSize(N) ((ATOMIC(N)? 1:Nodes[N].Size))
-
- #define COLUMNS 72 /* Number of chars/output line */
- int CheckColumns=COLUMNS; /* Number of chars/line expected on inp */
- unsigned checksum; /* Checksum of input bytes */
- long NBytes; /* Bytes read/written */
- long Length; /* Target length of file. */
- NodeID Previous = UNUSED; /* For state machine. */
-
- #define BBSIZE 1024 /* Size of buffer for meta-chars -- */
- char BinBuf[BBSIZE]; /* Used to buffer meta-output whilst */
- int BinN; /* holding partial bit-stuff word. */
- int Partials = 1; /* FLAG: nonzero iff saving partials. */
-
- #define OutBin(bb) BinBuf[BinN++] = bb /* Output a byte to BinBuf */
-
-
- NodeInit()
- { register i, j;
-
- for (i=0; i<256; i++) Prefixes[i] = UNUSED;
-
- for (i=0; i<NNodes; i++) /* Fix up node pool */
- { Nodes[i].Left = UNUSED; /* Free node. */
- Nodes[i].Refs = 0; /* No references. */
- Nodes[i].Size = 0;
- Nodes[i].Next = (i+1+NNodes)%NNodes;
- Nodes[i].Prev = (i-1+NNodes)%NNodes;
- }
- LRUHead = 0; /* Head of LRU chain. */
- Previous = UNUSED;
- }
-
- /* Allocate & return a node, bumping ref counts of inferiors.
- * Finds least recently used node whose ref count is zero.
- * Ref cnt of returned node is zero.
- */
-
- NodeID NewNode(left, right)
- NodeID left, right;
- { register struct Node *p;
- int n, oldn, oldnn = -1;
-
- /* Show left, right as used FIRST, to prevent re-allocating. */
- if (!ATOMIC(left)) Nodes[left].Refs++;
- if (!ATOMIC(right)) Nodes[right].Refs++;
-
- /* Follow LRU chain, looking for candidate to recycle. */
- for (oldn = n = Nodes[LRUHead].Prev; ; n = p->Prev)
- { if (n == oldnn) Error("Node alloc!");/* "Can't happen!" */
- oldnn = oldn;
- p = &Nodes[n];
- if (p->Refs == 0) /* Unused node? */
- { KillNode(n); /* Yup, recycle it. */
- break;
- }
- }
-
- p->Left = left;
- p->Right = right;
- p->Refs = 0; /* Presumably no superiors yet */
- p->Prefix = UNUSED; /* No left superiors, yet */
- p->Size = NodeSize(left)+NodeSize(right);
-
- /* Splice onto proper Prefix thread: */
- if (ATOMIC(left))
- { p->Thread = Prefixes[DATA(left)];
- Prefixes[DATA(left)] = n;
- }
- else { p->Thread = Nodes[left].Prefix;
- Nodes[left].Prefix = n;
- }
-
- Touch(n); /* Bring it to head of LRU chn */
- return n;
- }
-
- /* De-allocate a node:
- */
- KillNode(n)
- { register struct Node *p;
- register NodeID *nid;
-
- p = &Nodes[n];
- if (p->Left == UNUSED) return; /* Unallocated node. */
-
- /* Splice out of prefix list. */
- if (ATOMIC(p->Left)) nid = &Prefixes[DATA(p->Left)];
- else nid = &(Nodes[p->Left].Prefix);
- for (; *nid != UNUSED; nid = &(Nodes[*nid].Thread))
- { if (*nid == n)
- { *nid = p->Thread;
- break;
- }
- }
-
- /* Deref inferior nodes. */
- if (!ATOMIC(p->Left)) Nodes[p->Left].Refs--;
- if (!ATOMIC(p->Right)) Nodes[p->Right].Refs--;
-
- p->Left = p->Right = UNUSED; /* Now nothings used. */
- p->Refs = 0;
- }
-
- /* "Touch" a nonterminal... ie, make it recent.
- */
- Touch(n)
- { int prev, next;
- register struct Node *p, *l;
-
- if (ATOMIC(n)) return; /* Shouldnt happen, but ... */
-
- p = &Nodes[n]; /* To become head of chain. */
- Nodes[p->Prev].Next = p->Next; /* Splice out node n. */
- Nodes[p->Next].Prev = p->Prev;
-
- l = &Nodes[LRUHead]; /* Current head of chain. */
- p->Prev = l->Prev; /* Splice in n as new head. */
- p->Next = LRUHead;
- Nodes[p->Prev].Next = n;
- l->Prev = n;
- LRUHead = n;
- }
-
- /* Drive the database state: call whenever a node is output. */
- NodeOut(n)
- NodeID n;
- {
- if (Previous != UNUSED) /* Define a new node. */
- { NodeID new;
- new = NewNode(Previous, n);
- }
- Previous = n;
- }
-
- /* Lookahead Input routines:
- */
-
- byte ibuf[1024]; /* Lookahead buffer */
- short ibufbase = 0, /* Base index of buffer */
- ibufn = 0; /* Bytes in buffer. */
-
- #define INEOF ((!ibufn)&&(feof(in))) /* Detect EOF on input. */
-
- Peek(n) /* Returns -1 iff can't. */
- { int nr;
- register ch;
-
- while (ibufn <= n)
- { if (n >= 1024) return -1;
- if (feof(in)) return -1; /* Can't read any more. */
- nr = (ibufbase+ibufn) & 1023;
- while (!feof(in) && (ibufn < 1024))
- { if ((ch = getc(in)) == EOF) break;
- ibuf[nr++] = ch;
- nr &= 1023;
- ++ibufn;
- }
- }
- return 0xFF & ibuf[(n+ibufbase) & 1023];
- }
-
- /* Remove next n input chars from buffer: */
- Remove(n)
- { ibufbase = (ibufbase+n) & 1023;
- ibufn -= n;
- }
-
-
-
-
- NodeID FindBest;
- int FindSize;
-
- /* Called with n=node, offset = next input symbol locn on match.
- * Updates FindBest, FindSize to best (longest) match.
- */
- MatchHit(n, offset)
- { NodeID thr;
- register struct Node *p;
-
- p = &Nodes[n];
- if (ATOMIC(n))
- { if (!FindSize) { FindSize = 1; FindBest = n; }
- }
- else
- { if (FindSize < p->Size) { FindSize = p->Size; FindBest = n; }
- thr = p->Prefix;
- FindRight(thr, offset);
- }
-
- }
-
- /* Follow a thread. Find all matches of right inferior, calling MatchHit.
- * Presumably, left nodes all match just fine.
- */
-
- FindRight(thr, offset)
- { register i;
-
- while (thr != UNUSED)
- { if (i = AbMatch(offset, Nodes[thr].Right))
- MatchHit(thr, offset+i);
- thr = Nodes[thr].Thread;
- }
- }
-
-
- /* Find longest applicable abbreviation.
- * Leaves Best node in FindBest, its size in FindSize;
- * Returns size of best abbreviation, or 0 iff none.
- */
-
- NodeID AbBest; /* AbMatch return values: NodeID, */
- int AbSize; /* Size of node. */
-
- Abbrev()
- { register i, j;
-
- if (((j = Peek(0)) == -1) || /* EOF. */
- ((i = Prefixes[j]) == UNUSED)) /* Nothing there. */
- return 0; /* Return failure. */
-
- FindBest = UNUSED;
- FindSize = 0;
- FindRight(i, 1); /* Search for match. */
-
- return FindSize;
- }
-
- /* Perform the recursive match.
- * Returns results in AbBest, AbSize.
- * Returns size, or 0 iff none.
- * offset is position in input stream, n is node ID.
- */
-
- AbMatch(offset, n)
- NodeID n;
- { register j;
-
- if (n == UNUSED) return 0;
- if (ATOMIC(n))
- { if (DATA(n) == Peek(offset))
- { AbBest = n;
- return AbSize = 1;
- }
- return 0;
- }
-
- if (j = AbMatch(offset, Nodes[n].Left))
- { register k;
- if (k = AbMatch(offset+j, Nodes[n].Right))
- { AbBest = n;
- return (AbSize = j+k);
- }
- }
- return 0;
- }
-
- /* Check for repeats.
- * Returns size of repeat string (in bytes), or 0 iff none.
- * Leaves repeat count in FindSize.
- * Guarantees repeat count < 64.
- */
- Repeat()
- { register size, i;
-
- if (Previous == UNUSED) return;
- if (ATOMIC(Previous)) /* String of atoms? */
- { for (i=DATA(Previous), size=0; Peek(size) == i; size++)
- if (size >= 63) break;
- return FindSize=size;
- }
- FindSize = 0;
-
- for (size=0; i = AbMatch(size, Previous);)
- { size += i;
- if (++FindSize >= 63) break;
- }
- return size;
- }
-
- /* Return pointer to directory-less file name: */
- char *BaseName(cc)
- register char *cc;
- { register char *dd;
- dd = cc;
- for (;*cc;++cc) if ((*cc == '/') || (*cc == '\\')) dd = cc+1;
- return dd;
- }
-
- char *extension(name)
- char *name;
- { while (*name && (*name != '.')) name++;
- return name;
- }
-
- main(argc, argv)
- char **argv;
- { char *arg, argn, tobsq = 0;
-
- while ((argc>1) && (*(arg=argv[1]) == '-')) /* OPTIONS */
- switch (*++arg)
- {
- case 'o': OldFlag = 1; argv++; argc--;
- Partials = 0;
- continue;
- case 'b': BFlag = 1; argv++; argc--; continue;
- default: Error("Illegal option '%s'", argv[1]);
- }
-
- if ((argc < 2) || (argc > 3)) Usage();
- from = argv[1];
-
-
- if (!strcmp(extension(from), ".bsq"))
- { if (argc > 2) strcpy(to, argv[2]);
- tobsq = 0;
- }
- else
- { if (argc > 2) strcpy(to, argv[2]);
- else { strcpy(to, BaseName(from));
- strcpy(extension(to), ".bsq");
- }
- tobsq = 1;
- }
-
- NodeInit();
-
- /* Open input and output files. *
- * NB: Files must be read/written in BINARY. If your C *
- * environment (eg Lattice MSDOS) requires some funny code to *
- * open files for binary I/O, do it here! */
-
- if (!(in = fopen(from, "r"))) Error("Can't read file %s", from);
- if (!tobsq) RdHeader();
-
- if (!(out = fopen(to, "w"))) Error("Can't write file %s", to);
-
- Msg("W1GOH Binary SQueeze 2.0: %s -> %s\r\n", from, to);
- if (tobsq) Squeeze();
- else UnSqueeze();
- return 0;
- }
-
-
- Usage()
- { Error("Usage: bsq infile [outfile]\r\n");
- }
-
- /************************************************************************
- * *
- * Actual conversion happens here. *
- * *
- * Algorithm: *
- * bytes 041 - 0xx are 6-bit data, bit-stuffed into output byte stream*
- * Each 8-bit output byte shifted into LastCh output fifo, whence *
- * LastCh[0] is most recent byte, LastCh[1] next most recent, etc *
- * Remaining 30 codes are META bytes, and cause output from defn *
- * (besides flushing any accumulated partial byte if !Partials) *
- ************************************************************************/
-
- /* File conversion: BINARY -> ASCII */
-
- Squeeze()
- { register ch, i;
-
- /* First, make space for a header: */
- WrHeader();
- crlf();
-
- NBytes = Length = 0;
- AscInit();
-
- for (;;)
- {
- if (!BFlag && (BinN < (BBSIZE-3)))
- { int rsize=0, rcnt, asize=0;
-
- if ((ch = Repeat()) && (FindSize > 1) && (ch > 4))
- { rsize = ch;
- rcnt = FindSize;
- }
-
- asize = Abbrev();
-
- if ((rsize-1) > asize) /* Use the repeat?? */
- { if (!Partials)
- FlushStuff(); /* Flush stuff state. */
- OutBin(METARPT-ASCBAS); /* Use a meta char. */
- OutBin(rcnt); /* Repeat count. */
- for (i=0; i<rsize; i++)
- { checksum += Peek(i);
- }
- Remove(rsize); /* Flush input string. */
- NBytes += rsize;
- continue;
- }
-
- if (asize) /* Else, use abbrev? */
- { if (!Partials)
- FlushStuff(); /* Flush stuff state. */
- OutBin(FindBest+64); /* Use a meta char. */
- NodeOut(FindBest); /* Now update machine */
- for (ch=0; ch<FindSize; ch++)
- { checksum += Peek(ch);
- }
- Remove(FindSize); /* Flush input string. */
- NBytes += FindSize;
- continue;
- }
- }
-
- if (!Partials && BinN) /* Compatibility = pain!*/
- { register i;
- for (i=0; i<BinN; i++) OutAscii(BinBuf[i]);
- BinN = 0;
- }
-
- ch = Peek(0);
- Remove(1);
- if (ch == -1) break;
- StuffOut(ch);
- NodeOut(MKATOM(ch));
- checksum += ch;
- ++NBytes;
- }
-
- AscFin();
-
- /* Then back to patch up header. */
- fseek(out, 0L, 0); /* rewind. */
- WrHeader();
- fclose(out);
- fclose(in);
- return 0;
- }
-
- /* Read header of .BSQ input file.
- * If file name in *to has not been set, it is formatted from the
- * name specified in the header (stripping off directories).
- */
-
- unsigned icheck; /* Input checksum from header */
- unsigned iversion; /* Input version number from hdr*/
- unsigned ipartn; /* Part number */
- unsigned inparts; /* Total number of parts */
-
- RdHeader()
- { register i, j;
-
- /* Try to read a new header: */
- i = fscanf(in, "=== BSQ ver %3d %6ld bytes %6u (%2u %2u) %s\n",
- &iversion, &Length, &icheck, &ipartn, &inparts, fname);
-
- if (i != 6)
- { /* Try for an old header: */
- fseek(in, 0L, 0);
- i = fscanf(in, "=== BSQ %d FILE %6ld %6u %s\n",
- &iversion, &Length, &icheck, fname);
- if (i != 4) Error("Non-BSQ header format on file %s", from);
- }
-
- if (iversion > VERSION)
- Error("This is BSQ version %d, file used newer version %d!",
- VERSION, iversion);
-
- for (i=0; fname[i]; i++) if (fname[i] < ' ') fname[i] = 0;
- if (!*to) /* Use this as output file? */
- strcpy(to, BaseName(fname));
-
- /* Version-specific features: */
- switch (iversion)
- { case 1: Partials = 0; CheckColumns = 0; break;
- }
- }
-
- /* Write a header to output file: */
- WrHeader()
- {
- if (OldFlag) /* Old version header? */
- fprintf(out, "=== BSQ %d FILE %6ld %6u %s",
- 1, NBytes, 0x7FFF & checksum, BaseName(from));
- else
- fprintf(out, "=== BSQ ver %3d %6ld bytes %6u (%2u %2u) %s",
- VERSION, NBytes, 0x7FFF & checksum, ipartn, inparts,
- BaseName(from));
- }
-
-
- #define LINSIZ 200 /* Maximum line size. */
- byte InBuf[LINSIZ]; /* Input line buffer */
- int InPtr=0, InSize=0; /* Number of chars in InBuf */
- int Suspect=0, LineNo = 1; /* Number of suspicious line. */
-
-
- GetC() /* Fetch an input character... */
- { while (InPtr == InSize) /* At end of buffer? */
- if (!GetLine()) return EOF; /* On end-of-file. */
- return InBuf[InPtr++];
- }
-
- GetLine() /* Fetch an input line. */
- { register i, ch; /* Returns zero iff EOF. */
- InSize = InPtr = 0;
- while (InSize == 0)
- { for (; InSize<LINSIZ; ) switch (ch = getc(in))
- { case EOF: return 0;
- case '\n': LineNo++;
- default: if (ch > ' ')
- { InBuf[InSize++] = ch; continue; }
- if (!InSize) continue;
- goto GotLine;
- }
- }
-
- GotLine: if (Suspect)
- Msg("Suspicious text in file %s, line %d\r\n", from, Suspect);
- if (CheckColumns && (InSize != CheckColumns))
- Suspect = LineNo;
- else Suspect = 0;
- return InSize;
- }
-
- UnSqueeze()
- { register ch;
- BinInit();
-
- for (;;)
- { ch = GetC();
- if (ch == EOF) break;
- if (ch > ' ') BinOut(ch);
- }
-
- BinFin();
- fclose(in);
- fclose(out);
-
- if (Length != NBytes) Error("File size: %s is %ld, %s was %ld",
- to, NBytes, fname, Length);
- if ((icheck & 0x7FFF) != (checksum & 0x7FFF))
- Error("Checksum error... %x %x\r\n", icheck, checksum);
- }
-
- /************************************************************************
- * *
- * ASCII -> BINARY conversion routines. *
- * AscInit() - prepare for output. *
- * StuffOut(byte) - Bit-stuff a byte of 8 bits. *
- * AscFin() - flush buffers *
- * *
- ************************************************************************/
-
- unsigned outbuf; /* Partial output bytes. */
- unsigned bits; /* number of bits in outbuf */
- unsigned outcol; /* output chars on current text line */
- unsigned runch, run; /* For run-length encoding. */
-
- AscInit()
- { checksum = outcol = outbuf = bits = 0;
- runch = run = 0;
- }
-
-
- /* Bit-stuff an 8-bit byte: */
- StuffOut(b)
- unsigned b;
- { register ch;
-
- outbuf <<= 8; /* Stuff in the new byte. */
- outbuf |= (b & ((1 << 8)-1));
- bits += 8; /* 8 more bits. */
-
- /* Now add to output, ASCBITS bits/output char: */
- while (bits >= ASCBITS)
- { ch = (outbuf >> (bits-ASCBITS)) & 0x3F; /* 6 Bits. */
- bits -= ASCBITS;
- OutAscii(ch);
- if (BinN)
- { register i;
- for (i=0; i<BinN; i++) OutAscii(BinBuf[i]);
- BinN = 0;
- }
- }
- }
-
- /* Flush the bit-stuffed output: */
- FlushStuff()
- { if (bits > 0) /* Output any remaining bits. */
- OutAscii(0x3F & (outbuf<<(ASCBITS-bits)));
- outbuf = bits = 0;
- if (BinN)
- { register i;
- for (i=0; i<BinN; i++) OutAscii(BinBuf[i]);
- BinN = 0;
- }
- }
-
- /* Output a number, relocated to printable ASCII range */
- OutAscii(b)
- { b += ASCBAS;
- putc(b, out);
- if (++outcol >= COLUMNS) crlf();
- }
-
- AscFin()
- { FlushStuff();
- if (outcol > 0) crlf();
- }
-
-
- /* Output a CR/LF: */
- crlf()
- { putc('\r', out); putc('\n', out);
- outcol = 0;
- }
-
- /************************************************************************
- * *
- * BINARY -> ASCII conversion routines. *
- * BinInit() - prepare for output. *
- * BinOut(byte) - output a byte of s bits. *
- * BinFin() - flush buffers & close. *
- * *
- ************************************************************************/
-
- BinInit()
- { checksum = outcol = outbuf = bits = 0;
- }
-
-
- /* Convert another ASCII input character to binary... */
-
- BinOut(b)
- unsigned b;
- { register i, ch;
-
- b &= 0177;
- if (b <= ' ') return; /* Ignore control chars. */
-
- ch = (b - 041) & ((1 << ASCBITS) -1); /* Extract ASCBITS */
-
- if (b >= METABAS)
- { if (!Partials) bits = 0; /* Flush bit-stuff pipe. */
- if (b == METARPT) /* Repeat character? */
- { if ((ch=GetC()) == EOF) return;
- ch = 0x3F & (ch - 041);/* Repeat count. */
- while (ch--) BinExp(Previous);
- return;
- }
- BinExp(b-METABAS); /* Expand the output. */
- NodeOut(b-METABAS); /* Run the state machine. */
- }
- else
- { BinOut1(ch); /* Bit-stuff input. */
- }
- }
-
-
- BinOut1(b)
- unsigned b;
- { register ch;
-
- outbuf <<= ASCBITS; /* Stuff in the new byte. */
- outbuf |= b;
- bits += ASCBITS;
-
- /* Now add to output, 8 bits/output char: */
- while (bits >= 8)
- { ch = ((outbuf >> (bits-8)) & 0xFF);
- NodeOut(MKATOM(ch)); /* Recognise it as output */
- bits -= 8;
- putc(ch, out);
- ++NBytes;
- checksum += ch;
- }
- }
-
- /* Expand & output binary bytes for a node. */
- BinExp(n)
- NodeID n;
- { register j;
-
- if (n == UNUSED) return 0;
- if (ATOMIC(n))
- { j = DATA(n);
- putc(j, out);
- ++NBytes;
- checksum += j;
- }
- else
- { BinExp(Nodes[n].Left);
- BinExp(Nodes[n].Right);
- }
- }
-
-
- BinFin()
- { if ((bits > 0) && (NBytes != Length)) /* Output any remaining bits.*/
- { outbuf &= ((1 << bits)-1);
- putc(outbuf, out);
- checksum += outbuf;
- NBytes++;
- }
- }
-
- /************************************************************************
- * Misc. utilitiy functions *
- ************************************************************************/
-
- Error(a0,a1,a2,a3,a4,a5) /* Fatal error handler -- */
- { fprintf(stderr, /* Called like printf. */
- a0,a1,a2,a3,a4,a5);
- fprintf(stderr, "\r\n");
- exit(-1);
- }
-
- Msg(a0,a1,a2,a3,a4,a5) /* Message, for debugging. */
- { fprintf(stderr, /* Called like printf. */
- a0,a1,a2,a3,a4,a5);
- fprintf(stderr, "\r\n");
- }
-