/* AppleWin : An Apple //e emulator for Windows Copyright (C) 1994-1996, Michael O'Brien Copyright (C) 1999-2001, Oliver Schmidt Copyright (C) 2002-2005, Tom Charlesworth Copyright (C) 2006-2010, Tom Charlesworth, Michael Pohoreski AppleWin is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. AppleWin is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with AppleWin; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* Description: Debugger * * Author: Copyright (C) 2006, Michael Pohoreski */ #include "StdAfx.h" #include "Debug.h" #include "../CPU.h" #include "../Memory.h" // Args ___________________________________________________________________________________________ int g_nArgRaw; Arg_t g_aArgRaw[ MAX_ARGS ]; // pre-processing Arg_t g_aArgs [ MAX_ARGS ]; // post-processing (cooked) const TCHAR TCHAR_LF = TEXT('\x0D'); const TCHAR TCHAR_CR = TEXT('\x0A'); const TCHAR TCHAR_SPACE = TEXT(' '); const TCHAR TCHAR_TAB = TEXT('\t'); // const TCHAR TCHAR_QUOTED = TEXT('"'); const TCHAR TCHAR_QUOTE_DOUBLE = TEXT('"'); const TCHAR TCHAR_QUOTE_SINGLE = TEXT('\''); const TCHAR TCHAR_ESCAPE = TEXT('\x1B'); // NOTE: ArgToken_e and g_aTokens must match! const TokenTable_t g_aTokens[ NUM_TOKENS ] = { // Input { TOKEN_ALPHANUMERIC, TYPE_STRING , 0 }, // Default, if doen't match anything else { TOKEN_AMPERSAND , TYPE_OPERATOR, "&" }, // bit-and { TOKEN_AT , TYPE_OPERATOR, "@" }, // reference results { TOKEN_BRACE_L , TYPE_STRING , "{" }, { TOKEN_BRACE_R , TYPE_STRING , "}" }, { TOKEN_BRACKET_L , TYPE_STRING , "[" }, { TOKEN_BRACKET_R , TYPE_STRING , "]" }, { TOKEN_BSLASH , TYPE_OPERATOR, "\\" }, { TOKEN_CARET , TYPE_OPERATOR, "^" }, // bit-eor, C/C++: xor, Math: POWER { TOKEN_COLON , TYPE_OPERATOR, ":" }, { TOKEN_COMMA , TYPE_OPERATOR, "," }, { TOKEN_DOLLAR , TYPE_STRING , "$" }, { TOKEN_EQUAL , TYPE_OPERATOR, "=" }, { TOKEN_EXCLAMATION , TYPE_OPERATOR, "!" }, // NOT { TOKEN_FSLASH , TYPE_OPERATOR, "/" }, // div { TOKEN_GREATER_THAN, TYPE_OPERATOR, ">" }, // TODO/FIXME: Parser will break up '>=' (needed for uber breakpoints) { TOKEN_HASH , TYPE_OPERATOR, "#" }, { TOKEN_LESS_THAN , TYPE_OPERATOR, "<" }, { TOKEN_MINUS , TYPE_OPERATOR, "-" }, // sub { TOKEN_PAREN_L , TYPE_OPERATOR, "(" }, { TOKEN_PAREN_R , TYPE_OPERATOR, ")" }, { TOKEN_PERCENT , TYPE_OPERATOR, "%" }, // mod { TOKEN_PIPE , TYPE_OPERATOR, "|" }, // bit-or { TOKEN_PLUS , TYPE_OPERATOR, "+" }, // add // { TOKEN_QUESTION , TYPE_OPERATOR, TEXT('?') }, // Not a token 1) wildcard needs to stay together with other chars { TOKEN_QUOTE_SINGLE, TYPE_QUOTED_1, "\'" }, { TOKEN_QUOTE_DOUBLE, TYPE_QUOTED_2, "\"" }, // for strings { TOKEN_SEMI , TYPE_STRING , ";" }, { TOKEN_SPACE , TYPE_STRING , " " }, // space is also a delimiter between tokens/args { TOKEN_STAR , TYPE_OPERATOR, "*" }, // Not a token 1) wildcard needs to stay together with other chars // { TOKEN_TAB , TYPE_STRING , TEXT('\t') } { TOKEN_TILDE , TYPE_OPERATOR, "~" }, // C/C++: Not. Used for console. { TOKEN_COMMENT_EOL , TYPE_STRING , "//" }, { TOKEN_GREATER_EQUAL,TYPE_OPERATOR, ">=" }, { TOKEN_LESS_EQUAL , TYPE_OPERATOR, "<=" }, { TOKEN_NOT_EQUAL , TYPE_OPERATOR , "!=" } }; // Arg ____________________________________________________________________________________________ //=========================================================================== int _Arg_1( int nValue ) { g_aArgs[1].nValue = nValue; return 1; } //=========================================================================== int _Arg_1( LPTSTR pName ) { int nLen = _tcslen( g_aArgs[1].sArg ); if (nLen < MAX_ARG_LEN) { _tcscpy( g_aArgs[1].sArg, pName ); } else { _tcsncpy( g_aArgs[1].sArg, pName, MAX_ARG_LEN ); } return 1; } /** @description Copies Args[iSrc .. iEnd] to Args[0] @param iSrc First argument to copy @param iEnd Last argument to end @return nArgs Number of new args Usually called as: nArgs = _Arg_Shift( iArg, nArgs ); //=========================================================================== */ int _Arg_Shift( int iSrc, int iEnd, int iDst ) { if (iDst < 0) return ARG_SYNTAX_ERROR; if (iDst >= MAX_ARGS) return ARG_SYNTAX_ERROR; int nArgs = (iEnd - iSrc); int nLen = nArgs + 1; if ((iDst + nLen) > MAX_ARGS) return ARG_SYNTAX_ERROR; while (nLen--) { g_aArgs[iDst] = g_aArgs[iSrc]; iSrc++; iDst++; } return nArgs; } //=========================================================================== int _Args_Insert( int iSrc, int iEnd, int nLen ) { iSrc += nLen; int iDst = iEnd + nLen; if (iDst >= MAX_ARGS) return ARG_SYNTAX_ERROR; if (iSrc >= MAX_ARGS) return ARG_SYNTAX_ERROR; while (nLen--) { g_aArgs[iDst] = g_aArgs[iSrc]; iSrc--; iDst--; } return 0; } static void ClearArg( Arg_t *pArg ) { pArg->sArg[0] = 0; pArg->nArgLen = 0; pArg->bSymbol = false; pArg->eDevice = NUM_DEVICES; // none pArg->eToken = NO_TOKEN ; // none pArg->bType = TYPE_STRING; pArg->nValue = 0; #if DEBUG_VAL_2 pArg->nVal2 = 0; #endif } //=========================================================================== void ArgsClear () { Arg_t *pArg = &g_aArgs[0]; Arg_t *pRaw = &g_aArgRaw[0]; for (int iArg = 0; iArg < MAX_ARGS; iArg++ ) { ClearArg( pArg ); ClearArg( pRaw ); pArg++; pRaw++; } } //=========================================================================== bool ArgsGetValue ( Arg_t *pArg, WORD * pAddressValue_, const int nBase ) { _ASSERT(pArg); if (pArg == NULL) return false; TCHAR *pSrc = & (pArg->sArg[ 0 ]); TCHAR *pEnd = NULL; if (pAddressValue_) { *pAddressValue_ = (WORD)(_tcstoul( pSrc, &pEnd, nBase) & _6502_MEM_END); return true; } return false; } //=========================================================================== bool ArgsGetImmediateValue ( Arg_t *pArg, WORD * pAddressValue_ ) { if (pArg && pAddressValue_) { if (pArg->eToken == TOKEN_HASH) { pArg++; return ArgsGetValue( pArg, pAddressValue_ ); } } return false; } // Read console input, process the raw args, turning them into tokens and types. //=========================================================================== int ArgsGet ( TCHAR * pInput ) { LPCTSTR pSrc = pInput; LPCTSTR pEnd = NULL; int nBuf = 0; ArgToken_e iTokenSrc = NO_TOKEN; ArgToken_e iTokenEnd = NO_TOKEN; ArgType_e iType = TYPE_STRING; int nLen; int iArg = 0; int nArg = 0; Arg_t *pArg = &g_aArgRaw[0]; // &g_aArgs[0]; g_pConsoleFirstArg = NULL; // BP FAC8:FACA // Range=3 // BP FAC8,2 // Length=2 // ^ ^^ ^^ // | || |pSrc // | || pSrc // | |pSrc // | pEnd // pSrc while ((*pSrc) && (iArg < MAX_ARGS)) { // Technically, there shouldn't be any leading spaces, // since pressing the spacebar is an alias for TRACE. // However, there is spaces between arguments pSrc = const_cast( SkipWhiteSpace( pSrc )); if (pSrc) { pEnd = FindTokenOrAlphaNumeric( pSrc, g_aTokens, NUM_TOKENS, &iTokenSrc ); if ((iTokenSrc == NO_TOKEN) || (iTokenSrc == TOKEN_ALPHANUMERIC)) { pEnd = SkipUntilToken( pSrc+1, g_aTokens, NUM_TOKENS, &iTokenEnd ); } if (iTokenSrc == TOKEN_COMMENT_EOL) break; //pArg->eToken = iTokenSrc; if (iTokenSrc == NO_TOKEN) { iTokenSrc = TOKEN_ALPHANUMERIC; } iType = g_aTokens[ iTokenSrc ].eType; if (iTokenSrc == TOKEN_SEMI) { // TODO - command separator, must handle non-quoted though! } if (iTokenSrc == TOKEN_QUOTE_DOUBLE) { pSrc++; // Don't store start of quote pEnd = SkipUntilChar( pSrc, CHAR_QUOTE_DOUBLE ); } else if (iTokenSrc == TOKEN_QUOTE_SINGLE) { pSrc++; // Don't store start of quote pEnd = SkipUntilChar( pSrc, CHAR_QUOTE_SINGLE ); } if (pEnd) { nBuf = pEnd - pSrc; } if (nBuf > 0) { // Does anyone actually "need" > 132 character output??? // Technically, we are capped via ParseInput(), g_aArgs[ iArg ] = g_aArgRaw[ iArg ]; //if (iTokenSrc == TOKEN_QUOTE_DOUBLE) // nLen = nBuf; nLen = MIN( nBuf, MAX_ARG_LEN ); // NOTE: see Arg_t.sArg[] // GH#481 _tcsncpy( pArg->sArg, pSrc, nLen ); pArg->sArg[ nLen ] = 0; pArg->nArgLen = nLen; pArg->eToken = iTokenSrc; pArg->bType = iType; if (iTokenSrc == TOKEN_QUOTE_DOUBLE) { pEnd++; } else if (iTokenSrc == TOKEN_QUOTE_SINGLE) { if (nLen > 1) { // Technically, chars aren't allowed to be multi-char // But we've extended the syntax to allow the user // to input High-Bit Apple Text } pEnd++; } pSrc = pEnd; iArg++; pArg++; if (iArg == 1) { g_pConsoleFirstArg = pSrc; } } } } if (iArg) { nArg = iArg - 1; // first arg is command } g_nArgRaw = iArg; return nArg; } //=========================================================================== bool ArgsGetRegisterValue ( Arg_t *pArg, WORD * pAddressValue_ ) { bool bStatus = false; if (pArg && pAddressValue_) { // Check if we refer to reg A X Y P S for ( int iReg = 0; iReg < (NUM_BREAKPOINT_SOURCES-1); iReg++ ) { // Skip Opcode/Instruction/Mnemonic if (iReg == BP_SRC_OPCODE) continue; // Skip individual flag names if ((iReg >= BP_SRC_FLAG_C) && (iReg <= BP_SRC_FLAG_N)) continue; // Handle one char names if ((pArg->nArgLen == 1) && (pArg->sArg[0] == g_aBreakpointSource[ iReg ][0])) { switch ( iReg ) { case BP_SRC_REG_A : *pAddressValue_ = regs.a & 0xFF; bStatus = true; break; case BP_SRC_REG_P : *pAddressValue_ = regs.ps & 0xFF; bStatus = true; break; case BP_SRC_REG_X : *pAddressValue_ = regs.x & 0xFF; bStatus = true; break; case BP_SRC_REG_Y : *pAddressValue_ = regs.y & 0xFF; bStatus = true; break; case BP_SRC_REG_S : *pAddressValue_ = regs.sp ; bStatus = true; break; default: break; } } else if (iReg == BP_SRC_REG_PC) { if ((pArg->nArgLen == 2) && (_tcscmp( pArg->sArg, g_aBreakpointSource[ iReg ] ) == 0)) { *pAddressValue_ = regs.pc ; bStatus = true; break; } } } } return bStatus; } //=========================================================================== void ArgsRawParse ( void ) { const int BASE = 16; // hex TCHAR *pSrc = NULL; TCHAR *pEnd = NULL; int iArg = 1; Arg_t *pArg = & g_aArgRaw[ iArg ]; int nArg = g_nArgRaw; WORD nAddressArg; WORD nAddressSymbol; WORD nAddressValue; while (iArg <= nArg) { pSrc = & (pArg->sArg[ 0 ]); nAddressArg = (WORD)(_tcstoul( pSrc, &pEnd, BASE) & _6502_MEM_END); nAddressValue = nAddressArg; bool bFound = false; if (! (pArg->bType & TYPE_NO_SYM)) { bFound = FindAddressFromSymbol( pSrc, & nAddressSymbol ); if (bFound) { nAddressValue = nAddressSymbol; pArg->bSymbol = true; } } if (! (pArg->bType & TYPE_VALUE)) // already up to date? pArg->nValue = nAddressValue; pArg->bType |= TYPE_ADDRESS; iArg++; pArg++; } } /** @param nArgs Number of raw args. Note: The number of args can be changed via: address1,length Length address1:address2 Range address1+delta Delta address1-delta Delta //=========================================================================== */ int ArgsCook ( const int nArgs ) { const int BASE = 16; // hex TCHAR *pSrc = NULL; TCHAR *pEnd2 = NULL; int nArg = nArgs; int iArg = 1; Arg_t *pArg = NULL; Arg_t *pPrev = NULL; Arg_t *pNext = NULL; WORD nAddressArg; WORD nAddressRHS; WORD nAddressSym; WORD nAddressVal; int nParamLen = 0; int nArgsLeft = 0; int nParenL = 0; int nParenR = 0; while (iArg <= nArg) { pArg = & (g_aArgs[ iArg ]); pSrc = & (pArg->sArg[ 0 ]); if (pArg->eToken == TOKEN_DOLLAR) // address { // TODO: Need to flag was a DOLLAR token for assembler pNext = NULL; nArgsLeft = (nArg - iArg); if (nArgsLeft > 0) { pNext = pArg + 1; _Arg_Shift( iArg + 1, nArgs, iArg ); nArg--; iArg--; // inc for start of next loop // Don't do register lookup pArg->bType |= TYPE_NO_REG; } else return ARG_SYNTAX_ERROR; } if (pArg->bType & TYPE_OPERATOR) // prev op type == address? { pPrev = NULL; // pLHS pNext = NULL; // pRHS nParamLen = 0; if (pArg->eToken == TOKEN_HASH) // HASH # immediate nParamLen = 1; nArgsLeft = (nArg - iArg); if (nArgsLeft < nParamLen) { return ARG_SYNTAX_ERROR; } pPrev = pArg - 1; // Pass wildstar '*' to commands if only arg if ((pArg->eToken == TOKEN_STAR) && (nArg == 1)) ; else if (nArgsLeft > 0) // These ops take at least 1 argument { pNext = pArg + 1; pSrc = &pNext->sArg[0]; nAddressVal = 0; if (ArgsGetValue( pNext, & nAddressRHS )) nAddressVal = nAddressRHS; bool bFound = FindAddressFromSymbol( pSrc, & nAddressSym ); if (bFound) { nAddressVal = nAddressSym; pArg->bSymbol = true; } // Comma and Colon are range operators, but they are not parsed here, // since args no longer have a 1st and 2nd value /* pPrev->eToken = TOKEN_COLON; pPrev->bType |= TYPE_ADDRESS; pPrev->bType |= TYPE_RANGE; */ if (pArg->eToken == TOKEN_AMPERSAND) // AND & delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue &= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_PIPE) // OR | delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue |= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_CARET) // XOR ^ delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue ^= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_PLUS) // PLUS + delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue += nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_MINUS) // MINUS - delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue -= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_PERCENT) // PERCENT % delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue %= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_STAR) // STAR * delta { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } pPrev->nValue *= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_FSLASH) // FORWARD SLASH / delta { if (pNext->eToken == TOKEN_FSLASH) // Comment { nArg = iArg - 1; return nArg; } if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { ArgsGetRegisterValue( pNext, & nAddressRHS ); } if (! nAddressRHS) nAddressRHS = 1; // divide by zero bug pPrev->nValue /= nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 2; } if (pArg->eToken == TOKEN_EQUAL) // EQUAL = assign { pPrev->nValue = nAddressRHS; pPrev->bType |= TYPE_VALUE; // signal already up to date nParamLen = 0; // need token for Smart BreakPoints } if (pArg->eToken == TOKEN_AT) // AT @ pointer de-reference { nParamLen = 1; _Arg_Shift( iArg + nParamLen, nArgs, iArg ); nArg--; pArg->nValue = 0; // nAddressRHS; pArg->bSymbol = false; int nPointers = g_vMemorySearchResults.size(); if ((nPointers) && (nAddressRHS < nPointers)) { pArg->nValue = g_vMemorySearchResults.at( nAddressRHS ); pArg->bType = TYPE_VALUE | TYPE_ADDRESS | TYPE_NO_REG | TYPE_NO_SYM; } nParamLen = 0; } if (pArg->eToken == TOKEN_HASH) // HASH # immediate { pArg->nValue = nAddressRHS; pArg->bSymbol = false; pArg->bType = TYPE_VALUE | TYPE_ADDRESS | TYPE_NO_REG | TYPE_NO_SYM; nParamLen = 0; } if (pArg->eToken == TOKEN_LESS_THAN) // < { nParamLen = 0; } if (pArg->eToken == TOKEN_GREATER_THAN) // > { nParamLen = 0; } if (pArg->eToken == TOKEN_EXCLAMATION) // NOT ! { if (! ArgsGetImmediateValue( pNext, & nAddressRHS )) { if (! ArgsGetRegisterValue( pNext, & nAddressRHS )) { nAddressRHS = nAddressVal; } } pArg->nValue = ~nAddressRHS; pArg->bType |= TYPE_VALUE; // signal already up to date // Don't remove, since "SYM ! symbol" needs token to remove symbol } if (pArg->eToken == TOKEN_PAREN_L) { nParenL++; if (nArgsLeft >= 2) { nParamLen = 1; // eat '(' _Arg_Shift( iArg + nParamLen, nArgs, iArg ); pNext = & (g_aArgs[ iArg + 1 ]); if (pNext->eToken == TOKEN_PAREN_R) { nParenR++; pArg->bSymbol = false; // This is static binding, instead of dynamic binding // i.e. memdump (BRKV) -> memdump (3F0), but if 3F0 changes later // the debugger won't know that it has // TODO: TYPE_INDIRECT // pArg->bType |= TYPE_INDIRECT; // pArg->nValue = nAddressVal; //nAddressVal = pNext->nValue; pArg->nValue = * (WORD*) (mem + nAddressVal); pArg->bType = TYPE_VALUE | TYPE_ADDRESS | TYPE_NO_REG; iArg++; // eat ')' nArg -= 2; nParamLen = 0; } else return ARG_SYNTAX_ERROR; // ERROR: unbalanced/unmatched ( ) } } if (pArg->eToken == TOKEN_PAREN_R) { nParenR++; if (nParenL == nParenR) { nParamLen = 1; } else return ARG_SYNTAX_ERROR; } if (nParamLen) { _Arg_Shift( iArg + nParamLen, nArgs, iArg ); nArg -= nParamLen; iArg = 0; // reset args, to handle multiple operators } } else return ARG_SYNTAX_ERROR; } else // not an operator, try (1) address, (2) symbol lookup { nAddressArg = (WORD)(_tcstoul( pSrc, &pEnd2, BASE) & _6502_MEM_END); if (! (pArg->bType & TYPE_NO_REG)) { ArgsGetRegisterValue( pArg, & nAddressArg ); } nAddressVal = nAddressArg; bool bFound = false; if (! (pArg->bType & TYPE_NO_SYM)) { bFound = FindAddressFromSymbol( pSrc, & nAddressSym ); if (bFound) { nAddressVal = nAddressSym; pArg->bSymbol = true; } } if (! (pArg->bType & TYPE_VALUE)) // already up to date? pArg->nValue = nAddressVal; pArg->bType |= TYPE_ADDRESS; } iArg++; } return nArg; } // Text Util ______________________________________________________________________________________ //=========================================================================== const char * ParserFindToken( const char *pSrc, const TokenTable_t *aTokens, const int nTokens, ArgToken_e * pToken_ ) { if (! pSrc) return NULL; const TCHAR *pName = NULL; int iToken; // Look-ahead for <= // Look-ahead for >= for (iToken = _TOKEN_FLAG_MULTI; iToken < NUM_TOKENS; iToken++ ) { pName = & (g_aTokens[ iToken ].sToken[0]); if ((pSrc[0] == pName[ 0 ]) && (pSrc[1] == pName[ 1 ])) { *pToken_ = g_aTokens[ iToken ].eToken; return pSrc + 2; } } const TokenTable_t *pToken = aTokens; for (iToken = 0; iToken < _TOKEN_FLAG_MULTI; iToken++ ) { pName = & (pToken->sToken[0]); if (*pSrc == *pName) { if ( pToken_ ) { *pToken_ = (ArgToken_e) iToken; } return pSrc + 1; } pToken++; } return NULL; } //=========================================================================== const TCHAR * FindTokenOrAlphaNumeric ( const TCHAR *pSrc, const TokenTable_t *aTokens, const int nTokens, ArgToken_e * pToken_ ) { if ( pToken_ ) *pToken_ = NO_TOKEN; const TCHAR *pEnd = pSrc; if (pSrc && (*pSrc)) { if (isalnum( *pSrc )) { if (pToken_) *pToken_ = TOKEN_ALPHANUMERIC; } else { pEnd = ParserFindToken( pSrc, aTokens, nTokens, pToken_ ); if (! pEnd) pEnd = pSrc; } } return pEnd; } //=========================================================================== void TextConvertTabsToSpaces( TCHAR *pDeTabified_, LPCTSTR pText, const int nDstSize, int nTabStop ) { int TAB_SPACING = 8; int TAB_SPACING_1 = 16; int TAB_SPACING_2 = 21; if (nTabStop) TAB_SPACING = nTabStop; LPCTSTR pSrc = pText; LPTSTR pDst = pDeTabified_; int nTab = 0; // gap left to next tab int nGap = 0; // actual gap int nCur = 0; // current cursor position while (pSrc && *pSrc && (nCur < nDstSize)) { if (*pSrc == CHAR_TAB) { if (nTabStop) { nTab = nCur % TAB_SPACING; nGap = (TAB_SPACING - nTab); } else { if (nCur <= TAB_SPACING_1) { nGap = (TAB_SPACING_1 - nCur); } else if (nCur <= TAB_SPACING_2) { nGap = (TAB_SPACING_2 - nCur); } else { nTab = nCur % TAB_SPACING; nGap = (TAB_SPACING - nTab); } } if ((nCur + nGap) >= nDstSize) break; for ( int iSpc = 0; iSpc < nGap; iSpc++ ) { *pDst++ = CHAR_SPACE; } nCur += nGap; } else if ((*pSrc == CHAR_LF) || (*pSrc == CHAR_CR)) { *pDst++ = 0; // *pSrc; nCur++; } else { *pDst++ = *pSrc; nCur++; } pSrc++; } *pDst = 0; } // @return Length of new string //=========================================================================== int RemoveWhiteSpaceReverse ( TCHAR *pSrc ) { int nLen = _tcslen( pSrc ); char *pDst = pSrc + nLen; while (nLen--) { pDst--; if (*pDst == CHAR_SPACE) { *pDst = 0; } else { break; } } return nLen; }