/*
 * nm [-1bcdfgruv] file
 */
#include <stdio.h>

#define	NSY	500
#define	NPS	20
#define	AB0	0127400
#define AB1	0003243
#define AB2	0127401
#define AB3	0007624
#define RBMAX	128

#define	GSD	1
#define	G_CSN	1
#define	G_ISN	2
#define	G_GSN	4
#define	G_PSN	5
#define	G_DEF	010

typedef unsigned addr_t;
typedef unsigned r50_t;

struct	rb
{
	int	type;
	char	data[RBMAX];
};

struct	gsde
{
	r50_t	g_name[2];
	char	g_flag;
	char	g_type;
	addr_t	g_value;
};

struct	sym
{
	char	s_name[6];
	char	s_flag;
	char	s_type;
	int	s_ps;
	addr_t	s_value;
};

struct	pst
{
	r50_t	p_name[2];
	addr_t	p_size;
};

struct	rb	rb;
struct	sym	sym[NSY];
struct	pst	pst[NPS];
char	*rbe;
int	nsy;
int	nps;
int	cps;
char	*fn;
FILE	*fp;
int	oflag;
int	bflag;
int	cflag;
int	dflag;
int	fflag;
int	gflag;
int	rflag;
int	uflag;
int	vflag;

main(argc, argv)
char *argv[];
{
	register int i, c;
	register char *p;

	fn = NULL;
	for(i=1; i<argc; ++i) {
		p = argv[i];
		if(*p == '-') {
			++p;
			while(c = *p++)
				switch(c) {

				case '1':
					++oflag;
					break;

				case 'b':
				case 'B':
					++bflag;
					break;

				case 'c':
				case 'C':
					++cflag;
					break;

				case 'd':
				case 'D':
					++dflag;
					break;

				case 'f':
				case 'F':
					++fflag;
					break;

				case 'g':
				case 'G':
					++gflag;
					break;

				case 'r':
				case 'R':
					++rflag;
					break;

				case 'u':
				case 'U':
					++uflag;
					break;

				case 'v':
				case 'V':
					++vflag;
					break;

				default:
					usage();
				}
		} else if(fn != NULL)
			usage();
		else
			fn = p;
	}
	if(fn == NULL)
		usage();
	if((fp=fopen(fn, "ru")) == NULL)
		error("%s: cannot open\n", fn);
	readin();
	if(dflag)
		deltas();
	if(!fflag)
		sort(vflag, rflag);
	output();
}

/*
 * Put out usage meessage.
 * Just exit.
 */
usage()
{
	error("Usage: nm [-1bcdfgruv] file\n");
}

/*
 * Read through the object or
 * symbol table file.
 * Save all of the symbols in
 * the big buffer.
 * Just exit if the file format
 * is wrong, or if a table
 * overflows.
 */
readin()
{
	register struct gsde *p;
	register struct sym  *sp;
	register int t;
	char ab[6];

	if(!getrec() || rb.type!=GSD)
		error("%s: bad file format\n", fn);
	do {
		if(rb.type != GSD)
			continue;
		for(p=rb.data; p<rbe; ++p) {
			t = p->g_type;
			if(t==G_CSN || t==G_PSN)
				cps = lookup(p);
			else if(t==G_GSN || t==G_ISN) {
				if(gflag && t==G_ISN)
					continue;
				if(!uflag && (p->g_flag&G_DEF)==0)
					continue;
				convert(ab, p->g_name);
				if(!cflag && csym(ab))
					continue;
				if(!bflag && bsym(ab))
					continue;
				if(nsy >= NSY)
					error("Too many symbols!\n");
				sp = &sym[nsy++];
				copy(sp->s_name, ab, 6);
				sp->s_type = t;
				sp->s_flag = p->g_flag;
				sp->s_ps = cps;
				sp->s_value = p->g_value;
			}
		}
	} while(getrec());
}

/*
 * Read a record from the
 * file into the record
 * buffer.
 * True return if the read
 * was ok.
 * Set up rbe to point just
 * beyond the record.
 */
getrec()
{
	register int n;

	n = fget(&rb, sizeof(rb), fp);
	if(feof(fp))
		return(0);
	rbe = &rb;
	rbe += n;
	return(1);
}

/*
 * Convert 6 radix 50 characters
 * to ascii.
 * Map `$' to `_' to agree
 * with the assembler.
 */
convert(a, r)
register char *a;
r50_t *r;
{
	register int n;

	r50toa(a, r, 2);
	n = 6;
	do {
		if(*a == '$')
			*a = '_';
		++a;
	} while(--n);
}

/*
 * Test if a 6 character symbol
 * is a C internal symbol.
 * These symbols are all numeric
 * except for a leading dot.
 */
csym(p)
register char *p;
{
	register int c, n;

	if(*p++ != '.')
		return(0);
	n = 5;
	do {
		c = *p++;
		if(c!=' ' && (c<'0' || c>'9'))
			return(0);
	} while(--n);
	return(1);
}

/*
 * Test if a 6 character symbol
 * is an RSX control block
 * symbol.
 * These symbols have the form
 * x.xxxx xx.xxx x_xxxx or xx_xxx
 */
bsym(p)
register char *p;
{
	if(p[1]=='.' || p[1]=='_' || p[2]=='.' || p[2]=='_')
		return(1);
	return(0);
}

/*
 * Lookup a psection.
 * Make new entry if new psection.
 * In any casee return the
 * psection number.
 */
lookup(g)
register struct gsde *g;
{
	register struct pst *p;
	register int i;

	for(i=0; i<nps; ++i)
		if(match(pst[i].p_name, g->g_name))
			return(i);
	if(nps >= NPS)
		error("Too many sections!\n");
	p = &pst[nps];
	p->p_name[0] = g->g_name[0];
	p->p_name[1] = g->g_name[1];
	p->p_size = g->g_value;
	return(nps++);
}

/*
 * Compare psection names.
 * Names are 2 words of (ugh)
 * radix 50.
 */
match(a, b)
register int *a, *b;
{
	if(a[0]==b[0] && a[1]==b[1])
		return(1);
	return(0);
}

/*
 * Run through the symbols, 
 * computing the first order
 * differencees for the -d option.
 * The last symbol requires
 * special handling.
 * For unknown reasons, the
 * last symbol sometimes gets
 * a funny size in spite of
 * this.
 */
deltas()
{
	register struct sym *p;

	sort(1, 0);
	for(p=&sym[0]; p<&sym[nsy-1]; ++p)
		p->s_value = (p+1)->s_value - p->s_value;
	p->s_value = pst[p->s_ps].p_size - p->s_value;
}

/*
 * Sort symbols.
 * v is 1 for sort on value.
 * r is 1 for reverse sort.
 */
sort(v, r)
{
	register struct sym *b, *m, *p;
	struct sym ts;

	for(b=&sym[0]; b<&sym[nsy-1]; ++b) {
		m = b;
		for(p=b+1; p<&sym[nsy]; ++p)
			if(cmp(v, r, m, p))
				m = p;
		if(m != b) {
			copy(&ts, m, sizeof(ts));
			copy(m,   b, sizeof(ts));
			copy(b, &ts, sizeof(ts));
		}
	}
}

/*
 * Compare routine for
 * sort.
 */
cmp(v, r, a, b)
register struct sym *a, *b;
{
	register int n;
	int c;

	c = 0;
	if(v) {
		if(a->s_value > b->s_value)
			c = 1;
	} else
		for(n=0; n<6; ++n) {
			if(a->s_name[n] > b->s_name[n]) {
				c = 1;
				break;
			}
			if(a->s_name[n] < b->s_name[n])
				break;
		}
	if(r)
		c = !c;
	return(c);
}

/*
 * Walk through the symbols, 
 * formatting them and writing
 * the result to the standard
 * output.
 * Undefined symbols get stars
 * as a value.
 */
output()
{
	register struct sym *p;
	register r50_t *np;
	register int ntl;
	int max;
	char ab[6];

	max = oflag ? 1 : 3;
	ntl = 0;
	for(p=&sym[0]; p<&sym[nsy]; ++p) {
		if(ntl >= max) {
			putchar('\n');
			ntl = 0;
		} else if(p != &sym[0])
			putchar(' ');
		printf("%.6s", p->s_name);
		printf(" %c ", p->s_type==G_GSN?'g':' ');
		if((p->s_flag&G_DEF) != 0)
			printf("%06o", p->s_value);
		else
			printf("******");
		np = pst[p->s_ps].p_name;
		if((np[0]==AB0 && np[1]==AB1) || (np[0]==AB2 && np[1]==AB3))
			printf("         ");
		else {
			convert(ab, np);
			printf(" (%.6s)", ab);
		}
		++ntl;
	}
	if(ntl)
		putchar('\n');
}
