/*
 *              cvobj  infile  outfile
 *
 *      Convert Interdata OBJ file to UNIX a.out file
 */

/* Relocation bits */
#define	RABS	00
#define	RTEXT	02
#define	RDATA	04
#define	REXT	010
#define	RHI	01

/* Symbol types */
#define	SABS	01
#define	STEXT	02
#define	SDATA	03
#define	SEXT	040

/* a.out header */

struct {
	int mword;
	int tsize;
	int dsize;
	int bsize;
	int ssize;
	int entrypt;
	int unused;
	int rflag;
} hdr;

/* External reference chain */
struct chain {
	struct chain *next;	/* link to next entry		*/
	int	defloc;		/* location of reference	*/
	int	refseg;		/* segment referred to		*/
				/* (ext ref: -(symbol no)	*/
	int	refloc;		/* location referred to		*/
				/* (ext ref: offset)		*/
};

/* Segment information */
struct segment {
	int loc;		/* location counter		*/
	int maxloc;		/* highest location so far	*/
	int offset[2];		/* file offsets - code, reloc	*/
	int nchar;		/* no. chars in buffers		*/
	struct chain *first;	/* chain of ext refs		*/
	char obuf[2][512];	/* buffers - code, reloc bits	*/
} pure, impure;

/* buffer for symbol table */
int	symbuf[128];
int	nsym;			/* no. words in buf		*/
int	symoff;			/* file offset of sym tab	*/
int	symno;			/* next available sym number	*/

/* table of common blocks */
#define	NCOM	32
struct combuf {
	int com_name[2];	/* name of common block */
	int com_sym;		/* symbol number */
	int com_len;		/* length of block */
	int com_off;		/* offset of beginning of block */
} combuf[NCOM];

struct combuf	*lastcom combuf;
int	bdata;			/* block data flag */

extern int	end;
struct segment	*curseg	{ &impure };
struct chain	*top	{ &end	};
struct chain	*avail	{ &end	};

/* tricky structure for accessing bytes of a word */
struct	{ char byte[];	};

int	ofile;

main(argc, argv)
char *argv[];
{
	if (argc != 3)
		error("Usage: cvobj infile outfile");
	close(0);
	if (open(argv[1], 0) != 0)
		error("Can't find input file");
	if ((ofile = creat(argv[2], 0666)) < 0)
		error("Can't create output file");

	pass1();

	/* If block data program, make another pass through input */
	if (bdata) {
		lseek(0, 0, 0);
		close(ofile);
		if ((ofile = creat(argv[2], 0666)) < 0)
			error("Can't create output file");
		blkdata();
	}

	putbuf(&pure);
	putbuf(&impure);
	putsymbuf();
	hdr.ssize = symno * 16;
	hdr.mword = 0407;
	lseek(ofile, 0, 0);
	write(ofile, &hdr, sizeof hdr);
	if (bdata == 0)
		pass2();
	exit(0);
}

pass1()
{
	register c, n;
	register struct combuf *cp;
	int name[2], word;

	readobj();

	for(;;) switch (c = cget()) {

	case 1:		/* End of program */
		return;

	case 2:		/* Reset sequence number */
		break;

	case 3:		/* Block data initialization */
		cp = getcom();
		cp->com_off = 1;	/* flag for second pass */
		bdata++;
		skip(3);
		break;

	case 5:		/* Pure address */
		curseg = &pure;
		goto addr;

	case 6:		/* Impure address */
		curseg = &impure;
addr:
		if ((n = get(3) - curseg->loc) != 0)
			org(n);
		break;

	case 11:	/* Common reference */
		cp = getcom();
		curseg->loc =- 4;
		dchain(-cp->com_sym, get(3));
		curseg->loc =+ 4;
		break;

	case 13:	/* Entry */
		name[0] = get(4);
		name[1] = get(4);
		symbol(name);
		symno++;
		switch (cget()) {
		case 4:
			putsym(SEXT+SABS, get(3));
			break;
		case 5:
			putsym(SEXT+STEXT, get(3));
			break;
		case 6:
			putsym(SEXT+SDATA, get(3) + hdr.tsize);
			break;
		default:
			error("Unknown entry symbol type");
		}
		break;

	case 14:	/* Common definition */
		cp = putcom();
		symno++;
		cp->com_len = n = get(3);
		putsym(SEXT, n);
		break;

	case 15:	/* Label */
		skip(8);
		break;

	case 16:	/* 3 bytes abs, 3 bytes pure */
		put(get(2), RABS);
	case 9:		/* 4 bytes pure */
		word = n = get(4);
		dchain(&pure, n&03777777);
		if (c==9)
			word.byte[0] = '\0';
		put(word>>16, RTEXT+RHI);
		put(n&0177777, RTEXT);
		break;

	case 17:	/* 3 bytes abs, 3 bytes impure */
		put(get(2), RABS);
	case 10:	/* 4 bytes impure */
		word = n = get(4);
		dchain(&impure, n&03777777);
		n = word.byte[0];
		word =+ hdr.tsize;
		word.byte[0] = c == 10 ? '\0' : n;
		put((n=word)>>16, RDATA+RHI);
		put(n&0177777, RDATA);
		break;

	case 18:	/* Entry address */
	case 19:	/* Chain definition address */
		skip(4);
		break;

	case 21:	/* 2 bytes abs, 2 bytes pure */
		put(get(2), RABS);
	case 7:		/* 2 bytes pure */
		put(get(2), RTEXT);
		break;

	case 22:	/* 2 bytes abs, 2 bytes impure */
		put(get(2), RABS);
	case 8:		/* 2 bytes impure */
		put(get(2) + hdr.tsize, RDATA);
		break;

	case 12:	/* EXTRN */
		name[0] = get(4);
		name[1] = get(4);
		symbol(name);
		putsym(SEXT, 0);
		switch (cget()) {
		case 5:
			mkchain(&pure, get(3));
			break;
		case 6:
			mkchain(&impure, get(3));
			break;
		default:
			error("Unknown extrn address type");
		}
		symno++;
		break;

	case 24:	/* Length of impure & pure segments */
		n = get(3);
		reset(n, get(3));
		break;

	case 25:	/* Perform fullword chain */
	case 27:	/* No-op	*/
		break;

	/*
	 * New code (undocumented!) produced by CAL-R05
	 *	- code 31 apparently means 'one byte absolute'
	 *	  It will be handled correctly only if followed immediately
	 *	  by an 'org' or another 1-byte code
	 */
	case 31:	/* 1 byte absolute? */
		put1byte(get(1));
		break;

	default:	/* Absolute data */
		if ((c =- 31) <= 0 || c > 60)
			error("Unknown control item");
		if (bdata)
			skip(c*2);
		else while (c--)
			put(get(2), RABS);

	}
}

/*
 * Define a common block and write its name to the symbol table
 * (returns pointer to common table entry)
 */
putcom()
{
	int name[2];
	register struct combuf *cp;

	if ((cp = lastcom) >= &combuf[NCOM])
		error("Too many common blocks");
	cp->com_name[0] = get(4);
	cp->com_name[1] = get(4);
	cp->com_sym = symno;
	symbol(cp->com_name);
	lastcom = ++cp;
	return(cp - 1);
}

/*
 * Look up a common block name & return a pointer to its common table entry
 */
getcom()
{
	int name[2];
	register struct combuf *cp;

	name[0] = get(4);
	name[1] = get(4);
	for (cp = combuf; cp < lastcom; cp++)
		if (cp->com_name[0] == name[0] && cp->com_name[1] == name[1])
			return(cp);
	error("Undefined common block reference");
}

/*
 * Link an entry into chain of possible external references
 * (sorted by location).
 */
dchain(seg, loc)
struct segment *seg;
{
	register struct chain *ch, *lastch, *av;

	if ((seg > 0) && loc >= seg->maxloc)		/* ignore forward refs */
		return;
	if ((av = avail) >= top)
		brk(top =+ 64);
	lastch = &curseg->first;
	for (ch = curseg->first; ch; ch = ch->next) {
		if (ch->defloc > curseg->loc)
			break;
		lastch = ch;
	}
	av->next = ch;
	lastch->next = av;
	av->defloc = curseg->loc;
	av->refseg = seg;
	av->refloc = loc;
	avail = ++av;
}

/*
 * Make chain of external references
 */
mkchain(seg, loc)
struct segment *seg;
{
	register struct chain *ch;

	for (ch = seg->first; ch;) {
		if (ch->defloc > loc)
			break;
		if (ch->defloc == loc && (ch->refseg > 0)) {
			seg = ch->refseg;
			loc = ch->refloc;
			ch->refseg = -symno;
			ch->refloc = 0;
			ch = seg->first;
			continue;
		}
		ch = ch->next;
	}
	seg->loc = loc;
	curseg = seg;
	dchain(-symno, 0);
}

pass2()
{
	register n;

	n = sizeof hdr;
	fixup(&pure, n, 0);
	fixup(&impure, n =+ hdr.tsize, 0);
	fixup(&pure, n =+ hdr.dsize, 1);
	fixup(&impure, n =+ hdr.tsize, 1);
}

fixup(seg, off, flag)
struct segment *seg;
{
	register struct chain *ch;
	static zero, s;

	for (ch = seg->first; ch; ch = ch->next)
		if (ch->refseg <= 0) {
			if (flag) {
				lseek(ofile, off + ch->defloc, 0);
				s = (-ch->refseg)<<20 | REXT+RHI<<16
					| (-ch->refseg)<<4 | REXT;
				write(ofile, &s, sizeof s);
			} else {
				lseek(ofile, off + ch->defloc + 1, 0);
				write(ofile, &ch->refloc.byte[1], 3);
			}
		}
}

/*
 * Read next record from object file
 */
char inbuff[126];
char *inp;

readobj()
{
	if (read(0, inbuff, 126) != 126)
		error("Input file i/o error");
	inp = &inbuff[4];
}

/*
 * Get next command code from object file
 */
cget()
{
	register n;

	if ((n = get(1)) == 0) {
		readobj();
		n = get(1);
	}
	return(n);
}

/*
 * Get next n bytes (1 - 4) from object file
 */
get(n)
{
	register i, w;

	w = *inp++;
	for (i=1; i<n; i++) {
		w =<< 8; w =| *inp++;
	}
	return(w);
}

/*
 * Skip next n bytes in object file
 */
skip(n)
{
	inp =+ n;
}

/*
 * Print error message and exit
 */
error(s)
char *s;
{
	char *p;

	for (p=s; *p; p++)
		;
	write(2, s, p-s);
	write(2, "\n", 1);
	exit(1);
}

/*
 * Initialize offsets & buffers
 */
reset(dsize, tsize)
{
	register n;

	hdr.dsize = dsize;
	hdr.tsize = tsize;

	pure.offset[0] = n = sizeof hdr;
	impure.offset[0] = n =+ tsize;
	pure.offset[1] = n =+ dsize;
	impure.offset[1] = n =+ tsize;
	impure.maxloc = impure.loc = pure.nchar = impure.nchar = 0;
	curseg = &impure;

	symoff = n =+ dsize;
	nsym = 0;
}

/*
 * Put a halfword of code & corresponding relocation bits to output
 */
put(code, rel)
{
	register struct segment *seg;
	register i;

	seg = curseg;
	if (seg->nchar >= 512)
		putbuf(seg);
	i = seg->nchar;

	seg->obuf[0][i] = code>>8;
	seg->obuf[0][i+1] = code&0377;
	seg->obuf[1][i] = rel>>8;
	seg->obuf[1][i+1] = rel&0377;

	seg->nchar = i+2;
	seg->loc =+ 2;
	if (seg->loc > seg->maxloc)
		seg->maxloc = seg->loc;
}

/*
 * Put out 1 absolute byte
 *	(This had better be followed by an org)
 */
put1byte(code)
{
	register struct segment *seg;
	register i;

	seg = curseg;
	if (seg->nchar >= 512)
		putbuf(seg);
	i = seg->nchar;

	seg->obuf[0][i] = code;
	seg->obuf[1][i] = 0;

	seg->nchar = ++i;
	seg->loc++;
	if (seg->loc > seg->maxloc)
		seg->maxloc = seg->loc;
}

/*
 * Adjust location in current segment by offset of (off)
 */
org(off)
{
	register struct segment *seg;
	register n;

	seg = curseg;
	if ((n = off) > 0 && seg->loc >= seg->maxloc && n < 512) {
		while (n--)
			put1byte(0);
		return;
	}
	putbuf(seg);
	seg->offset[0] =+ n;
	seg->offset[1] =+ n;
	seg->loc =+ n;
	if (seg->loc > seg->maxloc)
		seg->maxloc = seg->loc;
}

/*
 * Flush output buffers
 */
putbuf(seg)
{
	register n, i;

	if ((n = seg->nchar) <= 0)
		return;
	for (i=0; i<=1; i++) {
		lseek(ofile, seg->offset[i], 0);
		write(ofile, seg->obuf[i], n);
		seg->offset[i] =+ n;
	}
	seg->nchar = 0;
}

/*
 * Copy a symbol name to the output file
 */
symbol(name)
char *name;
{
	int sym[2];
	register c, i;
	register char *p, *q;

	p = sym;	q = name;
	for (i=8; i>0; i--) {
		if ((c = *q++) == ' ')
			c = '\0';
		*p++ = c;
	}
	putsym(sym[0], sym[1]);
}

/*
 * Write two words to the symbol table
 */
putsym(s1, s2)
{
	if (nsym >= 128)
		putsymbuf();
	symbuf[nsym++] = s1;
	symbuf[nsym++] = s2;
}

/*
 * Flush the symbol table buffer
 */
putsymbuf()
{
	if (nsym <= 0)
		return;
	lseek(ofile, symoff, 0);
	write(ofile, symbuf, nsym*4);
	symoff =+ nsym*4;
	nsym = 0;
}

/*
 * Second pass for block data program --
 *	turn initialized common blocks to data
 */
blkdata()
{
	register struct combuf *cp;
	register n, c;

	/* find total length of commons */
	n = 0;
	for (cp = combuf; cp < lastcom; cp++)
		if (cp->com_off) {
			cp->com_off = n;
			n =+ cp->com_len;
			cp->com_len = 0;
		}

	/* reset buffers & re-process input */
	reset(n, 0);

	readobj();
	for (;;) switch (c = cget()) {

	case 1:		/* End of program */
		return;

	case 2:		/* Reset sequence number */
		break;

	case 3:		/* block data initialization */
		cp = getcom();
		org(cp->com_off + get(3) - curseg->loc);
		break;

	case 14:	/* Common definition */
		cp = getcom();
		skip(3);
		symbol(cp->com_name);
		if (cp->com_len)
			putsym(SEXT, cp->com_len);
		else
			putsym(SEXT+SDATA, cp->com_off);
		break;

	case 24:	/* Length of impure & pure segments */
		skip(6);
		break;

	default:
		if ((c =- 31) <= 0 || c > 60)
			error("Illegal control item in block data program");
		while (c--)
			put(get(2), RABS);
	}
}
