#include "fidoterm.h"
#include "mem.h"
#include <filexfer.h>
#include <xfbuf.h>
#include <ctype.h>
#include <malloc.h>

/* 

Script file processor. This processes characters from a text file
and provides a character stream for output to the modem, and performs
functions on this incoming text stream. 

unattended()
	Returns true if there is a script running unattended. (So we
	can avoid "hit any key to continue" dead ends.)

isscript()
	Returns true if a script is running.

scriptcmd()
	Returns queued up FidoTerm-command characters, else NUL. 

getscr()
	Returns queued up characters for output. Characters are returned
	no more often than 'throttle' mS. Returns: NUL == nothing queued,
	-1 == busy, else the character 01h -- 255h.

doscript(fn)
char *fn;
	Starts execution of the given script file.


stop_script()
	Stops script execution. Used to abort a script.

script()
	Called continuously from within the main character
	echo loop, executes script instructions. Returns true
	if a script is running.

script_abort(c)
char c;
	If c == cmdchar, asks if we should abort the script. If so
	it shuts everything down and returns true; else it restores 
	the original status line message and returns 0.
*/

extern unsigned zmttype;		/* Zmodem Tx behaviour */
extern unsigned zmrtype;		/* Zmodem Rx behaviour */

char *cpyscrarg();

#define MAXLEVEL 10	/* maximum call depth */
#define MAXARG 10	/* max. local arguments (&1 - &10) */
#define MAXSIZE 40	/* max. size of string registers */

/*
Our machine description:

&A		the numeric accumulator, runs 0 - 65535
&C		a timer that continuously increments by seconds
&T		the trap register, script execution time limit
&E		the boolean register, set by various instructions (READ ONLY)
&S		the character shift register from the input stream (READ ONLY)
&1 - &10	general purpose registers
*/

static char far *memptr;	/* ptr to our script text buffer */
static char far *freeptr;	/* ptr to next free position in above */
static unsigned memsize = 0;	/* total space in script text buffer (0 == unallocated) */

static int accum;		/* the accumulator */
static char sr[MAXSIZE];	/* modem stream string compare shift register */
static char error;		/* the error register */

static scrfile = -1;		/* opened file */
static scrfn[SS];		/* its name */

static long trap_reg;		/* trap time register */
static char trap_label[SS];	/* trap label */
static int trap_level;		/* trap subroutine level */

static char cdtrap_reg;		/* CD trap flag */
static char cdtrap_label[SS];	/* CD trap label */
static int cdtrap_level;	/* CD trap subroutine level */

static char clkflag = 0;	/* to get it started first time (KLUDGE!) */
static long clk_tick;		/* millisecond counter */

static unsigned throttle = 10;	/* character output speed */
static long char_tick;		/* character output timer */

/* The stack contains the current executing state of the script
machine. */

static int level = 0;		/* current stack position */
struct {
	char far *text;		/* ptr to start of subroutine text */
	char far *ip;		/* current Instruction Pointer */
	char fname[SS];		/* script file name, */
	char arg[MAXARG][SS];	/* the string registers */
} stack[MAXLEVEL];		/* stack of script files & local args */

static char recog[MAXARG];	/* the recognizer */
static char sreg[MAXLEVEL][SS];	/* the data stack */
static int slevel;		/* data-stack pointer, */

static char text[SS] = "";	/* buffer for text output */
static char *scrptr = text;	/* pointer to the text */

static char scrcmd[SS] = "";	/* buffer for script --> FidoTerm commands */
static char *cmdptr = scrcmd;	/* pointer to the text */

static char scrunatt;		/* 1 == unattended mode */
static char tracer;		/* 1 == trace instructions */

/* Script machine command/tokens */

#define LABEL 255		/* used to flag jump-to labels */

#define PUSH 2
#define POP 3
#define FIDOTERM 4
#define TIME 5
#define FILES 6
#define UNATTENDED 7
#define THROTTLE 8
#define DELAY 9

#define BREAK 20
#define DTR 21
#define CD_BIT 22
#define IO_PORT 23

#define FOPEN 30
#define FNEW 31
#define FCLOSE 32
#define FREAD 33
#define FWRITE 34

#define CALL 40
#define RETURN 41
#define TRAP 42
#define TRACE 43
#define CONSOLE 44
#define CDTRAP 45

#define KEYBD 50
#define INPUT 51
#define ASK 52
#define PAUSE 53
#define MESSAGE 54
#define OUTPUT 55
#define COPY 56
#define QUIET 57
#define MINPUT 58

#define REG_1 62
#define REG_2 63
#define REG_3 64
#define REG_4 65
#define REG_5 66
#define REG_6 67
#define REG_7 68
#define REG_8 69
#define REG_9 70
#define REG_10 61

#define REG_A 72
#define REG_B 73
#define REG_C 74
#define REG_E 75
#define REG_R 76
#define REG_S 77
#define REG_T 78

#define ZRXTYPE 80
#define ZTXTYPE 81
#define NODE 82
#define ZMPATH 83
#define FILELOG 84
#define TESTLOG 85
#define PROTOCOL 86
#define WCOLOR 87
#define CURSOR 88

#define ADD_OP 90
#define AND_OP 91
#define SUB_OP 92

#define JMP 100
#define JZ 101
#define JNZ 102
#define DJNZ 103
#define JERROR 104

#define MATCH 120
#define IF_OP 121
#define SAMPLE 122

/* Instruction names must be upper case. */

struct {
	char *keyword;		/* script command name */
	char token;		/* its token number */

} command_table[] = {

	"LABEL",LABEL,		/* not parsed via table; here for tracing */
				/* (but "LABEL <labelname>" works!!) */
	"PUSH",PUSH,
	"POP",POP,
	"FIDOTERM",FIDOTERM,	/* preferred word first */
	"$$",FIDOTERM,
	"TIME",TIME,
	"FILES",FILES,
	"UNATTENDED",UNATTENDED,
	"THROTTLE",THROTTLE,
	"DELAY",DELAY,

	"BREAK",BREAK,
	"DTR",DTR,
	"CD-BIT",CD_BIT,
	"IO-PORT",IO_PORT,

	"FOPEN",FOPEN,
	"FNEW",FNEW,
	"FCLOSE",FCLOSE,
	"FREAD",FREAD,
	"FWRITE",FWRITE,
	"QUIET",QUIET,

	"SAMPLE",SAMPLE,

	"KEYBD",KEYBD,
	"INPUT",INPUT,
	"ASK",ASK,
	"PAUSE",PAUSE,
	"MESSAGE",MESSAGE,
	"OUTPUT",OUTPUT,
	"COPY",COPY,
	"MINPUT",MINPUT,

	"CALL",CALL,
	"RETURN",RETURN,
	"TRAP",TRAP,
	"TRACE",TRACE,
	"CONSOLE",CONSOLE,
	"CD-TRAP",CDTRAP,

	"&R=",REG_R,
	"&S=",REG_S,
	"&1=",REG_1,
	"&2=",REG_2,
	"&3=",REG_3,
	"&4=",REG_4,
	"&5=",REG_5,
	"&6=",REG_6,
	"&7=",REG_7,
	"&8=",REG_8,
	"&9=",REG_9,
	"&10=",REG_10,

	"MATCH",MATCH,
	"IF",IF_OP,

	"&A=",REG_A,
	"&B=",REG_B,
	"&C=",REG_C,
	"&E=",REG_E,
	"&T=",REG_T,

	"ADD",ADD_OP,
	"AND",AND_OP,
	"SUB",SUB_OP,
	"JMP",JMP,
	"JZ",JZ,
	"JNZ",JNZ,
	"DJNZ",DJNZ,
	"JERROR",JERROR,

	"ZRXTYPE",ZRXTYPE,
	"ZTXTYPE",ZTXTYPE,
	"NODE",NODE,
	"ZMODEM-PATH",ZMPATH,
	"FILE-LOG",FILELOG,
	"TEST-LOG",TESTLOG,
	"PROTOCOL",PROTOCOL,
	"COLOR",WCOLOR,
	"CURSOR-HEIGHT",CURSOR,
	"",0
};

/* Return true if a script is running in unattended mode. */

unattended() {

	return(level && scrunatt);
}

/* Return true if there is a script file running. */

isscript() {

	return(level);
}

/* If there is a script command character (queued up by the FIDOTERM command)
return it now. */

scriptcmd() {

	if (*cmdptr) {
		return(*cmdptr++);
	}
	return(NUL);
}

/* If there is a character waiting, return it if > throttle time, else
return -1 as busy indicator. */

getscr() {

	if (*scrptr) {
		if (char_tick >= throttle) {	/* issue characters no faster */
			char_tick= 0L;		/* than every N mS */
			return(*scrptr++);

		} else return(-1);		/* flag busy */
	}
	return(NUL);				/* flag nothing queued */
}

/* Start a script file process. */

doscript(fn)
char *fn;
{
	if (memsize == 0) {			/* first time through */
		for (memsize= 65535; memsize > 1000; memsize -= 100) {
			memptr= (char far *)_fmalloc(memsize);
			if (memptr != NULL) break;	/* get biggest block possible */
		}
		if (memptr == NULL) {		/* if < 1000 available */
			msg("Not enough memory to execute script");
			return;
		}
	}
	if (! clkflag) {			/* first time only */
		++clkflag;
		set_tick(&clk_tick);		/* start the tickers running */
		set_tick(&char_tick);
	}
	freeptr= memptr;			/* nothing in text buffer */
	slevel= 0;				/* stack is empty */
	throttle= 10;				/* default throttle speed */
	trap_reg= 0;				/* turn off the trap */
	cdtrap_reg= 0;				/* no carrier-detect trap */
	tracer= 0;				/* turn off tracing */
	scrfile= -1;				/* no file open */
	level= 0;				/* nothing running */
	error= 0;				/* no error */
	scrunatt= 0;				/* query on error */

	clrsr();				/* clear the shift register */
	doclose("");				/* close any open file */
	docall(fn);				/* start the script */
	if (level) msg("Running script \"%s\"",fn);
}

/* Stop all script files. */

stop_script() {

	while (level) doret("");
	doclose("");		/* close any open file */
	scrfile= -1;
	level= 0;		/* initially clear */
	error= 0;		/* no error */
	scrunatt= 0;		/* query on error */
}

/* If a script file is open, read lines from it and execute it; return
0 if no script file open. */

script() {
char far *cp;		/* "far" ptr into script */
char buff[SS];		/* "near" buffer */
char far *fartonear(char *, char far *);

	if (level) {
		script_abort(keyhit());			/* manual abort */
		check_trap();				/* check trap timer */
		if (*(cp= stack[level].ip)) {		/* if another instruction */
			stack[level].ip= fartonear(buff,cp); /* convert it (AARGH) */
			script_exec(buff); 		/* execute it */

		} else doret("");			/* close and return */
	}
/**/	if (! level) conflg= 0;
	return(level);
}

/* Copy a line of (instruction) text, from a 'char far *' to a normal "C"
char *, return the *far) pointer to the next character. Don't go past
the NULL terminator. */

static char far *fartonear(np,fp)
char *np;
char far *fp;
{
	while (*np= *fp) {			/* copy each character */
		++fp;				/* advance, but not beyond NUL */
		if (*np == '\r') break;		/* stop at end of line (CR) */
		++np; 				/* advance if not at end(s) */
	}
	*np= NUL;				/* terminate dest */
	return(fp);				/* return ptr to next */
}

/* Execute one script instruction. */

static script_exec(ip)
char *ip;
{
int i;
char c,*cp,buff[SS];
char *ra;		/* raw arg string */
char fa[SS];		/* first expanded arg */
char na[SS];		/* next expanded arg */
unsigned n;		/* possible numeric value for 'fa' */
char token;		/* command token */

	token= *ip++;				/* save the token */
	ra= ip;					/* ra == ptr to raw argument text */
	ip= cpyscrarg(buff,ip);
	scrmac(fa,buff);			/* fa == first argument */
	n= atoi(fa);				/*  n == numeric first arg */

	cpyscrarg(buff,skip_delim(ip));
	scrmac(na,buff);			/* na == next argument */

	if (tracer) {				/* do manual tracing */
		for (i= 0; *command_table[i].keyword; ++i) {
			if (command_table[i].token == token) break;
		}
		msg("TRACE: %s %s %s",command_table[i].keyword,fa,na);
		while (! (c= keyhit()) );	/* get a key */
		script_abort(c);		/* (check it) */
	}

	switch (token) {

	case LABEL: break;				/* (LABEL token) */

	case PUSH: dopush(fa); break;			/* PUSH string */
	case POP: dopop(ra); break;			/* POP register */

	case FIDOTERM: dotalk(fa); return;		/* command */
	case TIME: dotime(fa); break;			/* hh:mm */
	case FILES: dofiles(fa); break;			/* filespec */
	case UNATTENDED: scrunatt= n; break;		/* n */
	case THROTTLE: if (n <= 500) throttle= n; break;/* n */
	case BREAK: mbreak(); break;			/* */
	case DTR: lower_dtr(); delay(50); raise_dtr(); break; /* */
	case CD_BIT: cd_bit= n; break;			/* n */
	case IO_PORT: uninit_async(); init_async(iodev= n - 1);	/* n */
		setbaud(rate); break;

	case FOPEN: doopen(fa); break;			/* filename */
	case FNEW: docreat(fa); break;			/* filename */
	case FCLOSE: doclose(fa); break;		/* */
	case FREAD: doread(fa); break;			/* */
	case FWRITE: dowrite(fa); break;		/* */

	case DELAY: dodelay(n); break;			/* milliseconds */
	case QUIET: doquiet(n); break;			/* millisecs */
	case SAMPLE: put_console(get_modem()); break;	/* */

	case KEYBD: dokeybd(fa); break;			/* char */
	case INPUT: scrargs(input("ENTER",fa),level); break; /* prompt */
	case ASK: error= askesc(fa); break;		/* question */
	case PAUSE: dopause(fa); break;			/* message */
	case MINPUT: dominput(fa); break;		/* message */
	case MESSAGE: domsg(fa); break;			/* message */
	case OUTPUT: scrset(fa); break;			/* string */
	case COPY: for (cp= fa; *cp; ) modout(parity(*cp++,parflg)); break; /* string */

	case CALL: docall(ra); break;			/* scriptfile */
	case RETURN: doret(fa); return;			/* value */
	case TRAP: dotrap(fa); break;			/* label */
	case TRACE: tracer= n; break;			/* flag */
	case CONSOLE: conflg= n; break;			/* flag */
	case CDTRAP: docdtrap(fa); break;		/* label */

	case REG_R: setpat(recog,fa); break;		/* &R= pattern */
	case REG_S: clrsr(); for (cp= fa; *cp; ++cp) 	/* &S= pattern */
			shiftin(*cp); break;
	case REG_1: setpat(stack[level].arg[0],fa); break;/* &1= pattern */
	case REG_2: setpat(stack[level].arg[1],fa); break;/* &2= pattern */
	case REG_3: setpat(stack[level].arg[2],fa); break;/* &3= pattern */
	case REG_4: setpat(stack[level].arg[3],fa); break;/* &4= pattern */
	case REG_5: setpat(stack[level].arg[4],fa); break;/* &5= pattern */
	case REG_6: setpat(stack[level].arg[5],fa); break;/* &6= pattern */
	case REG_7: setpat(stack[level].arg[6],fa); break;/* &7= pattern */
	case REG_8: setpat(stack[level].arg[7],fa); break;/* &8= pattern */
	case REG_9: setpat(stack[level].arg[8],fa); break;/* &9= pattern */
	case REG_10: setpat(stack[level].arg[9],fa); break;/* &10= pattern */

	case MATCH: domatch(fa,na); break;		/* pattern limit */
	case IF_OP: setpat(buff,fa); 
		if (match(buff)) dojmp(na); break;	/* pattern label */

	case REG_A: accum= n; break;			/* value */
	case REG_B: if (setbaud(n)) rate= n; break;	/* value */
	case REG_C: clk_tick= 1000L * n; break;		/* value */
	case REG_E: error= (n ? 1 : 0); break;		/* value */
	case REG_T: trap_reg= n * 1000L; clk_tick= 0L; break; /* value */

	case ADD_OP: accum += n; break;			/* value */
	case AND_OP: accum &= n; break;			/* value */
	case SUB_OP: accum -= n; break;			/* value */
	case JMP: dojmp(fa); break;			/* label */
	case JZ: if (!accum) dojmp(fa); break;		/* label */
	case JNZ: if (accum) dojmp(fa); break;		/* label */
	case DJNZ: if (accum) --accum; 			/* label */
		if (accum) dojmp(fa); break;
	case JERROR: if (error) dojmp(fa);		/* label */
		error= 0; break;

	case ZRXTYPE: zmrtype= n; break;		/* n */
	case ZTXTYPE: zmttype= n; break;		/* n */
	case ZMPATH: fixdir(fa); 			/* path */
		strip_path(zmpath,fa); break;
	case FILELOG: strcpy(xferlog,fa); break;	/* filename */
	case TESTLOG: strcpy(testlog,fa); break;	/* filename */
	case PROTOCOL: doprotocol(fa); break;		/* protocol name */
	case WCOLOR: status.def_attribute= n; 		/* n */
		msg(""); cmd_timer= 0L; break;
	case CURSOR: screen.cursor_height= n; break;

	case NODE: donodelist(fa); break;		/* z:n/f */
	}
}

/* Strip comments from a line of text; truncate it at the semicolon, then work
backwards deleting delimiters. This ignores semicolons embedded within quotes
and handles literal quote chars (\") properly. */

static clip_cmt(cp)
char *cp;
{
char q;		/* 1 == inside a quote */

	q= 0;
	for ( ; *cp; ++cp) {
		if (*cp == '\\') {		/* if literal follows */
			++cp;			/* point to it */
			continue;		/* then go skip it */
		}
		if (*cp == '"') {		/* start/end of a quote */
			q ^= 1;			/* toggle flag */
			continue;		/* (no sense looking at it) */
		}
		if (q) continue;		/* ignore inside quotes */

		if (*cp == ';') {		/* search for a semicolon */
			*cp= NUL;		/* kill it, */
			while (delim(*--cp)) 	/* kill all delims */
				*cp= NUL;	/* backwards */
			break;
		}
	}
}

/* Install one character in the shift register. */

shiftin(c)
char c;
{
int i;
	for (i= 0; i < (sizeof(sr) - 2); i++) 	/* shift full length */
		sr[i]= sr[i + 1];		/* shift the register */
	sr[i]= tolower(c & 0x7f);		/* install the new char */
}

/* Clear the shift register. */

static clrsr() {
int i;

	for (i= 0; i < sizeof(sr); i++) sr[i]= NUL;
}

/* Install a pattern in a string register; convert to all lower case
and bound to maximum length. Return the string length. */

static setpat(reg,np)
char *reg,*np;
{
int i;
char *cp;

	cp= reg;
	for (i= MAXSIZE; i--;) 
		if (! (*cp++= *np++)) break;
	*cp= NUL;				/* force an end, */
	stolower(reg);				/* make lower case */
	return(strlen(reg));			/* return the length */
}

/* Return true if the pattern matches the text now in the shift register. */

static match(r)
char *r;	/* the pattern to match, */
{
char *p;

	if (! *r) {				/* special case null string */
		p= sr + sizeof(sr) - 2;		/* check for empty SR */
		return(*p ? 0 : 1);		/* match only if both null */
	}
	p= sr + sizeof(sr) - strlen(r) - 1;	/* the input stream */
	while (*r) {				/* match characters */
		if ((*r != '?') && (*r != *p))	/* if not a wildcard or a match */
			return(0);		/* return mismatch */
		++r; ++p;
	}
	return(1);
}

/* Ask if we should abort the running script file. Return true if so. */

script_abort(c)
char c;
{
	if (! level) return(0);			/* no script running */
	if (c != cmdchar) return(0);		/* not command character */

	if (ask("Stop running the script file")) {
		stop_script();			/* stop everything, */
		msg("Script aborted");		/* tell the user */
		conflg= 0;

	} else msg("");				/* restore previous msg */

	return(level == 0);			/* 0 == not running */
}

/* Set the string variables %1 to %9 from the passed string. If no
argument, set the string to null. The level passed is the new one,
that is being set. This allows %n to reference the current string
variables when setting the next levels args. The args are unprocessed,
since they are parsed just before each funtion is called. */

static scrargs(np,level)
char *np;
int level;
{
int i;

	np= skip_delim(np);
	for (i= 0; i < MAXARG; i++) {
		*stack[level].arg[i]= NUL;			/* zap it first */
		if (*np) {					/* if an arg, */
			np= cpyscrarg(stack[level].arg[i],np);
			np= skip_delim(np);			/* skip spaces */
		}
	}
}

/* Copy a script arg; either a single word or a quoted string. Return a pointer
to the start of the next arg. */

static char *cpyscrarg(dp,sp)
char *dp,*sp;
{
char lastc,c,q;

	if (*sp == '"') q= *sp++; else q= NUL;		/* optional quote stripping */
	lastc= NUL;
	while (c= *sp) {
		++sp;				/* next ... */
		if ((c == q) && (lastc != '\\')) /* if the quote char & not quoted */
			break;			/* end of argument */
		if (!q && delim(c)) break;	/* else stop if a delimiter */
		*dp++= c;			/* else part of same arg */
		lastc= c;			/* remember last char */
	}
	*dp= NUL;

	return(sp);
}

/* Expand the macro substitutions from any command line args. */

static scrmac(dp,sp)
char *dp,*sp;
{
char *cp,c;
int i;

	while (c= *sp++) {
		if (c == '^') {				/* control characters */
			c= *sp;				/* get the next char */
			if (c) ++sp;			/* (dont skip the NUL!) */
			c= toupper(c) & 0xbf;		/* convert it */

		} else if (c == '\\') {			/* if literal, */
			c= *sp;				/* get the next char */
			if (c) ++sp;			/* (dont skip the NUL!) */
			switch (tolower(c)) {
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					c= atoi(--sp); 
					while (isdigit(*sp)) ++sp;
					break;

				case 'r': c= CR; break;
				case 'n': c= LF; break;
				case 'b': c= BS; break;
				case 'e': c= ESC; break;
				case 'c': c= ETX; break;

				default: break;		/* else leave literal as-is */
			}

		} else if (c == '&') {			/* if a register */
			c= *sp;				/* get the next char */
			if (c) ++sp;			/* (dont skip the NUL!) */
			switch (tolower(c)) {
				case '0':
				case '1':
				case '2':
				case '3':
				case '4':
				case '5':
				case '6':
				case '7':
				case '8':
				case '9':
					i= atoi(--sp) - 1; 
					while (isdigit(*sp)) ++sp;
					if (i >= MAXARG) break;
					cp= stack[level].arg[i];
copystr:;				while (*dp= *cp++) ++dp;
					break;

				case 'r': cp= recog; goto copystr;
				case 's': for (i= sizeof(sr) - 2; i >= 0; i--)
						if (! sr[i]) break;
					cp= &sr[++i]; goto copystr; 

				case 'a': i= accum; goto copynum;
				case 'b': i= rate; goto copynum;
				case 'c': i= clk_tick / 1000L; goto copynum;
				case 'e': i= error; goto copynum;
				case 't': i= trap_reg; goto copynum;
copynum:;				sprintf(dp,"%ld",0L+i);	/* a number */
					while (*dp) dp++;	/* go to the end */
					break;
			}
			c= NUL;					/* we dont use it */
		}
		if (c) *dp++= c;
	}
	*dp= NUL;
}

/* Put a string in the buffer for output to the modem. Dont overflow
the buffer; if necessary, output some. */

static scrset(s)
char *s;
{
int n;
char c;

	while (*s) {			/* add new text to the queue */
		strcpy(text,scrptr);	/* copy remaining text to buff start */
		n= strlen(text);	/* to increase avail. room */
		scrptr= &text[n];

		while (++n < sizeof(text)) { /* add new text to the end */
			if (! *s) break;/* but dont exceed buff size */
			*scrptr++= *s++;
		}
		*scrptr= NUL;		/* terminate it */
		scrptr= text;		/* point to first char */
		if (! *s) break;	/* see it all fit */

		put_modem(get_console());/* make room for it! */
/*		put_console(get_modem());
*/		if (! isscript()) break; /* (aborted script, etc) */
	}
}

/* Check to see if either the trap timer has gone off, or the carrier-loss
trap; branch to the appropriate routine as necessary. */
static check_trap() {

	if (trap_reg != 0L) {			/* if the trap is set, */
		if (clk_tick >= trap_reg) {	/* and has gone off, */
			trap_reg= 0;		/* disable it, */
			while (level > trap_level)
				doret("");
			dojmp(trap_label);
		}
	}
	if (cdtrap_reg && ! cd()) {		/* if enabled, and CD is off, */
		cdtrap_reg= 0;			/* disable it, */
		while (level > cdtrap_level)
			doret("");
		dojmp(cdtrap_label);
	}
}

/* Open a script file, try the current directory first, then look
through the path. Return the handle or -1 if error. */

static openp(fn,mode)
char *fn;
int mode;
{
int f;
char *cp,buff[SS];
char *getenv();

	if ((f= open(fn,mode)) != -1) 	/* if in current directory, */
		return(f);		/* just return it */

	cp= getenv("PATH"); 		/* else find the PATH, */
	if ((int)cp == 0) cp= "";	/* if none, use current */

	do {				/* try each path, */
		cpyarg(buff,cp);	/* copy one pathname, */
		if (strlen(buff) > 0 && (buff[strlen(buff) - 1] != '\\'))
			strcat(buff,"\\"); /* add a \ if none in the path */
		strcat(buff,fn);	/* add the filename, */
		f= open(buff,mode);	/* try to open, */
		if (f != -1) return(f);	/* return the open handle, */
		cp= next_arg(cp);	/* next ... */
	} while (*cp);

	return(-1);
}

/*
	PUSH string

Put the string onto the stack; error if overflow. */

static dopush(cp)
char *cp;
{
	if (slevel >= MAXLEVEL) {
		msg("Can't PUSH \"%s\", stack full",cp);
		stop_script();
		return;
	}
	cp[MAXSIZE]= NUL;
	strcpy(sreg[slevel++],cp);
}

/*
	NODE z:n/f

Locate the specified node info. */

static donodelist(np)
char *np;
{
struct _node d;

	open_node();
	if (node_files()) {
		msg("Nodelist file(s) missing");
		stop_script();
		return;
	}
	cpy_node(&d,&id);
	set_nn(np,&d);
	error= (find_ndat(&d) == -1 ? 1 : 0);
	close_node();
	if (error) return;

	sprintf(stack[level].arg[0],"%u",ndat.rate);
	strcpy(stack[level].arg[1],ndat.phone);
	strcpy(stack[level].arg[2],ndat.name);
	strcpy(stack[level].arg[3],ndat.city);
	strcpy(stack[level].arg[4],ndat.pwd);
	sprintf(stack[level].arg[5],"%u",ndat.cost);
}

/*
	POP register

Pop the stack into the specified register; error if underflow. We are
passed the raw register name so we have to parse a bit. */

static dopop(ra)
char *ra;
{
char *cp,e;
int n;

	if (slevel <= 0) {
		msg("Can't POP %s, stack underflow",ra);
		stop_script();
		return;
	}
	e= 0;					/* assume no error */
	cp= sreg[--slevel]; n= atoi(cp);	/* prepare the stack value */

	if (*ra == '"') ++ra;			/* skip any leading quote */
	if (*ra != '&') ++e;			/* oops, must be reg name! */
	else switch (ra[1]) {
		case 'a': accum= n; break;
		case 'c': clk_tick= n * 1000L; break;
		case 't': trap_reg= n * 1000L; clk_tick= 0L; break;
		case 'b': if (setbaud(n)) rate= n; break;
		case 'e': error= (n ? 1 : 0); break;

		case 'r': strcpy(recog,cp); break;
		case 's': while (*cp) shiftin(*cp++); break;

		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			n= atoi(&ra[1]) - 1;
			if (n <= MAXARG) setpat(stack[level].arg[n],cp);
			else ++e;
			break;

		default: ++e; break;
	}
	if (e) {
		msg("No such register: POP \"%s\"",ra);
		stop_script();
	}
}
						
/*
	FOPEN filename

Open the specified file, error if one is already open. */

static doopen(np)
char *np;
{
	if (scrfile != -1) {
		msg("File %s is already open",scrfn);
		stop_script();
		return;
	}
	cpyarg(scrfn,np);			/* make a clean filename */
	scrfile= openp(scrfn,OPEN_RW);		/* open it, */
	error= (scrfile == -1);
	if (error && !scrunatt) {
		msg("Can't find file \"%s\"",scrfn);
		stop_script();
		return;
	}
}

/*
	FNEW filename

Open the specified file, error if one is already open. */

static docreat(np)
char *np;
{
	if (scrfile != -1) {
		msg("File %s is already open",scrfn);
		stop_script();
		return;
	}
	cpyarg(scrfn,np);			/* make a clean filename */
	scrfile= creat(scrfn,CREAT_RW);
	if (scrfile == -1) {
		msg("Can't create file \"%s\"?",scrfn);
		stop_script();
	}
}

/*
	READ

Read a line from the open file, parse it into the &1 - &n variables. */

static doread(np)
char *np;
{
char buff[256];

	if (scrfile == -1) {
		msg("There is no file open to READ from!");
		stop_script();
		return;
	}
	error= rline(scrfile,buff,sizeof(buff)) ? 0 : 1;/* set &E if EOF */
	scrargs(buff,level);				/* set possible arguments */
}

/*
	WRITE 

Write a line to the open file. */

static dowrite(np)
char *np;
{
int i;
char buff[SS];

	if (scrfile == -1) {
		msg("There is no file open to WRITE to!");
		stop_script();
		return;
	}
	lseek(scrfile,0L,2);			/* write at EOF only */
	for (i= 0; i < MAXARG; i++) {		/* write each arg */
		sprintf(buff,"\"%s\"",stack[level].arg[i]);
		error= write(scrfile,buff,strlen(buff)) == strlen(buff) ? 0 : 1;
		if (error) {
			if (! scrunatt) {
				msg("Error writing file\"%s\"",scrfn);
				stop_script();
				return;

			} else break;
		}
		if (i < MAXARG - 1) write(scrfile,",",1);
	}
	write(scrfile,"\r\n",2);
}
/*
	CLOSE

Close any open file. */

static doclose(np)
char *np;
{
	if (scrfile != -1) close(scrfile);
	scrfile= -1;
}

/* Process a string as a FidoTerm command. The string needs an ESCape prepended,
and a CR appended. */

static dotalk(np)
char *np;
{
char buff[SS];

	buff[0]= cmdchar;			/* build a command string */
	strcpy(&buff[1],np);
	strcat(buff,"\r");
	strcpy(cmdptr= scrcmd,buff);		/* set script command output text */
}

/*
	PROTOCOL name

	Set file transfer protocol name.
*/

static doprotocol(np)
char *np;
{
	switch (tolower(*np)) {
		case 't': filemode= TELINKC; break;
		case 'x': filemode= XMODEMC; break;
		case 'c': filemode= XMODEM; break;
		case 'z': filemode= ZMODEM; break;
		case 'a': filemode= ASCII; break;
		default:
			msg("No such PROTOCOL type in script %s",stack[level].fname);
			stop_script();
			return;
	}
}

/*
	TRAP label

	Set the trap to go off to the label in the current script file. */

static dotrap(np)
char *np;
{
	if (!*np) {
		msg("Missing TRAP label in script %s",stack[level].fname);
		stop_script();
		return;
	}
	strcpy(trap_label,np);			/* remember the label */
	trap_level= level;			/* remember the level */
}

/*
	CD-TRAP label

	Set the carrier-loss trap to branch. */

static docdtrap(np)
char *np;
{
	if (! *np) {
		msg("Missing CD-TRAP label in script %s",stack[level].fname);
		stop_script();
		return;
	}
	strcpy(cdtrap_label,np);		/* remember the label */
	cdtrap_level= level;			/* remember the level */
	cdtrap_reg= 1;
}

/*
	MINPUT <message>

	Output the message, input text from the modem with the usual
	editing commands, until a CR or ESCape is received. ESCape sets
	the &E error flag.
*/

static dominput(np)
char *np;
{
#define LINELEN 40
char buff[LINELEN];
int c,count;

	scrset(np);			/* queue message for output */
	count= 0;
	while (1) {
		c= get_modem();				/* input modem characters */
		put_modem(get_console());		/* (flush output) */
		if (! isscript()) break;
		if (! c) continue;			/* wait for it */

		if (error= (c == cmdchar)) break;	/* end of line */
		if (c == CR) break;			/* end of line */

		switch (c) {
			case -1: break;			/* no char */

			case BS:
			case DEL:			/* delete character */
			case XOF:
				c= count ? 1 : 0;	/* chars to backspace */
backspace:;			while (c--) {
					--count;	/* one less char, */
					scrset("\010 \010");
				}
				break;

			case NAK: count= 0; break;	/* delete line */

			case VT:
			case CAN:			/* erase line */
			case ETX:
			case EM:
				c= count; goto backspace;

			default:				/* insert character */
				if (c < ' ') c= NUL;		/* no control chars */
				if (count >= LINELEN) c= NUL;	/* too many */
				if (! c) break;

				buff[count++]= c;		/* add new char */
				buff[count]= NUL;		/* terminate it */
				scrset(&buff[count - 1]);	/* echo it */
				break;
		}
	}
	buff[count]= NUL;				/* (terminate for debugging) */
	clrsr(); 					/* put result in &S */
	for (c= 0; c < count; c++)
		shiftin(buff[c]); 
}

/*
	MESSAGE <message>

	Output the message to the command line. */

static domsg(np)
char *np;
{
char *cp,buff[SS];

	cp= buff;
	while (*np) {
		if (*np >= ' ') *cp++= *np;	/* printable chars as-is */
		else {				/* expand control chars */
			*cp++= '\\';
			switch (*np) {
				case CR: *cp++= 'r'; break;
				case LF: *cp++= 'n'; break;
				case TAB: *cp++= 't'; break;
				default:
					sprintf(cp,"%02d",*np);
					while (*cp) ++cp;
					break;
			}
		}
		++np;
	}
	*cp= NUL;
	msg(buff);
}

/* 
	CALL <scriptfile> <args ...>

Start execution of a script file. This loads the script file into memory,
stripping off blank lines and comments, and tokenizes the commands.

Note that this checks to see if the script file is already loaded; if so,
it simply points this stack entries' script text pointer to the copy already
in memory.

Note that this gets the raw, unprocessed, command line. */

static docall(rp)
char *rp;
{
int n,f;
char *cp,name[SS];
char buff[256];		/* long input lines */
static int script_line(char far *,char *);

	if (level + 1 >= MAXLEVEL) {
		msg("Too many nested script calls: CALL %s from %s",name,stack[level].fname);
		close(f);
		stop_script();
		return;
	}
	cp= cpyscrarg(buff,rp);			/* copy off the script name */
	scrmac(name,buff);			/* expand it, etc */
	scrmac(buff,skip_delim(cp));		/* expand possible arguments */
	cp= name;
	while (*cp) {
		if (*cp == '.') break;		/* find the extention */
		++cp;
	}
	strcpy(cp,".SCR");			/* force .SCR */
	stoupper(name);				/* (for error messages, etc) */

	for (n= 0; n <= level; n++) {		/* if its already loaded */
		if (same(stack[n].fname,name)) {
			++level;		/* next level */
			scrargs(buff,level);	/* set possible arguments */
			stack[level].text= stack[n].text; /* point to */
			return;			/* existing script text */
		}
	}
	f= openp(name,0);			/* try to open it */
	if (f == -1) {
		msg("Can't find script file %s",name);
		stop_script();			/* abort processing */
		return;
	}
	++level;				/* next level */
	scrargs(buff,level);			/* set possible arguments */
	strcpy(stack[level].fname,name);	/* current script name */
	stack[level].text= 			/* point to where code loads */
	    stack[level].ip= freeptr;		/* and ptr to 1st instruction */

	while (rline(f,buff,sizeof(buff))) {	/* read script file lines */
		clip_cmt(buff);			/* strip off comments FIRST (; is a delimiter!) */
		cp= skip_delim(buff);		/* skip leading blank stuff */
		if (! *cp) continue;		/* skip blank lines */

		if (memsize - (freeptr - memptr) < sizeof(buff)) {/* check for room! */
			msg("Ran out of memory loading script file %s",name);
			stop_script();
			break;
		}
		n= script_line(freeptr,cp);	/* process the line */
		if (n == -1) {			/* if error, */
			if (strlen(cp) > 40)	/* if long string, */
				strcpy(&cp[40],"..");	/* truncate */
			msg("Bad instruction in script %s: \"%s\"",name,cp);
			stop_script();		/* abort everything */
			break;
		}
		freeptr += n;			/* space consumed */
	}
	close(f);				/* close the file */
	*freeptr++= NUL;			/* terminate the script */
}

/* Convert the given script file line for storage; tokenize the command
and append the (possible) argument text. Append a trailing CR. 

For example:

	"ADD &A 100	;a comment\r"		input, ASCII script command
	"<x>&A 100\r"				stripped, tokenized 

Where <x> is a token from the table.

Instruction lines with labels are converted to two lines; one with a
LABEL "instruction" and another with the command itself.

Returns 0 for blank/comment lines, the number of bytes copied, or -1 if
the keyword was not valid. */

static int script_line(p,ln)
char far *p;		/* where to put text (output) */
char *ln;		/* line read from file (input) */
{
char buff[SS];
int i,n;
int addscrtext(char far *,char *);

	n= 0;					/* total chars installed */

again:	if (! *ln) return(n);			/* return if no (more) to do */

	cpyarg(buff,ln);			/* buff == instruction word */
	stoupper(buff);				/* force upper case */
	ln= next_arg(ln);			/* ln == possible args */

/* Check for labels first. Old style are DOS-type comment-as-command; they
begin with a colon, on a line by themselves. New style have a trailing colon,
and optionally have a command on the same line. */

	if (*buff == ':') {			/* if an OLD STYLE label */
		strcpy(buff,&buff[1]);		/* remove leading colon */
		goto add_label;			/* (how appropriate) */
	}
	if (buff[strlen(buff) - 1] == ':') {	/* if a label */
		buff[strlen(buff) - 1]= NUL;	/* strip off trailing colon */
add_label:;	*p++= LABEL;			/* flag as label "instruction" */
		n += addscrtext(p,buff);	/* add to text buffer */
		p += n - 1;			/* point to free space */
		goto again;			/* re-process an instruction */
	}

/* Now, finally, look up the instruction in the keyword/token table. */

	for (i= 0; *command_table[i].keyword; ++i) {	/* find the word, */
		if (same(command_table[i].keyword,buff)) { /* if a match, */
			*p++= command_table[i].token;	/* insert the token */
			n += addscrtext(p,ln);		/* and args */
			return(n);
		}
	}
	return(-1);					/* not found! */
}

/* Copy (near) text into the script text buffer, return the number of
characters copied PLUS TWO -- +1 for the token (copied in previously) 
and +1 for the terminating CR. */

static addscrtext(tp,cp)
char far *tp;		/* script text ptr */
char *cp;		/* text to copy in */
{
int n;

	for (n= 2; *cp; ) {
		*tp++= *cp++;		/* command args */
		++n;			/* count chars */
	}
	*tp= '\r';			/* add line end */
	return(n);			/* token + chars + line end CR */
}

/*
	RETURN <n>

	Close a script file, pop back up one level. If <n> is non zero,
set the ERROR value. */

static doret(np)
char *np;
{
	if (isdigit(*np)) error= atoi(np);	/* set error value */

	if (level > 0) --level;			/* execute a return */
	else level= 0;
}

/*
	FILES <filespec>

	Set the accumulator to the number of files that match the
file specification. */

static dofiles(np)
char *np;
{
char path[SS];
struct _xfbuf xfbuf;

	cpyarg(path,np);
	if (! *path) strcpy(path,"*.*");
	xfbuf.s_attrib= 0;
	for (accum= 0; _find(path,accum,&xfbuf); ++accum);
}
	
/*
	TIME hh:mm

	Wait until the specified time of day. */

static dotime(np)
char *np;
{
int h,m,lmin,hour,mins;
char *cp;

	cp= np;
	hour= atoi(cp);			/* read hour first, */
	while (*cp) {			/* look for the colon */
		if (*cp == ':') break;
		++cp;
	}
	if (*cp++ != ':') {
		msg("Illegal time \"%s\"",np);
		stop_script();
		return;
	}
	mins= atoi(cp);			/* get minutes */

	if ((hour > 23) || (mins > 59)) {
		msg("Illegal time \"%s\"; 23:59 maximum!",np);
		stop_script();
		return;
	}

	lmin= -1;
	do {
		do {
			h= gtod3(4);		/* read the hour, */
			m= gtod3(5);		/* and the minute */
		} while ((h != gtod3(4)) || (m != gtod3(5))); 

		put_modem(get_console());	/* flush output, etc */
		put_console(get_modem());
		if (! isscript()) break;	/* watch for abort */

		if (m != lmin) {
			lmin= m;
			msg("Time is now %02d:%02d, waiting for %02d:%02d",
				h,m, hour,mins);
		}

	} while ((h != hour) || (m != mins));	/* wait til time */
}

/*
	DELAY <n>

	Delay <n> milliseconds. Delay 1 second if no argument is passed. */

static dodelay(n)
int n;
{
int h;
long timer;
char c;

	if (!n) n= 1000;			/* default to 1 sec, */

	h= set_tick(&timer);			/* get a milisec timer */
	if (h == -1) {
		msg("FidoTerm error: Cant allocate a DELAY timer");
		stop_script();
		return;
	}
	timer= 0L;
	while (timer < n) {
		put_modem(get_console());	/* input/output */
		put_console(get_modem());
		if (! isscript()) break;	/* check keyboard */
	}
	clr_tick(h);				/* stop the timer */
}

/*
	MATCH <pattern> <n>

	Search for <pattern>, for up to one minute or <n> seconds
if present. If the string is not found &E is set true. */

static domatch(np,na)
char *np;
{
int i,h;
long timer,limit;
char buff[SS],c;

	setpat(buff,np);				/* working pattern */
	limit= atoi(na) * 1000L;			/* check possible 2nd arg */
	if (limit == 0L) limit= 60000L;			/* else default to 1 minute */

	h= set_tick(&timer);				/* get a milisec timer */
	if (h == -1) {
		msg("FidoTerm error: Cant allocate a MATCH timer");
		stop_script();
		return;
	}
	timer= 0L;				/* global timer */
	clrsr();
	while ((timer < limit) || (limit == 0L)) { /* start counting */
		put_modem(get_console());	/* in and out */
		put_console(get_modem());
		if (! isscript()) break;	/* watch for abort */
		if (match(buff)) break;		/* compare against SR */
	}
	clr_tick(h);				/* stop the timer */
	error= ((timer >= limit) && 		/* error if timeout */
	  (limit > 0L));			/* and non-zero limit */
}

/* 
	JMP <label>

	Search for the line in the file that defines the
label. Execution will proceed with the next line following. */

static dojmp(np)
char *np;
{
char *cp;
char label[SS];		/* supplied label to jump to */
char arg[SS],ln[SS];

	cpyarg(label,np);			/* clean copy of the label */
	if (! *label) {
		msg("Missing JMP <label>, in script %s",stack[level].fname);
		stop_script();
		return;
	}
	stoupper(label);			/* for comparison */
	stack[level].ip= stack[level].text;	/* start at top of script */

	while (*(stack[level].ip= fartonear(ln,stack[level].ip))) {
		if (ln[0] != LABEL) continue;	/* find a LABEL command */

		cpyarg(arg,&ln[1]);		/* copy out label, */
		stoupper(arg);			/* for comparison */
		if (same(label,arg)) return;	/* found it! */
	}
	msg("Can't find label %s: in script %s",label,stack[level].fname);
	stop_script();
}

/*
	KEYBD <c>

	Let the user type to the modem; terminate when <c> is hit,
or CR if none specified. */

static dokeybd(np)
char *np;
{
char c,e;

	c= *np; if (! c) c= CR;			/* default is CR */
	do {
		put_modem(get_console());	/* else output it */
		put_console(get_modem());
		if (! isscript()) break;	/* check for abort */

	} while (e != c);
}

/*
	QUIET <n>

	Wait for <n> milliseconds of quiet line. */

static doquiet(n)
int n;
{
char c;

	if (! n) n= 1000;			/* default to 1 sec */
	chr_timer= 0L;				/* start afresh */
	while (chr_timer < n) {			/* wait for quiet */
		put_modem(get_console());	/* input, output */
		put_console(get_modem());
		if (! isscript()) break;
	}
}

/*
	PAUSE "string"

	Display the message, wait for a key to be hit. */

static dopause(np)
char *np;
{
char c;

	msg(np);				/* display the message, */
	while (! (c= keyhit()) );
	script_abort(c);			/* get a key, check for abort */
}

