WingHexExplorer2/src/class/asbuilder.cpp

1061 lines
40 KiB
C++

/*==============================================================================
** Copyright (C) 2024-2027 WingSummer
**
** This program is free software: you can redistribute it and/or modify it under
** the terms of the GNU Affero General Public License as published by the Free
** Software Foundation, version 3.
**
** This program 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 Affero General Public License for more
** details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <https://www.gnu.org/licenses/>.
** =============================================================================
*/
#include "asbuilder.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QObject>
BEGIN_AS_NAMESPACE
asBuilder::asBuilder() {
engine = nullptr;
module = nullptr;
includeCallback = nullptr;
includeParam = nullptr;
pragmaCallback = nullptr;
pragmaParam = nullptr;
}
void asBuilder::SetIncludeCallback(INCLUDECALLBACK_t callback,
void *userParam) {
includeCallback = callback;
includeParam = userParam;
}
void asBuilder::SetPragmaCallback(PRAGMACALLBACK_t callback, void *userParam) {
pragmaCallback = callback;
pragmaParam = userParam;
}
int asBuilder::StartNewModule(asIScriptEngine *inEngine,
const char *moduleName) {
if (inEngine == nullptr)
return -1;
engine = inEngine;
module = inEngine->GetModule(moduleName, asGM_ALWAYS_CREATE);
if (module == nullptr)
return -1;
ClearAll();
return 0;
}
asIScriptEngine *asBuilder::GetEngine() { return engine; }
asIScriptModule *asBuilder::GetModule() { return module; }
unsigned int asBuilder::GetSectionCount() const {
return (unsigned int)(includedScripts.size());
}
QString asBuilder::GetSectionName(unsigned int idx) const {
if (idx >= includedScripts.size())
return {};
return includedScripts.at(idx);
}
// Returns 1 if the section was included
// Returns 0 if the section was not included because it had already been
// included before Returns <0 if there was an error
int asBuilder::AddSectionFromFile(const QString &filename) {
// The file name stored in the set should be the fully resolved name because
// it is possible to name the same file in multiple ways using relative
// paths.
auto fullpath = QFileInfo(filename).absoluteFilePath();
if (IncludeIfNotAlreadyIncluded(fullpath)) {
int r = LoadScriptSection(fullpath);
if (r < 0)
return r;
else
return 1;
}
return 0;
}
// Returns 1 if the section was included
// Returns 0 if the section was not included because it had already been
// included before Returns <0 if there was an error
int asBuilder::AddSectionFromMemory(const char *sectionName,
const char *scriptCode,
unsigned int scriptLength, int lineOffset) {
if (IncludeIfNotAlreadyIncluded(sectionName)) {
int r = ProcessScriptSection(scriptCode, scriptLength, sectionName,
lineOffset);
if (r < 0)
return r;
else
return 1;
}
return 0;
}
int asBuilder::BuildModule() { return Build(); }
void asBuilder::DefineWord(const QString &word) {
if (!definedWords.contains(word)) {
definedWords.append(word);
}
}
void asBuilder::ClearAll() {
includedScripts.clear();
#if AS_PROCESS_METADATA == 1
currentClass.clear();
currentNamespace.clear();
foundDeclarations.clear();
typeMetadataMap.clear();
funcMetadataMap.clear();
varMetadataMap.clear();
#endif
}
bool asBuilder::IncludeIfNotAlreadyIncluded(const QString &filename) {
if (includedScripts.contains(filename)) {
// Already included
return false;
}
// Add the file to the set of included sections
includedScripts.append(filename);
return true;
}
int asBuilder::LoadScriptSection(const QString &filename) {
// Open the script file
QFile f(filename);
if (!f.open(QFile::ReadOnly)) {
// Write a message to the engine's message callback
auto msg = QObject::tr("Failed to open script file ") +
QStringLiteral("'") +
QFileInfo(filename).absoluteFilePath() + QStringLiteral("'");
engine->WriteMessage(filename.toUtf8(), 0, 0, asMSGTYPE_ERROR,
msg.toUtf8());
// TODO: Write the file where this one was included from
return -1;
}
// Read the entire file
auto code = f.readAll();
f.close();
// Process the script section even if it is zero length so that the name is
// registered
return ProcessScriptSection(code, code.length(), filename, 0);
}
int asBuilder::ProcessScriptSection(const QByteArray &script, int length,
const QString &sectionname,
int lineOffset) {
QVector<QPair<QString, bool>> includes;
// Perform a superficial parsing of the script first to store the metadata
if (length)
modifiedScript = script.left(length);
else
modifiedScript = script;
// First perform the checks for #if directives to exclude code that
// shouldn't be compiled
unsigned int pos = 0;
int nested = 0;
while (pos < modifiedScript.size()) {
asUINT len = 0;
asETokenClass t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
if (t == asTC_UNKNOWN && modifiedScript[pos] == '#' &&
(pos + 1 < modifiedScript.size())) {
int start = pos++;
// Is this an #if directive?
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
Q_UNUSED(t);
QByteArray token = modifiedScript.mid(pos, len);
pos += len;
if (token == "if") {
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
if (t == asTC_WHITESPACE) {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
}
if (t == asTC_IDENTIFIER) {
QByteArray word = modifiedScript.mid(pos, len);
// Overwrite the #if directive with space characters to
// avoid compiler error
pos += len;
OverwriteCode(start, pos - start);
// Has this identifier been defined by the application or
// not?
if (!definedWords.contains(word)) {
// Exclude all the code until and including the #endif
pos = ExcludeCode(pos);
} else {
nested++;
}
}
} else if (token == "endif") {
// Only remove the #endif if there was a matching #if
if (nested > 0) {
OverwriteCode(start, pos - start);
nested--;
}
}
} else
pos += len;
}
#if AS_PROCESS_METADATA == 1
// Preallocate memory
QString name, declaration;
QVector<QString> metadata;
declaration.reserve(100);
#endif
// Then check for meta data and pre-processor directives
pos = 0;
while (pos < modifiedScript.size()) {
asUINT len = 0;
asETokenClass t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
if (t == asTC_COMMENT || t == asTC_WHITESPACE) {
pos += len;
continue;
}
QString token = modifiedScript.mid(pos, len);
#if AS_PROCESS_METADATA == 1
// Skip possible decorators before class and interface declarations
if (token == "shared" || token == "abstract" || token == "mixin" ||
token == "external") {
pos += len;
continue;
}
// Check if class or interface so the metadata for members can be
// gathered
if (currentClass.isEmpty() &&
(token == "class" || token == "interface")) {
// Get the identifier after "class"
do {
pos += len;
if (pos >= modifiedScript.size()) {
t = asTC_UNKNOWN;
break;
}
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
} while (t == asTC_COMMENT || t == asTC_WHITESPACE);
if (t == asTC_IDENTIFIER) {
currentClass = modifiedScript.mid(pos, len);
// Search until first { or ; is encountered
while (pos < modifiedScript.length()) {
engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
// If start of class section encountered stop
if (modifiedScript[pos] == '{') {
pos += len;
break;
} else if (modifiedScript[pos] == ';') {
// The class declaration has ended and there are no
// children
currentClass.clear();
pos += len;
break;
}
// Check next symbol
pos += len;
}
}
continue;
}
// Check if end of class
if (currentClass != "" && token == "}") {
currentClass = "";
pos += len;
continue;
}
// Check if namespace so the metadata for members can be gathered
if (token == "namespace") {
// Get the scope after "namespace". It can be composed of multiple
// nested namespaces, e.g. A::B::C
do {
do {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
} while (t == asTC_COMMENT || t == asTC_WHITESPACE);
if (t == asTC_IDENTIFIER) {
if (currentNamespace != "")
currentNamespace += "::";
currentNamespace += modifiedScript.mid(pos, len);
}
} while (
t == asTC_IDENTIFIER ||
(t == asTC_KEYWORD && modifiedScript.mid(pos, len) == "::"));
// Search until first { is encountered
while (pos < modifiedScript.length()) {
engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
// If start of namespace section encountered stop
if (modifiedScript[pos] == '{') {
pos += len;
break;
}
// Check next symbol
pos += len;
}
continue;
}
// Check if end of namespace
if (currentNamespace != "" && token == "}") {
size_t found = currentNamespace.lastIndexOf("::");
if (found >= 0) {
currentNamespace.remove(found, currentNamespace.size() - found);
} else {
currentNamespace = "";
}
pos += len;
continue;
}
// Is this the start of metadata?
if (token == "[") {
// Get the metadata string
pos = ExtractMetadata(pos, metadata);
// Determine what this metadata is for
int type;
ExtractDeclaration(pos, name, declaration, type);
// Store away the declaration in a map for lookup after the build
// has completed
if (type > 0) {
SMetadataDecl decl(metadata, name, declaration, type,
currentClass, currentNamespace);
foundDeclarations.push_back(decl);
}
} else
#endif
// Is this a preprocessor directive?
if (token == "#" && (pos + 1 < modifiedScript.size())) {
int start = pos++;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
if (t == asTC_IDENTIFIER) {
token = modifiedScript.mid(pos, len);
if (token == "include") {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos,
&len);
if (t == asTC_WHITESPACE) {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos,
&len);
}
if (t == asTC_VALUE && len > 2 &&
(modifiedScript[pos] == '"' ||
modifiedScript[pos] == '\'')) {
// Get the include file
QString includefile =
modifiedScript.mid(pos + 1, len - 2);
pos += len;
// Make sure the includeFile doesn't contain any
// line breaks
auto p = includefile.indexOf('\n');
if (p >= 0) {
// TODO: Show the correct line number for the
// error
auto str =
QObject::tr(
"Invalid file name for #include; "
"it contains a line-break: ") +
QStringLiteral("'") + includefile.left(p) +
QStringLiteral("'");
engine->WriteMessage(sectionname.toUtf8(), 0, 0,
asMSGTYPE_ERROR,
str.toUtf8());
} else {
// Store it for later processing
includes.append({includefile, true});
// Overwrite the include directive with space
// characters to avoid compiler error
OverwriteCode(start, pos - start);
}
}
if (t == asTC_KEYWORD && modifiedScript[pos] == '<') {
pos += len;
// find the next '>'
auto rpos = pos;
bool found = false;
for (; rpos < modifiedScript.size(); ++rpos) {
if (modifiedScript[rpos] == '>') {
found = true;
break;
}
if (modifiedScript[rpos] == '\n') {
break;
}
}
if (found) {
QString includefile =
modifiedScript.mid(pos, rpos - pos)
.trimmed();
pos = rpos + 1;
// Make sure the includeFile doesn't contain any
// line breaks
auto p = includefile.indexOf('\n');
auto ws = includefile.indexOf(' ');
if (!includefile.isEmpty() && p >= 0 &&
ws >= 0) {
// TODO: Show the correct line number for
// the error
auto str =
QObject::tr(
"Invalid file name for #include; "
"it contains a line-break: ") +
QStringLiteral("'") +
includefile.left(p) +
QStringLiteral("'");
engine->WriteMessage(sectionname.toUtf8(),
0, 0, asMSGTYPE_ERROR,
str.toUtf8());
} else {
// Store it for later processing
includes.append({includefile, false});
// Overwrite the include directive with
// space characters to avoid compiler error
OverwriteCode(start, pos - start);
}
} else {
auto str = QObject::tr(
"Invalid file name for #include; "
"it contains a line-break or "
"unpaired symbol");
engine->WriteMessage(sectionname.toUtf8(), 0, 0,
asMSGTYPE_ERROR,
str.toUtf8());
}
}
} else if (token == "pragma") {
// Read until the end of the line
pos += len;
for (; pos < modifiedScript.size() &&
modifiedScript[pos] != '\n';
pos++)
;
// Call the pragma callback
auto pragmaText =
modifiedScript.mid(start + 7, pos - start - 7);
int r =
pragmaCallback
? pragmaCallback(pragmaText, this, pragmaParam)
: -1;
if (r < 0) {
// TODO: Report the correct line number
engine->WriteMessage(
sectionname.toUtf8(), 0, 0, asMSGTYPE_ERROR,
QObject::tr("Invalid #pragma directive")
.toUtf8());
return r;
}
// Overwrite the pragma directive with space characters
// to avoid compiler error
OverwriteCode(start, pos - start);
}
} else {
// Check for lines starting with #!, e.g. shebang
// interpreter directive. These will be treated as comments
// and removed by the preprocessor
if (modifiedScript[pos] == '!') {
// Read until the end of the line
pos += len;
for (; pos < modifiedScript.size() &&
modifiedScript[pos] != '\n';
pos++)
;
// Overwrite the directive with space characters to
// avoid compiler error
OverwriteCode(start, pos - start);
}
}
}
// Don't search for metadata/includes within statement blocks or
// between tokens in statements
else {
pos = SkipStatement(pos);
}
}
// Build the actual script
engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true);
module->AddScriptSection(sectionname.toUtf8(), modifiedScript.data(),
modifiedScript.size(), lineOffset);
if (includes.size() > 0) {
// If the callback has been set, then call it for each included file
if (includeCallback) {
for (QVector<QString>::size_type n = 0; n < includes.size(); n++) {
auto inc = includes[n];
int r = includeCallback(inc.first, inc.second, sectionname,
this, includeParam);
if (r < 0)
return r;
}
} else {
// By default we try to load the included file from the relative
// directory of the current file
// Determine the path of the current script so that we can resolve
// relative paths for includes
auto path = QFileInfo(sectionname).filePath();
// Load the included scripts
for (QVector<QString>::size_type n = 0; n < includes.size(); n++) {
// If the include is a relative path, then prepend the path of
// the originating script
auto inc = includes.at(n);
if (!QFileInfo(inc.first).isAbsolute()) {
includes[n].first = path + QDir::separator() + inc.first;
}
// Include the script section
int r = AddSectionFromFile(includes[n].first);
if (r < 0)
return r;
}
}
}
return 0;
}
int asBuilder::Build() {
int r = module->Build();
if (r < 0)
return r;
#if AS_PROCESS_METADATA == 1
// After the script has been built, the metadata strings should be
// stored for later lookup by function id, type id, and variable index
for (int n = 0; n < (int)foundDeclarations.size(); n++) {
SMetadataDecl &decl = foundDeclarations[n];
module->SetDefaultNamespace(decl.nameSpace.toUtf8());
if (decl.type == MDT_TYPE) {
// Find the type id
int typeId = module->GetTypeIdByDecl(decl.declaration.toUtf8());
Q_ASSERT(typeId >= 0);
if (typeId >= 0)
typeMetadataMap.insert(typeId, decl.metadata);
} else if (decl.type == MDT_FUNC) {
if (decl.parentClass.isEmpty()) {
// Find the function id
asIScriptFunction *func =
module->GetFunctionByDecl(decl.declaration.toUtf8());
Q_ASSERT(func);
if (func)
funcMetadataMap.insert(func->GetId(), decl.metadata);
} else {
// Find the method id
int typeId = module->GetTypeIdByDecl(decl.parentClass.toUtf8());
Q_ASSERT(typeId > 0);
auto it = classMetadataMap.find(typeId);
if (it == classMetadataMap.end()) {
classMetadataMap.insert(typeId,
SClassMetadata(decl.parentClass));
it = classMetadataMap.find(typeId);
}
asITypeInfo *type = engine->GetTypeInfoById(typeId);
asIScriptFunction *func =
type->GetMethodByDecl(decl.declaration.toUtf8());
Q_ASSERT(func);
if (func)
it.value().funcMetadataMap.insert(func->GetId(),
decl.metadata);
}
} else if (decl.type == MDT_VIRTPROP) {
if (decl.parentClass.isEmpty()) {
// Find the global virtual property accessors
asIScriptFunction *func = module->GetFunctionByName(
(QStringLiteral("get_") + decl.declaration).toUtf8());
if (func)
funcMetadataMap.insert(func->GetId(), decl.metadata);
func = module->GetFunctionByName(
(QStringLiteral("set_") + decl.declaration).toUtf8());
if (func)
funcMetadataMap.insert(func->GetId(), decl.metadata);
} else {
// Find the method virtual property accessors
int typeId = module->GetTypeIdByDecl(decl.parentClass.toUtf8());
Q_ASSERT(typeId > 0);
auto it = classMetadataMap.find(typeId);
if (it == classMetadataMap.end()) {
classMetadataMap.insert(typeId,
SClassMetadata(decl.parentClass));
it = classMetadataMap.find(typeId);
}
asITypeInfo *type = engine->GetTypeInfoById(typeId);
asIScriptFunction *func = type->GetMethodByName(
(QStringLiteral("get_") + decl.declaration).toUtf8());
if (func)
it.value().funcMetadataMap.insert(func->GetId(),
decl.metadata);
func = type->GetMethodByName(
(QStringLiteral("set_") + decl.declaration).toUtf8());
if (func)
it.value().funcMetadataMap.insert(func->GetId(),
decl.metadata);
}
} else if (decl.type == MDT_VAR) {
if (decl.parentClass.isEmpty()) {
// Find the global variable index
int varIdx =
module->GetGlobalVarIndexByName(decl.declaration.toUtf8());
Q_ASSERT(varIdx >= 0);
if (varIdx >= 0)
varMetadataMap.insert(varIdx, decl.metadata);
} else {
int typeId = module->GetTypeIdByDecl(decl.parentClass.toUtf8());
Q_ASSERT(typeId > 0);
// Add the classes if needed
auto it = classMetadataMap.find(typeId);
if (it == classMetadataMap.end()) {
classMetadataMap.insert(typeId,
SClassMetadata(decl.parentClass));
it = classMetadataMap.find(typeId);
}
// Add the variable to class
asITypeInfo *objectType = engine->GetTypeInfoById(typeId);
int idx = -1;
// Search through all properties to get proper declaration
for (asUINT i = 0; i < (asUINT)objectType->GetPropertyCount();
++i) {
const char *name;
objectType->GetProperty(i, &name);
if (decl.declaration == name) {
idx = i;
break;
}
}
// If found, add it
Q_ASSERT(idx >= 0);
if (idx >= 0)
it.value().varMetadataMap.insert(idx, decl.metadata);
}
} else if (decl.type == MDT_FUNC_OR_VAR) {
if (decl.parentClass.isEmpty()) {
// Find the global variable index
int varIdx =
module->GetGlobalVarIndexByName(decl.name.toUtf8());
if (varIdx >= 0)
varMetadataMap.insert(varIdx, decl.metadata);
else {
asIScriptFunction *func =
module->GetFunctionByDecl(decl.declaration.toUtf8());
Q_ASSERT(func);
if (func)
funcMetadataMap.insert(func->GetId(), decl.metadata);
}
} else {
int typeId = module->GetTypeIdByDecl(decl.parentClass.toUtf8());
Q_ASSERT(typeId > 0);
// Add the classes if needed
auto it = classMetadataMap.find(typeId);
if (it == classMetadataMap.end()) {
classMetadataMap.insert(typeId,
SClassMetadata(decl.parentClass));
it = classMetadataMap.find(typeId);
}
// Add the variable to class
asITypeInfo *objectType = engine->GetTypeInfoById(typeId);
int idx = -1;
// Search through all properties to get proper declaration
for (asUINT i = 0; i < (asUINT)objectType->GetPropertyCount();
++i) {
const char *name;
objectType->GetProperty(i, &name);
if (decl.name == name) {
idx = i;
break;
}
}
// If found, add it
if (idx >= 0)
it.value().varMetadataMap.insert(idx, decl.metadata);
else {
// Look for the matching method instead
asITypeInfo *type = engine->GetTypeInfoById(typeId);
asIScriptFunction *func =
type->GetMethodByDecl(decl.declaration.toUtf8());
if (func)
it.value().funcMetadataMap.insert(func->GetId(),
decl.metadata);
}
}
}
}
module->SetDefaultNamespace("");
#endif
return 0;
}
int asBuilder::SkipStatement(int pos) {
asUINT len = 0;
// Skip until ; or { whichever comes first
while (pos < (int)modifiedScript.length() && modifiedScript[pos] != ';' &&
modifiedScript[pos] != '{') {
engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
pos += len;
}
// Skip entire statement block
if (pos < (int)modifiedScript.length() && modifiedScript[pos] == '{') {
pos += 1;
// Find the end of the statement block
int level = 1;
while (level > 0 && pos < (int)modifiedScript.size()) {
asETokenClass t = engine->ParseToken(
modifiedScript.data() + pos, modifiedScript.size() - pos, &len);
if (t == asTC_KEYWORD) {
if (modifiedScript[pos] == '{')
level++;
else if (modifiedScript[pos] == '}')
level--;
}
pos += len;
}
} else
pos += 1;
return pos;
}
// Overwrite all code with blanks until the matching #endif
int asBuilder::ExcludeCode(int pos) {
asUINT len = 0;
int nested = 0;
while (pos < (int)modifiedScript.size()) {
engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
if (modifiedScript[pos] == '#') {
modifiedScript[pos] = ' ';
pos++;
// Is it an #if or #endif directive?
engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
QString token = modifiedScript.mid(pos, len);
OverwriteCode(pos, len);
if (token == "if") {
nested++;
} else if (token == "endif") {
if (nested-- == 0) {
pos += len;
break;
}
}
} else if (modifiedScript[pos] != '\n') {
OverwriteCode(pos, len);
}
pos += len;
}
return pos;
}
// Overwrite all characters except line breaks with blanks
void asBuilder::OverwriteCode(int start, int len) {
auto code = modifiedScript.data() + start;
for (int n = 0; n < len; n++) {
if (*code != '\n')
*code = ' ';
code++;
}
}
#if AS_PROCESS_METADATA == 1
int asBuilder::ExtractMetadata(int pos, QVector<QString> &metadata) {
metadata.clear();
// Extract all metadata. They can be separated by whitespace and comments
for (;;) {
QString metadataString;
// Overwrite the metadata with space characters to allow compilation
modifiedScript[pos] = ' ';
// Skip opening brackets
pos += 1;
int level = 1;
asUINT len = 0;
while (level > 0 && pos < (int)modifiedScript.size()) {
asETokenClass t = engine->ParseToken(
modifiedScript.data() + pos, modifiedScript.size() - pos, &len);
if (t == asTC_KEYWORD) {
if (modifiedScript[pos] == '[')
level++;
else if (modifiedScript[pos] == ']')
level--;
}
// Copy the metadata to our buffer
if (level > 0)
metadataString.append(modifiedScript.mid(pos, len));
// Overwrite the metadata with space characters to allow compilation
if (t != asTC_WHITESPACE)
OverwriteCode(pos, len);
pos += len;
}
metadata.push_back(metadataString);
// Check for more metadata. Possibly separated by comments
asETokenClass t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
while (t == asTC_COMMENT || t == asTC_WHITESPACE) {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
}
if (modifiedScript[pos] != '[')
break;
}
return pos;
}
int asBuilder::ExtractDeclaration(int pos, QString &name, QString &declaration,
int &type) {
declaration.clear();
type = 0;
int start = pos;
QString token;
asUINT len = 0;
asETokenClass t = asTC_WHITESPACE;
// Skip white spaces, comments, and leading decorators
do {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
token = modifiedScript.mid(pos, len);
} while (t == asTC_WHITESPACE || t == asTC_COMMENT || token == "private" ||
token == "protected" || token == "shared" || token == "external" ||
token == "final" || token == "abstract");
// We're expecting, either a class, interface, function, or variable
// declaration
if (t == asTC_KEYWORD || t == asTC_IDENTIFIER) {
token = modifiedScript.mid(pos, len);
if (token == "interface" || token == "class" || token == "enum") {
// Skip white spaces and comments
do {
pos += len;
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
} while (t == asTC_WHITESPACE || t == asTC_COMMENT);
if (t == asTC_IDENTIFIER) {
type = MDT_TYPE;
declaration = modifiedScript.mid(pos, len);
pos += len;
return pos;
}
} else {
// For function declarations, store everything up to the start of
// the statement block, except for succeeding decorators (final,
// override, etc)
// For variable declaration store just the name as there can only be
// one
// We'll only know if the declaration is a variable or function
// declaration when we see the statement block, or absense of a
// statement block.
bool hasParenthesis = false;
int nestedParenthesis = 0;
declaration.append(modifiedScript.mid(pos, len));
pos += len;
for (; pos < (int)modifiedScript.size();) {
t = engine->ParseToken(modifiedScript.data() + pos,
modifiedScript.size() - pos, &len);
token = modifiedScript.mid(pos, len);
if (t == asTC_KEYWORD) {
if (token == "{" && nestedParenthesis == 0) {
if (hasParenthesis) {
// We've found the end of a function signature
type = MDT_FUNC;
} else {
// We've found a virtual property. Just keep the
// name
declaration = name;
type = MDT_VIRTPROP;
}
return pos;
}
if ((token == "=" && !hasParenthesis) || token == ";") {
if (hasParenthesis) {
// The declaration is ambigous. It can be a variable
// with initialization, or a function prototype
type = MDT_FUNC_OR_VAR;
} else {
// Substitute the declaration with just the name
declaration = name;
type = MDT_VAR;
}
return pos;
} else if (token == "(") {
nestedParenthesis++;
// This is the first parenthesis we encounter. If the
// parenthesis isn't followed by a statement block, then
// this is a variable declaration, in which case we
// should only store the type and name of the variable,
// not the initialization parameters.
hasParenthesis = true;
} else if (token == ")") {
nestedParenthesis--;
}
} else if (t == asTC_IDENTIFIER) {
// If a parenthesis is already found then the name is
// already known so it must not be overwritten
if (!hasParenthesis)
name = token;
}
// Skip trailing decorators
if (!hasParenthesis || nestedParenthesis > 0 ||
t != asTC_IDENTIFIER ||
(token != "final" && token != "override" &&
token != "delete" && token != "property"))
declaration += token;
pos += len;
}
}
}
return start;
}
QVector<QString> asBuilder::GetMetadataForType(int typeId) {
return typeMetadataMap.value(typeId);
}
QVector<QString> asBuilder::GetMetadataForFunc(asIScriptFunction *func) {
return funcMetadataMap.value(func->GetId());
}
QVector<QString> asBuilder::GetMetadataForVar(int varIdx) {
return varMetadataMap.value(varIdx);
}
QVector<QString> asBuilder::GetMetadataForTypeProperty(int typeId, int varIdx) {
if (classMetadataMap.contains(typeId)) {
return varMetadataMap.value(varIdx);
}
return {};
}
QVector<QString>
asBuilder::GetMetadataForTypeMethod(int typeId, asIScriptFunction *method) {
if (method) {
if (classMetadataMap.contains(typeId)) {
return funcMetadataMap.value(method->GetId());
}
}
return {};
}
#endif
END_AS_NAMESPACE