//********************************************************************************
//*	 	HP21xx Absolute Binary Load Format Conversion Utility
//* 
//*	See use message near end of file.
//*
//* 2014 Oct: V1 / bhilpert
//*
//********************************************************************************
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "util.h"

extern int			abs_getWord();

extern void			Abs_oStart();
extern void			Abs_oFin();
extern void			Abs_oWord();


#define WEOF			-1					// host int size must be > 16 bits


//* END Misc
//********************************************************************************
//* Abs to XNS

void
AbsToXns( absIn, xnsOut )
	FILE			*absIn, *xnsOut;
 {
	int			i;
	int			len, w, checksum;

	repeat								// for each absolute block
	 {	len = fgetc( absIn );					// first byte is length
		if( len == EOF )
			break;
		if( len == 0 )						// assume zero-length blocks are empty (!!)
			continue;
		fgetc( absIn );						// 2nd byte is unused

		w = abs_getWord( absIn );				// get 2-byte address
		if( w == WEOF )
			goto errEOF;
		fprintf( xnsOut, "\nlen=%d %6o:", len, w );		// output XNS address, with len as comment

		checksum = w;						// address contributes to checksum
		for( i=0; i<len; ++i )					// transfer data words
		 {	if( (i % 16) == 0 ) fprintf( xnsOut, "\n" );
			w = abs_getWord( absIn );
			if( w == WEOF )
				goto errEOF;
			fprintf( xnsOut, "%6o~", w );
			checksum += w;					// include data in checksum
		 }

		w = abs_getWord( absIn );				// get and check checksum
		if( w == WEOF )
		 {errEOF:
			fflush( xnsOut );
			fprintf( stderr, "  -- AX: unexpected EOF\n" );
			break;
		 }
		if( w != (checksum&0xFFFF) )
		 {	fflush( xnsOut );
			fprintf( stderr, "  -- AX: bad checksum\n" );
			break;
		 }
	 }

	fprintf( xnsOut, "\n!\n" );						// output XNS termination
 }


//* END Abs to XNS
//********************************************************************************
//* XNS to Abs

void
XnsToAbs( xnsIn, absOut, blkLenMax )
	FILE			*xnsIn, *absOut;
	int			blkLenMax;
 {
	int			curAddr;
	int			num, c;

	Abs_oStart( absOut, blkLenMax );

	curAddr = 0;
	num = 0;							// number state-variable

	while( (c=fgetc(xnsIn)) != EOF )				// get character, exit on EOF
	 {	switch( c )
		 {	case '0': case '1': case '2': case '3':		// accumulate digits into number
			case '4': case '5': case '6': case '7':
				num = (num<<3) | (c-'0');
				break;

			case ':':					// address
				curAddr = num;
				num = 0;				// start new number
		 		break;

			case '~':					// enter data word
				Abs_oWord( curAddr++, num );
				num = 0;				// start new number
				break;

			case '!':					// exit
				goto fin;

			default:					// any other input clears number state
				num = 0;
				break;
		 }
	 }

 fin:	Abs_oFin();
 }


//* END XNS to ABS 2
//********************************************************************************
//* Assembler Listing to Absolute Format

#define INSTR_IDX		( 4+2+5+1+6+2+5+1 )					// column index of instructions in listing


void
LstToAbs( lstIn, absOut, blkLenMax )
	FILE			*lstIn, *absOut;
	int			blkLenMax;
 {
	char			line[200];
	int			listLineNo, listAddr, listData, i;

	Abs_oStart( absOut, blkLenMax );

	while( fgets(line,200,lstIn) )							// get a line from listing
	 {	i = sscanf( line, "%4d %5o %6o", &listLineNo, &listAddr, &listData );	// parse off octal address and data word
		if( i < 3 )
			continue;

		line[INSTR_IDX+3] = '\0';						// truncate line after instruction/directive
		if( strlen(line)!=INSTR_IDX+3 || strcmp(&(line[INSTR_IDX]),"BSS")==0 )	// skip if there was no directive, also skip if BSS segment
			continue;

		Abs_oWord( listAddr, listData );					// output data word at address
	 }

	Abs_oFin();
 }


//* END Listing to Absolute
//********************************************************************************
//* Prepare BASIC listing to be read via TAPE command:
//*	- strip 8th bit
//*	- delete all nulls in source
//*	- delete characters preceding first line number
//*	- change CR, LF & CRLF to CRLF followed by <nullCnt> nulls
//*	- delete characters following EOL if not line number
//*	- add leader of 12 nulls
//*	- add trailer of 12 nulls


#define S_BOF			0
#define S_EOL			1
#define S_TXT			2


void
fputCLN( dstF, doCR, doLF, nullCnt )
	FILE			*dstF;
	int			doCR, doLF, nullCnt;
 {
	if( doCR ) fputc( '\r', dstF );

	while( nullCnt-- ) fputc( '\0', dstF );

	if( doLF ) fputc( '\n', dstF );
 }


void
PrepareBASICListing( srcF, dstF, doCR, doLF, nullCnt )
	FILE			*srcF, *dstF;
	int			doCR, doLF, nullCnt;
 {
	int			state;
	int			c;

	if( nullCnt < 0 ) nullCnt = 1;

	state = S_BOF;

	while( (c=fgetc(srcF)) != EOF )							// get character, exit on EOF
	 {	c &= 0x7F;								// strip 8th bit
		switch( c )
		 {	case '\0':							// null: delete
		 		break;

			case '\r':							// cr
			case '\n':							// lf
				if( state == S_BOF )					// delete until we've seen a line number
					break;

				state = S_EOL;
				break;

			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				if( state == S_BOF )					// put leader if first line number
					fputCLN( dstF, FALSE, FALSE, 12 );
				if( state == S_EOL )					// put EOL if pending
					fputCLN( dstF, doCR, doLF, nullCnt );

				fputc( c, dstF );

				state = S_TXT;
				break;

			default:							// content
				if( state==S_BOF || state==S_EOL )			// delete until we've seen a line number
					break;

				fputc( c, dstF );
				break;
		 }
	 }

	if( state != S_BOF )
	 {	fputCLN( dstF, doCR, doLF, nullCnt );					// put final EOL
		fputCLN( dstF, FALSE, FALSE, 12 );					// put trailer nulls
	 }
 }


//* END Listing Pad & Clean
//********************************************************************************
//* Main

char				useMsg[] =
	"  -- Use:  hp21cvt -ax                 < {absFile}  > {xnsFile}    : convert ABS to octal XNS\n"
	"           hp21cvt -xa [-m {blockMax}] < {xnsFile}  > {absFile}    : convert octal XNS to ABS\n"
	"           hp21cvt -la [-m {blockMax}] < {listFile} > {absFile}    : extract octal object code from assembler listing and output as ABS\n"
	"           hp21cvt -pbl [-m {#nulls}] [-cr] [-lf] < {srcFile}  > {dstFile}    : prepare basic listing\n"
	"           hp21cvt -u                                              : display this message\n"
	"     Options:\n"
	"                 -m {blockMax} : maximum number of data words per block (default 255)\n";


int
main( argc, argv )
	int			argc;
	char**			argv;
 {
	int			mode, blkMax, doCR, doLF, i;

	//------------------------------
	// Command line configuration

	mode = 0;
	blkMax = -1;
	doCR = TRUE;
	doLF = TRUE;

	for( i=1; i<argc; ++i )
		if(      strcmp(argv[i],"-ax") == 0 )
			mode = 'a';
		else if( strcmp(argv[i],"-xa") == 0 )
			mode = 'x';
		else if( strcmp(argv[i],"-la") == 0 )
			mode = 'l';
		else if( strcmp(argv[i],"-pbl") == 0 )
			mode = 'b';
		else if( strcmp(argv[i],"-m") == 0 )
		 {	if( ++i == argc ) goto useFail;
			if( sscanf(argv[i],"%d",&blkMax) != 1 ) goto useFail;
		 }
		else if( strcmp(argv[i],"-cr") == 0 )
			doCR = FALSE;
		else if( strcmp(argv[i],"-lf") == 0 )
			doLF = FALSE;
		else if( strcmp(argv[i],"-u") == 0 )
			goto useFail;
		else
			goto useFail;

	if( mode == 0 )
	 {useFail:
		fprintf( stderr, useMsg, argv[0] );
		exit( -1 );
	 }

	//------------------------------
	// Activity

	switch( mode )
	 {	case 'a':	AbsToXns( stdin, stdout );		break;
		case 'x':	XnsToAbs( stdin, stdout, blkMax );	break;
		case 'l':	LstToAbs( stdin, stdout, blkMax );	break;
		case 'b':	PrepareBASICListing( stdin, stdout, doCR, doLF, blkMax );	break;
	 }

	//------------------------------
	// Termination

	return( 0 );
 }


//* EOF
//********************************************************************************
