/*****************************************************************
 *  GlueMaker - makes glue routines from .fd files - by Talin    *
 *  Copyright (c) 1989 Elf Tech                                  *
 *****************************************************************
 *  Revision History:                                            *
 *    Version 1.0 - Released 4 Feb 1989                          *
 *****************************************************************/

#include "exec/types.h"
#include "exec/memory.h"
#include "libraries/dosextens.h"
#include "setjmp.h"
#include "stdio.h"

#define	NEWLINE			0x0a
#define RETURN			0x0d
#define TAB				0x09

#define MEM_SIZE		30000		/* 30 K */

/* file buffer variables */

void					*AllocMem();
APTR	 				Open();
LONG					in_size;				/* size of input file (.fd) */
char 					*memstart=NULL,			/* start of file in memory */
						*memend;				/* end of file in memory */

/* parsing variables */

char					*scan,					/* current scan point in source */
						*put,					/* address of string to insert */
						*name,
						*line_start,			/* start of current line */
						*find_line_end();		/* find ending CR */
LONG					nval,					/* numeric value parsed */
						txlen,					/* text string parsed */
						digits,					/* # of digits parsed */
						linenum;				/* current line number */

UBYTE					priv_flag,				/* ##private flag */
						jump_flag;				/* jump flag */

short					reg_num,		/* register number, 0-15 */
						regs_saved,		/* number of registers saved on stack */
						arg_count;

UWORD					regs_used;		/* registers used, flag bits */

BYTE					reg_args[16],	/* registers passed as parameters */
						reg_sizes[16],	/* and what size the args were */
						reg_type[16];	/* if we needed an lea */

UBYTE					savestring[50];	/* movem register list for save/restore */
UBYTE					argstring[50];	/* movem register list for unstack */

/* te following table defines which regs may be used, which need to be restored,
	etc. It may be changed to alter the register model.
*/

#define REG_SCRATCH		0				/* ok to use this register as scratch */
#define REG_PRESERVE	1				/* preserve this register */
#define	REG_SPECIAL		2				/* leave this register alone */

UBYTE reg_protect[] = {
	REG_SCRATCH,						/* d0 */
	REG_SCRATCH,						/* d1 */
	REG_SCRATCH,						/* d2 */
	REG_SCRATCH,						/* d3 */
	REG_PRESERVE,						/* d4 */
	REG_PRESERVE,						/* d5 */
	REG_PRESERVE,						/* d6 */
	REG_PRESERVE,						/* d7 */
	REG_SCRATCH,						/* a0 */
	REG_SCRATCH,						/* a1 */
	REG_PRESERVE,						/* a2 */
	REG_PRESERVE,						/* a3 */
	REG_PRESERVE,						/* a4 */
	REG_PRESERVE,						/* a5 */
	REG_SPECIAL,						/* a6 */
	REG_SPECIAL,						/* a7 */
};

jmp_buf env;							/* setjmp for errors */

UBYTE	boldstring[] = { 0x1b, '[' };	/* to print boldface on CON: */
UBYTE	unboldstring[] = { 0x1b, '[' };	/* to print normal on CON: */

#define SCAT	goto exit_pgm;			/* standard exit macro */
#define EOB(x)	(x >= memend)			/* end of buffer */

char *seek_char(), *reg_string(),*rindex();

APTR	gluefile=NULL;					/* file handle of current output file */
extern int Enable_Abort;				/* used by Chk_Abort */

main(argc, argv) LONG argc; UBYTE **argv;
{	APTR				infile=NULL, makefile=NULL, vectorfile=NULL, lvofile=NULL;
	short				jmp=0,
						length;

	static char		/*	yn[2],	*/			/* yes or no */
						libname[40],		/* name of library we are gluemaking */
						lvoname[40],		/* name of lvo file */
						filename[80],
						funcname[40],		/* name of current .fd function */
						basename[40],		/* name of library base */
						jumpname[40],		/* for ##jump directive */
						vectorfilename[80],	/* pathname of 'vector.i' file */
						gluefilepath[80],	/* path for glue files */
						gluefilename[120];	/* name of current output file */

	LONG				flength,			/* lengths of above strings */
						mflength,
						vflength,
						gplength=0;

	if (!Input()) return 0;					/* can't run from workbench */

	Enable_Abort = 0;						/* don't abort, let me handle it */

	if (jmp = setjmp(env)) SCAT;			/* set jump for error */

	if (argc != 2)
	{	puts("GlueMaker - By Talin.");
		puts("Usage: GlueMaker <input file>");
		puts("");
		puts("The glue file has 5 parameters:");
		puts("FDFILE=filename         (name of funcdef file, '.fd' added.)");
		puts("OUTDIR=dirname          (name of directory to put asm routines)");
		puts("LIBFILE=filename        (name of resulting link library, '.lib' added)");
		puts("PROTECT                 (use this to preserve d2/d3 in glue routines)");
		puts("");
		SCAT;
	}

	{	FILE *fd = fopen(argv[1],"r");
		char inbuf[128],*c;

		if (!fd) error("Cannot open input file.");

		while (fgets(inbuf,128,fd))
		{	if (inbuf[0])
			{	if (c = rindex(inbuf,'\n')) *c = 0;
				if (!strncmp(inbuf,"FDFILE=",7)) strcpy(filename,&inbuf[7]);
				else if (!strncmp(inbuf,"OUTDIR=",7)) strcpy(gluefilepath,&inbuf[7]);
				else if (!strncmp(inbuf,"LIBFILE=",8)) strcpy(libname,&inbuf[8]);
				else if (!strncmp(inbuf,"PROTECT",7))
				{	reg_protect[2] = reg_protect[3] = REG_PRESERVE; }
				else error("Unrecognized option");
			}
		}
		gplength = strlen(gluefilepath);

		fclose(fd);
	}
	
	if (gplength > 0 && gluefilepath[gplength-1] != ':')
		strcat(gluefilepath,"/");

	strcat(filename,".fd");					/* name of input file */

	if (!(memstart = AllocMem(MEM_SIZE,0))) error("Not Enough Memory.");
	if (!(infile = Open(filename,MODE_OLDFILE))) error("Couldn't open input file.");

	in_size = Read(infile,memstart,MEM_SIZE);
	Close(infile); infile = NULL;

	if (in_size >= MEM_SIZE) error("Input File Too Big.");
	printf("Read %ld bytes from source file.\n",in_size);

	line_start = scan = memstart;
	memend = memstart + in_size;

	priv_flag = jump_flag = FALSE;

	/* now build the LVO file path string */

	strcpy(gluefilename,gluefilepath);
	strcat(gluefilename,libname);
	strcat(gluefilename,"_lvo.asm");

	if (!(lvofile = Open(gluefilename,MODE_NEWFILE)))
		error("Couldn't open lvo file.");

	strcpy(vectorfilename,gluefilepath);
	strcat(vectorfilename,libname);
	strcat(vectorfilename,"_lvo.i");

	if (!(vectorfile = Open(vectorfilename,MODE_NEWFILE)))
		error("Couldn't open vector file.");

	strcpy(gluefilename,gluefilepath);
	strcat(gluefilename,"makefile");

	if (!(makefile = Open(gluefilename,MODE_NEWFILE)))
		error("Couldn't open make file.");

	/* now, start parsing the file */

	APrintf(makefile,"#\n# makefile for %s.lib - created by GlueMaker\n#\n\n",libname);
	APrintf(makefile,".asm.o:\n		as -D -C -o $@ $*.asm\n\n");
	APrintf(makefile,"OBS=");

	APrintf(vectorfile,"* Vector list for glue routines.\n\n");

	while (TRUE)
	{	abortcheck();
		if (match_word("##base"))
		{	/* get the global base variable... */
			whitespace();
			put = basename;
			if (!get_symbol(40)) fatal("Base name too long.");
			if (txlen == 0) fatal("Invalid base name.");
			printf("Library Base Pointer is <%s>.\n",basename);
		}
		else if (match_word("##jump"))
		{	whitespace();
			put = jumpname;
			if (!get_symbol(40)) fatal("Jump name too long.");
			if (txlen == 0) jump_flag = FALSE; else jump_flag = TRUE;
		}
		else if (match_word("##bias"))
		{	get_number();
			/* get the library bias... */
			/* I don't know how the library bias is used, so... */
			printf("Bias is: <%ld>.\n",nval);
		}
		else if (match_word("##private"))
		{	/* set the 'private' flag.. (don't generate glue files) */
			priv_flag = TRUE;
		}
		else if (match_word("##public")) priv_flag = FALSE;		/* set private off */
		else if (match_word("##end")) break;					/* done */
		else if (match_word("*")) { ; }							/* comment */
		else
		{	/* it's a definition */
			put = funcname;
			if (!get_symbol(40)) fatal("Function name too long.");
			if (txlen == 0) fatal("Invalid symbol name.");
			printf("Defining %s.\n",funcname);
			if (match_char('('))			/* function arguments... */
			{	short i;
				if (seek_char(')'))			/* look for closing paren */
				{	regs_saved = 0;
					regs_used = NULL;
					arg_count = 0;

					if (match_char('('))	/* registers used */
					{	while (TRUE)
						{	if (match_char(')')) break;
							reg_sizes[arg_count] = 4;			/* size = longword */
							reg_type[arg_count] = 0;			/* normal */
							if (match_char('&')) reg_type[arg_count] = 1;

							if (get_register())
							{	if (match_word(".l") || match_word(".L"))
									reg_sizes[arg_count] = 4;
								else if (match_word(".w") || match_word(".W"))
									reg_sizes[arg_count] = 2;
								else if (match_word(".b") || match_word(".B"))
									reg_sizes[arg_count] = 1;

								reg_args[arg_count++] = reg_num;
								if (arg_count >= 16) fatal("Too many arguments");
								if (reg_protect[reg_num] == REG_SPECIAL)
									fatal("Can't use that register.");
								else if (regs_used & (1<<reg_num))
									fatal("Already used that register");

								regs_used |= 1<<reg_num;
								if (reg_protect[reg_num] == REG_PRESERVE)
									regs_saved++;

								/* now get the delimeter... */
								if (match_char('/') || match_char(',')) ;
							}
							else fatal ("Delimeter Expected.");
						}
 					}

					/* now, write out the routine...*/

					abortcheck();

					APrintf(vectorfile,"			LIBVEC	%s\n",funcname);

					if (priv_flag) goto priv;

					APrintf(makefile,"	%s.o \\\n",funcname);

					strcpy(gluefilename,gluefilepath);
					strcat(gluefilename,funcname);
					strcat(gluefilename,".asm");

					if (!(gluefile = Open(gluefilename,MODE_NEWFILE)))
						error("Couldn't open glue source file.");

/*					APrintf(gluefile,"\n**** Glue File: %s.c ****\n",funcname); */
					APrintf(gluefile,"* Glue routine for %s() - created by GlueMaker\n",funcname);
					APrintf(gluefile,"*\n");
					APrintf(gluefile,"*	");
					Write(gluefile,line_start,scan-line_start);
					APrintf(gluefile,"\n*\n");
					APrintf(gluefile,"			xref	%s,_LVO%s\n",
						basename,funcname);
					APrintf(gluefile,"			xdef	_%s\n",funcname);
					if (jump_flag) APrintf(gluefile,"			xref	%s\n",
						jumpname);
					APrintf(gluefile,"_%s:\n",funcname);
					if (regs_saved > 1)
					{	reg_move_string();			/* create movem string */
						APrintf(gluefile,"			movem.l	%s,-(sp)\n",savestring);
					}
					else if (regs_saved)
					{	reg_move_string();
						APrintf(gluefile,"			move.l	%s,-(sp)\n",savestring);
					}

					reg_params(4+4*regs_saved);

					APrintf(gluefile,"			move.l	%s,a6\n",basename);
					if (regs_saved)
					{	APrintf(gluefile,"			jsr		_LVO%s(a6)\n",funcname);

						if (regs_saved==1)
						{	APrintf(gluefile,"\t\t\tmove.l	(sp)+,%s\n",savestring);
						}
						else APrintf(gluefile,"\t\t\tmovem.l	(sp)+,%s\n",savestring);
						if (jump_flag)
							APrintf(gluefile,"\t\t\tbra		%s\n",jumpname);
						else APrintf(gluefile,"\t\t\trts\n");
					}
					else
					{	if (jump_flag)
							APrintf(gluefile,"\t\t\tpea		%s\n",jumpname);
						APrintf(gluefile,"\t\t\tjmp		_LVO%s(a6)\n",funcname);
					}

					Close(gluefile);
					gluefile = NULL;

					priv: ;
				}
				else fatal("Matching parenthesis needed");
			}
			else fatal("Arguments not defined.");
			/* now, skip the arguments */
			/* and get to the registers... (if any) */
		}
		/* skip to the next line - check for garbage on line? */
		seek_eol();
	}
	APrintf(makefile,"\n\nall: $(OBS) %s_lvo.o\n		lb %s.lib $(OBS) %s_lvo.o\n\n",libname,libname,libname);

	APrintf(lvofile,
		"* ======================================================================\n");
	APrintf(lvofile,
		"* Library Vector File for %s.lib\n",libname);
	APrintf(lvofile,
		"* Created By GlueMaker\n");
	APrintf(lvofile,
		"* ======================================================================\n");
	APrintf(lvofile,"\n");
	APrintf(lvofile,"\t\t\tINCLUDE\t\"exec/types.i\"\n");
	APrintf(lvofile,"\t\t\tINCLUDE\t\"exec/libraries.i\"\n");
	APrintf(lvofile,"\n");
	APrintf(lvofile,"LIBVEC\t\tMACRO\n");
	APrintf(lvofile,"\t\t\txdef\t_LVO\\1\n");
	APrintf(lvofile,"_LVO\\1\t\tequ\t\tCOUNT_LIB\n");
	APrintf(lvofile,"COUNT_LIB\tset\t\tCOUNT_LIB-LIB_VECTSIZE\n");
	APrintf(lvofile,"\t\t\tendm\n");
	APrintf(lvofile,"\n");
	APrintf(lvofile,"\t\t\tLIBINIT\n");
	APrintf(lvofile,"\n");
	APrintf(lvofile,"\t\t\tinclude\t\t\"%s_lvo.i\"\n",libname);
	APrintf(lvofile,"\n");
	APrintf(lvofile,"\t\t\tend\n");

	puts("Exiting.");
exit_pgm:
	if (gluefile) Close(gluefile);
	if (lvofile) Close(lvofile);
	if (makefile) Close(makefile);
	if (vectorfile) Close(vectorfile);
	if (memstart) FreeMem(memstart,MEM_SIZE);
}

/* print fatal global error message */

error(message) char *message;
{	printf("ERROR: %s.\n",message);
	longjmp(env,1);
}

abortcheck()
{	if (Chk_Abort())
	{	puts("Aborted.");
		longjmp(env,1);
	}
}

/* print fatal error message caused by specific line */

fatal(message) char *message;
{	char *current;
	current = scan;
	skip_line();
	offending_line(current,scan);
	printf("ERROR: %s on line %ld.\n",message,linenum);
	longjmp(env,2);
}

/* print warning message */

warn(message) char *message;
{	char *end;
	end = find_line_end();
	offending_line(scan,end);
	printf("WARNING: %s on line %ld.\n",message,linenum);
}

char underline_on[] = { 0x9b,'4','m',0 };
char underline_off[] = { 0x9b,'0','m',0 };

/* string copy with limits; works slightly different from strncpy() */

char *scpy(str1,str2,n) register char *str1,*str2; register long n;
{	char *hold;
	hold = str1;
	while (n-- && (*str1++ = *str2++)) ;
	*str1 = '\0';
	return (hold);
}

/* print out the line that caused the error */

offending_line(a,b) char *a,*b;
{	char stuff[80];
	int i,j;
	i = a - line_start;
	j = b - a;
	if (i > 79)
	{	scpy(stuff,line_start,79);
		printf("%s$\n",stuff);
		return;
	}
	scpy(stuff,line_start,i);
	printf("%s%s",stuff,underline_on);
	if (i+j > 79)
	{	scpy(stuff,a,79-i);
		printf("%s$",stuff);
	}
	else
	{	scpy(stuff,a,j);
		printf(stuff);
	}
	printf("%s\n",underline_off);
}

match_char(c) char c;
{	whitespace();
	if (*scan == c) { scan++; return TRUE; }
	return FALSE;
}

/* match a word, case sensitive */

match_word(p) char *p;
{	char *s;

	whitespace();
	s = scan;
	while (*p) if (*s++ != *p++) return FALSE;
	scan = s;
	return TRUE;
}

/* parse a number */

get_number()
{	whitespace();
	digits = nval = 0;
	while (*scan >= '0' && *scan <= '9')
	{	nval = nval * 10 + (*scan++ - '0');
		digits++;
	}
}

/* expect a symbol name */

get_symbol(maxlen) short maxlen;
{	txlen = 0;
	while ( (*scan >= 'a' && *scan <= 'z') ||
			(*scan >= 'A' && *scan <= 'Z') ||
			(*scan >= '0' && *scan <= '9') ||
			 *scan == '_' )
	{	if (txlen >= maxlen-2) { *put++=0; return FALSE; }
		*put++=*scan++; txlen++;
	}
	*put++ = 0;
	return TRUE;
}

/* skip until a certain character is found */

char *seek_char(c) char c;
{	char *s;
	s = scan;
	while (!EOB(s) && *s != RETURN && *s != NEWLINE && *s != ';')
	{	if (*s++ == c) return scan=s; }
	return NULL;
}

/* skip over blank characters */

whitespace()
{	while (*scan == ' ' || *scan == TAB) scan++;
}

/* skip this line, maintain error-handling info */

seek_eol()
{	while (!(EOB(scan)))
	{	if (*scan == RETURN || *scan == NEWLINE)
		{	linenum++;
			line_start = scan + 1;
			scan++;
			break;
		}
		else scan++;
	}
}

skipcomma()
{	whitespace();
	if (*scan == ',') scan++;
}

skip()
{	while (!EOB(scan) && *scan != NEWLINE && *scan != RETURN) scan++;
}

skip_line()
{	scan = find_line_end();
}

/* find end of line, do not change error-handling info */

char *find_line_end()
{	char *at;
	at = scan;
	while (*at != RETURN && *at != NEWLINE) at++;
	return at;
}

/* parse a register name */

get_register()
{	char c;
	if (match_char('a') || match_char('A')) reg_num = 8;
	else if (match_char('d') || match_char('D')) reg_num = 0;
	else return FALSE;
	c = *scan++;
	if (c >= '0' && c <= '9') reg_num += (c - '0');
	else return FALSE;
	return TRUE;
}

char	rstring[3] = "A0";

/* return a register name, given the number */

char *reg_string(regnum) short regnum;
{	ins_regname(regnum,rstring);
	return rstring;
}

/* insert a register name into a byte array */

ins_regname(regnum,string) short regnum; char *string;
{	if (regnum & 8) *string++ = 'A'; else *string++ = 'D';
	*string++ = '0' + (regnum & 7);
}

/* generate a 'movem'-style register string. */

UBYTE	regbuf[17];			/* 16 registers */

/* I use 17 as a sentinel case */

reg_move_string()
{	short 		i, reg, runstart, runlength, runflag;
	UBYTE		*str=savestring;

		/* first, clear all the elements in the array to zero */

	for (i=0; i<17; i++) regbuf[i] = 0;

		/* now, mark off the registers that need to be saved */

	for (i=0; i<arg_count; i++)
	{	reg = reg_args[i];			/* get the register that was used */
		if (reg_protect[reg] == REG_PRESERVE)
		{	regbuf[reg] = 1;
		}
	}

		/* now, figure out which ones have runs... */

	runstart = -1;			/* not in a run */
	for (i=0; i<17; i++)
	{	/* start of run */
		if (runstart >= 0)
		{	if (regbuf[i] == 0 || i == 8)	/* cut off run starting at A0 */
			{	runlength = i - runstart;
				if (str != savestring) *str++ = '/';	/* seperator */
				if (runlength==1) { ins_regname(runstart,str); str += 2; }
				else
				{	ins_regname(runstart,str); str+=2;
					*str++ = '-';
					ins_regname(i-1,str); str+=2;
				}
				runstart = -1;
			}
		}
		if (runstart < 0 && regbuf[i]) { runstart = i; }
	}
	*str++ = 0;
}

/* see which parameters can be filled via 'movem' */

reg_params(offset)
{	short		i, last, runlength, reg;
	UBYTE		*str=argstring;

	last = -1;						/* last register pushed (none) */
	runlength = 0;

	if (arg_count == 0) return;

	for (i=0; i<arg_count; i++)
	{	reg = reg_args[i];
		if (reg > last && !reg_type[i] && runlength >= 0) runlength++;
		else
		{	*str++ = 0;

			if (runlength < 0)
				APrintf(gluefile,"\t\t\tlea.l	%ld(sp),%s\n",offset,argstring);
			else if (runlength)
				APrintf(gluefile,"\t\t\tmovem.l	%ld(sp),%s\n",offset,argstring);
			else APrintf(gluefile,"\t\t\tmove.l	%ld(sp),%s\n",offset,argstring);

			if (runlength < 0) offset += 4;
			else offset += (runlength * 4);
			str = argstring;

			if (reg_type[i]) runlength = -1;
			else runlength = 1;
		}

		if (runlength>1) *str++ = '/';
		ins_regname(reg,str); str+=2;
		
		last = reg;
	}

	*str++ = 0;

	if (runlength < 0) APrintf(gluefile,"\t\t\tlea.l	%ld(sp),%s\n",offset,argstring);
	else if (runlength) APrintf(gluefile,"\t\t\tmovem.l	%ld(sp),%s\n",offset,argstring);
	else APrintf(gluefile,"\t\t\tmove.l	%ld(sp),%s\n",offset,argstring);
}

#define MAXLINE		128

APTR		afile;
UWORD		APrint_size;
char		abuf[MAXLINE+1];

a_put_char(c) char c;
{	abuf[APrint_size++] = c;
	if (APrint_size >= MAXLINE || c == '\n')
	{	Write(afile,abuf,APrint_size);
		APrint_size = 0;
	}
}

/* APrintf - printfs into AmigaDOS file handle */

APrintf(file,string,args) APTR file; char *string; LONG args;
{	APrint_size = 0;							/* start at zero */
	afile = file;
	format(a_put_char,string,&args);					/* format the string */
	if (APrint_size) { Write(file,abuf,APrint_size); APrint_size = 0; }
}

GetLine(string,length) char *string; short length;
{	char *c;
	fgets(string,length,stdin);
	if (c = rindex(string,'\n')) *c = 0;
	abortcheck();
	return strlen(string);
}
