/**************************************************************
 *
 *	CRISP - Custom Reduced Instruction Set Programmers Editor
 *
 *	(C) Paul Fox, 1989
 *
 *    Please See COPYRIGHT notice.
 *
 **************************************************************/
# include	<stdio.h>
# include	"list.h"

# define	MAX_ATOMS	32000	/* Max. number of atoms in a macro */
					/* definition.			   */
# define	MAX_STRINGS	2048	/* Max. strings in macro file.	   */
# define	MAX_GLOBALS	256	/* Max. no. of global statements   */
					/* in program.			   */
#ifndef TRUE
# define	TRUE	1
# define	FALSE	0
# endif

/* Resolves dummy references *************/
BUFFER	*bheadp;
BUFFER	*curbp;
WINDOW	*wheadp;
WINDOW	*curwp;
int	*cur_line;
int	*cur_col;
int	nest_level;
LISTV	*argv;
int	pflag = 0;
/*-------------------*/
MACRO	macro_tbl[MAX_MACROS];
u_int32	m_offsets[MAX_MACROS];
int	macro_cnt;
char	*bpath;
extern	int	cm_running;
struct f fps[MAX_FILES];
struct	f	*fp_ptr;
int	a_flag = FALSE;
int l_flag = 0;
static int L_flag = 0;		/* TRUE if we want more disassembly info.	*/
int	s_flag = FALSE;	/* TRUE if size info. only.			*/
int	nglobals;	/* Number of global statements found so far.	*/
u_int32	globals[MAX_GLOBALS]; /* Table of indexes to global statements.	*/

extern DEFINE	*def_head,
		*def_ptr;
extern int cm_version;

FILE	*fp;			/* File pointer for output file.	*/

char	buf[BUFSIZ];		/* Temporary working buffer.		*/
char	*output_file;		/* Name of output file.			*/
CM	cm_header = {CM_MAGIC};	/* Header for output file.		*/
int	string_count;		/* Count of literals in list.		*/
char	**string_table;
char	*str_table;		/* Pointer to string table for disassembly.*/
LIST	*atom_start;		/* Pointer to base of compiled macro.	*/
int	atom_count;		/* Count of atoms in buffer.		*/

extern	BUILTIN builtin[];
extern	int	sizeof_builtin;
extern	BUILTIN *lookup_builtin();

/**********************************************************************/
/*   Following  table  used  to  print atom names out in ASCII. Must  */
/*   be in same order as definitions in cm.h			      */
/**********************************************************************/
char	*atom_names[] = {
	"HALT",
	"INT",
	"STR",
	"LIST",
	"NULL",
	"ID",
	"END",
	"POLY",
	"LIT",
	"RSTR",
	"FLOAT"
	};
/**********************************************************************/
/*   Count of each atom type.					      */
/**********************************************************************/
int	num_atoms[(int) F_FLOAT];

/**********************************************************************/
/*   Prototype definitions.					      */
/**********************************************************************/
void	swap_cm_header PROTO((CM *));
int	do_switches PROTO((int, char **));
void	usage PROTO((void));
void	print_perc PROTO((void));
void	patom PROTO((LIST *, LIST *, char *));
char	*strdup();
char	*get_string();
void	disassemble();
void	write_output_file PROTO((char *));
int	yyparse();
void	yyerror();
void	execute_macro();
u_int32 WGET32();
u_int16 WGET16();

int
main(argc, argv)
int	argc;
char	**argv;
{	int	arg_index = do_switches(argc, argv);
	MACRO	*mp;
	int	exit_status = 0;
	int	print_msg;
	char	obuf[BUFSIZ];
	struct stat sbuf;

	cm_running = TRUE;
	if ((bpath = getenv("BPATH")) == NULL)
		bpath = "/usr/local/crisp/macros";


	fp_ptr = &fps[0]-1;
	if (arg_index >= argc)
		usage();
	print_msg = arg_index < argc - 1;
	for ( ; arg_index < argc; arg_index++) {
		char	*file = argv[arg_index];
		int	len = strlen(file);
		atom_count = 0;
		if (strlen(file) > 3 && strcmp(file + len -3, ".cm") == 0) {
			disassemble(file);
			continue;
			}
		if (print_msg)
			printf("Compiling %s...\n", file);
		if (init_fp(TERMINAL, file) < 0) {
			perror(file);
			continue;
			}

		macro_cnt = 0;
		nglobals = 0;
		if (yyparse() != 0) {
			printf("Compilation of %s unsuccessful.\n", file);
			exit_status = -1;
			continue;
			}

		/***********************************************/
		/*   Open  output  file.  If output file is a  */
		/*   directory,  then  put  the compiled file  */
		/*   in the specified directory.	       */
		/***********************************************/
		if (output_file && stat(output_file, &sbuf) >= 0 &&
			(sbuf.st_mode & S_IFMT) == S_IFDIR) {
			if (len <= 2 || strcmp(file + len - 2, ".m") != 0)
				sprintf(obuf, "%s/%s.cm", output_file, file);
			else
				sprintf(obuf, "%s/%.*s.cm", output_file, len - 2, file);
			}
		else if (output_file == NULL) {
			if (len <= 2 || strcmp(file + len -2, ".m") != 0)
				sprintf(obuf, "%s.cm", file);
			else
				sprintf(obuf, "%.*s.cm", len - 2, file);
			}
		else
			strcpy(obuf, output_file);

		write_output_file(obuf);
		if (a_flag)
			print_perc();
		/*----------------------------------------------
		 *   Dump internal form of macro if asked for.
		 *----------------------------------------------*/
		for (mp = macro_tbl; mp < &macro_tbl[macro_cnt]; mp++) {
			if (l_flag) {
				list_macro(0, mp->m_list);
				printf("\n");
				}
			/*chk_free(mp->m_name);*/
			/*chk_free(mp->m_list);*/
			}

		def_head = NULL;
		def_ptr = NULL;
		output_file = NULL;
		}
	exit(exit_status);
	return 0;
}
void
write_output_file(filename)
char *filename;
{
	int	i;
	u_int32	base;
	long	ftell();
	u_int32	offset;
	LIST	*lp;

	if ((fp = fopen(filename, FOPEN_W_BINARY)) == NULL) {
		perror(filename);
		exit(1);
		}

	if ((atom_start = (LIST *) chk_alloc(MAX_ATOMS)) == NULL) {
		fprintf(stderr, "Not enough room to compile macros\n");
		exit(1);
		}
	if ((string_table = (char **) chk_alloc(sizeof (char *) * MAX_STRINGS)) == NULL) {
		fprintf(stderr, "Not enough room to allocate string table\n");
		exit(1);
		}

	cm_header.cm_num_macros = macro_cnt;
	cm_header.cm_version = cm_version;
	if (fwrite((char *) &cm_header, sizeof (CM), 1, fp) != 1) {
output_error:
		perror(output_file);
		exit(1);
		}

	if (fwrite((char *) m_offsets, sizeof (u_int32), macro_cnt+2, fp) != 
			macro_cnt+2)
		goto output_error;

	base = ftell(fp);
	string_count = 0;
	for (i = 0; i < macro_cnt; i++) {
		int n = macro_tbl[i].m_size;
		LIST	*lpend;
		if (L_flag)
			printf("\n*** Macro %d:\n", i);
		lp = macro_tbl[i].m_list;
		lpend = lp + n;
		m_offsets[i] = (ftell(fp) - (long) base) / sizeof (LIST);
		while (lp < lpend) {
			char *str = "";
			if (*lp == F_STR || *lp == F_LIT)
				str = get_string(lp);
			else if (*lp == F_ID) {
				int id = LGET16(lp);
				if (strcmp(builtin[id].name, "global") == 0) {
					globals[nglobals++] = m_offsets[i] +
						(lp - macro_tbl[i].m_list);
					}
				}
			if (L_flag)
				patom(macro_tbl[i].m_list, lp, str);
			lp += sizeof_atoms[*lp];
			}
		if (fwrite((char *) macro_tbl[i].m_list, sizeof (LIST), n, fp) != n)
			goto output_error;
		}
	if (ftell(fp) & 3)
		fwrite("PAD", (int) (4 - (ftell(fp) & 3)), 1, fp);
	m_offsets[macro_cnt] = ftell(fp) - (long) base;
	m_offsets[macro_cnt+1] = string_count;
	/*------------------------------------------
	 *   Now write out table of string offsets from here.
	 *------------------------------------------*/
	for (offset = 0, i = 0; i < string_count; i++) {
		u_int32 o = WGET32(offset);
		if (fwrite((char *) &o, sizeof o, 1, fp) != 1)
			goto output_error;
		offset += strlen(string_table[i]) + 1;
		}
	/*------------------------------------------
	 *   Now write out string table.
	 *------------------------------------------*/
	for (i = 0; i < string_count; i++) {
		int	len = strlen(string_table[i]);
		if (fwrite(string_table[i], len+1, 1, fp) != 1)
			goto output_error;
		}
	if (ftell(fp) & 3)
		fwrite("PAD", (int) (4 - (ftell(fp) & 3)), 1, fp);
	cm_header.cm_globals = ftell(fp);
	swap_words(globals, nglobals);
	if (nglobals && fwrite((char *) globals, sizeof globals[0] * nglobals, 1, fp) != 1)
		goto output_error;
	rewind(fp);
	cm_header.cm_num_atoms = atom_count;
	cm_header.cm_num_globals = nglobals;
	cm_header.cm_num_strings = string_count;
	swap_cm_header(&cm_header);
	if (fwrite((char *) &cm_header, sizeof (CM), 1, fp) != 1)
		goto output_error;
	swap_words(m_offsets, macro_cnt+2);
	if (fwrite((char *) m_offsets, sizeof (u_int32), macro_cnt+2, fp) != 
			macro_cnt+2)
		goto output_error;
	fclose(fp);

	chk_free((char *) string_table);
	chk_free((char *) atom_start);
}
char *
get_string(lp)
register LIST	*lp;
{
	register char **cpp;
	register char **cpend = &string_table[string_count];
	char	*str = (char *) LGET32(lp);
	static char buf[128];

	for (cpp = string_table; cpp < cpend; cpp++)
		if (**cpp == *str && strcmp(*cpp, str) == 0) {
			strcpy(buf, str);
			LPUT32(lp, (long) (cpp - string_table));
			chk_free(str);
			return buf;
			}
	*cpp = str;
	LPUT32(lp, (long) string_count++);
	return str;
}
void
usage()
{
	fprintf(stderr, "Usage: cm [-aLl] [-o output_file] file-name ...\n\n");
	fprintf(stderr, "	-a	Print atom percentages.\n");
	fprintf(stderr, "	-l	List macro expansions.\n");
	fprintf(stderr, "	-L	Print detailed disassembly info.\n");
	fprintf(stderr, "	-q	Quiet error messages.\n");
	fprintf(stderr, "	-s	Print size of .cm file only.\n");
	fprintf(stderr,	"		(Use with .cm file only).\n");
	fprintf(stderr, "	-o file	Name of compiled output file.\n");
	exit(1);
}

int
do_switches(ac, av)
int	ac;
char	**av;

{
	int	c;
	extern char *optarg;
	int	errflag = 0;
	extern int optind;

	while ((c = getopt(ac, av, "acLqldo:s")) != EOF)
		switch (c) {
			case 'a':	a_flag = TRUE; break;
			case 'l':	l_flag = 1;	break;
			case 'q': {
				break;
				}
			case 'L':	L_flag = 1;	break;
			case 'o':
				output_file = optarg;
				break;
			case 's':	s_flag = TRUE;	break;
			default:
				usage();
			}
	
	if (errflag)
		usage();
	return optind;
}
int
mac_compare(mac1, mac2)
char	*mac1;
MACRO	*mac2;
{
	return strcmp(mac1, mac2->m_name);
}
void
enter_macro(list)
LIST	*list;
{	register MACRO	*mptr;
	char	name[64];

	if (*list != F_STR) {
		printf("Macro name must be an id\n");
		return;
		}
	strncpy(name, (char *) LGET32(list), 64);
	list += sizeof_atoms[F_STR];
	if (macro_cnt && (mptr = (MACRO *) bsearch(name, macro_tbl, macro_cnt, 
			sizeof macro_tbl[0], mac_compare))) {
		/***********************************************/
		/*   delete_macro()  not  yet  implemented --  */
		/*   we just lose memory.		       */
		/***********************************************/
# if 0
		delete_macro(mptr->m_list);
# endif
		}
	else {
		MACRO	*mp_end = &macro_tbl[macro_cnt];
		if (macro_cnt >= MAX_MACROS-1) {
			printf("Macro table full\n");
			return;
			}
		for (mptr = macro_tbl; mptr < mp_end; mptr++)
			if (strcmp(name, mptr->m_name) < 0) {
				for ( ; mp_end >= mptr; mp_end--)
					mp_end[1] = mp_end[0];
				break;
				}
		macro_cnt++;
		mptr->m_name = strdup(name);
		}
	mptr->m_list = list;
}
void
trace_list(lp)
LIST	*lp;
{
}
void
list_macro(level, lp)
int	level;
LIST	*lp;
{
}
void
disassemble(file)
char	*file;
{
	FILE	*fp = fopen(file, "r");
	long	l;
	int	i;
	CM	*cm;
	struct stat stat_buf;
	u_int32	*vm_offsets;
	u_int32	*soffsets;
	int	nm;
	LIST	*lp;
	char	*str;
	LIST	*base_list;

	printf("\n*** File: %s\n\n", file);

	for (i = 0; i < sizeof num_atoms / sizeof num_atoms[0]; )
		num_atoms[i++] = 0;
		
	if (fp == NULL || stat(file, &stat_buf) < 0) {
		perror(file);
		exit(1);
		}
	cm = (CM *) chk_alloc((unsigned) stat_buf.st_size);
	if (read(fileno(fp), (char *) cm, (int) stat_buf.st_size) != 
		(int) stat_buf.st_size) {
		fprintf(stderr, "Read() error on .cm file");
		exit(1);
		}

	/***********************************************/
	/*   Make  sure  header  is  in  the  natural  */
	/*   byte order.			       */
	/***********************************************/
	swap_cm_header(cm);
	if (cm->cm_version != cm_version) {
		fprintf(stderr, ".cm file has wrong version number - %d\n",
			cm->cm_version);
		fprintf(stderr, "Current version is %d\n", cm_version);
		exit(1);
		}

	vm_offsets = (u_int32 *) (cm + 1);

	swap_words(vm_offsets, cm->cm_num_macros + 2);
	base_list = (LIST *) (vm_offsets + cm->cm_num_macros + 2);

	soffsets = (u_int32 *) (((char *) base_list) + 
			vm_offsets[cm->cm_num_macros]);

	str_table = (char *) (soffsets + cm->cm_num_strings);

	swap_words(soffsets, cm->cm_num_strings);			
	if (cm->cm_magic != CM_MAGIC) {
		fprintf(stderr, "%s: invalid magic number\n", file);
		exit(1);
		}
	printf("cm_magic	:	%04x\n", cm->cm_magic);
	printf("cm_version	:	%04x\n", cm->cm_version);
	printf("cm_num_macros	:	%04x\n", cm->cm_num_macros);
	printf("cm_num_atoms	:	%04x\n", cm->cm_num_atoms);
	printf("cm_globals	:	%04x\n", cm->cm_globals);
	printf("cm_num_strings	:	%04x\n", cm->cm_num_strings);
	if (s_flag)
		goto end_of_function;

	printf("\n");
	for (i = 0; i < cm->cm_num_macros; i++)
		printf("Macro %d, offset = atom #%ld\n", i, vm_offsets[i]);
	printf("String table : %08lx\n", 
		vm_offsets[cm->cm_num_macros]);
	printf("\n");

	nm = 0;
	lp = base_list;
	while (lp < base_list + cm->cm_num_atoms) {
		str = "";
		if (*lp == F_STR || *lp == F_LIT) {
			l = LGET32(lp);
			str = str_table + soffsets[l];
			}
		if (strcmp(str, "macro") == 0 && l_flag)
			printf("\n*** Macro %d:\n", nm++);
		patom(base_list, lp, str);
		if (sizeof_atoms[*lp] == 0)
			lp++;
		else
			lp += sizeof_atoms[*lp];
		}

	if (l_flag == 0)
		goto end_of_function;
	printf("String Table:\n");
	for (i = 0; i < cm->cm_num_strings; i++) {
		printf("  String %2d: Offset=%04lx ", i, soffsets[i]);
		fflush(stdout);
		printf("'%s'\n", str_table + soffsets[i]);
		}
	printf("\n");
end_of_function:
	if (a_flag)
		print_perc();
	chk_free((char *) cm);
}
/**********************************************************************/
/*   Function  to  print  out  table of percentage of each atom type  */
/*   used. Used for optimisations.				      */
/**********************************************************************/
void
print_perc()
{	int	i;
	int	natoms = 0;
	int	total_size = 0;
	
	/***********************************************/
	/*   First work out how many atoms in total.   */
	/***********************************************/
	for (i = 0; i < sizeof num_atoms / sizeof num_atoms[0]; i++) {
		natoms += num_atoms[i];
		total_size += num_atoms[i] * sizeof_atoms[i];
		}
		
	/***********************************************/
	/*   Avoid divide by zero problems below.      */
	/***********************************************/
	if (natoms == 0)
		natoms = 1;
	if (total_size == 0)
		total_size = 1;
	/***********************************************/
	/*   Now print out total.		       */
	/***********************************************/
	printf("Type          Count    %%count      size     %%size\n");
	for (i = 0; i < sizeof num_atoms / sizeof num_atoms[0]; i++) {
		printf("%-10s %8d  %8ld  %8ld  %8ld\n",
			atom_names[i],
			num_atoms[i],
			((long) num_atoms[i] * 100) / natoms,
			num_atoms[i] * sizeof_atoms[i],
			((long) num_atoms[i] * sizeof_atoms[i] * 100) / total_size);
		}
	printf("Total:     %8d            %8d\n", natoms, total_size);
}
void
execute_macro(lp)
LIST	*lp;
{	char	name[64];
	char	*macro_keywd;
	extern char	*strdup();
	LIST	*lpn;
	extern int 	sizeof_macro;

	if (macro_cnt >= MAX_MACROS-1) {
		printf("Macro table full\n");
		return;
		}
	lpn = lp + sizeof_atoms[*lp];
	if (*lpn != F_STR && *lpn != F_ID) {
		yyerror("Macro must start with a name\n");
		exit(1);
		}
	strcpy(name, *lpn == F_ID ? 
			builtin[LGET16(lpn)].name : (char *) LGET32(lpn));
	macro_keywd = *lp == F_ID ? 
			builtin[LGET16(lp)].name : (char *) LGET32(lp);
	if (strcmp(macro_keywd, "macro") != 0 && 
	    strcmp(macro_keywd, "replacement") != 0)
		return;
	if (strcmp(macro_keywd, "macro") == 0 && *lpn == F_ID)
		printf("Warning: '%s' redefines a builtin.\n", name);
	macro_tbl[macro_cnt].m_name = strdup(name);
	macro_tbl[macro_cnt].m_size = sizeof_macro;
	atom_count += sizeof_macro;
	if (l_flag) {
		printf("Entering macro '%s'\n", name);
		list_macro(0, lp);
		}
	macro_tbl[macro_cnt].m_list = lp;
	macro_cnt++;
}
void
patom(first_atom, mptr, str)
LIST	*first_atom;
LIST	*mptr;
char	*str;
{
	char	buf[10];
	int	atom_no = mptr - first_atom;

	num_atoms[*mptr]++;

	if (l_flag == 0 || mptr == NULL)
		return;
	if (*mptr == F_STR && strcmp(str, "macro") == 0)
		printf("\n");
	sprintf(buf, "0x%02x", *mptr);
	printf("Atom %02x: ", atom_no);
	if (L_flag) {
		switch (*mptr) {
		  case F_HALT:
		  case F_END:
			printf("[%02x/........] ", *mptr);
			break;
		  case F_LIST:
		  case F_ID:
			printf("[%02x/....%04x] ", *mptr, LGET16(mptr));
			break;
		  default:
			printf("[%02x/%08lx] ", *mptr, LGET32(mptr));
			break;
		  }
		}
		
	switch (*mptr) {
	  case F_HALT:
	  	printf("F_HALT\n");
		break;
	  case F_END:
	  	printf("*** End ***\n");
		break;
	  case F_INT:
	  	printf("F_INT   %08lx\n", LGET32(mptr));
		break;
	  case F_LIT:
	  	printf("F_LIT   \"%s\"\n", str);
		break;
	  case F_STR:
	  	printf("F_STR   \"%s\"\n", str);
		break;
	  case F_FLOAT: {
	  	double	val;
		LGET_FLOAT(mptr, &val);
	  	printf("F_FLOAT  %g\n", val);
		break;
		}
	  case F_ID:
	  	printf("F_ID    %s\n", builtin[LGET16(mptr)].name);
		break;
	  case F_LIST: {
	  	int	i;
	  	printf("F_LIST  ");
		i = LGET16(mptr);
		if (i == 0)
			printf("======\n");
		else
			printf("--> %x\n", atom_no + i);
		break;
		}
	  case F_NULL:
	  	printf("F_NULL\n");
		break;
	  default:
	  	printf("<%02x>\n", *mptr);
	  }

}
void
ewprintf(s, a, b, c, d, e, f, g, h, i, j, k)
char	*s;
int	a, b, c, d, e, f, g, h, i, j, k;
{
	fprintf(stderr, s, a, b, c, d, e, f, g, h, i, j, k);
	fprintf(stderr, "\n");
}
void
errorf(s, a, b, c, d, e, f, g, h, i, j, k)
char	*s;
int	a, b, c, d, e, f, g, h, i, j, k;
{
	fprintf(stderr, s, a, b, c, d, e, f, g, h, i, j, k);
	fprintf(stderr, "\n");
}
int
sys_read(fd, buf, size)
int	fd;
char	*buf;
int	size;
{	int	n;
	int	osize = size;
	do {
		n = read(fd, buf, size);
		size -= n;
		buf += n;
		}
	while (n > 0);
	return osize - size;
}

/* Stub routine. */
void
exec1 (lp1, lp2)
LIST *lp1, *lp2;
{
}
