mirror of
https://github.com/fadden/ciderpress.git
synced 2024-09-29 02:54:33 +00:00
526 lines
15 KiB
C++
526 lines
15 KiB
C++
|
/*
|
||
|
* 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;
|
||
|
}
|