/*
* Author: Pawel Jablonski
* E-mail: pj@xirx.net
* WWW: xirx.net
* GIT: git.xirx.net
*
* License: You can use this code however you like
* but leave information about the original author.
* Code is free for non-commercial and commercial use.
*/
#include "parser.h"
/**
* Constructor of the parser
*
* @param dataList Data list
*/
Parser::Parser(DataList &dataList) : dataList(dataList)
{
}
/**
* Parse a file
*
* @param path Path to the file
*
* @return Status of parsing the file
*/
bool Parser::parseFile(const QString &path)
{
QTextStream out(stdout);
QList<QString> fileList;
QList<QString> mapList;
fileList.append(QFileInfo(path).absoluteFilePath());
for(int i = 0; i < fileList.size(); i++)
{
QString filePath = fileList.at(i);
QFile file(filePath);
QStringList lineList;
if(!file.open(QIODevice::ReadOnly))
{
out << QString("ERROR: Cannot open file: \"%1\"\n").arg(filePath);
return(false);
}
while(!file.atEnd())
{
lineList.append(file.readLine().trimmed());
}
file.close();
QSharedPointer<Data> data(new Data(filePath));
QSharedPointer<CodeSection> codeSection(new CodeSection(""));
for(int j = 0; j < lineList.size(); j++)
{
// Trimm line
QString line = lineList.at(j);
int offset = line.indexOf(QRegularExpression("(?<!\\\\);|^;"));
if(offset >= 0)
{
line = line.left(offset).trimmed();
}
if(line.length() <= 0)
{
continue;
}
// Find label
offset = line.indexOf(QRegularExpression("\\s+"));
QString label;
if(offset > 0)
{
label = line.left(offset).trimmed();
}
else
{
label = line;
}
line = line.mid(label.length()).trimmed();
// Find external or global
bool isExternal = false;
bool isGlobal = false;
if(!label.compare("external", Qt::CaseInsensitive))
{
if(this->dataList.getMode() == DataList::Mode::Rom)
{
isExternal = true;
}
else
{
out << QString("ERROR: Special keyword \"external\" is not allowed in APP mode, found at line %1 in file: \"%2\"\n").arg(j + 1).arg(filePath);
return(false);
}
}
if(!label.compare("global", Qt::CaseInsensitive))
{
isGlobal = true;
}
if(isExternal || isGlobal)
{
offset = line.indexOf(QRegularExpression("\\s+"));
if(offset > 0)
{
label = line.left(offset).trimmed();
}
else
{
label = line;
}
line = line.mid(label.length()).trimmed();
}
// Find code section
if((label.left(1) == ".") && (label.right(1) == ":"))
{
if(label.mid(1, (label.length() - 2)).contains(QRegularExpression("^([0-9a-zA-Z_-]+)$")))
{
if(!data->addCodeSection(codeSection))
{
out << QString("ERROR: Reuse of section name: \"%1\", found at line %2 in file: \"%3\"\n").arg(codeSection->getName()).arg(j + 1).arg(filePath);
return(false);
}
codeSection = QSharedPointer<CodeSection>(new CodeSection(label.left(label.length() - 1)));
if(isExternal)
{
if(!this->dataList.addExternalCodeSection(codeSection))
{
out << QString("ERROR: Reuse of external section name: \"%1\", found at line %2 in file: \"%3\"\n").arg(codeSection->getName()).arg(j + 1).arg(filePath);
return(false);
}
}
else if(isGlobal)
{
if(!this->dataList.addGlobalCodeSection(codeSection))
{
out << QString("ERROR: Reuse of global section name: \"%1\", found at line %2 in file: \"%3\"\n").arg(codeSection->getName()).arg(j + 1).arg(filePath);
return(false);
}
}
if(line.length() == 0)
{
continue;
}
offset = line.indexOf(QRegularExpression("\\s+"));
if(offset > 0)
{
label = line.left(offset).trimmed();
}
else
{
label = line;
}
line = line.mid(label.length()).trimmed();
}
else
{
out << QString("ERROR: Syntax error at line %1 in file: \"%2\"\n").arg(j + 1).arg(filePath);
return(false);
}
}
// Parse include
if(!label.compare("include", Qt::CaseInsensitive))
{
QString tPath;
if(QFileInfo(line).isAbsolute())
{
tPath = line;
}
else
{
tPath = QFileInfo(QFileInfo(filePath).absolutePath().append("/").append(line)).absoluteFilePath();
}
bool exists = false;
for(int j = 0; j < this->dataList.size(); j++)
{
if(this->dataList.at(j)->getFileName() == tPath)
{
exists = true;
break;
}
}
if(!exists)
{
for(const QString &fileFor : fileList)
{
if(fileFor == tPath)
{
exists = true;
break;
}
}
}
if(!exists)
{
fileList.append(tPath);
}
continue;
}
// Parse map
if(!label.compare("map", Qt::CaseInsensitive))
{
if(this->dataList.getMode() != DataList::Mode::App)
{
out << QString("ERROR: Special keyword \"map\" is not allowed in ROM mode, found at line %1 in file: \"%2\"\n").arg(j + 1).arg(filePath);
return(false);
}
QString tPath;
if(QFileInfo(line).isAbsolute())
{
tPath = line;
}
else
{
tPath = QFileInfo(QFileInfo(filePath).absolutePath().append("/").append(line)).absoluteFilePath();
}
bool exists = false;
for(int k = 0; k < this->dataList.size(); k++)
{
if(this->dataList.at(k)->getFileName() == tPath)
{
exists = true;
break;
}
}
if(!exists)
{
for(const QString &mapFor : mapList)
{
if(mapFor == tPath)
{
exists = true;
break;
}
}
}
if(!exists)
{
mapList.append(tPath);
}
QFile file(tPath);
QStringList lineList;
if(!file.open(QIODevice::ReadOnly))
{
out << QString("ERROR: Cannot open file: \"%1\"\n").arg(tPath);
return(false);
}
while(!file.atEnd())
{
lineList.append(file.readLine().trimmed());
}
file.close();
for(int j = 0; j < lineList.size(); j++)
{
// Trimm line
QString line = lineList.at(j);
int offset = line.indexOf(QRegularExpression("(?<!\\\\);|^;"));
if(offset >= 0)
{
line = line.left(offset).trimmed();
}
if(line.length() <= 0)
{
continue;
}
QStringList lineItems = line.split(QRegularExpression("\\s+"));
if(!this->parseMap(j, tPath, lineItems))
{
return(false);
}
}
continue;
}
// Parse ram
if(!label.compare("var", Qt::CaseInsensitive))
{
QSharedPointer<RamItem> ramItem = this->parseRam(j, line);
if(!ramItem.isNull())
{
if(!data->addRamItem(ramItem))
{
out << QString("ERROR: Reuse of name: \"%1\", found at line %2 in file: \"%3\"\n").arg(ramItem->getName()).arg(j + 1).arg(filePath);
return(false);
}
if(isExternal)
{
if(!this->dataList.addExternalRamItem(ramItem))
{
out << QString("ERROR: Reuse of external name: \"%1\", found at line %2 in file: \"%3\"\n").arg(ramItem->getName()).arg(j + 1).arg(filePath);
return(false);
}
}
else if(isGlobal)
{
if(!this->dataList.addGlobalRamItem(ramItem))
{
out << QString("ERROR: Reuse of global name: \"%1\", found at line %2 in file: \"%3\"\n").arg(ramItem->getName()).arg(j + 1).arg(filePath);
return(false);
}
}
}
else
{
out << QString("ERROR: Syntax error at line %1 in file: \"%2\"\n").arg(j + 1).arg(filePath);
return(false);
}
continue;
}
// Parse flash
if(!label.compare("const", Qt::CaseInsensitive))
{
QSharedPointer<FlashItem> flashItem = this->parseFlash(j, line);
if(!flashItem.isNull())
{
if(!data->addFlashItem(flashItem))
{
out << QString("ERROR: Reuse of name: \"%1\", found at line %2 in file: \"%3\"\n").arg(flashItem->getName()).arg(j + 1).arg(filePath);
return(false);
}
if(isExternal)
{
if(!this->dataList.addExternalFlashItem(flashItem))
{
out << QString("ERROR: Reuse of external name: \"%1\", found at line %2 in file: \"%3\"\n").arg(flashItem->getName()).arg(j + 1).arg(filePath);
return(false);
}
}
else if(isGlobal)
{
if(!this->dataList.addGlobalFlashItem(flashItem))
{
out << QString("ERROR: Reuse of global name: \"%1\", found at line %2 in file: \"%3\"\n").arg(flashItem->getName()).arg(j + 1).arg(filePath);
return(false);
}
}
}
else
{
out << QString("ERROR: Syntax error at line %1 in file: \"%2\"\n").arg(j + 1).arg(filePath);
return(false);
}
continue;
}
// Parse code
QSharedPointer<CodeItem> codeItem = this->parseCode(j, label, line);
if(!codeItem.isNull())
{
codeSection->addCodeItem(codeItem);
}
else
{
out << QString("ERROR: Syntax error at line %1 in file: \"%2\"\n").arg(j + 1).arg(filePath);
return(false);
}
}
if(!data->addCodeSection(codeSection))
{
out << QString("ERROR: Reuse of section name: \"%1\", found in file: \"%2\"\n").arg(codeSection->getName(), filePath);
return(false);
}
this->dataList.append(data);
}
return(true);
}
/**
* Parse a map
*
* @param i Line number
* @param mapPath Path to the map file
* @param lineItems List of the line items
*
* @return Status of parsing the map
*/
bool Parser::parseMap(int i, const QString &mapPath, const QStringList &lineItems)
{
QTextStream out(stdout);
QString name;
unsigned int address;
QString tAddress;
if(lineItems.size() == 3)
{
name = lineItems.at(0);
tAddress = lineItems.at(2);
if((name.left(1) == ".") && (lineItems.at(1) == "="))
{
if(name.mid(1).contains(QRegularExpression("^([0-9a-zA-Z_-]+)$")))
{
if(tAddress.contains(QRegularExpression("^(0x[0-9abcdefABCDEF]+)$")))
{
bool tOk;
address = tAddress.midRef(2).toUInt(&tOk, 16);
if(tOk)
{
if(address < Data::MEMORY_SIZE)
{
QSharedPointer<CodeSection> codeSection(new CodeSection(name));
codeSection->setAddress(address);
if(!this->dataList.addExternalCodeSection(codeSection))
{
out << QString("ERROR: Reuse of external section name: \"%1\", found at line %2 in file: \"%3\"\n").arg(name).arg(i + 1).arg(mapPath);
return(false);
}
return(true);
}
}
}
}
}
}
else if(lineItems.size() == 5)
{
name = lineItems.at(2);
tAddress = lineItems.at(4);
if((!lineItems.at(0).compare("external", Qt::CaseInsensitive)) && (lineItems.at(3) == "="))
{
if(name.contains(QRegularExpression("^([0-9a-zA-Z_-]+)$")))
{
if(tAddress.contains(QRegularExpression("^(0x[0-9abcdefABCDEF]+)$")))
{
bool tOk;
address = tAddress.midRef(2).toUInt(&tOk, 16);
if(tOk)
{
if(address < Data::MEMORY_SIZE)
{
if(!lineItems.at(1).compare("var", Qt::CaseInsensitive))
{
QSharedPointer<RamItem> ramItem(new RamItem(0, name, ""));
ramItem->setAddress(address);
if(!this->dataList.addExternalRamItem(ramItem))
{
out << QString("ERROR: Reuse of external section name: \"%1\", found at line %2 in file: \"%3\"\n").arg(name).arg(i + 1).arg(mapPath);
return(false);
}
}
else if(!lineItems.at(1).compare("const", Qt::CaseInsensitive))
{
QSharedPointer<FlashItem> flashItem(new FlashItem(0, name, QList<QString>()));
flashItem->setAddress(address);
if(!this->dataList.addExternalFlashItem(flashItem))
{
out << QString("ERROR: Reuse of external section name: \"%1\", found at line %2 in file: \"%3\"\n").arg(name).arg(i + 1).arg(mapPath);
return(false);
}
}
return(true);
}
}
}
}
}
}
out << QString("ERROR: Syntax error at line %1 in file: \"%2\"\n").arg(i + 1).arg(mapPath);
return(false);
}
/**
* Parse a variable line
*
* @param i Line number
* @param line Line raw data
*
* @return RAM Item object
*/
QSharedPointer<RamItem> Parser::parseRam(int i, QString &line)
{
QSharedPointer<RamItem> ramItem(nullptr);
bool parseError = false;
QString name;
QString arraySize;
if(line.length() == 0)
{
parseError = true;
}
if(line.contains(QRegularExpression("^([a-zA-Z0-9_-]+)$")))
{
name = line.trimmed();
}
else if(line.contains(QRegularExpression(R"(^([a-zA-Z0-9_-]+)\[([0-9]+)\]$)")))
{
int offset = line.indexOf("[");
name = line.left(offset).trimmed();
arraySize = line.mid((offset + 1), (line.length() - offset - 2));
}
else
{
parseError = true;
}
if(!parseError)
{
ramItem.reset(new RamItem((i + 1), name, arraySize));
}
return(ramItem);
}
/**
* Parse a const line
*
* @param i Line number
* @param line Line raw data
*
* @return Flash Item object
*/
QSharedPointer<FlashItem> Parser::parseFlash(int i, QString &line)
{
QSharedPointer<FlashItem> flashItem(nullptr);
bool parseError = false;
QString name;
QStringList valueList;
int offset = line.indexOf(QRegularExpression("\\s+"));
if(offset > 0)
{
name = line.left(offset).trimmed();
line = line.mid(name.length()).trimmed();
if(line.length() > 0)
{
valueList = line.split(QRegularExpression(R"((?<!\\),)"));
}
if(valueList.empty())
{
parseError = true;
}
}
else
{
parseError = true;
}
if(!parseError)
{
for(int i = 0; i < valueList.size(); i++)
{
QString value = valueList.at(i);
value = value.remove(QRegularExpression(R"((?<!\\)\\)")).trimmed();
valueList.replace(i, value);
if(valueList.at(i).length() == 0)
{
parseError = true;
break;
}
}
if(!parseError)
{
flashItem.reset(new FlashItem((i + 1), name, valueList));
}
}
return(flashItem);
}
/**
* Parse a code line
*
* @param i Line number
* @param label Name of the label
* @param line Line raw data
*
* @return Code Item object
*/
QSharedPointer<CodeItem> Parser::parseCode(int i, const QString &label, QString &line)
{
QSharedPointer<CodeItem> codeItem(nullptr);
bool parseError = false;
QStringList paramList;
if(line.length() > 0)
{
paramList = line.split(QRegularExpression("(?<!\\\\),"));
}
for(int i = 0; i < paramList.length(); i++)
{
paramList.replace(i, paramList.at(i).trimmed());
if(paramList.at(i).isEmpty())
{
parseError = true;
break;
}
}
if(!parseError)
{
for(int i = 0; i < paramList.length(); i++)
{
if(paramList.at(i).at(0) != "'")
{
paramList.replace(i, paramList.at(i));
}
else
{
paramList.replace(i, QString(paramList.at(i)).remove(QRegularExpression(R"((?<!\\)\\)")).trimmed());
}
}
codeItem.reset(new CodeItem((i + 1), label, paramList));
}
return(codeItem);
}