// ibm1130_fmt.c : interpret tabs in 1130 Assembler or Fortran source
// Bob Flanders
// -------------------------------------------------------------------------------------------
//
// These routines are used by ibm1130_cr.c when the user has indicated
// that the input text is formatted with tabs. Input lines are edited
// into the appropriate column format. Three edit modes are recognized:
//
// Assembler mode:
//		Input lines of the form
//
//			[label]<whitespace>[opcode]<tab>[tag][L]<tab>[argument]
//
//	    are rearranged so that the input fields are placed in the appropriate columns
//
//		The label must start on the first character of the line. If there is no label, 
//		the first character(s) before the opcode must be whitespace. Following the opcode, there
//		MUST be a tab character, followed by the format and tag. Following the format and tag 
//		may be exactly one whitespace character, and then starts the argument.
//
//	    Input lines with * in column 1 and blank lines are turned into Assembler comments,
//		with the * in the Opcode field.
//
// 		Assembler directive lines at the beginning of the deck must be preceded by
//		! to indicate that they are not comments. For example,
//
//		!*LIST
//		* This is a comment
//
// Fortran mode:
//		Input lines of the form
//
//			[label]<tab>statement
//
//	    or
//
//			[label]<tab>Xcontinuation
//
//		where X is a non alphabetic contination character are rearranged in the
//		appropriate manner:
//
//					 1		   2
//			12345678901234567890...
//			------------------------
//  	    label statement
//  	    labelXcontinuation
//
//		However, you must take care that you don't end up with statement text after column 72.
//
//		Input lines with * or C in column 1 are left alone (comments and directives)
//
//		(The ! escape is not used before Fortran directives as before Assembler directives)
//
// Tab mode:
//		Tabs are replaced with spaces. Tab settings are assumed to be eight characters wide,
//		as is standard for vi, notepad, etc.
// -------------------------------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <ctype.h>
#include <string.h>
#include "ibm1130_fmt.h"

#define MAXLINE		81									// maximum output line size
#define WORKSZ		256									// size for tab work area
#define TAGOFFSET	12									// offset for tag field
#define	FMTOFFSET	11									// offset for format field

#define MIN(a,b)	((a < b) ? a : b)
#define AMSG		" with Assembler Reformat"
#define FMSG		" with FORTRAN Reformat"
#define AFORMAT		"%20.20s%-60.60s"," "
#define ACOMMENTFMT	"%20.20s%-60.60s"," "
#define ABLANKLINE	"%20.20s*"," "
#define FFORMAT		"%-5.5s %-74.74s"
#define FCONTFMT	"%-5.5s%-75.75s"

char 	gszLabel[6];									// work area for label
char	gszArg[MAXLINE];								// .. argument
char	gszOutput[MAXLINE];								// .. output
short	gaiAsmTabs[] = {7,12,15,20,25,30,35,40,45,52,0};// tab stops for assembler

short	gaiPlainTabs[] = {9, 17, 25, 33, 41, 49, 57, 65, 73, 0};// tab stops for just plain tabs

// ///////////////
// helper routines

/////////////////////////////////////////////////// 
// ExpandTabs: Expand tabs to spaces

char* ExpandTabs(char*	p_szInbuf,						// expand tabs .. input buffer
				 char*	p_szOutbuf,						// .. output buffer
				 short*	p_aiTabs)						// .. array of tab stops (1 based) -- 0 end of array
{
short	iI,													// input position
		iO,													// output position 
		iT;													// next tab stop
		
char	cX;													// character to test

	iI = 0;													// init input position
	iO = 0;													// init output position
	iT = 0;													// init tab stop
	
	while ((cX = *(p_szInbuf + iI))	!= 0)					// while there are characters
	{
		if (cX == '\t')										// q. tab character?
		{													// a. yes ..
			while ((p_aiTabs[iT] <= iO + 1)					// search for next valid stop ..
					&& (p_aiTabs[iT] != 0))					// .. or end of table
				iT++;										// .. go to next tab
				
			if (p_aiTabs[iT] != 0)							// q. end of tab array?
			{												// a. no .. 
				while (iO < (p_aiTabs[iT] - 1))				// fill to tab with blanks
						*(p_szOutbuf + iO++) = ' ';			// .. put in a blank
						
			}
			else											// Otherwise ...
				*(p_szOutbuf + iO++) = ' ';					// .. Translate to blank
		}													
		else												// Otherwise .. not tab
			*(p_szOutbuf + iO++) = cX;						// .. save the input char
			
		iI++;												// next input character
	}
	
	*(p_szOutbuf + iO) = 0;									// end the string..
	return p_szOutbuf;										// .. return output area addr
}

/////////////////////////////////////
// extract next token, modify pointer

char* GetToken(char* p_szOut,							// output location
			   int	 p_iLen,							// max output length
			   char**p_pszToken)						// pointer to input token
{
int		iI;													// work integer
char*	pszX;												// work pointer

	pszX = *p_pszToken;										// get pointer to token

	for (iI = 0; *(pszX + iI) && (!isspace(*(pszX + iI)));)	// while not whitespace & not end
		iI++;												// .. count token length

	memset(p_szOut, 0, p_iLen);								// zero out output area

	if (iI > 0)												// q. any chars?
		strncpy(p_szOut, *p_pszToken, MIN(iI, p_iLen-1)); 	// a. yes.. copy max of p_iLen-1

	*p_pszToken += iI;										// point beyond token
	return p_szOut;											// .. return token pointer
}

/////////////////////////////////////////////////////////
// EditToAsm - convert tab-formatted text line to 1130 Assembler format

char *EditToAsm (char* p_pszEdit)						// convert line to 1130 assembler
{
char	pszLine[MAXLINE];									// source line
char	pszWork[WORKSZ];									// work buffer
char	acTFWrk[2];											// tag/format work area
size_t	iI;													// work integer

	if (p_pszEdit == NULL)									// q. null request?
		return AMSG;										// a. yes .. return display message

	if (*p_pszEdit == '!')									// leave lines starting with ! alone
		return EditToWhitespace(p_pszEdit+1);

	if (*p_pszEdit == '*')									// q. comment line?
	{														// a. yes.. 
		strncpy(pszWork, EditToWhitespace(p_pszEdit), MAXLINE);		// .. convert any tabs
		sprintf(gszOutput, ACOMMENTFMT, pszWork);			// .. put the comment out there in the opcode column
		return gszOutput;									// .. and return it
	}

	strncpy(pszLine, p_pszEdit, MAXLINE-1);					// copy the line local
	
	ExpandTabs(pszLine, pszWork, gaiAsmTabs);				// expand the tabs
	strncpy(pszLine, pszWork, MAXLINE-1);					// copy the line back 
	
	for (iI = strlen(pszLine); iI--;)						// trim trailing whitespace
	{
		if (*(pszLine + iI) <= ' ')							// q. space or less?
			*(pszLine + iI) = 0;							// a. yes .. remove it
		else												// otherwise
			break;											// .. done. Leave loop.
	}

	if (strlen(pszLine) == 0)								// q. blank line?
	{														// a. yes ..  Assembler abhors these so
		sprintf(gszOutput, ABLANKLINE);						// format as comment statement
		return gszOutput;									// .. and return it
	}


	// TODO: Add code to process a strip switch 
	// comment?

	if (strlen(pszLine) > (TAGOFFSET + 1))					// q. line long enough?
	{														// a. yes.. reorder tag/format
		memcpy(acTFWrk, pszLine + FMTOFFSET, 2);			// get tag/format
		memset((pszLine + FMTOFFSET), ' ', 2);				// .. blank 'em out
		
		for (iI = 0; iI < 2; iI ++)
			if (isalpha(acTFWrk[iI]))						// q. alpha char?
				*(pszLine + FMTOFFSET) = acTFWrk[iI];		// a. yes .. make it format
			else if (isdigit(acTFWrk[iI]))					// q. digit?
				*(pszLine + TAGOFFSET) = acTFWrk[iI];		// a. yes .. make it the tag
	}

	sprintf(gszOutput, AFORMAT, pszLine);					// format the line
		
	return gszOutput;										// return formatted line
}

/////////////////////////////////////////////////////////
// EditToFortran - convert tab-formatted input text line to FORTRAN format
// (a la DEC Fortran)

char *EditToFortran(char* p_pszEdit)					// convert line to 1130 assembler
{
char	pszLine[MAXLINE];									// source line
char*	pszWork;											// work pointer
size_t	iI;													// work integer
int		bContinue;											// true if continue

	if (p_pszEdit == NULL)									// q. null request?
		return FMSG;										// a. yes .. return display message

	if (strchr(p_pszEdit, '\t') == NULL)					// q. no tab in the line?
		return p_pszEdit;									// a. nope, return line as is, assume it's formatted correctly

	if (*p_pszEdit == 'C' || *p_pszEdit == '*' || *p_pszEdit == '\0')	// q. comment or directive or blank line?
	{														// a. yes.. don't restructure
		return EditToWhitespace(p_pszEdit);
	}

	strncpy(pszLine, p_pszEdit, MAXLINE-1);					// copy the line local

	for (iI = strlen(pszLine); iI--;)						// trim trailing whitespace
	{
		if (*(pszLine + iI) <= ' ')							// q. space or less?
			*(pszLine + iI) = 0;							// a. yes .. remove it
		else												// otherwise
			break;											// .. done. Leave loop.
	}

	// TODO: Add code to process a strip switch 
	// comment?

	pszWork = (char*) pszLine;								// set pointer to line
	GetToken(gszLabel, 6, &pszWork);						// get the line, if any.

	pszWork++;												// skip tab/whitespace

															// continuation...
	bContinue = ((isdigit(*pszWork) && (*pszWork != '0'))	// if first char non-zero digit
			|| (!isspace(*pszWork) && !isalpha(*pszWork))); // .. or non-alpha non-blank
	
	memset(gszArg, 0, MAXLINE);								// .. and arguments

	strncpy(gszArg, pszWork, 75);							// copy rest to argument

	sprintf(gszOutput, (bContinue) ? FCONTFMT : FFORMAT,	// format the line
						gszLabel,							// .. statement #
						gszArg);							// .. code 
		
	return gszOutput;										// return formatted line
}

/////////////////////////////////////////////////////////
// EditToWhitespace - expand tabs at 8 space intervals.

char*	EditToWhitespace(char *p_pszEdit)
{
int		iI;													/* work integer				*/
char	pszLine[MAXLINE];									// source line
char	pszWork[WORKSZ];									// work buffer

	if (p_pszEdit == NULL)									// q. null request?
		return AMSG;										// a. yes .. return display message

	strncpy(pszLine, p_pszEdit, MAXLINE-1);					// copy the line local
	
	ExpandTabs(pszLine, pszWork, gaiPlainTabs);				// expand the tabs
	strncpy(gszOutput, pszWork, MAXLINE-1);					// copy the line back 
	
	for (iI = strlen(gszOutput); iI--;)						// look at each character
	{
		if (*(gszOutput + iI) <= ' ')						// q. space or less?
			*(gszOutput + iI) = 0;							// a. yes .. remove it
		else												// otherwise
			break;											// .. done. Leave loop.
	}


	return gszOutput;										/* ... return buffer		*/
}
