static char *SccsId = "@(#)dmpack.c 3.15 (TU-Delft) 03/26/91";
/**********************************************************

Name/Version      : The Design Data Management Project

Language          : C
Operating system  : UNIX SYSTEM V
Host machine      : HP9000/S500

Author(s)         : S. de Graaf
                    A. van der Hoeven
                    T.G.R. van Leuken
                    N. van der Meijs
		    T. Vogel
		    P. van der Wolf
Creation date     : 04-Dec-1986
Modified by       : S. de Graaf
Modification date : 26-Apr-1988


        Delft University of Technology
        Department of Electrical Engineering
        Network Theory Section
        Mekelweg 4 - P.O.Box 5031
        2600 GA DELFT
        The Netherlands

        Phone : 015 - 786234

        COPYRIGHT (C) 1985-1988, All rights reserved
**********************************************************/

/*
** This file contains two efficient routines for 
** writing and reading of
**
**	(signed) integers
**	7-bit variable length ascii strings
**	8-bit bytes
**
** If it were desired to write floats, the mantisse
** and the exponent should be treated as two signed
** integers.
**
** A data reduction of more than 50% when compared to ascii
** files, is achieved by converting numbers into a 
** radix-13 representation and storing the digits in only
** 4 bits (as opposed to 8).
** Further data reduction is achieved by dropping of
** trailing zeros.
**
** USAGE (see the test driver):
**
**	 _dmPack (fp, fmt, va_alist);
**	 _dmUnpack (fp, fmt, va_alist);
**
** A test driver is included: cc -DDRIVER ...
*/

#include "dmstd.h"
#  ifdef __STDC__
# include <stdarg.h>
#  else /* not __STDC__ */
# include <varargs.h>
#  endif /* else not __STDC__ */

#define BASE   0xD		/* Radix */
#define MIN    0xD		/* Separator for Negative Num */
#define SEP    0xE		/* Field Separator */
#define EOR    0xF		/* End of Record */

#define EVEN   0
#define ODD    1

static unsigned  CHAR;

#define PUTNIBBLE(x) (parity==ODD?\
    (parity = EVEN, putc ((CHAR|(x)&017), fp)):\
    (parity = ODD,  CHAR = (unsigned)((x) << 4)))

/*
** On a two's complement machine and when EOF == -1,
** (like HP and Gould,) EOF as returned by getc looks like
** EOR and this is just what we want.
*/
#define GETNIBBLE(fp) (parity==ODD?\
    (parity = EVEN, (unsigned)CHAR&017):\
    (parity = ODD,  (unsigned)((CHAR=getc(fp))>>4)&017))

/* VARARGS2 */
#  ifdef __STDC__
_dmPack ( FILE * fp, char  *fmt, ...)
#  else /* not __STDC__ */
_dmPack (fp, fmt, va_alist)
FILE * fp;
register char  *fmt;
va_dcl
#  endif /* else not __STDC__ */
{
#if defined (hpux) || defined (sun) || defined (cadmus) || defined (apollo)
    unsigned char  *s;
#else
    char   *s;
#endif
    register long    n;
    register double d;
    int      sep = 0;		/* separator required? */
    int      parity = EVEN;	/* even-odd nibble */
    int      nil_cnt = 0;

    int     n1;
    int     w;
    static long  maxint = 0x001fffff;
    static double   maxdouble;

    va_list ap;
#  ifdef __STDC__
    va_start (ap, fmt);
#  else /* not __STDC__ */
    va_start (ap);
#  endif /* else not __STDC__ */

    maxdouble = (double) maxint;

    fmt--;
loop: 
    switch (*++fmt) {
	default: 
	    dmerrno = DME_DDM;
	    dmError ("_dmPack: bad fmt");
	    goto loop;
	case ' ': 
	case '\t': 
	case '\n': 
	    goto loop;

	case 'F':		/* double to be written as scaled int */
	    d = va_arg (ap, double);
	    if (d > maxdouble || d < -maxdouble) {
	        dmerrno = DME_PUT;
		dmError ("Integer Overflow");
		return (-1);
	    }
	    else
		n = (d == 0.0 ? 0 : (long) (1000.0 * d + (d > 0 ? 0.5 : -0.5)));
	    goto caseD;

	case 'D': 		/* integer argument */
	case 'W': 		/* fieldwith 16 bytes */
	    n = va_arg (ap, long);

	caseD:
	/* 
	 ** If the number is 0 and the format is not 'W',
	 ** this field will be stripped of if it is the last
	 ** field or if it is followed by zeros only.
	 ** Therefore, we do not write a 0 now, but just
	 ** remember that it should be written if followed
	 ** by a non-zero.
	 */
	    if (n == 0 && *fmt != 'W') {
		nil_cnt++;
		goto loop;
	    }
	    else {
		for ( ; nil_cnt; nil_cnt--) {
		    if (sep) {
			PUTNIBBLE (SEP);
		    }
		    sep = 1;
		    PUTNIBBLE (0);
		}
	    }

	/* 
	 ** If the format is 'W', the field must be 16 bytes
	 ** wide and the record 4*16+1=65 bytes.
	 ** Extra, leading, SEP nibbles are first added
	 ** in order to make the field 16 bytes wide.
	 ** The +1 byte is automatically OK because of the
	 ** record separator (and the fill-nibble).
	 */
	    if (*fmt == 'W') {
		w = ((n1 = n) < 0 || sep) ? 31 : 32;
		while (n1 /= BASE)
		    w--;
		while (--w > 0)
		    PUTNIBBLE (SEP);
	    }

	/* 
	 ** If the number is negative, only a MIN nibble is
	 ** needed, no SEP nibble.
	 */
	    if (n < 0) {
		PUTNIBBLE (MIN);
		n = (unsigned) (-n);
	    }
	    else
		if (sep)
		    PUTNIBBLE (SEP);
	    sep = 1;
	/* 
	 ** Here comes the actual conversion.
	 ** Numbers are stored with the
	 ** least significant digits first!!!
	 */
	    while (n > BASE - 1) {
		PUTNIBBLE (n % BASE);
		n /= BASE;
	    };
	    PUTNIBBLE (n);

	    goto loop;

	case 'A':
	case 'S': 
	/* 
	 ** 7-bits ascii string argument 
	 ** The string has variable length, and is
	 ** terminated by the SEP or EOR nibble.
	 ** Therefore, the characters of the string
	 ** must be written in coded form.
	 ** For case 'A', the string can contain white space.
	 */
#if defined (hpux) || defined (sun) || defined (cadmus) || defined (apollo)
	    s = va_arg (ap, unsigned char *);
#else
	    s = va_arg (ap, char *);
#endif
	/*
	 ** If there are still zeros to be written,
	 ** write these first.
	 */
	    for ( ; nil_cnt; nil_cnt--) {
		if (sep) {
		    PUTNIBBLE (SEP);
		}
		sep = 1;
		PUTNIBBLE (0);
	    }

	    if (sep)
		PUTNIBBLE (SEP);
	    sep = 1;
	    s--;
	    while (++s && *s) {
		PUTNIBBLE (*s / BASE);
		PUTNIBBLE (*s % BASE);
	    }
	    goto loop;

	case 'C': 
	/* 
	 ** 8-bits byte argument
	 ** This argument is of fixed length, and
	 ** therefore does not need a terminator.
	 ** So, no coding is needed but the byte is 
	 ** stored literally.
	 ** The byte is always stored in two parts,
	 ** but this is not needed in case the byte starts
	 ** at an even nibble position.
	 */
	    n = va_arg (ap, int); /* C converts char to int before pushing
				     it on the stack, so pop an int */
	/*
	 ** If there are still zeros to be written,
	 ** write these first.
	 */
	    for ( ; nil_cnt; nil_cnt--) {
		if (sep) {
		    PUTNIBBLE (SEP);
		}
		sep = 1;
		PUTNIBBLE (0);
	    }

	    if (sep)
		PUTNIBBLE (SEP);
	    sep = 0;		/* No separator after single byte */
	    PUTNIBBLE (n >> 4);	/* NIBBLE macro does masking */
	    PUTNIBBLE (n);
	    goto loop;

	case '\0': 		/* format exhausted */
	/* 
	 ** Throw away any trailing zeros
	 */
	    nil_cnt = 0;

	/* 
	 ** The total record always occupies a whole number
	 ** of bytes, so add a fill nibble if the number
	 ** of nibbles now in the record is even.
	 */
	    if (parity == EVEN)
		CHAR = (unsigned)(SEP << 4);

	    putc ((CHAR | EOR & 017), fp);

	    return (ferror (fp) ? -1 : 0);
    }
}

/* VARARGS2 */
#  ifdef __STDC__
_dmUnpack ( FILE * fp, char  *fmt, ...)
#  else /* not __STDC__ */
_dmUnpack (fp, fmt, va_alist)
FILE * fp;
register char  *fmt;
va_dcl
#  endif /* else not __STDC__ */
{
    register long   *ip;
    register char  *cp;
    double   *dp;
    long       itmp;

    register int    n;
    unsigned int    nibble;
    char   *Fmt;

    int     sep = 0;		/* separator required? */
    int     parity = EVEN;	/* even-odd nibble */
    int     nmatch = 0;		/* no matched formats */

    int     f;

    va_list ap;
#  ifdef __STDC__
    va_start (ap, fmt);
#  else /* not __STDC__ */
    va_start (ap);
#  endif /* else not __STDC__ */

    nibble = GETNIBBLE (fp);

    fmt--;
    Fmt = fmt;
loop: 
    if (feof (fp)) {
	if (fmt == Fmt) {
	    return (EOF);
	}
	dmerrno = DME_GET;
	dmError ("premature End Of File");
	return (EOF);
    }
    switch (*++fmt) {
	default: 
	    dmerrno = DME_DDM;
	    dmError ("_dmUnpack: bad fmt");
	    goto loop;

	case ' ': 		/* white space */
	case '\t': 
	case '\n': 
	    goto loop;

	case 'F':		/* double that was written as scaled int */
	    ip = &itmp;
	    itmp = 0;
	    dp = va_arg (ap, double *);
	    nmatch++;

	    if (nibble == EOR) {	/* Deleted trailing zero */
		*dp = 0;
		goto loop;
	    }
	    goto caseD;

	case 'D': 		/* integer argument */
	case 'W': 		/* fieldwith 16 bytes */

	    ip = va_arg (ap, long *);
	    *ip = 0;
	    nmatch++;

	    if (nibble == EOR)	/* Deleted trailing zero */
		goto loop;

	/* 
	 ** If the format is 'W', get the leading SEP nibbles first
	 */
	    if (*fmt == 'W') {
		while (nibble == SEP)
		    nibble = GETNIBBLE (fp);
	    }

	caseD:
	    if (nibble == MIN) {
		f = -1;
		nibble = GETNIBBLE (fp);
	    }
	    else {
		f = 1;
		if (sep && *fmt != 'W')
		    nibble = GETNIBBLE (fp);
	    }
	    sep = 1;

	/* 
	 ** While nibble < BASE it is a valid digit
	 */
	    while (nibble < BASE) {
		*ip += f * nibble;
		f *= BASE;
		nibble = GETNIBBLE (fp);
	    }

	    if (*fmt == 'F') {
		*dp = *ip / 1000.0;
	    }

	    goto loop;

	case 'S': 		/* string argument */
	case 'A':
	    cp = va_arg (ap, char *);
	    nmatch++;
	    if (nibble == EOR) {
		*cp = '\0';
		goto loop;
	    }
	    if (sep)
		nibble = GETNIBBLE (fp);
	    sep = 1;

	    while (nibble < BASE) {
		*cp = nibble * BASE;
		*cp++ += GETNIBBLE (fp);
		nibble = GETNIBBLE (fp);
	    };
	    *cp = '\0';
	    goto loop;

	case 'C': 		/* byte argument */
	    cp = va_arg (ap, char *);
	    if (sep)
		nibble = GETNIBBLE (fp);
	    sep = 0;		/* No separator after single byte */

	    *cp = nibble << 4;
	    *cp |= GETNIBBLE (fp);
	    nibble = GETNIBBLE (fp);
	    nmatch++;
	    goto loop;

	case '\0': 		/* format exhausted */
	/* 
	 ** The current nibble is possibly the EOR
	 ** nibble or the fill nibble, but there is no
	 ** need to get the EOR nibble in the latter
	 ** case, because with a new call to _dmUnpack
	 ** the next byte is got.
	 ** A new record always starts on a byte-boundary.
	 */
	    return (ferror (fp) ? EOF : nmatch);
    };
}

# ifdef DRIVER

int dmerrno;
dmError () {}

main (argc, argv)
int     argc;
char   *argv[];
{
    if (strcmp (argv[1], "-p") == 0)
	pack ();
    else
	unpack ();
}

pack ()
{
    int     r,
            i1,
            i2;
    char    c;
    char    s1[100];
    char    s2[100];

    r = _dmPack (stdout, "C D D W S A F D D\n",
			'x', 0, 123, 0, "abc", "de fg", 1.1, 0, 0);
    if (r < 0) fprintf (stderr, "error");
    r = _dmPack (stdout, "C D D W S A F D D\n",
			'x', 123, 0, 456, "abc", "", 1.1, 0, 1);
    if (r < 0) fprintf (stderr, "error");
    r = _dmPack (stdout, "C D D W S A F D D\n",
			'x', 0, 0, 456, "abc", "a", -1.2, 0, 0);
    if (r < 0) fprintf (stderr, "error");
}

unpack ()
{
    int     r,
            i1,
            i2,
            i3,
            i4,
            i5;
    double  d;
    char    c;
    char    s1[100];
    char    s2[100];
    for (;;) {
	r = _dmUnpack (stdin, "CDDWSAFDD\n",
			&c, &i1, &i2, &i3, s1, s2, &d, &i4, &i5);
	if (r == EOF)
	    break;
	fprintf (stdout, "%c %d %d %d \"%s\" \"%s\" %f %d %d result: %d\n",
			c, i1, i2, i3, s1, s2, d, i4, i5, r);
    }
}
#endif /* DRIVER */
