/*************************************************************************\
* Copyright (c) 2002 The University of Chicago, as Operator of Argonne
*     National Laboratory.
* Copyright (c) 2002 The Regents of the University of California, as
*     Operator of Los Alamos National Laboratory.
* EPICS BASE Versions 3.13.7
* and higher are distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution. 
\*************************************************************************/
/*#define	DEBUG	1*/
/**************************************************************************
			GTA PROJECT   AT division
	Copyright, 1990, The Regents of the University of California.
		         Los Alamos National Laboratory

< 	parse.c,v 1.3 1995/10/19 16:30:16 wright Exp
	DESCRIPTION: Parsing support routines for state notation compiler.
	 The 'yacc' parser calls these routines to build the tables
	 and linked lists, which are then passed on to the phase 2 routines.

	ENVIRONMENT: UNIX
	HISTORY:
19nov91,ajk	Replaced lstLib calls with built-in links.
20nov91,ajk	Removed snc_init() - no longer did anything useful.
20nov91,ajk	Added option_stmt() routine.
28apr92,ajk	Implemented new event flag mode.
29opc93,ajk	Implemented assignment of pv's to array elements.
29oct93,ajk	Implemented variable class (VC_SIMPLE, VC_ARRAY, & VC_POINTER).
29oct93,ajk	Added 'v' (vxWorks include) option.
17may94,ajk	Removed old event flag (-e) option.
***************************************************************************/

/*====================== Includes, globals, & defines ====================*/
#include	<ctype.h>
#include	<stdio.h>
#include	"parse.h"	/* defines linked list structures */
#include	<math.h>

#ifndef	TRUE
#define	TRUE	1
#define	FALSE	0
#endif	/*TRUE*/

int	debug_print_opt = 0;	/* Debug level (set by source file) */

char	*prog_name;		/* ptr to program name (string) */

char	*prog_param;		/* parameter string for program stmt */

Expr	*defn_c_list;		/* definition C code list */

Expr	*ss_list;		/* Start of state set list */

Expr	*exit_code_list;	/* Start of exit code list */

Var	*var_list = NULL;	/* start of variable list */
Var	*var_tail = NULL;	/* tail of variable list */

Chan	*chan_list = NULL;	/* start of DB channel list */
Chan	*chan_tail = NULL;	/* tail of DB channel list */

Expr	*global_c_list;		/* global C code following state program */

/*+************************************************************************
*  NAME: program_name
*
*  CALLING SEQUENCE: none
*	type		argument	I/O	description
*	-----------------------------------------------------------------
*	char		*pname		I	ptr to program name string
*
*  RETURNS:
*
*  FUNCTION: Save program name for global use.
*
*-*************************************************************************/
program_name(pname, pparam)
char	*pname, *pparam;
{
	prog_name = pname;
	prog_param = pparam;
#ifdef	DEBUG
	fprintf(stderr, "program name = %s\n", prog_name);
#endif
	return;
}

/* Parsing a declaration statement */
decl_stmt(type, class, name, s_length1, s_length2, value)
int	type;		/* variable type (e.g. V_FLOAT) */
int	class;		/* variable class (e.g. VC_ARRAY) */
char	*name;		/* ptr to variable name */
char	*s_length1;	/* array lth (1st dim, arrays only) */
char	*s_length2;	/* array lth (2nd dim, [n]x[m] arrays only) */
char	*value;		/* initial value or NULL */
{
	Var		*vp;
	int		length1, length2;
	extern int	line_num;

#ifdef	DEBUG
	fprintf(stderr, 
	 "variable decl: type=%d, class=%d, name=%s, ", type, class, name);
#endif
	length1 = length2 = 1;

	if (s_length1 != NULL)
	{
		length1 = atoi(s_length1);
		if (length1 <= 0)
			length1 = 1;
	}

	if (s_length2 != NULL)
	{
		length2 = atoi(s_length2);
		if (length2 <= 0)
			length2 = 1;
	}

#ifdef	DEBUG
	fprintf(stderr, "length1=%d, length2=%d\n", length1, length2);
#endif
	/* See if variable already declared */
	vp = (Var *)findVar(name);
	if (vp != 0)
	{
		fprintf(stderr, "variable %s already declared, line %d\n",
		 name, line_num);
		return;
	}
	/* Build a struct for this variable */
	vp = allocVar();
	addVar(vp); /* add to var list */
	vp->name = name;
	vp->class = class;
	vp->type = type;
	vp->length1 = length1;
	vp->length2 = length2;
	vp->value = value; /* initial value or NULL */
	vp->chan = NULL;
	return;
}

/* Option statement */
option_stmt(option, value)
char		*option; /* "a", "r", ... */
int		value;	/* TRUE means +, FALSE means - */
{
	extern int	async_opt, conn_opt, debug_opt, line_opt, 
			reent_opt, warn_opt, vx_opt, newef_opt;

	switch(*option)
	{
	    case 'a':
		async_opt = value;
		break;
	    case 'c':
		conn_opt = value;
		break;
	    case 'd':
		debug_opt = value;
		break;
 		case 'e':
		newef_opt = value;
		break;

	    case 'l':
		line_opt = value;
		break;
	    case 'r':
		reent_opt = value;
		break;
	    case 'w':
		warn_opt = value;
		break;
	    case 'v':
		vx_opt = value;
		break;
	}
	return;
}

/* "Assign" statement: Assign a variable to a DB channel.
 * Format: assign <variable> to <string;
 * Note: Variable may be subscripted.
 */
assign_single(name, db_name)
char	*name;		/* ptr to variable name */
char	*db_name;	/* ptr to db name */
{
	Chan		*cp;
	Var		*vp;
	int		subNum;
	extern int	line_num;

#ifdef	DEBUG
	fprintf(stderr, "assign %s to \"%s\";\n", name, db_name);
#endif	/*DEBUG*/
	/* Find the variable */
	vp = (Var *)findVar(name);
	if (vp == 0)
	{
		fprintf(stderr, "assign: variable %s not declared, line %d\n",
		 name, line_num);
		return;
	}

	cp = vp->chan;
	if (cp != NULL)
	{
		fprintf(stderr, "assign: %s already assigned, line %d\n",
		 name, line_num);
		return;
	}

	/* Build structure for this channel */
	cp = (Chan *)build_db_struct(vp);

	cp->db_name = db_name;	/* DB name */

	/* The entire variable is assigned */
	cp->count = vp->length1 * vp->length2;

	return;
}

/* "Assign" statement: assign an array element to a DB channel.
 * Format: assign <variable>[<subscr>] to <string>; */
assign_subscr(name, subscript, db_name)
char	*name;		/* ptr to variable name */
char	*subscript;	/* subscript value or NULL */
char	*db_name;	/* ptr to db name */
{
	Chan		*cp;
	Var		*vp;
	int		subNum;
	extern int	line_num;

#ifdef	DEBUG
	fprintf(stderr, "assign %s[%s] to \"%s\";\n", name, subscript, db_name);
#endif	/*DEBUG*/
	/* Find the variable */
	vp = (Var *)findVar(name);
	if (vp == 0)
	{
		fprintf(stderr, "assign: variable %s not declared, line %d\n",
		 name, line_num);
		return;
	}

	if (vp->class != VC_ARRAY1 && vp->class != VC_ARRAY2)
	{
		fprintf(stderr, "assign: variable %s not an array, line %d\n",
		 name, line_num);
		return;
	}

	cp = vp->chan;
	if (cp == NULL)
	{
		/* Build structure for this channel */
		cp = (Chan *)build_db_struct(vp);
	}
	else if (cp->db_name != NULL)
	{
		fprintf(stderr, "assign: array %s already assigned, line %d\n",
		 name, line_num);
		return;
	}

	subNum = atoi(subscript);
	if (subNum < 0 || subNum >= vp->length1)
	{
		fprintf(stderr, 
		 "assign: subscript %s[%d] is out of range, line %d\n",
		 name, subNum, line_num);
		return;
	}

	if (cp->db_name_list == NULL)
		alloc_db_lists(cp, vp->length1); /* allocate lists */
	else if (cp->db_name_list[subNum] != NULL)
	{
		fprintf(stderr, "assign: %s[%d] already assigned, line %d\n",
		 name, subNum, line_num);
		return;
	}
	
	cp->db_name_list[subNum] = db_name;
	cp->count = vp->length2; /* could be a 2-dimensioned array */

	return;
}

/* Assign statement: assign an array to multiple DB channels.
 * Format: assign <variable> to { <string>, <string>, ... };
 * Assignments for double dimensioned arrays:
 * <var>[0][0] assigned to 1st db name,
 * <var>[1][0] assigned to 2nd db name, etc.
 * If db name list contains fewer names than the array dimension,
 * the remaining elements receive NULL assignments.
 */
assign_list(name, db_name_list)
char	*name;		/* ptr to variable name */
Expr	*db_name_list;	/* ptr to db name list */
{
	Chan		*cp;
	Var		*vp;
	int		elem_num;
	extern int	line_num;

#ifdef	DEBUG
	fprintf(stderr, "assign %s to {", name);
#endif	/*DEBUG*/
	/* Find the variable */
	vp = (Var *)findVar(name);
	if (vp == 0)
	{
		fprintf(stderr, "assign: variable %s not declared, line %d\n",
		 name, line_num);
		return;
	}

	if (vp->class != VC_ARRAY1 && vp->class != VC_ARRAY2)
	{
		fprintf(stderr, "assign: variable %s is not an array, line %d\n",
		 name, line_num);
		return;
	}

	cp = vp->chan;
	if (cp != NULL)
	{
		fprintf(stderr, "assign: variable %s already assigned, line %d\n",
		 name, line_num);
		return;
	}

	/* Build a db structure for this variable */
	cp = (Chan *)build_db_struct(vp);

	/* Allocate lists */
	alloc_db_lists(cp, vp->length1); /* allocate lists */

	/* fill in the array of pv names */
	for (elem_num = 0; elem_num < vp->length1; elem_num++)
	{
		if (db_name_list == NULL)
			break; /* end of list */

#ifdef	DEBUG
		fprintf(stderr, "\"%s\", ", db_name_list->value);
#endif	/*DEBUG*/
		cp->db_name_list[elem_num] = db_name_list->value; /* DB name */
		cp->count = vp->length2;
 
		db_name_list = db_name_list->next;
	}
#ifdef	DEBUG
		fprintf(stderr, "};\n");
#endif	/*DEBUG*/

	return;
}

/* Build a db structure for this variable */
build_db_struct(vp)
Var		*vp;
{
	Chan		*cp;

	cp = allocChan();
	addChan(cp);		/* add to Chan list */

	/* make connections between Var & Chan structures */
	cp->var = vp;
	vp->chan = cp;

	/* Initialize the structure */
	cp->db_name_list = 0;
	cp->mon_flag_list = 0;
	cp->ef_var_list = 0;
	cp->ef_num_list = 0;
	cp->num_elem = 0;
	cp->mon_flag = 0;
	cp->ef_var = 0;
	cp->ef_num = 0;

	return (int)cp;
}

/* Allocate lists for assigning multiple pv's to a variable */
alloc_db_lists(cp, length)
Chan		*cp;
int		length;
{
	/* allocate an array of pv names */
	cp->db_name_list = (char **)calloc(sizeof(char **), length);

	/* allocate an array for monitor flags */
	cp->mon_flag_list = (int *)calloc(sizeof(int **), length);

	/* allocate an array for event flag var ptrs */
	cp->ef_var_list = (Var **)calloc(sizeof(Var **), length);

	/* allocate an array for event flag numbers */
	cp->ef_num_list = (int *)calloc(sizeof(int **), length);

	cp->num_elem = length;

}

/* Parsing a "monitor" statement.
 * Format:
 * 	monitor <var>; - monitor a single variable or all elements in an array.
 * 	monitor <var>[<m>]; - monitor m-th element of an array.
 */
monitor_stmt(name, subscript)
char		*name;		/* variable name (should be assigned) */
char		*subscript;	/* element number or NULL */
{
	Var		*vp;
	Chan		*cp;
	int		subNum;
	extern int	line_num;

#ifdef	DEBUG
	fprintf(stderr, "monitor_stmt: name=%s[%s]\n", name, subscript);
#endif	/*DEBUG*/

	/* Find the variable */
	vp = (Var *)findVar(name);
	if (vp == 0)
	{
		fprintf(stderr, "assign: variable %s not declared, line %d\n",
		 name, line_num);
		return;
	}

	/* Find a channel assigned to this variable */
	cp = vp->chan;
	if (cp == 0)
	{
		fprintf(stderr, "monitor: variable %s not assigned, line %d\n",
		 name, line_num);
		return;
	}

	if (subscript == NULL)
	{
		if (cp->num_elem == 0)
		{	/* monitor one channel for this variable */
			cp->mon_flag = TRUE;
			return;
		}

		/* else monitor all channels in db list */
		for (subNum = 0; subNum < cp->num_elem; subNum++)
		{	/* 1 pv per element of the array */
			cp->mon_flag_list[subNum] = TRUE;
		}
		return;
	}

	/* subscript != NULL */
	subNum = atoi(subscript);
	if (subNum < 0 || subNum >= cp->num_elem)
	{
		fprintf(stderr, "monitor: subscript of %s out of range, line %d\n",
		 name, line_num);
		return;
	}

	if (cp->num_elem == 0 || cp->db_name_list[subNum] == NULL)
	{
		fprintf(stderr, "monitor: %s[%d] not assigned, line %d\n",
		 name, subNum, line_num);
		return;
	}

	cp->mon_flag_list[subNum] = TRUE;
	return;
}
	
/* Parsing "sync" statement */
sync_stmt(name, subscript, ef_name)
char		*name;
char		*subscript;
char		*ef_name;
{
	Chan		*cp;
	Var		*vp;
	extern int	line_num;
	int		subNum;

#ifdef	DEBUG
	fprintf(stderr, "sync_stmt: name=%s, subNum=%s, ef_name=%s\n",
	 name, subscript, ef_name);
#endif	/*DEBUG*/

	vp = (Var *)findVar(name);
	if (vp == 0)
	{
		fprintf(stderr, "sync: variable %s not declared, line %d\n",
		 name, line_num);
		return;
	}

	cp = vp->chan;
	if (cp == 0)
	{
		fprintf(stderr, "sync: variable %s not assigned, line %d\n",
		 name, line_num);
		return;
	}

	/* Find the event flag varible */
	vp = (Var *)findVar(ef_name);
	if (vp == 0 || vp->type != V_EVFLAG)
	{
		fprintf(stderr, "sync: e-f variable %s not declared, line %d\n",
		 ef_name, line_num);
		return;
	}

	if (subscript == NULL)
	{	/* no subscript */
		if (cp->db_name != NULL)
		{	/* 1 pv assigned to this variable */
			cp->ef_var = vp;
			return;
		}

		/* 1 pv per element in the array */
		for (subNum = 0; subNum < cp->num_elem; subNum++)
		{
			cp->ef_var_list[subNum] = vp;
		}
		return;
	}

	/* subscript != NULL */
	subNum = atoi(subscript);
	if (subNum < 0 || subNum >= cp->num_elem)
	{
		fprintf(stderr,
		 "sync: subscript %s[%d] out of range, line %d\n",
		 name, subNum, line_num);
		return;
	}
	cp->ef_var_list[subNum] = vp; /* sync to a specific element of the array */

	return;
}
	
/* Definition C code */
defn_c_stmt(c_list)
Expr		*c_list; /* ptr to C code */
{
#ifdef	DEBUG
	fprintf(stderr, "defn_c_stmt\n");
#endif
	if (defn_c_list == 0)
		defn_c_list = c_list;
	else
		link_expr(defn_c_list, c_list);	
	
	return;
}

/* Global C code (follows state program) */
global_c_stmt(c_list)
Expr		*c_list; /* ptr to C code */
{
	global_c_list = c_list;

	return;
}


/* Add a variable to the variable linked list */
addVar(vp)
Var		*vp;
{
	if (var_list == NULL)
		var_list = vp;
	else
		var_tail->next = vp;
	var_tail = vp;
	vp->next = NULL;
}
	
/* Find a variable by name;  returns a pointer to the Var struct;
	returns 0 if the variable is not found. */
Var *findVar(name)
char		*name;
{
	Var		*vp;

	for (vp = var_list; vp != NULL; vp = vp->next)
	{
		if (strcmp(vp->name, name) == 0)
		{
			return vp;
		}
	}
	return 0;
}

/* Add a channel to the channel linked list */
addChan(cp)
Chan		*cp;
{
	if (chan_list == NULL)
		chan_list = cp;
	else
		chan_tail->next = cp;
	chan_tail = cp;
	cp->next = NULL;
}

/* Set debug print opt */
set_debug_print(opt)
char	*opt;
{
	debug_print_opt = atoi(opt);
}

/* Parsing "program" statement */
program(prog_list)
Expr		*prog_list;
{
	ss_list = prog_list;
#ifdef	DEBUG
	fprintf(stderr, "----Phase2---\n");
#endif	/*DEBUG*/
	phase2(ss_list);

	exit(0);
}

/* Exit code */
exit_code(ep)
Expr		*ep;
{
	exit_code_list = ep;
	return 0;
}

/* Build an expression list (hierarchical):
	Builds a node on a binary tree for each expression primitive.
 */
Expr *expression(type, value, left, right)
int		type;		/* E_BINOP, E_ASGNOP, etc */
char		*value;		/* "==", "+=", var name, contstant, etc. */	
Expr		*left;		/* LH side */
Expr		*right;		/* RH side */
{
	Expr		*ep;
	extern int	line_num, c_line_num;
	extern char	*src_file;
#ifdef	DEBUG
	extern char	*stype[];
#endif
	/* Allocate a structure for this item or expression */
	ep = allocExpr();
#ifdef	DEBUG
	fprintf(stderr,
	 "expression: ep=%d, type=%s, value=\"%s\", left=%d, right=%d\n",
	 ep, stype[type], value, left, right);
#endif
	/* Fill in the structure */
	ep->next = (Expr *)0;
	ep->last = ep;
	ep->type = type;
	ep->value = value;
	ep->left = left;
	ep->right = right;
	if (type == E_TEXT)
		ep->line_num = c_line_num;
	else
		ep->line_num = line_num;
	ep->src_file = src_file;

	return ep;
}

/* Link two expression structures and/or lists.  Returns ptr to combined list.
   Note:  ->last ptrs are correct only for 1-st and last structures in the list */
Expr *link_expr(ep1, ep2)
Expr		*ep1;	/* beginning of 1-st structure or list */
Expr		*ep2;	/* beginning 2-nd (append it to 1-st) */
{
	Expr		*ep;

	if (ep1 == 0)
		return ep2; /* shouldn't happen */
	else if (ep2 == 0)
		return ep1;

	(ep1->last)->next = ep2;
	ep1->last = ep2->last;
#ifdef	DEBUG
	fprintf(stderr, "link_expr(");
	for (ep = ep1; ; ep = ep->next)
	{
		fprintf(stderr, "%d, ", ep);
		if (ep == ep1->last)
			break;
	}
	fprintf(stderr, ")\n");	
#endif	/*DEBUG*/
	return ep1;
}

/* Interpret pre-processor code */
pp_code(line, fname)
char		*line;
char		*fname;
{
	extern int		line_num;
	extern char		*src_file;

	line_num = atoi(line);
	src_file = fname;
}

/* The ordering of this list must correspond with the ordering in parse.h */
char	*stype[] = {
	"E_EMPTY", "E_CONST", "E_VAR", "E_FUNC", "E_STRING", "E_UNOP", "E_BINOP",
	"E_ASGNOP", "E_PAREN", "E_SUBSCR", "E_TEXT", "E_STMT", "E_CMPND",
	"E_IF", "E_ELSE", "E_WHILE", "E_SS", "E_STATE", "E_WHEN",
	"E_FOR", "E_X", "E_PRE", "E_POST", "E_BREAK", "E_COMMA",
	"E_?", "E_?", "E_?", "E_?", "E_?", "E_?", "E_?", "E_?", "E_?" };
