/* $Id$ */

/* Command grammar */
{
#include	<stdio.h>
#include	<alloc.h>
#include	<signal.h>

#include	"ops.h"
#include	"class.h"
#include	"position.h"
#include	"file.h"
#include	"idf.h"
#include	"symbol.h"
#include	"tree.h"
#include	"langdep.h"
#include	"token.h"
#include	"expr.h"
#include	"misc.h"

extern char	*Salloc();
extern char	*strchr();
extern char	*strcpy();
extern void	signal_child();
extern FILE	*db_in;
extern int	disable_intr;
extern p_tree	run_command, print_command;

struct token	tok, aside;
int		errorgiven = 0;
int		child_interrupted = 0;
int		interrupted = 0;
int		eof_seen = 0;

static int	shellescape();

static int	extended_charset = 0;
static int	in_expression = 0;

#define binprio(op)	((*(currlang->binop_prio))(op))
#define unprio(op)	((*(currlang->unop_prio))(op))
}
%start Commands, commands;

%lexical LLlex;

commands
  { p_tree com, lastcom = 0;
    int give_prompt;
  }
:
			{ errorgiven = 0; }
  [ %persistent command_line(&com)
    [	'\n'		{ give_prompt = 1; }
    |	%default ';'	{ give_prompt = 0; }
    ]
			{ if (com) {
				if (lastcom) {
					freenode(lastcom);
					lastcom = 0;
				}
				if (errorgiven) {
					if (com != run_command) freenode(com);
					com = 0;
				}
				else {
					enterlog(com);
					eval(com);
			  		if (repeatable(com)) {
						lastcom = com;
					}
					else if (! in_status(com) &&
					        com != run_command &&
						com != print_command) {
						freenode(com);
						com = 0;
					}
				}
			  } else if (lastcom && ! errorgiven) {
				enterlog(lastcom);
				eval(lastcom);
			  }
			  if (give_prompt) {
			  	errorgiven = 0;
				interrupted = 0;
				prompt();
			  }
			}
  ]*
;

command_line(p_tree *p;)
:
			{ *p = 0; }
[
  list_command(p)
| file_command(p)
| run_command(p)
| stop_command(p)
| when_command(p)
| continue_command(p)
| step_command(p)
| next_command(p)
| regs_command(p)
| where_command(p)
| STATUS		{ *p = mknode(OP_STATUS); }
| DUMP			{ *p = mknode(OP_DUMP); }
| RESTORE opt_num(p)	{ *p = mknode(OP_RESTORE, *p); }
| delete_command(p)
| print_command(p)
| display_command(p)
| trace_command(p)
| set_command(p)
| help_command(p)
| FIND qualified_name(p){ *p = mknode(OP_FIND, *p); }
| WHICH qualified_name(p){ *p = mknode(OP_WHICH, *p); }
| able_command(p)
| '!'			{ (void) shellescape();
			  *p = mknode(OP_SHELL);
			}
| source_command(p)
| log_command(p)
| frame_command(p)
|
]
;

frame_command(p_tree	*p;)
:
  FRAME 
  [			{ *p = mknode(OP_FRAME, (p_tree) 0); }
  | count(p)		{ *p = mknode(OP_FRAME, *p); }
  | '-' count(p)	{ *p = mknode(OP_DOWN, *p); }
  | '+' count(p)	{ *p = mknode(OP_UP, *p); }
  ]
;

source_command(p_tree *p;)
:
  SOURCE		{ extended_charset = 1; }
  name(p)		{ (*p)->t_idf = str2idf((*p)->t_str, 0); }
  			{ *p = mknode(OP_SOURCE, *p);
			  extended_charset = 0;
			}
;

log_command(p_tree *p;)
:
  LOG			{ extended_charset = 1; }
  [ name(p)		{ (*p)->t_idf = str2idf((*p)->t_str, 0); }
  |			{ *p = 0; }
  ]
  			{ *p = mknode(OP_LOG, *p);
			  extended_charset = 0;
			}
;

where_command(p_tree *p;)
:
  WHERE opt_num(p)	{ *p = mknode(OP_WHERE, *p); }
;

list_command(p_tree *p;)
  { p_tree t1 = 0, t2 = 0; }
:
  LIST
  [
  | position(&t1)
  | qualified_name(&t1)
  ]
  [ ',' count(&t2)
  | '-' 
    [	count(&t2)	{ t2->t_ival = - t2->t_ival; }
    |			{ t2 = mknode(OP_INTEGER, -100000000L); }
    ]
  |
  ]
			{ *p = mknode(OP_LIST, t1, t2); }
;

file_command(p_tree *p;)
:
  XFILE			{ extended_charset = 1; }
  [			{ *p = 0; }
  | name(p)		{ (*p)->t_idf = str2idf((*p)->t_str, 0); }
  ]			{ *p = mknode(OP_FILE, *p);
			  extended_charset = 0;
			}
;

help_command(p_tree *p;)
:
  [ HELP | '?' ]
  			{ *p = mknode(OP_HELP, (p_tree) 0); }
  [ name(&(*p)->t_args[0])?
  | '?'			{ (*p)->t_args[0] = mknode(OP_NAME, str2idf("help",0), (char *) 0); }
  | '!'			{ (*p)->t_args[0] = mknode(OP_NAME, (struct idf *) 0, "!"); }
  ]
;

run_command(p_tree *p;)
:
  RUN			{ extended_charset = 1; }
  args(p)		{ *p = mknode(OP_RUN, *p);
			  extended_charset = 0;
			}
| RERUN			{ if (! run_command) {
				error("no run command given yet");
			  }
			  else *p = run_command;
			}
  [ '?'			{ *p = mknode(OP_PRCOMM, *p); }
  |
  ]
;

stop_command(p_tree *p;)
  { p_tree whr = 0, cond = 0; }
:
  STOP
  where(&whr)?
  condition(&cond)?	{ if (! whr && ! cond) {
				error("no position or condition");
				*p = 0;
			  }
			  else *p = mknode(OP_STOP, whr, cond);
			}
;

trace_command(p_tree *p;)
  { p_tree whr = 0, cond = 0, exp = 0; }
:
  TRACE
  [ ON format_expression(&exp) ]?
  where(&whr)?
  condition(&cond)?	{ *p = mknode(OP_TRACE, whr, cond, exp); }
;

continue_command(p_tree *p;)
  { long l; p_tree pos = 0; }
:
  CONT
  [ INTEGER		{ l = tok.ival; }
  |			{ l = 1; }
  ]
  [ AT position(&pos) ]?
  			{ *p = mknode(OP_CONT, mknode(OP_INTEGER, l), pos); }
;

when_command(p_tree *p;)
  { p_tree	whr = 0, cond = 0; }
:
  WHEN
  where(&whr)?
  condition(&cond)?	{ *p = mknode(OP_WHEN, whr, cond, (p_tree) 0); 
			  p = &(*p)->t_args[2];
			}
  '{' 
  command_line(p)
  [ ';'			{ if (*p) {
				*p = mknode(OP_LINK, *p, (p_tree) 0);
			  	p = &((*p)->t_args[1]);
			  }
			}
    command_line(p)
  ]*
  '}'
			{ if (! whr && ! cond) {
				error("no position or condition");
			  }
			  else if (! *p) {
				error("no commands given");
			  }
			}
;

step_command(p_tree *p;)
:
  STEP			{ *p = mknode(OP_STEP, (p_tree) 0); }
  count(&(*p)->t_args[0])?
;

next_command(p_tree *p;)
:
  NEXT			{ *p = mknode(OP_NEXT, (p_tree) 0); }
  count(&(*p)->t_args[0])?
;

regs_command(p_tree *p;)
:
  REGS			{ *p = mknode(OP_REGS, (p_tree) 0); }
  count(&(*p)->t_args[0])?
;

delete_command(p_tree *p;)
:
  DELETE num_list(p)?	{ *p = mknode(OP_DELETE, *p); }
;

print_command(p_tree *p;)
:
  PRINT 
  [ format_expression_list(p)
			{ *p = mknode(OP_PRINT, *p); }
  |
			{ *p = mknode(OP_PRINT, (p_tree) 0); }
  ]
;

display_command(p_tree *p;)
:
  DISPLAY format_expression_list(p)
			{ *p = mknode(OP_DISPLAY, *p); }
;

format_expression_list(p_tree *p;)
:
  format_expression(p)
  [ ','			{ *p = mknode(OP_LINK, *p, (p_tree) 0);
			  p = &((*p)->t_args[1]);
			}
    format_expression(p)
  ]*
;

format_expression(p_tree *p;)
  { p_tree	p1; }
:
  expression(p, 0)
  [ '\\' name(&p1)	{ register char *c = p1->t_str;
			  while (*c) {
				if (! strchr("doshcax", *c)) {
					error("illegal format: %c", *c);
					break;
				}
				c++;
			  }
			  *p = mknode(OP_FORMAT, *p, p1);
			}
  |
  ]
;

set_command(p_tree *p;)
:
  SET expression(p, 0)	{ *p = mknode(OP_SET, *p, (p_tree) 0); }
  TO expression(&((*p)->t_args[1]), 0)
;

able_command(p_tree *p;)
:
  [ ENABLE 		{ *p = mknode(OP_ENABLE, (p_tree) 0); }
  | DISABLE 		{ *p = mknode(OP_DISABLE, (p_tree) 0); }
  ]
  num_list(&(*p)->t_args[0])?
;

num_list(p_tree *p;)
:
  num(p)
  [ ','			{ *p = mknode(OP_LINK, *p, (p_tree) 0); }
    num(&(*p)->t_args[1])
  ]*
;

condition(p_tree *p;)
:
  IF expression(p, 0)
;

where(p_tree *p;)
:
  IN qualified_name(p)	{ *p = mknode(OP_IN, *p, (p_tree) 0); }
  [ AT position(&((*p)->t_args[1])) ]?
|
  AT position(p)
;

expression(p_tree *p; int level;)
  { int currprio, currop; }
:			{ in_expression++; }
  factor(p)
  [ %while ((currprio = binprio(currop = (int) tok.ival)) > level)
	[ BIN_OP | PREF_OR_BIN_OP ] 
			{ *p = mknode(OP_BINOP, *p, (p_tree) 0);
			  (*p)->t_whichoper = currop;
			}
	expression(&((*p)->t_args[1]), currprio)
  |
	SEL_OP		{ *p = mknode(OP_BINOP, *p, (p_tree) 0);
			  (*p)->t_whichoper = (int) tok.ival;
			}
	name(&(*p)->t_args[1])
  |
	'['		{ *p = mknode(OP_BINOP, *p, (p_tree) 0);
			  (*p)->t_whichoper = E_ARRAY;
			}
	expression(&(*p)->t_args[1], 0)
	[	','	{ *p = mknode(OP_BINOP, *p, (p_tree) 0);
			  (*p)->t_whichoper = E_ARRAY;
			}
		expression(&(*p)->t_args[1], 0)
	]*
	']'
  ]*
			{ in_expression--; }
;

factor(p_tree *p;)
:
  [
  	%default EXPRESSION	/* lexical analyzer will never return this token */
			{ *p = mknode(OP_INTEGER, 0L); }
  |
  	'(' expression(p, 0) ')'
  |
  	INTEGER		{ *p = mknode(OP_INTEGER, tok.ival); }
  |
  	REAL		{ *p = mknode(OP_REAL, tok.fval); }
  |
  	STRING		{ *p = mknode(OP_STRING, tok.str); }
  |
  	qualified_name(p)
  |
  			{ *p = mknode(OP_UNOP, (p_tree) 0);
			  (*p)->t_whichoper = (int) tok.ival;
			}
  	[ PREF_OP 
  	| PREF_OR_BIN_OP
			{ (*currlang->fix_bin_to_pref)(*p); }
  	]
  	expression(&(*p)->t_args[0], unprio((*p)->t_whichoper))
  ]
  [ %while(1)
	POST_OP		{ *p = mknode(OP_UNOP, *p);
			  (*p)->t_whichoper = (int) tok.ival;
			}
  ]*
;

position(p_tree *p;)
  { p_tree lin;
    char *str;
  }
:
  [ STRING		{ str = tok.str; }
    ':'
  |			{ if (! listfile) str = 0;
			  else str = listfile->sy_idf->id_text;
			}
  ]
  count(&lin)		{ *p = mknode(OP_AT, lin->t_ival, str);
			  freenode(lin);
			}
;

args(p_tree *p;)
  { int first_time = 1; }
:
  [			{ if (! first_time) {
				*p = mknode(OP_LINK, *p, (p_tree) 0);
				p = &((*p)->t_args[1]);
			  }
			  first_time = 0;
			}
	arg(p)
  ]*
;

arg(p_tree *p;)
:
  name(p)
|
  '>' name(p)		{ (*p)->t_oper = OP_OUTPUT; }
|
  '<' name(p)		{ (*p)->t_oper = OP_INPUT; }
;

count(p_tree *p;)
:
  INTEGER		{ *p = mknode(OP_INTEGER, tok.ival); }
;

opt_num(p_tree *p;)
:
  num(p)
|
			{ *p = 0; }
;

num(p_tree *p;)
:
  count(p)
|
  '-' count(p)		{ (*p)->t_ival = - (*p)->t_ival; }
;

qualified_name(p_tree *p;)
:
  name(p)
  [	'`'		{ *p = mknode(OP_SELECT, *p, (p_tree) 0); }
	name(&((*p)->t_args[1]))
  ]*
;

name(p_tree *p;)
:
  [ XFILE
  | LIST
  | RUN
  | RERUN
  | STOP
  | WHEN
  | AT
  | IN
  | IF
  | %default NAME
  | CONT
  | STEP
  | NEXT
  | REGS
  | WHERE
  | STATUS
  | PRINT
  | DELETE
  | DUMP
  | RESTORE
  | TRACE
  | ON
  | SET
  | TO
  | FIND
  | DISPLAY
  | WHICH
  | HELP
  | DISABLE
  | ENABLE
  | SOURCE
  | FRAME
  | LOG
  ]			{ *p = mknode(OP_NAME, tok.idf, tok.str); }
;

{
int
LLlex()
{
  register int c;

  if (ASIDE) {
	tok = aside;
	ASIDE = 0;
	return TOK;
  }
  do {
	c = getc(db_in);
	if (interrupted && c == EOF) {
		c = ' ';
		interrupted = 0;
		continue;
	}
  } while (c != EOF && class(c) == STSKIP);
  if (c == EOF) {
	eof_seen = 1;
	return c;
  }
  if (extended_charset && in_ext(c)) {
	TOK = get_name(c);
	return TOK;
  }
  switch(class(c)) {
  case STSTR:
	TOK = (*currlang->get_string)(c);
	break;
  case STIDF:
	if (in_expression) TOK = (*currlang->get_name)(c);
	else TOK = get_name(c);
	break;
  case STNUM:
	TOK = (*currlang->get_number)(c);
	break;
  case STNL:
	TOK = c;
	break;
  case STSIMP:
	if (! in_expression) {
		TOK = c;
		break;
	}
	/* Fall through */
  default:
	TOK = (*currlang->get_token)(c);
	break;
  }
  return TOK;
}

int
get_name(c)
  register int	c;
{
  char	buf[512+1];
  register char	*p = &buf[0];
  register struct idf *id;

  do {
	if (p - buf < 512) *p++ = c;
	c = getc(db_in);
  } while ((extended_charset && in_ext(c)) || in_idf(c));
  ungetc(c, db_in);
  *p++ = 0;
  if (extended_charset) {
	tok.idf = 0;
	tok.str = Salloc(buf, (unsigned) (p - buf));
	return NAME;
  }
  id = str2idf(buf, 1);
  tok.idf = id;
  tok.str = id->id_text;
  return id->id_reserved ? id->id_reserved : NAME;
}

extern char *symbol2str();

LLmessage(t)
{
  if (t > 0) {
  	if (! errorgiven) {
		error("%s missing before %s", symbol2str(t), symbol2str(TOK));
	}
	aside = tok;
  }
  else if (t == 0) {
  	if (! errorgiven) {
		error("%s unexpected", symbol2str(TOK));
	}
  }
  else if (! errorgiven) {
	error("EOF expected");
  }
  errorgiven = 1;
}

static void
catch_del()
{
  signal(SIGINT, catch_del);
  if (! disable_intr) {
  	signal_child(7);
  	child_interrupted = 1;
  }
  interrupted = 1;
}

void
init_del()
{
  signal(SIGINT, catch_del);
}

static void
ctch()
{
  /* Only for shell escapes ... */
  signal(SIGINT, ctch);
}

#define SHBUFSIZ	512

static int
shellescape()
{
  register char *p;			/* walks through command */
  static char previous[SHBUFSIZ];	/* previous command */
  char comm[SHBUFSIZ];			/* space for command */
  register int cnt;			/* prevent array bound errors */
  register int c;			/* current char */
  register int lastc = 0;		/* will contain the previous char */

  p = comm;
  cnt = SHBUFSIZ-2;
  while (c = getc(db_in), c != '\n') {
	switch(c) {
	  case '!':
		/*
		 * An unescaped ! expands to the previous
		 * command, but disappears if there is none
		 */
		if (lastc != '\\') {
			if (*previous) {
				int len = strlen(previous);
				if ((cnt -= len) <= 0) break;
				strcpy(p,previous);
				p += len;
			}
		}
		else {
			*p++ = c;
		}
		continue;
	  case '%':
		/*
		 * An unescaped % will expand to the current
		 * filename, but disappears is there is none
		 */
		if (lastc != '\\') {
			if (listfile) {
				int len = strlen(listfile->sy_idf->id_text);
				if ((cnt -= len) <= 0) break;
				strcpy(p,listfile->sy_idf->id_text);
				p += len;
			}
		}
		else {
			*p++ = c;
		}
		continue;
	  default:
		lastc = c;
		if (cnt-- <= 0) break;
		*p++ = c;
		continue;
	}
	break;
  }
  *p = '\0';
  if (c != '\n') {
	warning("shell command too long");
  	while (c != '\n') c = getc(db_in);
  }
  ungetc(c, db_in);
  strcpy(previous, comm);
  signal(SIGINT, ctch);
  cnt = system(comm);
  signal(SIGINT, catch_del);
  return cnt;
}
}
