/*
 * parse.c -- This is a hand-coded HPGL parser, to see if I can get
 *            somewhat better performance than the YACC parser gives
 *            me.
 *
 * Author  : Dieter Muller
 * Ported to X11 & Line Type fixes: Andy Gerber            
 * Created : Wed Oct 31 21:13:38 1990
 */

/* Header Files */

#include <stdio.h>
#include <math.h>
#include <varargs.h>
#include <sys/param.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <math.h>
# include "y.tab.h"		/* as generated by the original grammar */

/*   X windows variables. */
extern XColor pen[];
extern Window main_window;
extern Font text_font;
extern XFontStruct *font_info;
extern short font_offset;
extern GC gc;
extern Display *dpy;
extern int span_count;
extern initialize_plotter();
extern int dont_scale;      /* should we scale coordinates? Not done if zooming */
extern int minWidth,minHeight;  /* size of X window for plotting */
/* Macro Definitions */

/* Variable Definitions */

YYSTYPE yylval;
double xScale = 1.0;
double yScale = 1.0;
static int pushed_token = -1;

/* Function Declarations */

void unget_token ();
int next_token ();
void eat_to_semi ();
void ignored ();
void error ();
int yywrap ();
void draw_line ();
void draw_rectangle();
void draw_text ();
void scale_coord ();
int get_coord_pair ();
void coord_list ();
void yyparse ();
void line_type_change();

/* pattern definitions for drawing dashed lines */
/* default |________|  */
/* line 0  |        |  */
/* line 1  |_       |  */
/* line 2  |____    |  */
/* line 3  |______  |  */
/* line 4  |______ _|  */
/* line 5  |_____ __|  */
/* line 6  |____ _ _|  */

static unsigned char line_types[][6] = {
  {1, 15},
  {1, 15},
  {8, 8},
  {12, 4},
  {13, 1, 1, 1},
  {12, 1, 2, 1},
  {9, 1, 2, 1, 2, 1}
};

static int line_type_elements[] = {2, 2, 2, 2, 4, 4, 6};

extern char      yytext [];


/**********************************************************************
 *     yyparse () --- Read HPGL statements, drawing the results as we
 *                    go.
 */

void yyparse ()
{
    extern FILE    * yyin;
    extern YYSTYPE   yylval;
    extern int       absolute;
    extern int       pen_down;
    extern int       is_color;
    extern int       cur_pen;
    extern int       rectangle;

    int token;
    
    while ((token = next_token ()) > 0)
      {
/* 	getc(stdin);
	printf("%s", yytext);
*/
	switch (token)
	{
	  case LT:
	    line_type_change();
	    break;
	  case LB:
	    draw_text (yylval.sb);
	    break;
	  case PA:
	    absolute = 1;
	    coord_list ();
	    break;
	  case PR:
	    absolute = 0;
	    coord_list ();
	    break;
	  case PU:
	    pen_down = 0;
	    coord_list ();
	    break;
	  case PD:
	    pen_down = 1;
	    coord_list ();
	    break;
	  case EA:
	    /* draw a rectangle */
	    rectangle = 1;
	    coord_list();
	    rectangle = 0;
	    break;
	  case RA:
	    /* This is a filled rectangle, not supported */
	    rectangle = 1;
	    coord_list();
	    rectangle = 0;
	    break;
	  case SP:
	    if (is_color)
	      {
		cur_pen = pen[yylval.i].pixel; 
		XSetForeground(dpy, gc, cur_pen); 
	      }
	    break;
	  case SC:
		scale_coord ();
	    break;
	  case RESET:
	    initialize_plotter ();
	    break;
	  case SEMICOLON:
	    break;
	  case EAT_TO_SEMI:
	    eat_to_semi();
	    break;
	  default:
	    error ("Unexpected token %d, symbol `%s'\n", token, yytext);
	    /*NOTREACHED*/
	}
    }

    return;
}    /* end of yyparse () */

/**********************************************************************
 *     line_type_change() --- change line type
 *
 *
 * Handle getting a LT changes, sees if line type was specified
 */

void line_type_change()
{
  int token;
  int new_lt;
  int result;
  XGCValues gc_vals;
  new_lt = -1;
  result = 0;
  token = next_token ();
  if (token == DIGIT)
    {
      new_lt = yylval.i;
      token = next_token (token);
      
      if (token != SEMICOLON)
	{
	  error ("Unexpected token %d, symbol `%s'\n", token, yytext);
	  eat_to_semi();
	}	
      result = 1;
    }
  else if (token == SEMICOLON)
    {
      new_lt = -99;
      result = 1;
    }

  if (new_lt == -99)
    {
      gc_vals.line_style = LineSolid;
      XChangeGC(dpy, gc, GCLineStyle, &gc_vals);
    }
  else if (new_lt >= 0 && new_lt <= 6)
    {
      gc_vals.line_style = LineOnOffDash;
      XChangeGC(dpy, gc, GCLineStyle, &gc_vals);
      XSetDashes(dpy, gc, 0, line_types[new_lt], line_type_elements[new_lt]);
    }

  if (!result)
    {
	unget_token (token);
	eat_to_semi ();
    }
      
}

/**********************************************************************
 *     coord_list () --- Handle parsing an optional list of
 *                       coordinates.
 *
 * A list of coordinates looks like INT, INT [, INT, INT].  Basically,
 * coordinate pairs separated by commas.
 */

void coord_list ()
{
    int token;

    while (get_coord_pair ())
    {
	token = next_token ();
	if (token != COMMA)
	{
	    unget_token (token);
	    break;
	}
    }

    return;
}    /* end of coord_list () */

/**********************************************************************
 *     get_coord_pair () --- Reads a pair of coordinates of the form
 *                           INTEGER COMMA INTEGER
 * 
 * Returns:
 * 
 *     SUCCESS           --- True, if we got a valid pair
 *     FAILURE           --- False, if something's wrong.  We also eat
 *                           through to the next semi-colon, if any.
 */

int get_coord_pair ()
{
    extern int last_x;
    extern int last_y;
    extern int x;
    extern int y;
    extern YYSTYPE yylval;
    extern int absolute;
    extern int pen_down;
    extern double xMin;
    extern double xMax;
    extern double yMin;
    extern double yMax;
    extern double xScale;
    extern double yScale;
    extern int minWidth;
    extern int minHeight;
    extern int rectangle;

    int token;
    int result;
    double scratch_x;
    double scratch_y;
    
    result = 0;
    token = next_token ();
    if (token == DIGIT || token == NUMBER)
    {
	if (token == DIGIT) scratch_x = yylval.i;
	else scratch_x = yylval.f;

	token = next_token (token);
	if (token == COMMA)
	{
	    token = next_token ();
	    if (token == DIGIT || token == NUMBER)
	    {
	      if (token == DIGIT) scratch_y = yylval.i;
	      else scratch_y = yylval.f;

	      last_x = x;
	      last_y = y;

		if (absolute)
		{
		  x = (scratch_x - xMin) * xScale;
		  y = (scratch_y - yMin) * yScale;
		}
		else
		{
		  x = x + (scratch_x - xMin) * xScale;
		  y = minHeight - y  + (scratch_y - yMin) * yScale;
		}

 		y = minHeight - y; 

		if (pen_down && !rectangle)
		  draw_line ();
		else if (rectangle)
		  draw_rectangle();
		
		result = 1;
	    }
	}
    }

    if (!result)
    {
	unget_token (token);
	eat_to_semi ();
    }

    return (result);
}    /* end of get_coord_pair () */

/**********************************************************************
 *     scale_coord () --- set up new user coordinate boundaries
 *
 * We need two pairs of comma-separated coordinates, a total of four
 * INTEGERs and three COMMAs.
 */

void scale_coord ()
{
    extern double xMin;
    extern double xMax;
    extern double yMin;
    extern double yMax;
    extern double xScale;
    extern double yScale;
    extern int minWidth;
    extern int minHeight;

    double scratch [4];
    int count;
    int token;
    int done;
    int result;

    result = 0;
    done = 0;
    count = 0;
    while (!done)
    {
	token = next_token ();
	if (token == DIGIT || token == NUMBER)
	{
	    if (token == DIGIT) scratch [count++] = yylval.i;
	    else scratch [count++] = yylval.f;

	    if (count < 4)
	    {
		token = next_token ();
		if (token != COMMA) /* die with !result */
		    done = 1;
	    }
	    else
	    {
	     if (!dont_scale)
	       {
		 xMin = scratch [0];
		 xMax = scratch [1];
		 yMin = scratch [2];
		 yMax = scratch [3];

		 xScale = minWidth / (xMax - xMin);
		 yScale = minHeight / (yMax - yMin);

		 /* Make sure x is in proportion to y */

 		 if (yScale < xScale)
		   {
		     xMax = xMin + minWidth/yScale;
		     xScale = minWidth / (xMax - xMin);
		     yScale = minHeight / (yMax - yMin);
		   }
		 else
		   {
		     yMax = yMin + minHeight/xScale;
		     xScale = minWidth / (xMax - xMin);
		     yScale = minHeight / (yMax - yMin);
		   }
	       }

	      result = 1;
	      done = 1;
	    }
	}
    }

    if (!result)
    {
	unget_token (token);
	eat_to_semi ();
    }

    return;
}    /* end of scale_coord () */

/**********************************************************************
 *     draw_text () --- Display some text at the current point
 * 
 * Arguments:
 * 
 *     text         -i- What to draw.
 */

void draw_text (txt)
char * txt;
{
    extern int x;
    extern int y;
    XTextItem text;

    text.nchars = strlen(txt);
    text.delta = 0;
    text.font = text_font;
    text.chars = txt;

    XDrawText(dpy, main_window, gc, x,y-font_offset, &text, 1);
    x += XTextWidth(font_info, txt, text.nchars);
    return;
}    /* end of draw_text () */

/**********************************************************************
 *     draw_line () --- Draw a vector between the last coordinate pair
 *                      and the current one
 */

void draw_line ()
{
    extern int last_x;
    extern int last_y;
    extern int x;
    extern int y;

/*     printf("Draw line %d %d %d %d\n", last_x, last_y, x, y); */
    XDrawLine(dpy, main_window, gc, last_x, last_y, x, y);
    ++span_count;
    return;
}    /* end of draw_line () */

/**********************************************************************
 *     draw_line () --- Draw a rectangle between the last coordinate pair
 *                      and the current one
 */

void draw_rectangle()
{
    extern int last_x;
    extern int last_y;
    extern int x;
    extern int y;

    XDrawLine(dpy, main_window, gc, last_x, last_y, last_x, y);
    XDrawLine(dpy, main_window, gc, last_x, last_y, x, last_y);
    XDrawLine(dpy, main_window, gc, last_x, y, x, y);
    XDrawLine(dpy, main_window, gc, x, last_y, x, y);

    span_count = span_count + 4;
    return;
}    /* end of draw_line () */

/**********************************************************************
 *     yywrap () --- Pacify yylex()
 * 
 * Returns:
 * 
 *     SUCCESS   --- Always true, to indicate EOF
 */

int yywrap ()
{
    return (1);
}    /* end of yywrap () */

/**********************************************************************
 *     error () --- Handles reporting syntax errors in a reasonably
 *                  nice way.
 * Arguments:
 *
 *     va_alist  --- A printf-like set of arguments.
 */

void error (va_alist)
va_dcl
{
    va_list args;
    char  * fmt;
    char    err_buf [MAXPATHLEN * 2];
    
    va_start (args);

    fmt = va_arg (args, char *);
    strcpy (err_buf, "\nSyntax error: ");
    (void) vsprintf (err_buf + strlen (err_buf), fmt, args);

    va_end (args);

    fflush (stdout);
    fputs (err_buf, stderr);

    return;
}    /* end of error () */

/**********************************************************************
 *     ignored () --- Handles reporting that we're ignoring something
 *                    nicely.
 *
 * Arguments:
 *
 *     va_alist  --- A printf-like set of arguments.
 */

void ignored (va_alist)
va_dcl
{
    va_list args;
    char  * fmt;
    char    err_buf [MAXPATHLEN * 2];
    
    va_start (args);

    fmt = va_arg (args, char *);
    strcpy (err_buf, "\nUnsupported command ignored: ");
    (void) vsprintf (err_buf + strlen (err_buf), fmt, args);
    va_end (args);

    fflush (stdout);
    fputs (err_buf, stderr);

    return;
}    /* end of ignored () */

/**********************************************************************
 *     eat_to_semi () --- Throw away input until we hit EOF or a
 *                        semi-colon
 */

void eat_to_semi ()
{
    int token;

    while (1)
    {
	token = next_token ();
	if (   token < 0
	    || token == SEMICOLON)
	{
	    break;
	}
    }

    return;
}    /* end of eat_to_semi () */

/**********************************************************************
 *     next_token () --- Returns the next token
 */

int next_token ()
{
    extern pushed_token;

    int token;
    
    if (pushed_token != -1)
    {
	token = pushed_token;
	pushed_token = -1;
    }
    else
    {
	token = yylex ();
    }

    return (token);
}    /* end of next_token () */

/**********************************************************************
 *     unget_token () --- Push a token back for later examination
 * 
 * Arguments:
 * 
 *     token          -i- The token to push back.
 */

void unget_token (token)
int token;
{
    extern int pushed_token;

    if (pushed_token != -1)
    {
	error ("Two pushes in a row");
	exit (1);
    }

    pushed_token = token;

    return;
}    /* end of unget_token () */








