/*
*	icalc - complex-expression parser
*
*	Yacc parser definition for icalc, a complex-number expression
*	parser, featuring arbitrarily named variables, user-defined
*	functions, startup files and special commands to ease certain tasks.
*
*	(C) Martin W Scott, 1991.
*/
%{
#include <stdio.h>
#include <ctype.h>
#include "complex.h"
#include "constant.h"
#include "memory.h"

RemKey *remkey;

Symbol	*ans;		/* set by init.c */
Symbol	*multi;		/* ditto */
Complex	treeval;	/* what tree evaluates to */
UserFunc *userfunc;	/* symbol for user-function definition */
int	noprompt;	/* if separator a ';', prompt disabled */
int	inparamlist;	/* are we at a functions parameter-list? */
int	indefn;		/* are we in function definition? */
int	silent;		/* should we display results? */
char	*infile;	/* file currently being read */
%}

%union {
	double	rval;
	Symbol	*sym;
	Node	*node;
	SymList *slist;
	ArgList *alist;
}
%start	list

%token	<rval>	NUMBER
%token	<sym>	VAR CONST C_BLTIN FUNCDEF UFUNC SFUNC COMMAND UNDEF
%token	<sym>	PARAMETER

%type	<node>	expr
%type	<sym>	symbol
%type	<slist>	parlist
%type	<alist>	arglist

%right	'='			/* assignment */
%left	'+'  '-'		/* standard arithmetic operators */
%left	'*'  '/'
%left	UMINUS			/* unary minus */
%right	'^'			/* exponentation */
%left	'\''			/* conjugate operator z' = conj(z) */
%%

list:	  /* nothing */
	| list separator	{ prompt(); }
	| list FUNCDEF symbol
		{
			if ($3->type == UFUNC)
			{
				clear_ufunc(&$3->u.ufunc);
			}
			else if ($3->type != UNDEF)
			{
				execerror($3->name, "already defined");
			}
			inparamlist = 1;
			rem_freeall();
			$3->type = UFUNC;
			userfunc = &$3->u.ufunc;
			init_ufunc(userfunc);
			(void)rem_setkey(&userfunc->remkey);
		}
	  '(' parlist ')'
		{
			inparamlist = 0;
			indefn = 1;
			userfunc->params = $6;
		}
	  '=' expr separator
		{
			userfunc->tree = $10;
			(void)rem_setkey(&remkey);
			indefn = 0;
			if (!silent)
			{
				fprintf(stdout, "\tfunction %s defined\n", $3->name);
			}
			prompt();
		}

	| list COMMAND separator	{ (*($2->u.vptr))(); prompt(); }
	| list expr separator
		{
			treeval = eval_tree($2);

			if (!silent)
				cprin(stdout, "\t", "\n", treeval);

			if (ans)	/* allocated successfully */
				ans->u.val = treeval;/* set 'last answer' const */

			/* free all mem associated with this tree */
			rem_freeall();
			prompt();
		}
	| list error separator
		{
			if (indefn)
			{
				indefn = 0;
				rem_setkey(&remkey);
			}			
			inparamlist = 0;
			yyerrok;
			prompt();
		}
	;

parlist:	/* none */	{ $$ = NULL; }
	| PARAMETER		{ $$ = addparam(NULL, $1); }
	| parlist ',' PARAMETER	{ $$ = addparam($1, $3); }
	;

arglist:	/* nothing */	{ $$ = NULL; }
	| expr			{ $$ = addarg(NULL, $1); }
	| arglist ',' expr	{ $$ = addarg($1, $3); }
	;

symbol:	  VAR
	| UFUNC
	;

separator: '\n'			{ noprompt = 0; }
	| ';'			{ noprompt = 1; }
	;

expr:	  NUMBER		{ $$ = n_number($1, 0.0); }
	| NUMBER 'i'		{ $$ = n_number(0.0, $1); }
	| 'i'			{ $$ = n_number(0.0, 1.0); }
	| CONST			{ $$ = n_symbol(CONST, $1); }
	| CONST '=' expr	{ execerror("invalid assignment to constant", $1->name); }
	| VAR			{ if ($1->type == UNDEF)
					warning("using zero for undefined symbol", $1->name);
				  $$ = n_symbol(VAR, $1);
				}
	| PARAMETER		{ $$ = n_symbol(PARAMETER, $1); }
	| VAR '=' expr		{ $1->type = VAR; $$ = n_asgn($1, $3); }
	| C_BLTIN '(' expr ')'	{ $$ = n_func(C_BLTIN, $1, $3); }
	| UFUNC '(' arglist ')' { $$ = n_func(UFUNC, $1, (Node *)$3); }
	| SFUNC '(' arglist ')' { $$ = n_func(SFUNC, $1, (Node *)$3); }
	| expr '+' expr		{ $$ = n_binop('+', $1, $3); }
	| expr '-' expr		{ $$ = n_binop('-', $1, $3); }
	| expr '*' expr		{ $$ = n_binop('*', $1, $3); }
	| expr '/' expr		{ $$ = n_binop('/', $1, $3); }
	| expr '^' expr		{ $$ = n_binop('^', $1, $3); }
	| expr '\''		{ $$ = n_unop('\'', $1); }
	| '(' expr ')'		{ $$ = n_unop('(', $2); }
	| '-' expr %prec UMINUS { $$ = n_unop(UMINUS , $2); }
	;

%%

#include <signal.h>
#include <setjmp.h>

#ifdef AMIGA

#include <libraries/dos.h>
#include <workbench/startup.h>
#include <proto/dos.h>

#define STARTUP_FILE	"s:icalc.init"

extern struct WBStartup *WBenchMsg;
struct WBArg *wbargv;
int wbargc;

char *nextarg(void);

#endif

void processfile(char *name);

#define	BANNER	"\033[1micalc v1.1a  (c) Martin W Scott, 1992\033[0m\n"
#define PROMPT	"\033[33micalc> \033[31m"

jmp_buf	begin;			/* error start */
int	lineno;			/* current line-number of input file */
FILE	*fin;			/* current input file */

yylex()		/* lexical analyser - cumbersome, but does the job (just) */
{
	int	c;

	while ((c = getc(fin)) == ' ' || c == '\t')	/* skip blanks */
		;

	if (c == EOF)				/* end of input */
		return 0;

	if (c == '.' || isdigit(c))		/* number */
	{
		ungetc(c, fin);
		fscanf(fin, "%lf", &yylval.rval);
		return NUMBER;
	}
	if (c == 'i')				/* possibly imaginary part */
	{
		if (!isalnum(c = getc(fin)) && c != '_')	/* yes, it is */
		{
			ungetc(c, fin);
			return 'i';
		}
		ungetc(c, fin);			/* no, fall through to next */
		c = 'i';			/* restore c to old value */
	}
	if (isalpha(c) || c == '_')		/* constant, var or builtin */
	{
		Symbol *s;
		char sbuf[100], *p = sbuf;
		
		do {
			*p++ = c;
		} while ((c = getc(fin)) != EOF && (isalnum(c) || c == '_'));
		ungetc(c, fin);	
		*p = '\0';

		if (inparamlist)
			s = allocsym(sbuf, PARAMETER);
		else
		{
			/* if in function definition, check it's argument
			   list for variable references BEFORE symtree	  */
			if (indefn && (s = findsym(userfunc->params, sbuf)))
				;
			else if (!(s = lookup(sbuf)))
				s = install(sbuf, UNDEF, zero);
		}

		yylval.sym = s;
		
		return s->type == UNDEF ? VAR : s->type;
	}
	if (c == '\\')			/* expression continued on next line */
	{
		while ((c = getc(fin)) != '\n' && c != EOF)
			;
		if (c == '\n')
		{
			lineno++;
			return yylex();			/* parse next line */
		}
		if (c == EOF)				/* end of input */
			return 0;
	}
	if (c == '#')			/* comment line */
	{
		while ((c = getc(fin)) != '\n' && c != EOF)
			;
		if (c == EOF)				/* end of input */
			return 0;
	}
	if (c == '\n')
		lineno++;

	return c;
}

void warning(s, t)	/* print warning messages s and t (t can be NULL) */
	char *s, *t;
{

	fprintf(stderr,"icalc: %s", s);
	if (t)
		fprintf(stderr," %s", t);
	if (infile)
	{
		fprintf(stderr," in %s,", infile);
		fprintf(stderr," near line %d\n", lineno);
	}
	fprintf(stderr,"\n");
}

void yyerror(s)		/* standard parse error notification */
	char *s;
{
	warning(s, NULL);
}

void execerror(s, t)	/* complete evaluation failure; advise, and restart */
	char *s;
	char *t;
{
	warning(s, t);
	longjmp(begin, 0);
}


void prompt()		/* display a prompt if circumstances are right */
{
	if (!noprompt && !infile && !silent)	/* interactive session */
		fprintf(stderr, PROMPT);
}


void fpecatch()		/* catch floating-point errors */
{
	execerror("floating point exception", NULL);
}


void main(argc,argv)	/* main program entry - parse arguments */
	char **argv;
{
	fprintf(stderr,BANNER);			/* hello... */

	(void)rem_setkey(&remkey);		/* set 'remember' key */
	init();					/* build initial symbol tree */

#ifdef AMIGA
	if (!access(STARTUP_FILE,0))		/* read startup file */
		processfile(STARTUP_FILE);
#endif

#ifdef AMIGA		/* process arguments - the Workbench way... */

	if (argc == 0)		/* ran from workbench */
	{
		char *name;

		wbargc = WBenchMsg->sm_NumArgs-1;
		wbargv = &(WBenchMsg->sm_ArgList)[1];

		while (name = nextarg())
			processfile(name);
	}
	else 
#endif
	if (argc == 1)	/* stdin only - fake argument list */
		processfile("-");
	else
	{
		while (--argc)
			processfile(*++argv);
	}
}

void processfile(name)	/* open 'name' and set as curently scanned file */
	char *name;
{
	if (!strcmp(name, "-"))			/* stdin */
	{
		fin = stdin;
		infile = NULL;
	} 
	else if (!(fin = fopen(name, "r")))	/* specified file */
	{
		fprintf(stderr, "icalc: can't open %s\n", name);
		return;
	}
	else infile = name;

	lineno = 1;			/* initialise line-count */

	setjmp(begin);			/* where to come back to */
	signal(SIGFPE, fpecatch);	/* catch math errors */
	prompt();
	yyparse();			/* start parsing */

	if (!infile) fclose(fin);	/* close opened file */
}	


#ifdef AMIGA

char *nextarg()		/* get next selected Workbench argument (icon) */
{
	static BPTR olddir = -1L;

	if (wbargc > 0)
	{
		olddir = CurrentDir(wbargv->wa_Lock);
		wbargc--;
		return (wbargv++)->wa_Name;
	}
	else if (wbargc-- == 0)
	{
		if (olddir != (-1L))
			CurrentDir(olddir);
		return "-";
	}
	else return NULL;
}

#endif
