[mod.sources] v08i082: Graph+, A Graph Plotting Program, Part02/03

sources-request@munnari.UUCP (02/28/87)

Submitted by: Alan Kent <ajk@goanna.oz.au>
Mod.sources: Volume 8, Issue 82
Archive-name: graph+/Part02

#! /bin/sh
# This is a shell archive, meaning:
# 1.  Remove everything above the #! /bin/sh line.
# 2.  Save the resulting test in a file
# 3.  Execute the file with /bin/sh (not csh) to create the files:
#
#		abort.c
#		adjacent.c
#		appendtb.c
#		attrnode.c
#		average.c
#		count.c
#		cpyentry.c
#		cpytable.c
#		cumulate.c
#		dumpgrph.c
#		eval.c
#		evaltab.c
#		freetab.c
#		fundeclr.c
#
# Created by ajk on Wed Feb 25 09:24:11 EST 1987
#
if test -f 'abort.c'
then
	echo shar: will not over-write existing file "'abort.c'"
else
echo extracting "'abort.c'"
sed 's/^X//' >abort.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X
Xextern int linenum;
Xextern char *infilename;
Xextern int warnings;
X
X
X
Xabort ( mesg , parm )
Xchar *mesg , *parm;
X{
X    fprintf ( stderr , "%s: near line %d: " , infilename , linenum );
X    fprintf ( stderr , mesg , parm );
X    fprintf ( stderr , "\n" );
X    while ( yyinput () > 0 );	/* read until EOF */
X    exit ( 1 );
X}
X
X
Xwarn ( mesg , parm )
Xchar *mesg , *parm;
X{
X    if ( warnings ) {
X	fprintf ( stderr , "%s: warning near line %d: " , infilename , linenum );
X	fprintf ( stderr , mesg , parm );
X	fprintf ( stderr , "\n" );
X    }
X}
X
SHAR_EOF
if test 850 -ne "`wc -c < 'abort.c'`"
then
	echo shar: error transmitting "'abort.c'" '(should have been 850 characters)'
fi
fi
if test -f 'adjacent.c'
then
	echo shar: will not over-write existing file "'adjacent.c'"
else
echo extracting "'adjacent.c'"
sed 's/^X//' >adjacent.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
X
Xtable_st *
Xadjacent ( tab1 , tab2 )
Xtable_st *tab1 , *tab2;
X{
X    table_st *p;
X    table_st *final;
X
X    if ( tab1 == NULL ) {
X	final = tab2;
X    }
X    else if ( tab2 == NULL ) {
X	final = tab1;
X    }
X    else {
X	if ( tab1->size != tab2->size ) {
X	    abort ( "ADJACENT: Cannot join tables of different length" );
X	}
X	else {
X	    for ( p = tab1; p->next != NULL; p = p->next );
X	    p->next = tab2;
X	    final = tab1;
X	}
X    }
X    return ( final );
X}
SHAR_EOF
if test 785 -ne "`wc -c < 'adjacent.c'`"
then
	echo shar: error transmitting "'adjacent.c'" '(should have been 785 characters)'
fi
fi
if test -f 'appendtb.c'
then
	echo shar: will not over-write existing file "'appendtb.c'"
else
echo extracting "'appendtb.c'"
sed 's/^X//' >appendtb.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
X
Xextern table_st *new_table ();
X
X
Xtable_st *
Xappend_tables ( tab1 , tab2 )
Xtable_st *tab1 , *tab2;
X{
X    register table_st *p1 , *p2;
X    table_st *newtab;
X    int count;
X    int i;
X
X
X    if ( tab1 == NULL )
X	return ( tab2 );
X    if ( tab2 == NULL )
X	return ( tab1 );
X    p1 = tab1;
X    p2 = tab2;
X    count = 0;
X    while ( p1 != NULL  &&  p2 != NULL ) {
X	p1 = p1->next;
X	p2 = p2->next;
X	count++;
X    }
X    if ( p1 != NULL  ||  p2 != NULL )
X	abort ( "Tables must be of same width to append them" );
X    newtab = new_table ( count , tab1->size + tab2->size );
X    for ( p1 = tab1, p2 = newtab; p1 != NULL; p1 = p1->next, p2 = p2->next )
X	for ( i = 0; i < tab1->size; i++ )
X	    p2->data[i] = p1->data[i];
X    for ( p1 = tab2, p2 = newtab; p1 != NULL; p1 = p1->next, p2 = p2->next )
X	for ( i = 0; i < tab2->size; i++ )
X	    p2->data[i+tab1->size] = p1->data[i];
X    free_table ( tab1 );
X    free_table ( tab2 );
X    return ( newtab );
X}
X
SHAR_EOF
if test 1271 -ne "`wc -c < 'appendtb.c'`"
then
	echo shar: error transmitting "'appendtb.c'" '(should have been 1271 characters)'
fi
fi
if test -f 'attrnode.c'
then
	echo shar: will not over-write existing file "'attrnode.c'"
else
echo extracting "'attrnode.c'"
sed 's/^X//' >attrnode.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
Xextern char *new ();
X
X/* create an attribute expression node */
X
Xattr_st *
Xattr_node ( node_type , value , left , right )
Xint node_type;
Xdouble value;
Xattr_st *left , *right;
X{
X    attr_st *p;
X
X    p = (attr_st *) new ( sizeof ( attr_st ) );
X    p->node_type = node_type;
X    p->value = value;
X    p->left = left;
X    p->right = right;
X    return ( p );
X}
X
SHAR_EOF
if test 691 -ne "`wc -c < 'attrnode.c'`"
then
	echo shar: error transmitting "'attrnode.c'" '(should have been 691 characters)'
fi
fi
if test -f 'average.c'
then
	echo shar: will not over-write existing file "'average.c'"
else
echo extracting "'average.c'"
sed 's/^X//' >average.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
X
Xdouble
Xaverage_fun ( table , from , to )
Xtable_st *table;
Xint from , to;
X{
X    int i;
X    double val;
X
X    if ( table == NULL )
X	abort ( "AVERAGE requires at least a single column table" );
X    if ( table->size < 1 ) {
X	warn ( "AVERAGE requires at least one value in table " );
X	return ( 0.0 );
X    }
X    if ( to >= table->size )
X	to = table->size - 1;
X    val = 0.0;
X    for ( i = from; i <= to; i++ )
X	val += table->data[i];
X    return ( val / (double)( to - from + 1 ) );
X}
X
SHAR_EOF
if test 813 -ne "`wc -c < 'average.c'`"
then
	echo shar: error transmitting "'average.c'" '(should have been 813 characters)'
fi
fi
if test -f 'count.c'
then
	echo shar: will not over-write existing file "'count.c'"
else
echo extracting "'count.c'"
sed 's/^X//' >count.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
X
Xdouble
Xcount_fun ( table , from , to )
Xtable_st *table;
X{
X    if ( table == NULL )
X	return ( 0.0 );
X    if ( to >= table->size )
X	to = table->size - 1;
X    if ( from > to )
X	return ( 0.0 );
X    return ( (double) to - from + 1 );
X}
X
SHAR_EOF
if test 567 -ne "`wc -c < 'count.c'`"
then
	echo shar: error transmitting "'count.c'" '(should have been 567 characters)'
fi
fi
if test -f 'cpyentry.c'
then
	echo shar: will not over-write existing file "'cpyentry.c'"
else
echo extracting "'cpyentry.c'"
sed 's/^X//' >cpyentry.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
Xcopy_entry ( table , to , from )
Xtable_st *table;
Xint to , from;
X{
X    table_st *p;
X
X    for ( p = table; p != NULL; p = p->next )
X	p->data[to] = p->data[from];
X}
X
SHAR_EOF
if test 498 -ne "`wc -c < 'cpyentry.c'`"
then
	echo shar: error transmitting "'cpyentry.c'" '(should have been 498 characters)'
fi
fi
if test -f 'cpytable.c'
then
	echo shar: will not over-write existing file "'cpytable.c'"
else
echo extracting "'cpytable.c'"
sed 's/^X//' >cpytable.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X
X
Xextern table_st *new_table ();
X
X
Xtable_st *
Xcopy_of_table ( oldtab )
Xtable_st *oldtab;
X{
X    int i;
X    register table_st *table , *ftp , *ttp;
X
X    if ( oldtab == NULL )
X	return ( NULL );
X    table = new_table ( num_cols ( oldtab ) , oldtab->size );
X    ftp = oldtab;
X    ttp = table;
X    while ( ftp != NULL ) {
X	for ( i = 0; i < table->size; i++ )
X	    ttp->data[i] = ftp->data[i];
X	ftp = ftp->next;
X	ttp = ttp->next;
X    }
X    return ( table );
X}
SHAR_EOF
if test 786 -ne "`wc -c < 'cpytable.c'`"
then
	echo shar: error transmitting "'cpytable.c'" '(should have been 786 characters)'
fi
fi
if test -f 'cumulate.c'
then
	echo shar: will not over-write existing file "'cumulate.c'"
else
echo extracting "'cumulate.c'"
sed 's/^X//' >cumulate.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include <math.h>
X#include "graph.h"
X#include "y.tab.h"
X
X
X
X
Xtable_st *
Xcumulate ( table , attr )
Xtable_st *table;
Xint attr;
X{
X    double total;
X    double *data;
X    int i;
X    table_st *p;
X
X    total = 0.0;
X    p = table;
X    if ( attr < 1 )
X	abort ( "Cannot CUMULATE on column $0" );
X    while ( --attr > 0  &&  p != NULL )
X	p = p->next;
X    if ( p == NULL )
X	abort ( "Illegal column to CUMULATE by" );
X    data = p->data;
X    for ( i = 0; i < p->size; i++ ) {
X	total += data[i];
X	data[i] = total;
X    }
X    return ( table );
X}
X
SHAR_EOF
if test 845 -ne "`wc -c < 'cumulate.c'`"
then
	echo shar: error transmitting "'cumulate.c'" '(should have been 845 characters)'
fi
fi
if test -f 'dumpgrph.c'
then
	echo shar: will not over-write existing file "'dumpgrph.c'"
else
echo extracting "'dumpgrph.c'"
sed 's/^X//' >dumpgrph.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X/*
X * WARNING: This file is extremely sensative to changes.
X *	Making one minor change may break other features.
X *   	This file probably needs to be re-written.
X */
X
X#include <stdio.h>
X#include <math.h>
X#include "graph.h"
X#include "y.tab.h"
X
X
X#define DEBUG		0
X#define DEBUG_LOG	0
X
X
X/* this info is for trying to place the text nicely on the screen */
X/* however, unfortunately it is different for all the different plotters! */
X
X#ifdef  LASER
X#define CHAR_WIDTH	48
X#define CHAR_HEIGHT	100
X#else
X#define CHAR_WIDTH	80
X#define CHAR_HEIGHT	100
X#endif
X
X
X#define XSPACE		4096
X#define YSPACE		4096
X#define XORIGIN		(15*CHAR_WIDTH)
X#define YORIGIN		650
X#define XRANGE		(XSPACE-XORIGIN)
X#define YRANGE		(YSPACE-YORIGIN-3*CHAR_HEIGHT)
X#define TICK_SIZE	50
X#define TEXT_GAP	400
X
X#define TICK_TEXT_GAP	400		/* minimum gap between ticks for log */
X#define TICK_GAP	40
X
X#define RAD		(XSPACE/200)
X
X#define LEGEND_LINE_LENGTH	400
X
X
Xextern double min_fun ();
Xextern double max_fun ();
Xextern double ceil ();
Xextern double pow ();
Xextern double log10 ();
X
X
Xextern graph_st graph[];
Xextern int num_graphs;
Xextern char *graph_label;
Xextern axis_st xaxis;
Xextern axis_st yaxis;
Xextern int horiz_legend;
Xextern int vert_legend;
X
X
X
Xstatic double lastx_clip;	/* buffered coordinate for clip function */
Xstatic double lasty_clip;
X
X
Xdump_graphs ()
X{
X    int i;
X
X    if ( num_graphs < 1 ) {
X	fprintf ( stderr , "No graphs to print\n" );
X	exit ( 0 );
X    }
X
X    if ( xaxis.linear == LOGRITHMIC ) {
X	for ( i = 0; i < num_graphs; i++ )
X	    log_tab ( graph[i].table , graph[i].table->next );
X    }
X
X    if ( yaxis.linear == LOGRITHMIC ) {
X	for ( i = 0; i < num_graphs; i++ )
X	    log_tab ( graph[i].table->next , graph[i].table );
X    }
X
X    /* plot(3x) routines */
X    openpl ();
X    erase ();
X    space ( 0 , 0 , XSPACE , YSPACE );
X
X    if ( graph_label != NULL ) {
X	move ( XORIGIN + XRANGE / 2 - strlen ( graph_label ) * CHAR_WIDTH / 2 ,
X	    YSPACE - CHAR_HEIGHT );
X	label ( graph_label );
X    }
X
X    determine_range ( XAXIS , &xaxis );
X    determine_range ( YAXIS , &yaxis );
X
X    axes ();
X
X    dump_legend ();
X
X    for ( i = 0; i < num_graphs; i++ )
X	draw_graph ( &graph[i] );
X
X    /* plot(3x) */
X    move ( 0 , 0 );
X    closepl ();
X}
X
X
X
X/* discards values <= 0.0 */
X
Xstatic
Xlog_tab ( table , othercol )
Xtable_st *table , *othercol;
X{
X    int i , j;
X    int warn_count;
X
X    warn_count = 0;
X    j = 0;
X    for ( i = 0; i < table->size; i++ ) {
X	if ( table->data[i] > 0.0 ) {
X	    othercol->data[j] = othercol->data[i];
X	    table->data[j] = log10 ( table->data[i] );
X	    j++;
X	}
X	else if ( warn_count++ == 0 )
X	    warn ( "negative or zero data to be plotted logrithmically discarded" );
X    }
X    table->size = j;
X    othercol->size = j;
X}
X
X
X
Xstatic
Xdraw_graph ( gptr )
Xgraph_st *gptr;
X{
X    int i , size;
X    int reseti;
X    int basex , basey;
X    double *xdata , *ydata;
X    double lastx , lasty;
X    double newx , newy;
X
X
X    /* necessary for clip function to work safely */
X
X    lasty_clip = -1;
X    lastx_clip = -1;
X
X    /* no data to plot */
X
X    if ( gptr->table->size < 1 )
X	return;
X
X    xdata = gptr->table->data;
X    ydata = gptr->table->next->data;
X    size = gptr->table->size;
X
X    /* draw the line (with clipping) */
X
X    if ( gptr->line_type != NO ) {
X	set_line ( gptr->line_type );
X	lastx = xdata[0];
X	lasty = ydata[0];
X	/* start from 0 (not 1) so a single point will come out as a dot */
X	for ( i = 0; i < size; i++ ) {
X	    newx = xdata[i];
X	    newy = ydata[i];
X	    clip_line ( lastx , lasty , newx , newy );
X	    lastx = newx;
X	    lasty = newy;
X	}
X    }
X
X    /* Draw any points along the line */
X
X    if ( gptr->point_type != 0 ) {
X	set_line ( SOLID );
X	for ( i = 0; i < size; i++ ) {
X
X	    basex = lxscale ( xdata[i] );
X	    basey = lyscale ( ydata[i] );
X
X	    if ( basex >= XORIGIN  &&  basex <= XORIGIN + XRANGE
X	    &&   basey >= YORIGIN  &&  basey <= YORIGIN + YRANGE )
X
X		draw_point ( gptr->point_type , basex , basey );
X	}
X    }
X
X    /* output the graph label */
X
X    set_line ( SOLID );
X    if ( gptr->label != NULL ) {
X	move ( lxscale ( lastx_clip ) , lyscale ( lasty_clip ) );
X	/*move ( lxscale ( xdata[size-1] ) , lyscale ( ydata[size-1] ) );*/
X	label ( gptr->label );
X    }
X}
X
X
X
Xdraw_point ( point_type , basex , basey )
Xint point_type , basex , basey;
X{
X    if ( point_type & MSK_TRIANGLE ) {
X	move ( basex - RAD , basey - RAD * 2 / 3 );
X	cont ( basex + RAD , basey - RAD * 2 / 3 );
X	cont ( basex , basey + RAD * 4 / 3 );
X	cont ( basex - RAD , basey - RAD * 2 / 3 );
X    }
X    
X    if ( point_type & MSK_CROSS ) {
X	move ( basex - RAD , basey - RAD );
X	cont ( basex + RAD , basey + RAD );
X	move ( basex - RAD , basey + RAD );
X	cont ( basex + RAD , basey - RAD );
X    }
X
X    if ( point_type & MSK_PLUS ) {
X	move ( basex - RAD , basey - RAD );
X	cont ( basex + RAD , basey + RAD );
X	move ( basex - RAD , basey + RAD );
X	cont ( basex + RAD , basey - RAD );
X    }
X
X    if ( point_type & MSK_CIRCLE ) {
X	circle ( basex , basey , RAD );
X    }
X    
X    if ( point_type & MSK_SQUARE ) {
X	/* *4/5 is just to make the shapes appear the same size */
X	move ( basex - RAD * 4 / 5 , basey - RAD * 4 / 5 );
X	cont ( basex + RAD * 4 / 5 , basey - RAD * 4 / 5 );
X	cont ( basex + RAD * 4 / 5 , basey + RAD * 4 / 5 );
X	cont ( basex - RAD * 4 / 5 , basey + RAD * 4 / 5 );
X	cont ( basex - RAD * 4 / 5 , basey - RAD * 4 / 5 );
X    }
X}
X
X
Xclip_line ( x1 , y1 , x2 , y2 )
Xdouble x1 , y1 , x2 , y2;
X{
X    double minx , miny , maxx , maxy;
X    int mx1 , my1 , mx2 , my2;
X    double xedge , yedge;
X
X
X    minx = xaxis.range.min;
X    miny = yaxis.range.min;
X    maxx = xaxis.range.max;
X    maxy = yaxis.range.max;
X
X    /* determine which sector the end points are in */
X
X    mx1 = sector ( x1 , minx , maxx );
X    my1 = sector ( y1 , miny , maxy );
X    mx2 = sector ( x2 , minx , maxx );
X    my2 = sector ( y2 , miny , maxy );
X
X    /* points must not be in same sector and off screen */
X
X    if ( mx1 * mx2 != 1  &&  my1 * my2 != 1 ) {
X
X	if ( mx1 != 0 ) {
X
X	    /* move point 1 nearer the x-edge of the frame */
X
X	    if ( mx1 == 1 )
X		xedge = maxx;
X	    else
X		xedge = minx;
X	    y1 = y1 + ( y2 - y1 ) * ( xedge - x1 ) / ( x2 - x1 );
X	    x1 = xedge;
X
X	    /* recompute sector to see if point needs another shift */
X
X	    my1 = sector ( y1 , miny , maxy );
X	}
X
X	if ( my1 != 0 ) {
X
X	    /* move point 1 nearer the y-edge of the frame */
X
X	    if ( my1 == 1 )
X		yedge = maxy;
X	    else
X		yedge = miny;
X	    x1 = x1 + ( x2 - x1 ) * ( yedge - y1 ) / ( y2 - y1 );
X	    y1 = yedge;
X
X	}
X
X	/* ok, now repeat the above for point 2 */
X
X	if ( mx2 != 0 ) {
X
X	    /* move point 2 nearer the x-edge of the frame */
X
X	    if ( mx2 == 1 )
X		xedge = maxx;
X	    else
X		xedge = minx;
X	    y2 = y2 + ( y1 - y2 ) * ( xedge - x2 ) / ( x1 - x2 );
X	    x2 = xedge;
X
X	    /* recompute sector to see if point needs another shift */
X
X	    my2 = sector ( y2 , miny , maxy );
X	}
X
X	if ( my2 != 0 ) {
X
X	    /* move point 2 nearer the y-edge of the frame */
X
X	    if ( my2 == 1 )
X		yedge = maxy;
X	    else
X		yedge = miny;
X	    x2 = x2 + ( x1 - x2 ) * ( yedge - y2 ) / ( y1 - y2 );
X	    y2 = yedge;
X
X	}
X
X	if ( x1 >= minx  &&  x1 <= maxx  &&  x2 >= minx  &&  x2 <= maxx 
X	&&   y1 >= miny  &&  y1 <= maxy  &&  y2 >= miny  &&  y2 <= maxy ) {
X
X	    /* use buffered move and cont calls where possible as some */
X	    /* plotters do dotted lines better if cont is used for continuous */
X	    /* lines. Using separate line() calls means the dotted lines */
X	    /* start again per line */
X
X	    if ( x1 != lastx_clip  ||  y1 != lasty_clip )
X		move ( xscale ( x1 ) , yscale ( y1 ) );
X	    cont ( xscale ( x2 ) , yscale ( y2 ) );
X	    lastx_clip = x2;
X	    lasty_clip = y2;
X	}
X    }
X}
X
X
Xstatic int
Xsector ( val , min , max )
Xdouble val , min , max;
X{
X    if ( val < min ) return ( -1 );
X    if ( val > max ) return ( 1 );
X    return ( 0 );
X}
X
X
Xstatic
Xset_line ( type )
Xint type;
X{
X    switch ( type ) {
X    case DOTTED : linemod ( "dotted" ); break;
X    case DOTDASHED : linemod ( "dotdashed" ); break;
X    case SHORTDASHED : linemod ( "shortdashed" ); break;
X    case LONGDASHED : linemod ( "longdashed" ); break;
X    default : linemod ( "solid" ); break;
X    }
X}
X
X
Xstatic int
Xxscale ( coord )
Xdouble coord;
X{
X    double xcoord;
X
X    xcoord = ( ( coord - xaxis.range.min )
X	/ ( xaxis.range.max - xaxis.range.min ) ) * (double)XRANGE;
X    return ( (int)xcoord + XORIGIN );
X}
X
X
Xstatic int
Xyscale ( coord )
Xdouble coord;
X{
X    double ycoord;
X
X    ycoord = ( ( coord - yaxis.range.min )
X	/ ( yaxis.range.max - yaxis.range.min ) ) * (double)YRANGE;
X    return ( (int)ycoord + YORIGIN );
X}
X
X
Xstatic int
Xlxscale ( coord )
Xdouble coord;
X{
X    int xcoord;
X
X    xcoord = xscale ( coord );
X    if ( xcoord < 0 )
X	return ( 0 );
X    if ( xcoord >= XSPACE )
X	return ( XSPACE - 1 );
X    return ( xcoord );
X}
X
X
Xstatic int
Xlyscale ( coord )
Xdouble coord;
X{
X    int ycoord;
X
X    ycoord = yscale ( coord );
X    if ( ycoord < 0 )
X	return ( 0 );
X    if ( ycoord >= YSPACE )
X	return ( YSPACE - 1 );
X    return ( ycoord );
X}
X
X
Xchar *
Xchoose_format ( paxis )
Xstruct axis_st *paxis;
X{
X    double first_log_tick ();
X
X    if ( paxis->linear == LOGRITHMIC ) {
X	paxis->power_10 = floor ( paxis->range.min );
X	paxis->format = "%.0f";
X	/* STILL NOT RIGHT
X	if ( xscale ( first_log_tick ( xaxis.range.min ) ) >= XRANGE + XORIGIN )
X	    paxis->format = "%.2f";
X	*/
X    }
X    else if ( paxis->auto_tick_size )
X	calc_ticks ( paxis , &paxis->tick_size , &paxis->power_10 );
X
X    if ( paxis->scale == AUTO
X    /* &&  paxis->linear != LOGRITHMIC ???? special scaling ok for log too? */
X    &&  paxis->auto_tick_size ) {
X	if ( paxis->power_10 == 1.0
X	||   paxis->power_10 == 2.0 ) {
X	    paxis->format = "%.0f";
X	    paxis->scale = NO;
X	}
X	else if ( paxis->tick_size >= 10.0 )
X	    paxis->format = "%.0f";
X    }
X    return ( ( paxis->user_format == NULL )
X	? paxis->format : paxis->user_format );
X}
X
X
Xstatic
Xaxes ()
X{
X    int xscale ();
X    int yscale ();
X    int lxscale ();
X    int lyscale ();
X    double first_log_tick ();
X    double next_log_tick ();
X    double next_10_tick ();
X
X    int base_x , base_y;
X    int tick_pos;
X    int last_tick_pos;
X    int last_text_pos;
X    int tick_num;
X    int loop_count;
X    int i;
X    double tick_value;
X    double base;
X    double xaxis_min , yaxis_min;
X    char buf[500];
X    char small_buf[2];
X    char *p , *beg;
X    int longest , length , num_lines , line_num;
X    int print_tick;
X    int best_log_ticks;
X    char *xformat , *yformat;
X
X
X    xformat = choose_format ( &xaxis );
X    yformat = choose_format ( &yaxis );
X
X    base_x = lxscale ( xaxis.range.min );
X    base_y = lyscale ( yaxis.range.min );
X    base = 10.0;
X    set_line ( SOLID );
X
X    if ( xaxis.frame != NO ) {
X
X	/* First the x-axis */
X
X	/* draw base line */
X
X	line ( base_x , base_y , base_x + XRANGE - 1 , base_y );
X
X	if ( xaxis.frame == OUTLINE )
X	    line ( base_x , base_y + YRANGE - 1 , base_x + XRANGE - 1 , base_y + YRANGE - 1 );
X
X	/* Label the axis */
X
X	if ( xaxis.label != NULL ) {
X	    sprintf ( buf ,
X		( xaxis.scale != AUTO  ||  xaxis.power_10 == 0.0 )
X		    ? "%s" : "%s x 10^%d" ,
X		xaxis.label ,
X		(int)xaxis.power_10 );
X	    move ( base_x + XRANGE / 2 - strlen ( buf ) * CHAR_WIDTH / 2 ,
X		base_y - TEXT_GAP );
X	    label ( buf );
X/* fprintf(stderr,"xaxis label '%s'\n",buf);*/
X	}
X
X	/* Put ticks on the axis */
X
X/*fprintf(stderr,"power_10 = %f\n",xaxis.power_10);*/
X	tick_num = 0;
X	loop_count = 0;
X	last_tick_pos = 0;
X	last_text_pos = 0;
X		
X	if ( xaxis.linear == LOGRITHMIC ) {
X	    xaxis_min = xaxis.range.min;
X
X#if DEBUG
Xfprintf(stderr,"min = %f\n",xaxis.range.min);
X#endif
X
X	    best_log_ticks = xscale ( first_log_tick ( xaxis.range.min ) )
X		< XRANGE + XORIGIN;
X	    best_log_ticks = 1;	/* override for now */
X
X#if DEBUG_LOG
Xfprintf(stderr,"best = %d, xscale(min) = %d\n",best_log_ticks,xscale(first_log_tick(xaxis.range.min)));
X#endif
X
X	}
X	else {
X
X	    /* now, round the value off! If we dont do that, min */
X	    /* values that are strange fractions will cause misleading */
X	    /* values to be printed (%.1f will round fractions to one */
X	    /* decimal place which will be totally wrong for a tick size */
X	    /* of 0.1 */
X
X	    xaxis_min = ceil ( xaxis.range.min
X		/ ( xaxis.tick_size  * pow ( base , xaxis.power_10 ) ) )
X		* ( xaxis.tick_size * pow ( base , xaxis.power_10 ) );
X#if DEBUG
Xfprintf(stderr,"x.min %f, x.tick_size %f, x.power_10 %f , xaxis_min %f\n",
Xxaxis.range.min,xaxis.tick_size,xaxis.power_10,xaxis_min);
X#endif
X	}
X
X	while ( 1 ) {
X
X	    /* determine value to display next to tick */
X
X	    if ( xaxis.linear == LOGRITHMIC ) {
X		if ( best_log_ticks ) {
X		    if ( loop_count == 0 ) {
X			tick_value = first_log_tick ( xaxis.range.min );
X#if DEBUG_LOG
Xfprintf(stderr,"min = %f, max = %f, tick = %f\n",xaxis.range.min,xaxis.range.max,tick_value);
X#endif
X		    }
X		    else
X			tick_value = next_log_tick ();
X		}
X		else {
X		    tick_value = pow ( 10.0 , xaxis.range.min )
X			+ (double)tick_num
X			* ( pow( 10.0 , xaxis.range.max )
X			    - pow ( 10.0 , xaxis.range.min ) ) / 5.0;
X#if DEBUG_LOG
Xfprintf(stderr,"tick value is %f\n",tick_value);
X#endif
X		}
X	    }
X	    else {
X		tick_value = xaxis_min + (double)tick_num
X		    * xaxis.tick_size * pow ( base , xaxis.power_10 );
X	    }
X	    loop_count++;
X	    
X	    /* determine offset along axis to place tick */
X
X	    if ( xaxis.linear == LOGRITHMIC )
X		tick_pos = xscale ( log10 ( tick_value ) );
X	    else
X		tick_pos = xscale ( tick_value );
X
X	    /* see if meant to auto scale value */
X
X	    if ( xaxis.scale == AUTO )
X		tick_value /= pow ( base , xaxis.power_10 );
X#if DEBUG_LOG
Xfprintf(stderr,"scaled tickvalue to %f\n",tick_value);
X#endif
X
X	    /* run out of axis? */
X
X	    if ( tick_pos > XRANGE + XORIGIN )
X		break;
X
X	    /* if logrithmic axis, must also check that ticks wont be */
X	    /* too close together */
X
X	    if ( xaxis.linear == LOGRITHMIC
X	    &&  tick_pos != xscale ( next_10_tick () - 1.0 ) ) {
X		/* is tick too close to last tick? */
X		if ( tick_pos < last_tick_pos + TICK_GAP ) {
X#if DEBUG_LOG
Xfprintf(stderr,"tick %d (%d) too close to LAST position\n",tick_num,tick_pos);
X#endif
X		    continue;		/* try next tick */
X		}
X		/* is tick too close to next 1,10,100,1000 value? */
X		if ( tick_pos + TICK_GAP > xscale ( next_10_tick () ) ) {
X#if DEBUG_LOG
Xfprintf(stderr,"tick %d (%d) too close to NEXT position\n",tick_num,tick_pos);
Xfprintf(stderr,"next = %f, xscale(next) = %d\n",next_10_tick (),xscale(next_10_tick()));
X#endif
X		    continue;		/* try next tick */
X		}
X		last_tick_pos = tick_pos;
X	    }
X
X	    /* draw the actual tick */
X
X	    if ( xaxis.frame == GRID )
X		line ( tick_pos , base_y + YRANGE - 1 , tick_pos , base_y );
X	    else
X		line ( tick_pos , base_y , tick_pos , base_y - TICK_SIZE );
X
X	    /* print the tick value */
X
X	    print_tick = 0;
X	    if ( xaxis.linear != LOGRITHMIC )
X		print_tick = 1;
X	    else if ( tick_pos >= last_text_pos + TICK_TEXT_GAP  ) {
X		if ( xscale ( 2.0 ) - xscale ( 1.0 ) <= TICK_TEXT_GAP ) {
X		    if ( tick_pos == xscale ( next_10_tick () - 1.0 ) )
X			print_tick = 1;
X		}
X		else {
X		    if ( tick_pos + TICK_TEXT_GAP <= xscale ( next_10_tick () ) )
X			print_tick = 1;
X		}
X	    }
X#if DEBUG|DEBUG_LOG
Xfprintf(stderr,"tick_pos = %d, 2.0-1.0 = %d, print it %d\n",
Xtick_pos,xscale(2.0)-xscale(1.0),print_tick);
X#endif
X
X	    if ( print_tick ) {
X		sprintf ( buf , xformat , tick_value );
X		move ( tick_pos - strlen ( buf ) * CHAR_WIDTH / 2 ,
X		    base_y - ( xaxis.frame == GRID ? 2 : 3 ) * TICK_SIZE );
X		label ( buf );
X		last_text_pos = tick_pos;
X#if DEBUG|DEBUG_LOG
Xfprintf(stderr,"TICK %d at %d: '%s'\n",tick_num,tick_pos,buf);
X#endif
X
X	    }
X
X	    /* loop until tick position off axis */
X
X	    tick_num++;
X	    if ( tick_num > 100 )
X		abort ( "axis tick algorithm failed!!" );
X	}
X    }
X
X    /* Second, the y-axis */
X
X    if ( yaxis.frame != NO ) {
X
X	/* draw base line */
X
X	line ( base_x , base_y , base_x , base_y + YRANGE - 1 );
X
X	if ( yaxis.frame == OUTLINE )
X	    line ( base_x , base_y + YRANGE - 1 , base_x + XRANGE - 1 , base_y + YRANGE - 1 );
X
X	/* Label the axis by writing values DOWN the axis */
X
X	if ( yaxis.label != NULL ) {
X	    sprintf ( buf ,
X		( yaxis.scale != AUTO  ||  yaxis.power_10 == 0.0 )
X		    ? "%s" : "%s x~10^%d" ,
X		yaxis.label ,
X		(int)yaxis.power_10 );
X
X#ifdef OLD_DOWN_AXIS
X	    for ( i = 0; buf[i] != '\0'; i++ ) {
X		move ( base_x - TEXT_GAP - 2 * CHAR_WIDTH , base_y + YRANGE / 2
X		    + strlen ( buf ) * CHAR_HEIGHT / 2 - i * CHAR_HEIGHT );
X		small_buf[0] = buf[i];
X		small_buf[1] = '\0';
X		label ( small_buf );
X	    }
X#else
X
X	    /* split label into many lines, each line separated by a space. */
X	    /* to allow a space to be force, ~ is mapped to a space at the */
X	    /* last moment. to place the text, each line is centred and */
X	    /* placed so that the longest line touches the edge of the */
X	    /* graph. */
X
X	    /* first find longest line and number of lines */
X
X	    longest = 0;
X	    num_lines = 0;
X	    p = buf;
X	    while ( *p != '\0' ) {
X		num_lines++;
X		length = 0;
X		while ( *p != ' '  &&  *p != '\0' ) {
X		    p++;
X		    length++;
X		}
X		if ( *p == ' ' )
X		    p++;
X		if ( length > longest )
X		    longest = length;
X	    }
X
X	    /* now print yaxis label by replacing blanks with \0 and */
X	    /* ~ with blanks and output each line */
X
X	    line_num = 0;
X	    p = buf;
X	    while ( *p != '\0' ) {
X		beg = p;
X		length = 0;
X		while ( *p != '\0'  &&  *p != ' ' ) {
X		    if ( *p == '~' )
X			*p = ' ';
X		    length++;
X		    p++;
X		}
X		if ( *p == ' ' )
X		    *p++ = '\0';
X		move ( ( longest * CHAR_WIDTH/2 ) - ( length * CHAR_WIDTH/2 ) ,
X		    base_y + YRANGE / 2 + num_lines * CHAR_HEIGHT
X		    - line_num * CHAR_HEIGHT * 3 / 2 );
X		label ( beg );
X		line_num++;
X	    }
X
X#endif
X	}
X
X	/* Put ticks on the axis */
X
X	tick_num = 0;
X	last_tick_pos = 0;
X	last_text_pos = 0;
X	loop_count = 0;
X	
X	if ( yaxis.linear == LOGRITHMIC ) {
X	    yaxis_min = yaxis.range.min;
X	}
X	else {
X
X	    /* now, round the value off! If we dont do that, min */
X	    /* values that are strange fractions will cause misleading */
X	    /* values to be printed (%.1f will round fractions to one */
X	    /* decimal place which will be totally wrong for a tick size */
X	    /* of 0.1 */
X
X	    yaxis_min = ceil ( yaxis.range.min
X		/ ( yaxis.tick_size * pow ( base , yaxis.power_10 ) ) )
X		* ( yaxis.tick_size * pow ( base , yaxis.power_10 ) );
X	}
X
X	while ( 1 ) {
X
X	    /* determine value to display next to tick */
X
X	    if ( yaxis.linear == LOGRITHMIC ) {
X		if ( loop_count == 0 )
X		    tick_value = first_log_tick ( yaxis.range.min );
X		else
X		    tick_value = next_log_tick ();
X	    }
X	    else {
X		tick_value = yaxis_min + (double)tick_num
X		    * yaxis.tick_size * pow ( base , yaxis.power_10 );
X	    }
X	    loop_count++;
X	    
X	    /* determine offset along axis to place tick */
X
X	    if ( yaxis.linear == LOGRITHMIC )
X		tick_pos = yscale ( log10 ( tick_value ) );
X	    else
X		tick_pos = yscale ( tick_value );
X
X	    /* see if meant to auto scale value */
X
X	    if ( yaxis.scale == AUTO )
X		tick_value /= pow ( base , yaxis.power_10 );
X
X	    /* run out of axis? */
X
X	    if ( tick_pos > YRANGE + YORIGIN )
X		break;
X
X	    /* if logrithmic axis, must also check that ticks wont be */
X	    /* too close together */
X
X	    if ( yaxis.linear == LOGRITHMIC
X	    &&  tick_pos != yscale ( next_10_tick () - 1.0 ) ) {
X		/* is tick too close to last tick? */
X		if ( tick_pos < last_tick_pos + TICK_GAP )
X		    continue;		/* try next tick */
X		/* is tick too close to next 1,10,100,1000 value? */
X		if ( tick_pos + TICK_GAP > yscale ( next_10_tick () ) )
X		    continue;		/* try next tick */
X		last_tick_pos = tick_pos;
X	    }
X
X	    /* draw the actual tick */
X
X	    if ( yaxis.frame == GRID )
X		line ( base_x + XRANGE - 1 , tick_pos , base_x , tick_pos );
X	    else
X		line ( base_x , tick_pos , base_x - TICK_SIZE , tick_pos );
X
X	    /* print the tick value */
X
X	    print_tick = 0;
X	    if ( yaxis.linear != LOGRITHMIC )
X		print_tick = 1;
X	    else if ( tick_pos >= last_text_pos + TICK_TEXT_GAP  ) {
X		if ( yscale ( 2.0 ) - yscale ( 1.0 ) <= TICK_TEXT_GAP ) {
X		    if ( tick_pos == yscale ( next_10_tick () - 1.0 ) )
X			print_tick = 1;
X		}
X		else {
X		    if ( tick_pos + TICK_TEXT_GAP <= yscale ( next_10_tick () ) )
X			print_tick = 1;
X		}
X	    }
X
X	    if ( print_tick ) {
X		sprintf ( buf , yformat , tick_value );
X		move ( base_x - strlen ( buf ) * CHAR_WIDTH
X		    - ( yaxis.frame == GRID ? 1 : 2 ) * TICK_SIZE ,
X		    tick_pos - CHAR_WIDTH / 2 );
X		label ( buf );
X		last_text_pos = tick_pos;
X	    }
X
X	    /* loop until tick position off axis */
X
X	    tick_num++;
X	    if ( tick_num > 100 )
X		abort ( "axis tick algorithm failed!!" );
X	}
X    }
X}
X
X
Xstatic
Xdetermine_range ( which , paxis )
Xint which;
Xaxis_st *paxis;
X{
X    double floor () , log10 () , pow ();
X    int i;
X    double best_min , best_max;
X    double new_min , new_max;
X    double round_max;
X    table_st *table;
X
X
X    /* only automatically calculate range if no range has been specified */
X    /* (This is indicated by the min value being greater than the max value) */
X
X    if ( paxis->range.min >= paxis->range.max ) {
X
X	/* scan through all the graphs and determine the largest and */
X	/* smallest value. */
X
X	if ( which == XAXIS )
X	    table = graph[0].table;
X	else
X	    table = graph[0].table->next;
X	best_min = min_fun ( table , 0 , table->size - 1 );
X	best_max = max_fun ( table , 0 , table->size - 1 );
X	for ( i = 1; i < num_graphs; i++ ) {
X	    if ( which == XAXIS )
X		table = graph[i].table;
X	    else
X		table = graph[i].table->next;
X	    new_min = min_fun ( table , 0 , table->size - 1 );
X	    new_max = max_fun ( table , 0 , table->size - 1 );
X	    if ( new_min < best_min )
X		best_min = new_min;
X	    if ( new_max > best_max )
X		best_max = new_max;
X	}
X
X	/* check for zero height graph */
X
X	if ( best_min == best_max ) {
X	    
X	    /* give the graph some height */
X
X	    if ( best_min == 0.0 ) {
X		best_min = -1.0;
X		best_max = 1.0;
X	    }
X	    else if ( best_min > 0.0 ) {
X		if ( paxis->linear == LOGRITHMIC )
X		    best_min = best_min / 2.0;
X		else
X		    best_min = 0;
X		best_max = 2.0 * best_max;
X	    }
X	    else {
X		best_min = best_min * 0.9;
X		best_max = best_max * 1.1;
X	    }
X	}
X
X	/* make min a nice round value (eg: 4-1000 => 0-1000) */
X
X	if ( best_max > 0.0 ) {
X	    round_max = pow ( (double)10.0 , floor ( log10 ( best_max / 3.0 ) ) );
X	    if ( round_max > 0.0 )
X		best_min = floor ( best_min / round_max ) * round_max * 0.999;
X	}
X	/*
X	if ( paxis->linear != LOGRITHMIC ) {
X	    if ( best_max > 0.0 ) {
X		round_max = pow ( (double)10.0 , floor ( log10 ( best_max ) ) );
X		best_min = floor ( best_min / round_max ) * round_max;
X	    }
X	}
X	else {
X	    if ( best_max > 1.0 ) {
X		round_max = pow ( (double)10.0 , floor ( log10 ( best_max ) ) );
X		best_min = floor ( best_min / round_max ) * round_max;
X	    }
X	}
X	*/
X#if 0
X	/* to stop 0->infinity graphs from hanging the program */
X	if ( paxis->linear == LOGRITHMIC ) {
X	    if ( best_min < best_max / 5.0 )
X		best_min = best_max / 5.0;
X			/* remember, this is 10^5.0 */
X	}
X#endif
X	paxis->range.min = best_min;
X	paxis->range.max = best_max;
X    }
X    else if ( paxis->linear == LOGRITHMIC ) {
X
X	/* convert values to actual values */
X
X	if ( paxis->range.min <= 0.0 )
X	    abort ( "Minimum value for axis on log graph is <= 0" );
X	paxis->range.min = log10 ( paxis->range.min );
X	paxis->range.max = log10 ( paxis->range.max );
X    }
X}
X
X
X
Xstatic
Xcalc_ticks ( paxis , tick , power )
Xaxis_st *paxis;
Xdouble *tick , *power;
X{
X    double log10 ();
X    double base , delta;
X    int power_of_10;
X    double tick_size;
X
X    base = 10.0;
X    delta = paxis->range.max - paxis->range.min;
X    power_of_10 = (int) floor ( log10 ( delta ) );
X    tick_size = delta / pow ( base , (double)(power_of_10+1) );
X    if ( tick_size <= 0.5 ) {
X	tick_size = ceil ( (double)( tick_size * 10.0 ) ) / (double)10.0;
X    }
X    else {
X	tick_size = 0.1;
X	power_of_10++;
X    }
X
X    /* round tick size off to x10 ^3,6,9,12,.... which people understand */
X    switch ( power_of_10 % 3 ) {
X    case 0 :		/* already mult of 3 */
X	break;
X    case 1 :		/* its just too high (by one) */
X	tick_size *= 10.0;
X	power_of_10--;
X	break;
X    case 2 :		/* its just too low (by one) */
X	/*
X	tick_size /= 10.0;
X	power_of_10++;
X	*/
X	tick_size *= 100.0;
X	power_of_10 -= 2;
X	break;
X    }
X
X    *tick = tick_size;
X    *power = (double)power_of_10;
X}
X
X
X
Xstatic int lt_value;
Xstatic int lt_power;
X
X
Xstatic double
Xfirst_log_tick ( value )
Xdouble value;
X{
X    double actual;
X    double ret_val;
X
X    actual = pow ( (double)10.0 , value );
X    lt_power = (int) floor ( log10 ( actual ) );
X    lt_value = (int) ceil ( actual / pow ( (double)10.0 , (double)lt_power ) );
X    while ( lt_value >= 10 ) {
X	lt_value /= 10;
X	lt_power++;
X    }
X    ret_val = (double)lt_value * pow ( (double)10.0 , (double)lt_power );
X/*fprintf(stderr,"first_log_tick: lt_power = %d, lt_value = %d, ret_val = %f\n",lt_power,lt_value,ret_val);*/
X    return ( ret_val );
X}
X
X
Xstatic double
Xnext_log_tick ()
X{
X    lt_value++;
X    while ( lt_value >= 10 ) {
X	lt_value /= 10;
X	lt_power++;
X    }
X    return ( (double)lt_value * pow ( (double)10.0 , (double)lt_power ) );
X}
X
X
Xstatic double
Xnext_10_tick ()
X{
X    return ( (double)( lt_power + 1 ) );
X}
X
X
Xstatic
Xdump_legend ()
X{
X    int i;
X    int num_lines;
X    int longest;
X    int basex , basey;
X
X
X    /* first determine how much is going to be output, and what is the */
X    /* longest string that is to be output */
X
X    num_lines = 0;
X    longest = 0;
X    for ( i = 0; i < num_graphs; i++ ) {
X	if ( graph[i].legend != NULL ) {
X	    num_lines++;
X	    if ( strlen ( graph[i].legend ) > longest )
X		longest = strlen ( graph[i].legend );
X	}
X    }
X
X    /* ok, now we want to work out where to start outputing the legend  */
X
X    switch ( horiz_legend ) {
X
X    case LEFT :
X	basex = XORIGIN + CHAR_WIDTH * 3;
X	break;
X
X    case CENTER :
X	basex = XORIGIN + XRANGE / 2 - ( longest * CHAR_WIDTH - LEGEND_LINE_LENGTH ) / 2;
X	break;
X    
X    case RIGHT :
X	basex = XORIGIN + XRANGE - longest * CHAR_WIDTH - 3 * CHAR_WIDTH - LEGEND_LINE_LENGTH - CHAR_WIDTH;
X	break;
X    }
X
X    switch ( vert_legend ) {
X
X    case TOP :
X	basey = YORIGIN + YRANGE - CHAR_HEIGHT * 2;
X	break;
X
X    case MIDDLE :
X	basey = YORIGIN + YRANGE / 2 + num_lines * CHAR_HEIGHT / 2;
X	break;
X
X    case BOTTOM :
X	basey = YORIGIN + CHAR_HEIGHT * 1 + num_lines * CHAR_HEIGHT;
X	break;
X    }
X
X    /* ok, now loop through and acutally output the legend! */
X
X    for ( i = 0; i < num_graphs; i++ ) {
X	if ( graph[i].legend != NULL ) {
X	    if ( graph[i].line_type != NO ) {
X		set_line ( graph[i].line_type );
X		line ( basex , basey , basex + LEGEND_LINE_LENGTH , basey );
X	    }
X	    set_line ( SOLID );
X	    draw_point ( graph[i].point_type , basex + LEGEND_LINE_LENGTH / 2 , basey );
X	    move ( basex + LEGEND_LINE_LENGTH + CHAR_WIDTH , basey - CHAR_HEIGHT / 3 );
X	    label ( graph[i].legend );
X	    basey -= CHAR_HEIGHT;
X	}
X    }
X}
SHAR_EOF
if test 27046 -ne "`wc -c < 'dumpgrph.c'`"
then
	echo shar: error transmitting "'dumpgrph.c'" '(should have been 27046 characters)'
fi
fi
if test -f 'eval.c'
then
	echo shar: will not over-write existing file "'eval.c'"
else
echo extracting "'eval.c'"
sed 's/^X//' >eval.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include <math.h>
X#include "graph.h"
X#include "y.tab.h"
X
X
Xextern double var_lookup ();
Xextern double call_var_fun ();
X
X
X/* evaluate an expression tree */
X
Xdouble
Xeval ( table , row , expr )
Xregister table_st *table;
Xregister int row;
Xregister attr_st *expr;
X{
X    register table_st *tp;
X    register int i;
X    int intval;
X    double value;
X    double val1 , val2;
X
X
X    if ( expr == NULL )
X	return ( 0.0 );
X
X    switch ( expr->node_type ) {
X
X    case PLUS :
X	value = eval ( table , row , expr->left )
X	+ eval ( table , row , expr->right );
X	break;
X
X    case MINUS :
X	value = ( eval ( table , row , expr->left )
X	- eval ( table , row , expr->right ) );
X	break;
X
X    case MULTIPLY :
X	value = ( eval ( table , row , expr->left )
X	* eval ( table , row , expr->right ) );
X	break;
X
X    case DIVIDE :
X	val1 = eval ( table , row , expr->left );
X	val2 =  eval ( table , row , expr->right );
X	if ( val2 == 0.0 ) {
X	    value = HUGE;
X	    warn ( "division by zero error" );
X	}
X	else
X	    value = val1 / val2;
X	break;
X
X    case MODULUS :
X	val1 = eval ( table , row , expr->left );
X	val2 = eval ( table , row , expr->right );
X	if ( val2 == 0.0 ) {
X	    value = 0.0;
X	    warn ( "modulus by zero error" );
X	}
X	else {
X	    while ( val1 > val2 )	/* % does not work with float's */
X		val1 -= val2;
X	    while ( val1 < 0.0 )
X		val1 += val2;
X	    value = val1;
X	}
X	break;
X
X    case LE :
X	value = ( eval ( table , row , expr->left )
X	<= eval ( table , row , expr->right ) );
X	break;
X
X    case LT :
X	value = ( eval ( table , row , expr->left )
X	< eval ( table , row , expr->right ) );
X	break;
X
X    case GE :
X	value = ( eval ( table , row , expr->left )
X	>= eval ( table , row , expr->right ) );
X	break;
X
X    case GT :
X	value = ( eval ( table , row , expr->left )
X	> eval ( table , row , expr->right ) );
X	break;
X
X    case EQ :
X	value = ( eval ( table , row , expr->left )
X	== eval ( table , row , expr->right ) );
X	break;
X
X    case NE :
X	value = ( eval ( table , row , expr->left )
X	!= eval ( table , row , expr->right ) );
X	break;
X
X    case QUEST :
X	value = ( eval ( table , row , expr->left ) != 0.0 )
X	    ? eval ( table , row , expr->right->left )
X	    : eval ( table , row , expr->right->right );
X	break;
X
X    case AND :
X	value = ( eval ( table , row , expr->left )
X	&& eval ( table , row , expr->right ) );
X	break;
X
X    case OR :
X	value = ( eval ( table , row , expr->left )
X	|| eval ( table , row , expr->right ) );
X	break;
X
X    case NOT :
X	value = ( eval ( table , row , expr->left ) == 0.0 );
X	break;
X
X    case NEG :
X	value = ( - eval ( table , row , expr->left ) );
X	break;
X
X    case FVAR_IDENT :
X	value = call_var_fun ( expr->ident , expr->parm_list , table , row );
X	break;
X
X    case VAR_IDENT :
X	value = var_lookup ( expr->ident );
X	break;
X
X    case NUMBER :
X	value = ( expr->value );
X	break;
X
X    case ATTR :
X	intval = (int) eval ( table , row , expr->left );
X	if ( intval == 0 )
X	    value = row + 1.0;
X	else {
X	    for ( i = 1 , tp = table; tp != NULL  &&  i < intval; i++ , tp = tp->next )
X		;
X	    if ( tp == NULL )
X		abort ( "Illegal attribute number selected from table" );
X	    if ( row >= tp->size )
X		abort ( "Internal error - accessing past end of array in eval()" );
X	    value = ( tp->data[row] );
X	}
X	break;
X
X    default :
X	value = 0.0;
X	abort ( "Unknown operator in eval() = %d" , expr->node_type );
X    }
X    return ( value );
X}
X
SHAR_EOF
if test 3653 -ne "`wc -c < 'eval.c'`"
then
	echo shar: error transmitting "'eval.c'" '(should have been 3653 characters)'
fi
fi
if test -f 'evaltab.c'
then
	echo shar: will not over-write existing file "'evaltab.c'"
else
echo extracting "'evaltab.c'"
sed 's/^X//' >evaltab.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X#include "y.tab.h"
X
X
Xextern table_st *append_tables ();
Xextern table_st *adjacent ();
Xextern table_st *project ();
Xextern table_st *select ();
Xextern table_st *sort ();
Xextern table_st *join ();
Xextern table_st *tab_lookup ();
Xextern table_st *group ();
Xextern table_st *cumulate ();
Xextern table_st *generate ();
Xextern table_st *call_tab_fun ();
Xextern table_st *new_table ();
Xextern table_st *copy_of_table ();
Xextern double eval ();
X
X
Xtable_st *
Xeval_tab ( expr )
Xtnode_st *expr;
X{
X    trow_st *trp;
X    tcol_st *tcp;
X    int num_rows , num_cols , tmp_cols;
X    table_st *newtab , *tp;
X    int row;
X
X
X    switch ( expr->operator ) {
X
X    case TABLE :
X	num_rows = 0;
X	num_cols = 0;
X	for ( trp = expr->const_table; trp != NULL; trp = trp->next ) {
X	    tmp_cols = 0;
X	    for ( tcp = trp->cols; tcp != NULL; tcp = tcp->next ) {
X		tmp_cols++;
X	    }
X	    if ( num_rows == 0 )
X		num_cols = tmp_cols;
X	    else
X		if ( num_cols != tmp_cols )
X		    abort ( "constant table has rows with different number of columns" );
X	    num_rows++;
X	}
X	newtab = new_table ( num_cols , num_rows );
X	row = 0;
X	for ( trp = expr->const_table; trp != NULL; trp = trp->next ) {
X	    for ( tcp = trp->cols, tp = newtab; tcp != NULL; tcp = tcp->next, tp = tp->next ) {
X		tp->data[ row ] = eval ( NULL , 0 , tcp->expr );
X	    }
X	    row++;
X	}
X	return ( newtab );
X	break;
X
X    case TAB_CONST :
X	return ( copy_of_table ( expr->table ) );
X    
X    case FTAB_IDENT :
X	return ( call_tab_fun ( expr->ident , expr->parm_list , NULL , 0 ) );
X
X    case APPEND :
X	return ( append_tables ( eval_tab ( expr->left ) ,
X	    eval_tab ( expr->right ) ) );
X    
X    case ADJACENT :
X	return ( adjacent ( eval_tab ( expr->left ) ,
X	    eval_tab ( expr->right ) ) );
X    
X    case PROJECT :
X	return ( project ( eval_tab ( expr->left ) , expr->expr_list ) );
X
X    case WHERE :
X	return ( select ( eval_tab ( expr->left ) , expr->expr ) );
X
X    case SORT :
X	return ( sort ( eval_tab ( expr->left ) , (int)eval ( NULL , 0 , expr->expr ) ) );
X
X    case JOIN :
X	return ( join ( eval_tab ( expr->left ) , eval_tab ( expr->right ) ,
X	    (int)eval ( NULL , 0 , expr->expr->left ) ,
X	    (int)eval ( NULL , 0 , expr->expr->right ) ) );
X
X    case CUMULATE :
X	return ( cumulate ( eval_tab ( expr->left ) , (int)eval ( NULL , 0 , expr->expr ) ) );
X
X    case GENERATE :
X	return ( generate ( expr->range , expr->interval ) );
X
X    case TAB_IDENT :
X	return ( tab_lookup ( expr->ident ) );
X
X    case GROUP :
X	return ( group ( eval_tab ( expr->left ) , expr->range , expr->interval , expr->ident ) );
X
X    default :
X	abort ( "unknown operator in eval_tab: %d" , expr->operator );
X
X    }
X    return ( NULL );
X}
SHAR_EOF
if test 2981 -ne "`wc -c < 'evaltab.c'`"
then
	echo shar: error transmitting "'evaltab.c'" '(should have been 2981 characters)'
fi
fi
if test -f 'freetab.c'
then
	echo shar: will not over-write existing file "'freetab.c'"
else
echo extracting "'freetab.c'"
sed 's/^X//' >freetab.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X
X#include <stdio.h>
X#include "graph.h"
X
X
Xfree_table ( table )
Xtable_st *table;
X{
X    table_st *p;
X
X    while ( table != NULL ) {
X	p = table->next;
X	release ( table->data );
X	release ( table );
X	table = p;
X    }
X}
X
SHAR_EOF
if test 509 -ne "`wc -c < 'freetab.c'`"
then
	echo shar: error transmitting "'freetab.c'" '(should have been 509 characters)'
fi
fi
if test -f 'fundeclr.c'
then
	echo shar: will not over-write existing file "'fundeclr.c'"
else
echo extracting "'fundeclr.c'"
sed 's/^X//' >fundeclr.c <<'SHAR_EOF'
X/*
X * Copyright (C) 1986   Alan Kent
X *
X * Permission is granted to freely distribute part or
X * all of this code as long as it is not for profit
X * and this message is retained in the code.
X *
X * No resposibility is taken for any damage or incorect
X * results this program generates.
X * 
X */
X
X
X#include <stdio.h>
X#include "graph.h"
X#include "y.tab.h"
X#include "math.h"
X
X
Xextern double eval ();
Xextern table_st *eval_tab ();
Xextern double min_fun ();
Xextern double max_fun ();
Xextern double sum_fun ();
Xextern double count_fun ();
Xextern double average_fun ();
X
X
X#define MIN_FUN		((attr_st *)2)
X#define MAX_FUN		((attr_st *)3)
X#define SUM_FUN		((attr_st *)4)
X#define COUNT_FUN	((attr_st *)5)
X#define AVERAGE_FUN	((attr_st *)6)
X#define LOG_FUN		((attr_st *)7)
X#define LN_FUN		((attr_st *)8)
X#define EXP_FUN		((attr_st *)9)
X#define POWER_FUN	((attr_st *)10)
X#define SQRT_FUN	((attr_st *)11)
X#define FLOOR_FUN	((attr_st *)12)
X#define CEIL_FUN	((attr_st *)13)
X#define ABS_FUN		((attr_st *)14)
X#define SIN_FUN		((attr_st *)15)
X#define COS_FUN		((attr_st *)16)
X#define ASIN_FUN	((attr_st *)17)
X#define ACOS_FUN	((attr_st *)18)
X#define ATAN_FUN	((attr_st *)19)
X#define ATAN2_FUN	((attr_st *)20)
X#define SINH_FUN	((attr_st *)21)
X#define COSH_FUN	((attr_st *)22)
X#define TANH_FUN	((attr_st *)23)
X#define HYPOT_FUN	((attr_st *)24)
X#define J0_FUN		((attr_st *)26)
X#define J1_FUN		((attr_st *)27)
X#define JN_FUN		((attr_st *)28)
X#define Y0_FUN		((attr_st *)29)
X#define Y1_FUN		((attr_st *)30)
X#define YN_FUN		((attr_st *)31)
X
X
X#define MAX_DEC		50
X
X
Xstatic struct declare_st {
X    char *name;
X    parm_st *parm_list;
X    attr_st *expr;
X    tnode_st *tab_expr;
X} dec [ MAX_DEC ];
X
Xstatic int num_dec;
X
X
X
Xpdef_fun ()
X{
X    static parm_st ptab;
X    static parm_st pexpr1 , pexpr2;
X
X    ptab.parm_type = TABLE;
X    ptab.ident = "";
X    ptab.next = NULL;
X
X    pexpr1.parm_type = VALUE;
X    pexpr1.ident = "";
X    pexpr1.next = NULL;
X
X    pexpr2.parm_type = VALUE;
X    pexpr2.ident = "";
X    pexpr2.next = &pexpr1;
X
X    fun_declare ( "min" , &ptab , MIN_FUN , NULL );
X    fun_declare ( "max" , &ptab , MAX_FUN , NULL );
X    fun_declare ( "sum" , &ptab , SUM_FUN , NULL );
X    fun_declare ( "count" , &ptab , COUNT_FUN , NULL );
X    fun_declare ( "average" , &ptab , AVERAGE_FUN , NULL );
X
X    fun_declare ( "sqrt" , &pexpr1 , SQRT_FUN , NULL );
X    fun_declare ( "log" , &pexpr1 , LOG_FUN , NULL );
X    fun_declare ( "ln" , &pexpr1 , LN_FUN , NULL );
X    fun_declare ( "exp" , &pexpr1 , EXP_FUN , NULL );
X    fun_declare ( "pow" , &pexpr2 , POWER_FUN , NULL );
X    fun_declare ( "floor" , &pexpr1 , FLOOR_FUN , NULL );
X    fun_declare ( "ceil" , &pexpr1 , CEIL_FUN , NULL );
X    fun_declare ( "abs" , &pexpr1 , ABS_FUN , NULL );
X
X    fun_declare ( "sin" , &pexpr1 , SIN_FUN , NULL );
X    fun_declare ( "cos" , &pexpr1 , COS_FUN , NULL );
X    fun_declare ( "asin" , &pexpr1 , ASIN_FUN , NULL );
X    fun_declare ( "acos" , &pexpr1 , ACOS_FUN , NULL );
X    fun_declare ( "atan" , &pexpr1 , ATAN_FUN , NULL );
X    fun_declare ( "atan2" , &pexpr2 , ATAN2_FUN , NULL );
X    fun_declare ( "sinh" , &pexpr1 , SINH_FUN , NULL );
X    fun_declare ( "cosh" , &pexpr1 , COSH_FUN , NULL );
X    fun_declare ( "tanh" , &pexpr1 , TANH_FUN , NULL );
X
X    fun_declare ( "hypot" , &pexpr2 , HYPOT_FUN , NULL );
X
X    fun_declare ( "j0" , &pexpr1 , J0_FUN , NULL );
X    fun_declare ( "j1" , &pexpr1 , J1_FUN , NULL );
X    fun_declare ( "jn" , &pexpr2 , JN_FUN , NULL );
X    fun_declare ( "y0" , &pexpr1 , Y0_FUN , NULL );
X    fun_declare ( "y1" , &pexpr1 , Y1_FUN , NULL );
X    fun_declare ( "yn" , &pexpr2 , YN_FUN , NULL );
X}
X
X
Xfun_declare ( name , parm_list , expr , tab_expr )
Xchar *name;
Xparm_st *parm_list;
Xattr_st *expr;
Xtnode_st *tab_expr;
X{
X    int i;
X
X    for ( i = 0; i < num_dec; i++ ) {
X	if ( strcmp ( name , dec[i].name ) == 0 ) {
X	    abort ( "cannot redeclare functions: '%s'" , name );
X	    return;
X	}
X    }
X    if ( num_dec >= MAX_DEC )
X	abort ( "Internal array overflow - too many functions declared" );
X    dec[num_dec].name = name;
X    dec[num_dec].parm_list = parm_list;
X    dec[num_dec].expr = expr;
X    dec[num_dec].tab_expr = tab_expr;
X    num_dec++;
X}
X
X
Xdouble
Xcall_var_fun ( name , parm_list , table , row )
Xchar *name;
Xparm_st *parm_list;
Xtable_st *table;
Xint row;
X{
X    int i;
X    double value;
X    parm_st *p1 , *p2;
X#define MAX_TAB 3
X#define MAX_VAR 3
X    table_st *tab_parm[ MAX_TAB ];
X    double var_parm[ MAX_VAR ];
X    int tab_num , var_num;
X
X    for ( i = 0; i < num_dec; i++ ) {
X	if ( strcmp ( dec[i].name , name ) == 0 ) {
X
X	    if ( dec[i].expr == NULL )
X		abort ( "value function with no expression found" );
X
X	    p1 = parm_list;
X	    p2 = dec[i].parm_list;
X	    var_num = 0;
X	    tab_num = 0;
X	    while ( p1 != NULL  &&  p2 != NULL ) {
X
X		if ( p1->parm_type != p2->parm_type )
X		    abort ( "type mismatch for parameter to function '%s'" , name );
X
X		if ( p1->parm_type == TABLE ) {
X		    tab_parm[ tab_num ] = eval_tab ( p1->tab_expr );
X		    tab_declare ( p2->ident , tab_parm[ tab_num ] );
X		    if ( tab_num + 1 < MAX_TAB )
X			tab_num++;
X		}
X		else {
X		    var_parm[ var_num ] = eval ( table , row , p1->expr );
X		    var_declare ( p2->ident , var_parm[ var_num ] );
X		    if ( var_num + 1 < MAX_VAR )
X			var_num++;
X		}
X		p1 = p1->next;
X		p2 = p2->next;
X	    }
X	    if ( p1 != NULL  ||  p2 != NULL )
X		abort ( "illegal parameter list for function '%s'" , name );
X
X	    switch ( dec[i].expr ) {
X
X	    case MIN_FUN :
X		value = min_fun ( tab_parm[0] , 0 , tab_parm[0]->size );
X		break;
X
X	    case MAX_FUN :
X		value = max_fun ( tab_parm[0] , 0 , tab_parm[0]->size );
X		break;
X
X	    case SUM_FUN :
X		value = sum_fun ( tab_parm[0] , 0 , tab_parm[0]->size );
X		break;
X
X	    case COUNT_FUN :
X		value = count_fun ( tab_parm[0] , 0 , tab_parm[0]->size );
X		break;
X
X	    case AVERAGE_FUN :
X		value = average_fun ( tab_parm[0] , 0 , tab_parm[0]->size );
X		break;
X
X	    case LOG_FUN :
X		value = log10 ( var_parm[0] );
X		break;
X	    
X	    case LN_FUN :
X		value = log ( var_parm[0] );
X		break;
X
X	    case EXP_FUN :
X		value = exp ( var_parm[0] );
X		break;
X	    
X	    case POWER_FUN :
X		value = pow ( var_parm[0] , var_parm[1] );
X		break;
X	    
X	    case SQRT_FUN :
X		value = sqrt ( var_parm[0] );
X		break;
X	    
X	    case FLOOR_FUN :
X		value = floor ( var_parm[0] );
X		break;
X	    
X	    case CEIL_FUN :
X		value = ceil ( var_parm[0] );
X		break;
X	    
X	    case ABS_FUN :
X		value = fabs ( var_parm[0] );
X		break;
X	    
X	    case SIN_FUN :
X		value = sin ( var_parm[0] );
X		break;
X
X	    case COS_FUN :
X		value = cos ( var_parm[0] );
X		break;
X
X	    case ASIN_FUN :
X		value = asin ( var_parm[0] );
X		break;
X
X	    case ACOS_FUN :
X		value = acos ( var_parm[0] );
X		break;
X
X	    case ATAN_FUN :
X		value = atan ( var_parm[0] );
X		break;
X
X	    case ATAN2_FUN :
X		value = atan2 ( var_parm[0] , var_parm[1] );
X		break;
X
X	    case SINH_FUN :
X		value = sinh ( var_parm[0] );
X		break;
X
X	    case COSH_FUN :
X		value = cosh ( var_parm[0] );
X		break;
X
X	    case TANH_FUN :
X		value = tanh ( var_parm[0] );
X		break;
X
X	    case HYPOT_FUN :
X		value = hypot ( var_parm[0] , var_parm[1] );
X		break;
X
X	    case J0_FUN :
X		value = sin ( var_parm[0] );
X		break;
X
X	    case J1_FUN :
X		value = j1 ( var_parm[0] );
X		break;
X
X	    case JN_FUN :
X		value = jn ( var_parm[0] );
X		break;
X
X	    case Y0_FUN :
X		value = y0 ( var_parm[0] );
X		break;
X
X	    case Y1_FUN :
X		value = y1 ( var_parm[0] );
X		break;
X
X	    case YN_FUN :
X		value = yn ( var_parm[0] );
X		break;
X
X	    default :
X		value = eval ( table , row , dec[i].expr );
X		break;
X	    }
X	    return ( value );
X	}
X    }
X    abort ( "Undefined function '%s' referenced" , name );
X}
X
X
Xtable_st *
Xcall_tab_fun ( name , parm_list , table , row )
Xchar *name;
Xparm_st *parm_list;
Xtable_st *table;
Xint row;
X{
X    int i;
X    table_st *newtab;
X    parm_st *p1 , *p2;
X
X    for ( i = 0; i < num_dec; i++ ) {
X	if ( strcmp ( dec[i].name , name ) == 0 ) {
X	    if ( dec[i].tab_expr == NULL )
X		abort ( "table function with no expression found" );
X	    p1 = parm_list;
X	    p2 = dec[i].parm_list;
X	    while ( p1 != NULL  &&  p2 != NULL ) {
X		if ( p1->parm_type != p2->parm_type )
X		    abort ( "type mismatch for parameter to function '%s'" , name );
X		if ( p1->parm_type == TABLE )
X		    tab_declare ( p2->ident , eval_tab ( p1->tab_expr ) );
X		else
X		    var_declare ( p2->ident , eval ( table , row , p1->expr ) );
X		p1 = p1->next;
X		p2 = p2->next;
X	    }
X	    if ( p1 != NULL  ||  p2 != NULL )
X		abort ( "illegal parameter list for function '%s'" , name );
X	    newtab = eval_tab ( dec[i].tab_expr );
X	    return ( newtab );
X	}
X    }
X    abort ( "Undefined function '%s' referenced" , name );
X}
X
X
X
Xis_fvar_ident ( name )
Xchar *name;
X{
X    int i;
X
X    for ( i = 0; i < num_dec; i++ ) {
X	if ( strcmp ( dec[i].name , name ) == 0 ) {
X	    return ( dec[i].expr != NULL );
X	}
X    }
X    return ( 0 );
X}
X
X
X
Xis_ftab_ident ( name )
Xchar *name;
X{
X    int i;
X
X    for ( i = 0; i < num_dec; i++ ) {
X	if ( strcmp ( dec[i].name , name ) == 0 ) {
X	    return ( dec[i].expr == NULL );
X	}
X    }
X    return ( 0 );
X}
X
X
X
Xcheck_group_fun ( name )
Xchar *name;
X{
X    int i;
X
X    for ( i = 0; i < num_dec; i++ ) {
X	if ( strcmp ( dec[i].name , name ) == 0 ) {
X	    if ( dec[i].expr == NULL )
X		abort ( "function '%s' must return a value" , name );
X	    if ( dec[i].parm_list == NULL
X	    ||   dec[i].parm_list->next != NULL
X	    ||   dec[i].parm_list->parm_type != TABLE )
X		abort ( "function '%s' expects a single table as parameters" );
X	    return;
X	}
X    }
X}
SHAR_EOF
if test 9436 -ne "`wc -c < 'fundeclr.c'`"
then
	echo shar: error transmitting "'fundeclr.c'" '(should have been 9436 characters)'
fi
fi
# end of shell archive
exit 0