home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
HAM Radio 3
/
hamradioversion3.0examsandprograms1992.iso
/
packet
/
bsq
/
bsq.c
< prev
next >
Wrap
C/C++ Source or Header
|
1986-02-24
|
25KB
|
856 lines
/************************************************************************
* 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");
}