mirror of
https://github.com/markdavidlong/AppleSAWS.git
synced 2025-02-22 05:28:58 +00:00
544 lines
17 KiB
C++
544 lines
17 KiB
C++
#include "ApplesoftRetokenizer.h"
|
|
#include "applesofttoken.h"
|
|
#include "util.h"
|
|
|
|
#include <QDebug>
|
|
#include <QRegularExpression>
|
|
|
|
ApplesoftRetokenizer::ApplesoftRetokenizer()
|
|
{
|
|
m_isParsed = false;
|
|
}
|
|
|
|
void ApplesoftRetokenizer::setData(QByteArray data)
|
|
{
|
|
m_data = data;
|
|
m_data_end = data.length();
|
|
m_isParsed = false;
|
|
}
|
|
|
|
void ApplesoftRetokenizer::parse(quint16 start_address)
|
|
{
|
|
if (m_isParsed)
|
|
{
|
|
qWarning("File is already parsed. Not reparsing.");
|
|
return;
|
|
}
|
|
|
|
//TODO: This could be changed to search for hidden space between applesoft lines
|
|
int idx = 0;
|
|
quint8 val = 0;
|
|
m_retokenized_lines.clear();
|
|
|
|
quint16 current_address = start_address;
|
|
while (idx < m_data.length()) {
|
|
ApplesoftLine line;
|
|
line.address = current_address;
|
|
|
|
line.next_address = makeWord(m_data[idx],m_data[idx+1]);
|
|
idx++; idx++;
|
|
|
|
line.linenum = makeWord(m_data[idx],m_data[idx+1]);
|
|
idx++; idx++;
|
|
|
|
if (line.next_address == 0x00) { break; }
|
|
do {
|
|
val = m_data[idx++];
|
|
ApplesoftToken token(val);
|
|
line.tokens.append(token);
|
|
} while (val != 0x00);
|
|
|
|
|
|
retokenizeLine(line);
|
|
|
|
current_address = line.next_address;
|
|
|
|
m_retokenized_lines.append(line);
|
|
}
|
|
|
|
m_data_end = idx;
|
|
|
|
if (idx < m_data.length()) {
|
|
qDebug() << QString("%1 byte(s) unaccounted for.").arg(m_data.length() - idx);
|
|
}
|
|
|
|
retokenizeLinesForFormatting();
|
|
|
|
m_isParsed = true;
|
|
}
|
|
|
|
void ApplesoftRetokenizer::retokenizeLinesForFormatting()
|
|
{
|
|
QVector<ApplesoftLine> retLines;
|
|
|
|
foreach(ApplesoftLine line, m_retokenized_lines)
|
|
{
|
|
int indentlevel = 1;
|
|
// quint16 linenum = line.linenum;
|
|
|
|
bool firstToken = true;
|
|
ApplesoftToken previousToken;
|
|
QMutableVectorIterator<ApplesoftToken> tokenIt(line.tokens);
|
|
while (tokenIt.hasNext())
|
|
{
|
|
ApplesoftToken token = tokenIt.next();
|
|
bool isFlowTarget = false;
|
|
|
|
QString tokenstr = token.getRawPrintableString();
|
|
if (firstToken)
|
|
{
|
|
if (!tokenstr.startsWith(" "))
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtLeadingSpaceTokenValue);
|
|
tokenIt.remove();
|
|
tokenIt.insert(tmptoken);
|
|
tokenIt.insert(token);
|
|
}
|
|
firstToken = false;
|
|
}
|
|
|
|
quint16 preTokenId = previousToken.getTokenId();
|
|
if (preTokenId == ApplesoftToken::ASGoto ||
|
|
preTokenId == ApplesoftToken::ASGosub ||
|
|
preTokenId == ApplesoftToken::ASThen)
|
|
{
|
|
isFlowTarget = false;
|
|
if (preTokenId == ApplesoftToken::ASGoto || preTokenId == ApplesoftToken::ASGosub)
|
|
{
|
|
isFlowTarget = true;
|
|
}
|
|
else if (preTokenId == ApplesoftToken::ASThen
|
|
&& token.getTokenId() == ApplesoftToken::IntegerTokenVal)
|
|
{
|
|
isFlowTarget = true;
|
|
}
|
|
if (isFlowTarget)
|
|
{
|
|
QPair<quint16,quint16> pair;
|
|
pair.first = line.linenum;
|
|
pair.second = token.getWordValue();
|
|
m_flowTargets.append(pair);
|
|
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtFlagFlowTargetNextTokenValue);
|
|
tokenIt.remove();
|
|
tokenIt.insert(tmptoken);
|
|
tokenIt.insert(token);
|
|
}
|
|
}
|
|
|
|
if (token.getTokenId() == ApplesoftToken::ASReturn)
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtReturnLineBreakTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
}
|
|
|
|
if (token.getTokenId() == ':')
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtIndentLineBreakTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
for (int ind = 0; ind < indentlevel; ind++)
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtIndentTabTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
}
|
|
if (!tokenIt.peekNext().getRawPrintableString().startsWith(" "))
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtIndentSpaceTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
}
|
|
}
|
|
if (token.getTokenId() == ApplesoftToken::ASThen)
|
|
{
|
|
indentlevel++;
|
|
if (tokenIt.peekNext().getTokenId() != ApplesoftToken::IntegerTokenVal)
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtIndentLineBreakTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
for (int ind = 0; ind < indentlevel; ind++)
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtIndentTabTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
}
|
|
if (!tokenIt.peekNext().getRawPrintableString().startsWith(" "))
|
|
{
|
|
ApplesoftToken tmptoken(ApplesoftToken::OptFmtIndentSpaceTokenValue);
|
|
tokenIt.insert(tmptoken);
|
|
}
|
|
}
|
|
}
|
|
|
|
previousToken = token;
|
|
}
|
|
retLines.append(line);
|
|
}
|
|
|
|
m_retokenized_lines = retLines;
|
|
}
|
|
|
|
void ApplesoftRetokenizer::retokenizeLine(ApplesoftLine &line)
|
|
{
|
|
QList<ApplesoftToken> tmptokens = QList<ApplesoftToken>::fromVector(line.tokens);
|
|
tmptokens = retokenizeRems(tmptokens);
|
|
tmptokens = retokenizeStrings(tmptokens);
|
|
tmptokens = retokenizeDataStatements(tmptokens);
|
|
tmptokens = retokenizeVariables(tmptokens);
|
|
tmptokens = retokenizeNumbers(tmptokens);
|
|
tmptokens = retokenizeNegativeNumbers(tmptokens);
|
|
line.tokens = tmptokens.toVector();
|
|
}
|
|
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeRems(QList<ApplesoftToken>&tmptokens)
|
|
{
|
|
// Handle REMs
|
|
ApplesoftToken token;
|
|
QList<ApplesoftToken> replacements;
|
|
|
|
QByteArray buffer;
|
|
|
|
bool inRem = false;
|
|
|
|
while (!tmptokens.isEmpty())
|
|
{
|
|
token = tmptokens.takeFirst();
|
|
|
|
if (!inRem) {
|
|
replacements.append(token);
|
|
if (token.getByteValue() == ApplesoftToken::ASRem)
|
|
{
|
|
inRem = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
buffer.append(token.getByteValue());
|
|
}
|
|
}
|
|
if (inRem) {
|
|
ApplesoftToken remstrtoken(ApplesoftToken::RemStringTokenVal, buffer);
|
|
replacements.append(remstrtoken);
|
|
buffer.clear();
|
|
inRem = false;
|
|
}
|
|
|
|
return replacements;
|
|
}
|
|
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeStrings(QList<ApplesoftToken>&tmptokens)
|
|
{
|
|
// Handle Strings
|
|
QList<ApplesoftToken> replacements;
|
|
|
|
QString buffer;
|
|
ApplesoftToken token;
|
|
|
|
bool inString = false;
|
|
|
|
while (!tmptokens.isEmpty())
|
|
{
|
|
token = tmptokens.takeFirst();
|
|
if (token.getTokenId() >= 0x80)
|
|
{
|
|
replacements.append(token);
|
|
// continue;
|
|
} else
|
|
|
|
if (token.getWordValue() == '"')
|
|
{
|
|
if (!inString)
|
|
{
|
|
inString = true;
|
|
buffer.append(token.getWordValue());
|
|
// continue;
|
|
}
|
|
else
|
|
{
|
|
buffer.append(token.getWordValue());
|
|
ApplesoftToken strtoken(ApplesoftToken::StringTokenVal, buffer);
|
|
replacements.append(strtoken);
|
|
buffer.clear();
|
|
inString = false;
|
|
// continue;
|
|
}
|
|
} else
|
|
|
|
if (inString)
|
|
{
|
|
buffer.append(token.getWordValue());
|
|
// continue;
|
|
} else
|
|
|
|
replacements.append(token);
|
|
}
|
|
|
|
return replacements;
|
|
}
|
|
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeDataStatements(QList<ApplesoftToken>&tmptokens)
|
|
{
|
|
// Handle DATAs
|
|
|
|
QList<ApplesoftToken> replacements;
|
|
ApplesoftToken token;
|
|
|
|
QList<ApplesoftToken> datatokenbuffer;
|
|
bool inData = false;
|
|
while (!tmptokens.isEmpty())
|
|
{
|
|
token = tmptokens.takeFirst();
|
|
|
|
if (!inData) {
|
|
replacements.append(token);
|
|
if (token.getTokenId() == ApplesoftToken::ASData)
|
|
{
|
|
inData = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
datatokenbuffer.append(token);
|
|
}
|
|
}
|
|
if (inData) {
|
|
QList<ApplesoftToken> dataTokens;
|
|
dataTokens = retokenizeDataPayload(datatokenbuffer);
|
|
replacements.append(dataTokens);
|
|
datatokenbuffer.clear();
|
|
inData = false;
|
|
}
|
|
return replacements;
|
|
}
|
|
|
|
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeDataPayload(QList<ApplesoftToken>& tmptokens)
|
|
{
|
|
QList<ApplesoftToken> retval;
|
|
|
|
ApplesoftToken token;
|
|
|
|
QString stringbuffer;
|
|
|
|
while (!tmptokens.isEmpty())
|
|
{
|
|
token = tmptokens.takeFirst();
|
|
if (token.getTokenId() == ApplesoftToken::StringTokenVal)
|
|
{
|
|
ApplesoftToken newToken(ApplesoftToken::DataStringTokenVal, token.getStringValue());
|
|
retval.append(newToken);
|
|
continue;
|
|
}
|
|
if (token.getWordValue() == ',')
|
|
{
|
|
if (!stringbuffer.isEmpty())
|
|
{
|
|
ApplesoftToken datastrtoken(ApplesoftToken::DataStringTokenVal, stringbuffer);
|
|
retval.append(datastrtoken);
|
|
stringbuffer.clear();
|
|
}
|
|
retval.append(token);
|
|
continue;
|
|
|
|
}
|
|
stringbuffer.append(token.getWordValue());
|
|
}
|
|
if (!stringbuffer.isEmpty())
|
|
{
|
|
ApplesoftToken datastrtoken(ApplesoftToken::DataStringTokenVal, stringbuffer);
|
|
retval.append(datastrtoken);
|
|
stringbuffer.clear();
|
|
}
|
|
return retval;
|
|
}
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeVariables(QList<ApplesoftToken>&tmptokens)
|
|
{
|
|
// Handle variable names
|
|
ApplesoftToken token;
|
|
|
|
QRegularExpression varregexp("[A-Za-z][A-Za-z0-9]*[$%]?\\(?");
|
|
|
|
QString parsestring;
|
|
// Parse the tokens to find assist
|
|
for (int idx = 0; idx < tmptokens.count();idx++)
|
|
{
|
|
token = tmptokens.at(idx);
|
|
|
|
if (token.getTokenId() < 0x0080 && token.getTokenId() > 0x0000)
|
|
{
|
|
parsestring.append(QChar(token.getWordValue()));
|
|
}
|
|
else
|
|
{
|
|
parsestring.append("_");
|
|
}
|
|
}
|
|
QList<QRegularExpressionMatch> matchstack;
|
|
QRegularExpressionMatchIterator matches = varregexp.globalMatch(parsestring);
|
|
// qDebug() << parsestring;
|
|
while (matches.hasNext()) {
|
|
QRegularExpressionMatch rematch = matches.next();
|
|
matchstack.push_front(rematch);
|
|
|
|
// qDebug() << "Capture " << " = " << rematch.capturedTexts() << "From: " << rematch.capturedStart()
|
|
// << "To: " << rematch.capturedEnd()-1 << "("<<rematch.capturedLength()<<")";
|
|
}
|
|
|
|
foreach(QRegularExpressionMatch rematch, matchstack)
|
|
{
|
|
QString text = rematch.captured(0);
|
|
int start = rematch.capturedStart();
|
|
int length = rematch.capturedLength();
|
|
|
|
quint16 tokentype = ApplesoftToken::FloatVarTokenVal;
|
|
if (text.contains("$")) {
|
|
tokentype = ApplesoftToken::StringVarTokenVal;
|
|
} else if (text.contains("%")) {
|
|
tokentype = ApplesoftToken::IntVarTokenVal;
|
|
}
|
|
|
|
if (text.contains("(")) {
|
|
if (tokentype == ApplesoftToken::FloatVarTokenVal) {
|
|
tokentype = ApplesoftToken::FloatAryVarTokenVal;
|
|
} else if (tokentype == ApplesoftToken::StringVarTokenVal) {
|
|
tokentype = ApplesoftToken::StringAryVarTokenVal;
|
|
} else {
|
|
tokentype = ApplesoftToken::IntAryVarTokenVal;
|
|
}
|
|
}
|
|
|
|
ApplesoftToken vartoken = ApplesoftToken(tokentype,text);
|
|
|
|
for (int idx = 0; idx < length; idx++)
|
|
{
|
|
tmptokens.removeAt(start);
|
|
}
|
|
tmptokens.insert(start,vartoken);
|
|
|
|
}
|
|
|
|
return tmptokens;
|
|
}
|
|
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeNumbers(QList<ApplesoftToken>&tmptokens)
|
|
{
|
|
// Handle numbers
|
|
ApplesoftToken token;
|
|
|
|
QRegularExpression varregexp("[0-9]+(\\.[0-9]*)?");
|
|
|
|
QString parsestring;
|
|
// Parse the tokens to find assist
|
|
for (int idx = 0; idx < tmptokens.count();idx++)
|
|
{
|
|
token = tmptokens.at(idx);
|
|
|
|
if (token.getTokenId() < 0x0080 && token.getTokenId() > 0x0000)
|
|
{
|
|
parsestring.append(QChar(token.getWordValue()));
|
|
}
|
|
else
|
|
{
|
|
parsestring.append("_");
|
|
}
|
|
}
|
|
QList<QRegularExpressionMatch> matchstack;
|
|
QRegularExpressionMatchIterator matches = varregexp.globalMatch(parsestring);
|
|
// qDebug() << parsestring;
|
|
while (matches.hasNext()) {
|
|
QRegularExpressionMatch rematch = matches.next();
|
|
matchstack.push_front(rematch);
|
|
|
|
// qDebug() << "Capture " << " = " << rematch.capturedTexts() << "From: " << rematch.capturedStart()
|
|
// << "To: " << rematch.capturedEnd()-1 << "("<<rematch.capturedLength()<<")";
|
|
}
|
|
|
|
foreach(QRegularExpressionMatch rematch, matchstack)
|
|
{
|
|
QString text = rematch.captured(0);
|
|
int start = rematch.capturedStart();
|
|
int length = rematch.capturedLength();
|
|
|
|
quint16 tokentype = ApplesoftToken::IntegerTokenVal;
|
|
if (text.contains(".")) {
|
|
tokentype = ApplesoftToken::FloatTokenVal;
|
|
}
|
|
|
|
ApplesoftToken vartoken = ApplesoftToken(tokentype,text);
|
|
|
|
for (int idx = 0; idx < length; idx++)
|
|
{
|
|
tmptokens.removeAt(start);
|
|
}
|
|
tmptokens.insert(start,vartoken);
|
|
|
|
}
|
|
|
|
return tmptokens;
|
|
}
|
|
|
|
QList<ApplesoftToken> ApplesoftRetokenizer::retokenizeNegativeNumbers(QList<ApplesoftToken>&tmptokens)
|
|
{
|
|
//TODO: Code to make determination of negative numbers vs. unary minus/math formulas.
|
|
// Prefixed '-' tokens for negative numbers should get merged with the integer value token.
|
|
// So, need to determine when we're in an expression vs when we're starting a number.
|
|
//
|
|
// A = -1 should retokenize.
|
|
// A = - 4 - 1 should retokenize -4
|
|
// A = - 4 - - 1 should retokenize -4 and -1
|
|
// A = 3 - 1 shoud not retokenize
|
|
// A = A - 1 should not
|
|
// A = PEEK(123) - 5 should not
|
|
// A = 4 * - 1 should
|
|
// A = (1 + 2) - 4 should not
|
|
// A = (1 + 2) + - 4 should
|
|
// A = (1 + 2) - - 4 should
|
|
// POKE - 4, 1 should
|
|
// PRINT + - 4 should
|
|
// PRINT - 4 should
|
|
// PRINT + + + - - - 4 should retokenize the last -4.
|
|
// A = 1 - - 4 should
|
|
// A = 1 - - - 4 should, probably, but it's errorprone to say the least,
|
|
// as are any multiple arbitrary +/-'s. Have to hope for the best here.
|
|
// Best bet would be to look at how Applesoft handles these values.
|
|
// A = - 0 is the same as 0
|
|
|
|
ApplesoftToken token;
|
|
|
|
QMutableListIterator<ApplesoftToken>it(tmptokens);
|
|
|
|
bool lastWasInt = false;
|
|
|
|
while (it.hasNext())
|
|
{
|
|
token = it.next();
|
|
if (token.getTokenId() == ApplesoftToken::IntegerTokenVal) lastWasInt = true;
|
|
else if (token.getTokenId() == ApplesoftToken::FloatTokenVal) lastWasInt = true;
|
|
else if (token.getTokenId() == ApplesoftToken::IntVarTokenVal) lastWasInt = true;
|
|
else if (token.getTokenId() == ApplesoftToken::FloatVarTokenVal) lastWasInt = true;
|
|
else if (token.getTokenId() == ')') lastWasInt = true;
|
|
else
|
|
if (token.getTokenId() == ApplesoftToken::ASMINUS)
|
|
{
|
|
if (!lastWasInt && it.hasNext() && it.peekNext().getTokenId() == ApplesoftToken::IntegerTokenVal)
|
|
{
|
|
it.remove();
|
|
token = it.next();
|
|
it.remove();
|
|
int val = token.getUnsignedIntegerValue() * -1;
|
|
token.setValue(val);
|
|
it.insert(token);
|
|
lastWasInt = true;
|
|
}
|
|
else
|
|
{
|
|
lastWasInt = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lastWasInt = false;
|
|
}
|
|
}
|
|
|
|
return tmptokens;
|
|
}
|
|
|