/*
 * CiderPress
 * Copyright (C) 2007 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Convert BASIC programs.
 */
#include "StdAfx.h"
#include "BASIC.h"


/* our color map */
const ReformatText::TextColor kDefaultColor = ReformatText::kColorDarkGrey;
const ReformatText::TextColor kLineNumColor = ReformatText::kColorDarkGrey;
const ReformatText::TextColor kKeywordColor = ReformatText::kColorBlack;
const ReformatText::TextColor kCommentColor = ReformatText::kColorMediumGreen;
const ReformatText::TextColor kStringColor  = ReformatText::kColorMediumBlue;
const ReformatText::TextColor kColonColor   = ReformatText::kColorRed;
 //kColorMediumGrey;

/*
 * ===========================================================================
 *		Applesoft BASIC
 * ===========================================================================
 */

/*
 * Applesoft BASIC file format:
 *
 *	<16-bit file length>  [DOS 3.3 only; not visible here]
 *	<line> ...
 *	<EOF marker ($0000)>
 *
 * Each line consists of:
 *	<16-bit address of next line (relative to $800)>
 *	<16-bit line number, usually 0-63999>
 *	<tokens | characters> ...
 *	<EOL marker ($00)>
 *
 * All values are little-endian.  Numbers are stored as characters.
 */

/*
 * Decide whether or not we want to handle this file.
 */
void
ReformatApplesoft::Examine(ReformatHolder* pHolder)
{
	ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;

	if (pHolder->GetFileType() == kTypeBAS)
		applies = ReformatHolder::kApplicProbably;

	pHolder->SetApplic(ReformatHolder::kReformatApplesoft, applies,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
	pHolder->SetApplic(ReformatHolder::kReformatApplesoft_Hilite, applies,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);

	if (pHolder->GetOption(ReformatHolder::kOptHiliteBASIC) != 0)
		pHolder->SetApplicPreferred(ReformatHolder::kReformatApplesoft_Hilite);
	else
		pHolder->SetApplicPreferred(ReformatHolder::kReformatApplesoft);
}

/*
 * Values from 128 to 234 are tokens in Applesoft.  Values from 235 to 255
 * show up as error messages.  The goal here is to produce values that are
 * human-readable and/or EXECable, so no attempt has been made to display
 * the error values.
 */
static const char gApplesoftTokens[128*8] = {
    "END\0    FOR\0    NEXT\0   DATA\0   INPUT\0  DEL\0    DIM\0    READ\0   "
    "GR\0     TEXT\0   PR#\0    IN#\0    CALL\0   PLOT\0   HLIN\0   VLIN\0   "
    "HGR2\0   HGR\0    HCOLOR=\0HPLOT\0  DRAW\0   XDRAW\0  HTAB\0   HOME\0   "
	"ROT=\0   SCALE=\0 SHLOAD\0 TRACE\0  NOTRACE\0NORMAL\0 INVERSE\0FLASH\0  "
	"COLOR=\0 POP\0    VTAB\0   HIMEM:\0 LOMEM:\0 ONERR\0  RESUME\0 RECALL\0 "
	"STORE\0  SPEED=\0 LET\0    GOTO\0   RUN\0    IF\0     RESTORE\0&\0      "
	"GOSUB\0  RETURN\0 REM\0    STOP\0   ON\0     WAIT\0   LOAD\0   SAVE\0   "
	"DEF\0    POKE\0   PRINT\0  CONT\0   LIST\0   CLEAR\0  GET\0    NEW\0    "
	"TAB(\0   TO\0     FN\0     SPC(\0   THEN\0   AT\0     NOT\0    STEP\0   "
	"+\0      -\0      *\0      /\0      ^\0      AND\0    OR\0     >\0      "
	"=\0      <\0      SGN\0    INT\0    ABS\0    USR\0    FRE\0    SCRN(\0  "
	"PDL\0    POS\0    SQR\0    RND\0    LOG\0    EXP\0    COS\0    SIN\0    "
	"TAN\0    ATN\0    PEEK\0   LEN\0    STR$\0   VAL\0    ASC\0    CHR$\0   "
	"LEFT$\0  RIGHT$\0 MID$\0   ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  "
	"ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  "
	"ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0  ERROR\0 "
};

/*
 * Make table available.
 */
/*static*/ const char* ReformatApplesoft::GetApplesoftTokens(void)
{
	return gApplesoftTokens;
}


/*
 * Reformat an Applesoft BASIC program into a text format that mimics the
 * output of the "LIST" command (with POKE 33,73 to suppress CRs).
 */
int
ReformatApplesoft::Process(const ReformatHolder* pHolder,
	ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
	ReformatOutput* pOutput)
{
	const unsigned char* srcPtr = pHolder->GetSourceBuf(part);
	long srcLen = pHolder->GetSourceLen(part);
	long length = srcLen;
	int retval = -1;

	if (srcLen > 65536)
		fUseRTF = false;
	if (fUseRTF) {
		if (id != ReformatHolder::kReformatApplesoft_Hilite)
			fUseRTF = false;
	}

	RTFBegin(kRTFFlagColorTable);

	/*
	 * Make sure there's enough here to get started.  We want to return an
	 * "okay" result because we want this treated like a reformatted empty
	 * BASIC program rather than a non-Applesoft file.
	 */
	if (length < 2) {
		WMSG0("  BAS truncated?\n");
		//fExpBuf.CreateWorkBuf();
		BufPrintf("\r\n");
		goto done;
	}

	while (length > 0) {
		unsigned short nextAddr;
		unsigned short lineNum;
		bool inQuote = false;
		bool inRem = false;

		nextAddr = Read16(&srcPtr, &length);
		if (nextAddr == 0) {
			/* ProDOS sticks an extra byte on the end? */
			if (length > 1) {
				WMSG1("  BAS ended early; len is %d\n", length);
			}
			break;
		}

		/* print line number */
		RTFSetColor(kLineNumColor);
		lineNum = Read16(&srcPtr, &length);
		BufPrintf(" %u ", lineNum);

		RTFSetColor(kDefaultColor);

		/* print a line */
		while (*srcPtr != 0 && length > 0) {
			if (*srcPtr & 0x80) {
				/* token */
				//RTFBoldOn();
				RTFSetColor(kKeywordColor);
				BufPrintf(" %s ", &gApplesoftTokens[((*srcPtr) & 0x7f) << 3]);
				//RTFBoldOff();
				RTFSetColor(kDefaultColor);

				if (*srcPtr == 0xb2) {
					// REM -- do rest of line in green
					RTFSetColor(kCommentColor);
					inRem = true;
				}
			} else {
				/* simple chracter */
				if (fUseRTF) {
					if (*srcPtr == '"' && !inRem) {
						if (!inQuote) {
							RTFSetColor(kStringColor);
							RTFPrintChar(*srcPtr);
						} else {
							RTFPrintChar(*srcPtr);
							RTFSetColor(kDefaultColor);
						}
						inQuote = !inQuote;
					} else if (*srcPtr == ':' && !inRem && !inQuote) {
						RTFSetColor(kColonColor);
						RTFPrintChar(*srcPtr);
						RTFSetColor(kDefaultColor);
					} else {
						RTFPrintChar(*srcPtr);
					}
				} else {
					BufPrintf("%c", *srcPtr);
				}
			}

			srcPtr++;
			length--;
		}

		if (inQuote || inRem)
			RTFSetColor(kDefaultColor);
		inQuote = inRem = false;

		srcPtr++;
		length--;

		if (!length) {
			WMSG0("  BAS truncated in mid-line\n");
			break;
		}

		RTFNewPara();
	}

done:
	RTFEnd();

	SetResultBuffer(pOutput);
	retval = 0;

//bail:
	return retval;
}


/*
 * ===========================================================================
 *		Integer BASIC
 * ===========================================================================
 */
#include "Asm.h"

/*
 * Integer BASIC file format (thanks to Paul Schlyter, pausch at saaf.se):
 *
 *	<16-bit file length>  [DOS 3.3 only; not visible here]
 *	<line> ...
 *
 * Each line consists of:
 *	<8-bit line length>
 *	<16-bit line number>
 *	<token | character | variable> ...
 *	<end-of-line token ($01)>
 *
 * Each line is a stream of bytes:
 *	$01: end of line
 *	$12-$7f: language token
 *	$b0-b9 ('0'-'9'): start of integer constant (first byte has no meaning?)
 *			next 16 bits hold the number
 *	$c1-da ('A'-'Z'): start of a variable name; ends on value <0x80
 *			next several bytes hold the name
 *
 * Most of the first $11 tokens are illegal except as part of an integer
 * constant, which just means that you can't type them into a program from
 * the keyboard.  If you POKE them in manually, things like "himem:" and
 * "del" will work.
 *
 * $ba-$c0 and $db-$ff are illegal except in a string constant.
 *
 * There is no end-of-file marker.
 */

/*
 * Tokens.
 */
static const char* const gIntegerTokens[128] = {
	"HIMEM:",	"<EOL>",	"_ ",		":",
	"LOAD ",	"SAVE ",	"CON ",		"RUN ",
	"RUN ",		"DEL ",		",",		"NEW ",
	"CLR ",		"AUTO ",	",",		"MAN ",
	"HIMEM:",	"LOMEM:",	"+",		"-",
	"*",		"/",		"=",		"#",
	">=",		">",		"<=",		"<>",
	"<",		"AND ",		"OR ",		"MOD ",

	"^ ",		"+",		"(",		",",
	"THEN ",	"THEN ",	",",		",",
	"\"",		"\"",		"(",		"!",
	"!",		"(",		"PEEK ",	"RND ",
	"SGN ",		"ABS ",		"PDL ",		"RNDX ",
	"(",		"+",		"-",		"NOT ",
	"(",		"=",		"#",		"LEN(",
	"ASC( ",	"SCRN( ",	",",		"(",

	"$",		"$",		"(",		",",
	",",		";",		";",		";",
	",",		",",		",",		"TEXT ",
	"GR ",		"CALL ",	"DIM ",		"DIM ",
	"TAB ",		"END ",		"INPUT ",	"INPUT ",
	"INPUT ",	"FOR ",		"=",		"TO ",
	"STEP ",	"NEXT ",	",",		"RETURN ",
	"GOSUB ",	"REM ",		"LET ",		"GOTO ",

	"IF ",		"PRINT ",	"PRINT ",	"PRINT ",
	"POKE ",	",",		"COLOR= ",	"PLOT ",
	",",		"HLIN ",	",",		"AT ",
	"VLIN ",	",",		"AT ",		"VTAB ",
	"=",		"=",		")",		")",
	"LIST ",	",",		"LIST ",	"POP ",
	"NODSP ",	"NODSP ",	"NOTRACE ",	"DSP ",
	"DSP ",		"TRACE ",	"PR# ",		"IN# "
};

/*
 * Decide whether or not we want to handle this file.
 */
void
ReformatInteger::Examine(ReformatHolder* pHolder)
{
	ReformatHolder::ReformatApplies apply = ReformatHolder::kApplicNot;
	if (pHolder->GetFileType() == kTypeINT) {
		if (ReformatSCAssem::IsSCAssem(pHolder)) {
			/* possibly intbasic */
			apply = ReformatHolder::kApplicMaybe;
		} else if (ReformatLISA3::IsLISA(pHolder)) {
			/* possibly intbasic */
			apply = ReformatHolder::kApplicMaybe;
		} else if (ReformatLISA4::IsLISA(pHolder)) {
			/* possibly intbasic */
			apply = ReformatHolder::kApplicMaybe;
		} else {
			/* definitely intbasic */
			apply = ReformatHolder::kApplicYes;
		}
	} else {
		/* not intbasic */
		apply = ReformatHolder::kApplicNot;
	}

	//apply = ReformatHolder::kApplicMaybe;		// DEBUG DEBUG

	pHolder->SetApplic(ReformatHolder::kReformatInteger, apply,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
	pHolder->SetApplic(ReformatHolder::kReformatInteger_Hilite, apply,
		ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);

	if (pHolder->GetOption(ReformatHolder::kOptHiliteBASIC) != 0)
		pHolder->SetApplicPreferred(ReformatHolder::kReformatInteger_Hilite);
	else
		pHolder->SetApplicPreferred(ReformatHolder::kReformatInteger);
}

/*
 * Reformat an Integer BASIC program into a text format that mimics the
 * output of the "LIST" command.
 */
int
ReformatInteger::Process(const ReformatHolder* pHolder,
	ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
	ReformatOutput* pOutput)
{
	const unsigned char* srcPtr = pHolder->GetSourceBuf(part);
	long srcLen = pHolder->GetSourceLen(part);
	long length = srcLen;
	//unsigned short val16;
	int retval = -1;

	//srcPtr += 0xff0; //0x228e;
	//srcLen -= 0xff0; //0x228e;

	if (srcLen > 65536)
		fUseRTF = false;
	if (fUseRTF) {
		if (id != ReformatHolder::kReformatInteger_Hilite)
			fUseRTF = false;
	}

	RTFBegin(kRTFFlagColorTable);

	/*
	 * Make sure there's enough here to get started.  We want to return an
	 * "okay" result because we want this treated like a reformatted empty
	 * BASIC program rather than a non-Integer file.
	 */
	if (length < 2) {
		WMSG0("  INT truncated?\n");
		BufPrintf("\r\n");
		goto done;
	}

	while (length > 0) {
		unsigned char lineLen;
		unsigned short lineNum;
		bool trailingSpace;
		bool newTrailingSpace = false;

		/* pull the length byte, which we sanity-check */
		lineLen = *srcPtr++;
		length--;
		if (lineLen == 0) {
			WMSG0("  INT found zero-length line?\n");
			break;
		}

		/* line number */
		RTFSetColor(kLineNumColor);
		lineNum = Read16(&srcPtr, &length);
		BufPrintf("%5u ", lineNum);
		RTFSetColor(kDefaultColor);

		trailingSpace = true;
		while (*srcPtr != 0x01 && length > 0) {
			if (*srcPtr == 0x28) {
				/* start of quoted text */
				RTFSetColor(kStringColor);
				BufPrintf("\"");
				length--;
				while (*++srcPtr != 0x29 && length > 0) {
					/* escape chars, but let Ctrl-D and Ctrl-G through */
					if (fUseRTF && *srcPtr != 0x84 && *srcPtr != 0x87)
						RTFPrintChar(*srcPtr & 0x7f);
					else
						BufPrintf("%c", *srcPtr & 0x7f);
					length--;
				}
				if (*srcPtr != 0x29) {
					WMSG0("  INT ended while in a string constant\n");
					break;
				}
				BufPrintf("\"");
				RTFSetColor(kDefaultColor);
				srcPtr++;
				length--;
			} else if (*srcPtr == 0x5d) {
				/* start of REM statement, run to EOL */
				//RTFBoldOn();
				RTFSetColor(kKeywordColor);
				BufPrintf("%sREM ", trailingSpace ? "" : " ");
				//RTFBoldOff();
				RTFSetColor(kCommentColor);
				length--;
				while (*++srcPtr != 0x01) {
					if (fUseRTF)
						RTFPrintChar(*srcPtr & 0x7f);
					else
						BufPrintf("%c", *srcPtr & 0x7f);
					length--;
				}
				RTFSetColor(kDefaultColor);
				if (*srcPtr != 0x01) {
					WMSG0("  INT ended while in a REM statement\n");
					break;
				}
			} else if (*srcPtr >= 0xb0 && *srcPtr <= 0xb9) {
				/* start of integer constant */
				srcPtr++;
				length--;
				if (length < 2) {
					WMSG0("  INT ended while in an integer constant\n");
					break;
				}
				int val;
				val = Read16(&srcPtr, &length);
				BufPrintf("%d", val);
			} else if (*srcPtr >= 0xc1 && *srcPtr <= 0xda) {
				/* start of variable name */
				while ((*srcPtr >= 0xc1 && *srcPtr <= 0xda) ||
					   (*srcPtr >= 0xb0 && *srcPtr <= 0xb9))
				{
					/* note no RTF-escaped chars in this range */
					BufPrintf("%c", *srcPtr & 0x7f);
					srcPtr++;
					length--;
				}
			} else if (*srcPtr < 0x80) {
				/* found a token; try to get the whitespace right */
				/* (maybe should've left whitespace on the ends of tokens
				    that are always followed by whitespace...?) */
				const char* token;
				token = gIntegerTokens[*srcPtr];
				//RTFBoldOn();
				if (*srcPtr == 0x03)	// colon
					RTFSetColor(kColonColor);
				else
					RTFSetColor(kKeywordColor);
				if (token[0] >= 0x21 && token[0] <= 0x3f || *srcPtr < 0x12) {
					/* does not need leading space */
					BufPrintf("%s", token);
				} else {
					/* needs leading space; combine with prev if it exists */
					if (trailingSpace)
						BufPrintf("%s", token);
					else
						BufPrintf(" %s", token);
				}
				if (token[strlen(token)-1] == ' ')
					newTrailingSpace = true;
				//RTFBoldOff();
				RTFSetColor(kDefaultColor);
				srcPtr++;
				length--;
			} else {
				/* should not happen */
				WMSG2("  INT unexpected value 0x%02x at byte %d\n",
					*srcPtr, srcPtr - pHolder->GetSourceBuf(part));

				/* skip past it and keep trying */
				srcPtr++;
				length--;
			}

			trailingSpace = newTrailingSpace;
			newTrailingSpace = false;
		} /*while line*/

		/* skip past EOL token */
		if (*srcPtr != 0x01 && length > 0) {
			WMSG0("bailing\n");		// must've failed during processing
			goto bail;
		}
		srcPtr++;
		length--;

		RTFNewPara();
	}

done:
	RTFEnd();

	SetResultBuffer(pOutput);
	retval = 0;

bail:
	return retval;
}
