2474 lines
66 KiB
C++
2474 lines
66 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 "qascodeparser.h"
|
|
|
|
#include <QStack>
|
|
|
|
#include "AngelScript/sdk/angelscript/source/as_scriptengine.h"
|
|
#include "AngelScript/sdk/angelscript/source/as_tokenizer.h"
|
|
|
|
/* Some information about AngelScript and this class:
|
|
* * This class is designed to get variables and functions symbol.
|
|
* * Strings are also included.
|
|
* * AngelScript's class and enum can not be nested unlike C++.
|
|
* * We only support class / mixin class / interface / function / enum.
|
|
*
|
|
* * auto type can not be derived by this class, PRs are welcomed
|
|
*/
|
|
|
|
QAsCodeParser::QAsCodeParser(asCScriptEngine *engine) : engine(engine) {
|
|
Q_ASSERT(engine);
|
|
_checkValidTypes = false;
|
|
_isParsingAppInterface = false;
|
|
}
|
|
|
|
QAsCodeParser::QAsCodeParser(asIScriptEngine *engine) {
|
|
this->engine = dynamic_cast<asCScriptEngine *>(engine);
|
|
Q_ASSERT(engine);
|
|
_checkValidTypes = false;
|
|
_isParsingAppInterface = false;
|
|
}
|
|
|
|
QAsCodeParser::~QAsCodeParser() {}
|
|
|
|
QList<QAsCodeParser::CodeSegment>
|
|
QAsCodeParser::parse(const QByteArray &codes) {
|
|
reset();
|
|
_code = codes;
|
|
return parseScript(false);
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol>
|
|
QAsCodeParser::parseAndIntell(qsizetype offset, const QByteArray &codes) {
|
|
return parseIntell(offset, parse(codes));
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol>
|
|
QAsCodeParser::parseIntell(qsizetype offset,
|
|
const QList<QAsCodeParser::CodeSegment> &segs) {
|
|
reset();
|
|
|
|
QList<Symbol> ret;
|
|
|
|
// first: global entries
|
|
for (auto &seg : segs) {
|
|
if (!seg.isValid()) {
|
|
continue;
|
|
}
|
|
|
|
Symbol sym;
|
|
sym.symtype = seg.type;
|
|
sym.scope = seg.scope;
|
|
sym.offset = seg.offset;
|
|
sym.name = seg.name;
|
|
|
|
switch (seg.type) {
|
|
case SymbolType::Function: {
|
|
sym.type = seg.additonalInfos.at(0);
|
|
sym.additonalInfo = seg.additonalInfos.at(1);
|
|
if (offset > seg.offset && offset < seg.end()) {
|
|
sym.children = parseStatementBlock(sym.scope, seg.codes,
|
|
offset - seg.offset);
|
|
}
|
|
} break;
|
|
case SymbolType::Enum:
|
|
if (offset < seg.offset || offset >= seg.end()) {
|
|
sym.children =
|
|
parseEnumerationContent(sym.scope, sym.name, seg.codes);
|
|
}
|
|
break;
|
|
case SymbolType::TypeDef:
|
|
if (offset < seg.offset || offset >= seg.end()) {
|
|
sym = parseTypedefContent(sym.scope, seg.codes);
|
|
}
|
|
break;
|
|
case SymbolType::Variable:
|
|
if (offset < seg.offset || offset >= seg.end()) {
|
|
ret << parseGlobalVarDecls(sym.scope, seg.codes);
|
|
continue;
|
|
}
|
|
break;
|
|
case SymbolType::FnDef:
|
|
if (offset < seg.offset || offset >= seg.end()) {
|
|
sym = parseFuncDefContent(sym.scope, seg.codes);
|
|
QByteArrayList argBuffer;
|
|
for (auto &c : sym.children) {
|
|
if (c.name.isEmpty()) {
|
|
argBuffer.append(c.type);
|
|
} else {
|
|
argBuffer.append(c.type + ' ' + c.name);
|
|
}
|
|
}
|
|
sym.additonalInfo = argBuffer.join(", ");
|
|
ret.append(sym);
|
|
continue;
|
|
}
|
|
break;
|
|
case SymbolType::Class: {
|
|
auto syms =
|
|
parseClassContent(offset - seg.offset, sym.scope, seg.codes);
|
|
// TODO: PRS, 'cause i have no need to code-complete a class
|
|
sym.inherit = syms.first;
|
|
sym.children = syms.second;
|
|
if (offset > seg.offset && offset < seg.end()) {
|
|
ret.append(syms.second);
|
|
}
|
|
} break;
|
|
case SymbolType::Interface: {
|
|
auto syms = parseInterfaceContent(offset - seg.offset, sym.scope,
|
|
seg.codes);
|
|
// TODO: PRS, 'cause i have no need to code-complete an interface
|
|
sym.inherit = syms.first;
|
|
sym.children = syms.second;
|
|
if (offset > seg.offset && offset < seg.end()) {
|
|
ret.append(syms.second);
|
|
}
|
|
} break;
|
|
case SymbolType::Invalid:
|
|
case SymbolType::Import:
|
|
continue;
|
|
}
|
|
|
|
ret.append(sym);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void QAsCodeParser::reset() {
|
|
_errorWhileParsing = false;
|
|
_isSyntaxError = false;
|
|
_checkValidTypes = false;
|
|
_isParsingAppInterface = false;
|
|
|
|
_sourcePos = 0;
|
|
_code.clear();
|
|
|
|
_lastToken.length = 0;
|
|
_lastToken.type = ttUnrecognizedToken;
|
|
_lastToken.pos = size_t(-1);
|
|
}
|
|
|
|
void QAsCodeParser::getToken(sToken *token) {
|
|
// Check if the token has already been parsed
|
|
if (_lastToken.pos == _sourcePos) {
|
|
*token = _lastToken;
|
|
_sourcePos += token->length;
|
|
|
|
if (token->type == ttWhiteSpace || token->type == ttOnelineComment ||
|
|
token->type == ttMultilineComment)
|
|
getToken(token);
|
|
|
|
return;
|
|
}
|
|
|
|
// Parse new token
|
|
size_t sourceLength = _code.length();
|
|
do {
|
|
if (_sourcePos >= sourceLength) {
|
|
token->type = ttEnd;
|
|
token->length = 0;
|
|
} else
|
|
token->type =
|
|
engine->tok.GetToken(_code.data() + _sourcePos,
|
|
sourceLength - _sourcePos, &token->length);
|
|
|
|
token->pos = _sourcePos;
|
|
|
|
// Update state
|
|
_sourcePos += token->length;
|
|
}
|
|
// Filter out whitespace and comments
|
|
while (token->type == ttWhiteSpace || token->type == ttOnelineComment ||
|
|
token->type == ttMultilineComment);
|
|
}
|
|
|
|
void QAsCodeParser::rewindTo(const sToken *token) {
|
|
// TODO: optimize: Perhaps we can optimize this further by having the parser
|
|
// set an explicit return point, after which each token will
|
|
// be stored. That way not just one token will be reused but
|
|
// no token will have to be tokenized more than once.
|
|
|
|
// Store the token so it doesn't have to be tokenized again
|
|
_lastToken = *token;
|
|
|
|
_sourcePos = token->pos;
|
|
}
|
|
|
|
void QAsCodeParser::setPos(size_t pos) {
|
|
_lastToken.pos = size_t(-1);
|
|
_sourcePos = pos;
|
|
}
|
|
|
|
void QAsCodeParser::rewindErrorTo(sToken *token) {
|
|
rewindTo(token);
|
|
|
|
_isSyntaxError = true;
|
|
_errorWhileParsing = true;
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol> QAsCodeParser::parseParameterListContent() {
|
|
QList<QAsCodeParser::Symbol> ret;
|
|
|
|
sToken t1;
|
|
getToken(&t1);
|
|
if (t1.type != ttOpenParenthesis) {
|
|
rewindErrorTo(&t1);
|
|
return ret;
|
|
}
|
|
|
|
getToken(&t1);
|
|
if (t1.type == ttCloseParenthesis) {
|
|
// Statement block is finished
|
|
return ret;
|
|
} else {
|
|
// If the parameter list is just (void) then the void token should be
|
|
// ignored
|
|
if (t1.type == ttVoid) {
|
|
sToken t2;
|
|
getToken(&t2);
|
|
if (t2.type == ttCloseParenthesis) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
rewindTo(&t1);
|
|
|
|
for (;;) {
|
|
// Parse data type
|
|
auto t = parseType(true, _isParsingAppInterface);
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
|
|
parseTypeMod(true);
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
|
|
Symbol se;
|
|
se.symtype = SymbolType::Variable;
|
|
se.type = t;
|
|
|
|
// Parse optional identifier
|
|
getToken(&t1);
|
|
if (t1.type == ttIdentifier) {
|
|
rewindTo(&t1);
|
|
|
|
auto iden = parseIdentifier();
|
|
se.name = getSymbolString(iden);
|
|
se.offset = iden.pos;
|
|
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
|
|
getToken(&t1);
|
|
}
|
|
|
|
ret.append(se);
|
|
|
|
// Parse optional expression for the default arg
|
|
if (t1.type == ttAssignment) {
|
|
// Do a superficial parsing of the default argument
|
|
// The actual parsing will be done when the argument is compiled
|
|
// for a function call
|
|
superficiallyParseExpression();
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
|
|
getToken(&t1);
|
|
}
|
|
|
|
// Check if list continues
|
|
if (t1.type == ttCloseParenthesis) {
|
|
return ret;
|
|
} else if (t1.type == ttListSeparator)
|
|
continue;
|
|
else {
|
|
rewindErrorTo(&t1);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
UNREACHABLE_RETURN;
|
|
}
|
|
|
|
void QAsCodeParser::superficiallyParseExpression() {
|
|
// Simply parse everything until the first , or ), whichever comes first.
|
|
// Keeping in mind that () and {} can group expressions.
|
|
sToken start;
|
|
getToken(&start);
|
|
rewindTo(&start);
|
|
|
|
asCString stack;
|
|
sToken t;
|
|
for (;;) {
|
|
getToken(&t);
|
|
|
|
if (t.type == ttOpenParenthesis)
|
|
stack += "(";
|
|
else if (t.type == ttCloseParenthesis) {
|
|
if (stack == "") {
|
|
// Expression has ended. This token is not part of expression
|
|
rewindTo(&t);
|
|
break;
|
|
} else if (stack[stack.GetLength() - 1] == '(') {
|
|
// Group has ended
|
|
stack.SetLength(stack.GetLength() - 1);
|
|
} else {
|
|
// Wrong syntax
|
|
rewindTo(&t);
|
|
rewindErrorTo(&t);
|
|
return;
|
|
}
|
|
} else if (t.type == ttListSeparator) {
|
|
if (stack == "") {
|
|
// Expression has ended. This token is not part of expression
|
|
rewindTo(&t);
|
|
break;
|
|
}
|
|
} else if (t.type == ttStartStatementBlock)
|
|
stack += "{";
|
|
else if (t.type == ttEndStatementBlock) {
|
|
if (stack == "" || stack[stack.GetLength() - 1] != '{') {
|
|
// Wrong syntax
|
|
rewindErrorTo(&t);
|
|
return;
|
|
} else {
|
|
// Group has ended
|
|
stack.SetLength(stack.GetLength() - 1);
|
|
}
|
|
} else if (t.type == ttEndStatement) {
|
|
// Wrong syntax (since we're parsing a default arg expression)
|
|
rewindErrorTo(&t);
|
|
return;
|
|
} else if (t.type == ttNonTerminatedStringConstant) {
|
|
rewindErrorTo(&t);
|
|
return;
|
|
} else if (t.type == ttEnd) {
|
|
// Wrong syntax
|
|
rewindErrorTo(&start);
|
|
return;
|
|
}
|
|
|
|
// Include the token in the node
|
|
}
|
|
}
|
|
|
|
void QAsCodeParser::superficiallyParseTemplateList() {
|
|
sToken t;
|
|
|
|
// Starts with '<'
|
|
getToken(&t);
|
|
if (t.type != ttLessThan) {
|
|
return;
|
|
}
|
|
|
|
// End with '>'
|
|
// Accept >> and >>> tokens too. But then force the tokenizer to move
|
|
// only 1 character ahead (thus splitting the token in two).
|
|
while (true) {
|
|
getToken(&t);
|
|
if (_code.length() <= t.pos) {
|
|
break;
|
|
}
|
|
if (_code[QString::size_type(t.pos)] == '>') {
|
|
setPos(t.pos + 1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QByteArray QAsCodeParser::parseType(bool allowConst, bool allowVariableType,
|
|
bool allowAuto) {
|
|
sToken t;
|
|
|
|
if (allowConst) {
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
if (t.type == ttConst) {
|
|
parseToken(ttConst);
|
|
if (_isSyntaxError)
|
|
return {};
|
|
}
|
|
}
|
|
|
|
// Parse scope prefix
|
|
parseOptionalScope();
|
|
|
|
// Parse the actual type
|
|
auto dt = parseDataType(allowVariableType, allowAuto);
|
|
if (_isSyntaxError)
|
|
return {};
|
|
|
|
// If the datatype is a template type, then parse the subtype within the < >
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
|
|
auto dtType = getSymbolString(dt);
|
|
if (engine->IsTemplateType(dtType) && t.type == ttLessThan) {
|
|
superficiallyParseTemplateList();
|
|
}
|
|
|
|
// Parse [] and @
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
while (t.type == ttOpenBracket || t.type == ttHandle) {
|
|
if (t.type == ttOpenBracket) {
|
|
parseToken(ttOpenBracket);
|
|
if (_isSyntaxError)
|
|
return {};
|
|
|
|
getToken(&t);
|
|
if (t.type != ttCloseBracket) {
|
|
rewindErrorTo(&t);
|
|
return {};
|
|
}
|
|
dtType.prepend("array<").append(">");
|
|
} else {
|
|
parseToken(ttHandle);
|
|
if (_isSyntaxError)
|
|
return {};
|
|
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
if (t.type == ttConst) {
|
|
parseToken(ttConst);
|
|
if (_isSyntaxError)
|
|
return {};
|
|
}
|
|
}
|
|
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
}
|
|
|
|
return dtType;
|
|
}
|
|
|
|
QAsCodeParser::Symbol
|
|
QAsCodeParser::parseFuncDefContent(const QByteArrayList &ns) {
|
|
Symbol sym;
|
|
|
|
sToken t1;
|
|
getToken(&t1);
|
|
|
|
if (t1.type != ttFuncDef) {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
|
|
sym.type = parseType(true);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
parseTypeMod(false);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
auto iden = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
sym.scope = ns;
|
|
sym.name = getSymbolString(iden);
|
|
sym.offset = iden.pos;
|
|
|
|
auto args = parseParameterListContent();
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
sym.children = args;
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttEndStatement) {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
|
|
sym.symtype = SymbolType::FnDef;
|
|
return sym;
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol>
|
|
QAsCodeParser::parseStatementBlock(const QByteArrayList &ns,
|
|
const QByteArray &code, qsizetype end) {
|
|
reset();
|
|
_code = code;
|
|
|
|
bool startBlockSkip = false;
|
|
|
|
sToken t1;
|
|
getToken(&t1);
|
|
if (t1.type != ttStartStatementBlock) {
|
|
rewindErrorTo(&t1);
|
|
return {};
|
|
}
|
|
|
|
QStack<QList<QAsCodeParser::Symbol>> syms;
|
|
|
|
auto getSymbols =
|
|
[](const QStack<QList<QAsCodeParser::Symbol>> &syms) -> auto {
|
|
QList<QAsCodeParser::Symbol> ret;
|
|
for (auto &symlist : syms) {
|
|
for (auto &sym : symlist) {
|
|
if (sym.symtype == SymbolType::Variable &&
|
|
!sym.type.isEmpty()) {
|
|
auto var = sym.name;
|
|
auto n = std::find_if(
|
|
ret.begin(), ret.end(),
|
|
[var](const QAsCodeParser::Symbol &sym) {
|
|
return sym.symtype == SymbolType::Variable &&
|
|
sym.name == var;
|
|
});
|
|
if (n != ret.end()) {
|
|
*n = sym;
|
|
} else {
|
|
ret.append(sym);
|
|
}
|
|
} else {
|
|
ret.append(sym);
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
syms.push({});
|
|
|
|
for (;;) {
|
|
while (!_isSyntaxError) {
|
|
getToken(&t1);
|
|
if (t1.type == ttStartStatementBlock) {
|
|
if (startBlockSkip) {
|
|
startBlockSkip = false;
|
|
} else {
|
|
syms.push({});
|
|
}
|
|
} else if (t1.type == ttEndStatementBlock) {
|
|
syms.pop();
|
|
} else if (t1.type == ttFor) {
|
|
// this block contains symbols
|
|
getToken(&t1);
|
|
if (t1.type != ttOpenParenthesis) {
|
|
_isSyntaxError = true;
|
|
}
|
|
syms.push({});
|
|
startBlockSkip = true;
|
|
continue;
|
|
} else {
|
|
rewindTo(&t1);
|
|
|
|
if (isVarDecl()) {
|
|
syms.top().append(parseDeclaration(ns));
|
|
} else {
|
|
_isSyntaxError = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_isSyntaxError) {
|
|
// Search for either ';', '{', '}', or end
|
|
getToken(&t1);
|
|
while (t1.type != ttEndStatement && t1.type != ttEnd &&
|
|
t1.type != ttStartStatementBlock &&
|
|
t1.type != ttEndStatementBlock) {
|
|
getToken(&t1);
|
|
if (t1.pos + t1.length > end) {
|
|
return getSymbols(syms);
|
|
}
|
|
}
|
|
|
|
if (t1.type == ttStartStatementBlock ||
|
|
t1.type == ttEndStatementBlock) {
|
|
rewindTo(&t1);
|
|
} else if (t1.type == ttEnd) {
|
|
return getSymbols(syms);
|
|
}
|
|
|
|
_isSyntaxError = false;
|
|
}
|
|
|
|
if (t1.pos + t1.length > end) {
|
|
return getSymbols(syms);
|
|
}
|
|
}
|
|
|
|
UNREACHABLE_RETURN;
|
|
}
|
|
|
|
void QAsCodeParser::parseTypeMod(bool isParam) {
|
|
sToken t;
|
|
|
|
// Parse possible & token
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
if (t.type == ttAmp) {
|
|
parseToken(ttAmp);
|
|
if (_isSyntaxError)
|
|
return;
|
|
|
|
if (isParam) {
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
|
|
if (t.type == ttIn || t.type == ttOut || t.type == ttInOut) {
|
|
int tokens[3] = {ttIn, ttOut, ttInOut};
|
|
parseOneOf(tokens, 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse possible + token
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
if (t.type == ttPlus) {
|
|
parseToken(ttPlus);
|
|
if (_isSyntaxError)
|
|
return;
|
|
}
|
|
|
|
// Parse possible if_handle_then_const token
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
if (identifierIs(t, IF_HANDLE_TOKEN)) {
|
|
parseToken(ttIdentifier);
|
|
if (_isSyntaxError)
|
|
return;
|
|
}
|
|
}
|
|
|
|
QByteArrayList QAsCodeParser::parseOptionalScope() {
|
|
QByteArrayList scopes;
|
|
|
|
sToken t1, t2;
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
if (t1.type == ttScope) {
|
|
rewindTo(&t1);
|
|
parseToken(ttScope);
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
}
|
|
while (t1.type == ttIdentifier && t2.type == ttScope) {
|
|
rewindTo(&t1);
|
|
auto id = parseIdentifier();
|
|
scopes.append(getSymbolString(id));
|
|
parseToken(ttScope);
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
}
|
|
|
|
// The innermost scope may be a template type
|
|
if (t1.type == ttIdentifier && t2.type == ttLessThan) {
|
|
auto type = getSymbolString(t1);
|
|
if (engine->IsTemplateType(type)) {
|
|
rewindTo(&t1);
|
|
parseIdentifier();
|
|
superficiallyParseTemplateList();
|
|
getToken(&t2);
|
|
if (t2.type == ttScope) {
|
|
// Template type is part of the scope
|
|
// Nothing more needs to be done
|
|
|
|
return scopes;
|
|
} else {
|
|
// The template type is not part of the scope
|
|
// Rewind to the template type and end the scope
|
|
rewindTo(&t1);
|
|
return scopes;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The identifier is not part of the scope
|
|
rewindTo(&t1);
|
|
|
|
return scopes;
|
|
}
|
|
|
|
sToken QAsCodeParser::parseRealType() {
|
|
sToken t1;
|
|
|
|
getToken(&t1);
|
|
if (!isPrimType(t1.type)) {
|
|
rewindErrorTo(&t1);
|
|
}
|
|
|
|
return t1;
|
|
}
|
|
|
|
sToken QAsCodeParser::parseDataType(bool allowVariableType, bool allowAuto) {
|
|
sToken t1;
|
|
|
|
getToken(&t1);
|
|
if (!isDataType(t1) && !(allowVariableType && t1.type == ttQuestion) &&
|
|
!(allowAuto && t1.type == ttAuto)) {
|
|
rewindErrorTo(&t1);
|
|
return t1;
|
|
}
|
|
|
|
return t1;
|
|
}
|
|
|
|
sToken QAsCodeParser::parseIdentifier() {
|
|
sToken t1;
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttIdentifier) {
|
|
rewindErrorTo(&t1);
|
|
}
|
|
|
|
return t1;
|
|
}
|
|
|
|
void QAsCodeParser::parseMethodAttributes() {
|
|
sToken t1;
|
|
|
|
for (;;) {
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
|
|
if (identifierIs(t1, FINAL_TOKEN) || identifierIs(t1, OVERRIDE_TOKEN) ||
|
|
identifierIs(t1, EXPLICIT_TOKEN) ||
|
|
identifierIs(t1, PROPERTY_TOKEN) || identifierIs(t1, DELETE_TOKEN))
|
|
;
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool QAsCodeParser::isPrimType(int tokenType) {
|
|
if (tokenType == ttVoid || tokenType == ttInt || tokenType == ttInt8 ||
|
|
tokenType == ttInt16 || tokenType == ttInt64 || tokenType == ttUInt ||
|
|
tokenType == ttUInt8 || tokenType == ttUInt16 ||
|
|
tokenType == ttUInt64 || tokenType == ttFloat || tokenType == ttBool ||
|
|
tokenType == ttDouble)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isDataType(const sToken &token) {
|
|
if (token.type == ttIdentifier) {
|
|
#ifndef AS_NO_COMPILER
|
|
if (_checkValidTypes) {
|
|
// Check if this is an existing type, regardless of namespace
|
|
auto type = getSymbolString(token);
|
|
if (!typeExist(type))
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
if (isPrimType(token.type))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::identifierIs(const sToken &t, const char *str) {
|
|
if (t.type != ttIdentifier)
|
|
return false;
|
|
|
|
return _code.sliced(t.pos, t.length) == QByteArray(str);
|
|
}
|
|
|
|
sToken QAsCodeParser::superficiallyParseStatementBlock() {
|
|
// This function will only superficially parse the statement block in order
|
|
// to find the end of it
|
|
sToken t1;
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttStartStatementBlock) {
|
|
rewindErrorTo(&t1);
|
|
return t1;
|
|
}
|
|
|
|
sToken start = t1;
|
|
|
|
int level = 1;
|
|
while (level > 0 && !_isSyntaxError) {
|
|
getToken(&t1);
|
|
if (t1.type == ttEndStatementBlock)
|
|
level--;
|
|
else if (t1.type == ttStartStatementBlock)
|
|
level++;
|
|
else if (t1.type == ttEnd) {
|
|
rewindErrorTo(&start);
|
|
return start;
|
|
}
|
|
}
|
|
|
|
return t1;
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseFunction() {
|
|
CodeSegment seg;
|
|
|
|
sToken t1;
|
|
getToken(&t1);
|
|
|
|
// A global function can be marked as shared and external
|
|
while (t1.type == ttIdentifier) {
|
|
if (identifierIs(t1, SHARED_TOKEN) ||
|
|
identifierIs(t1, EXTERNAL_TOKEN)) {
|
|
rewindTo(&t1);
|
|
parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
} else
|
|
break;
|
|
|
|
getToken(&t1);
|
|
}
|
|
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
// If it is a global function, or a method, except constructor and
|
|
// destructor, then the return type is parsed
|
|
sToken t2;
|
|
getToken(&t2);
|
|
rewindTo(&t1);
|
|
|
|
QByteArray rettype;
|
|
|
|
if (t1.type != ttBitNot && t2.type != ttOpenParenthesis) {
|
|
auto id = parseType(true);
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
rettype = id;
|
|
|
|
parseTypeMod(false);
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
} else {
|
|
rewindErrorTo(&t1);
|
|
return seg;
|
|
}
|
|
|
|
// the first is returning value type
|
|
seg.additonalInfos.append(rettype);
|
|
|
|
auto iden = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
seg.name = getSymbolString(iden);
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttOpenParenthesis) {
|
|
rewindErrorTo(&t1);
|
|
return seg;
|
|
}
|
|
|
|
getToken(&t1);
|
|
// the second is params raw string
|
|
if (t1.type == ttCloseParenthesis) {
|
|
// Statement block is finished
|
|
seg.additonalInfos.append(QByteArray());
|
|
} else {
|
|
auto begin = t1.pos;
|
|
while (true) {
|
|
getToken(&t1);
|
|
if (t1.type == ttCloseParenthesis) {
|
|
break;
|
|
}
|
|
// just some easier way to dectect errors
|
|
switch (t1.type) {
|
|
case ttStartStatementBlock:
|
|
case ttEndStatementBlock:
|
|
case ttEndStatement:
|
|
case ttEnd:
|
|
rewindErrorTo(&t1);
|
|
return seg;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
auto end = t1.pos;
|
|
seg.additonalInfos.append(_code.sliced(begin, end - begin));
|
|
}
|
|
|
|
// TODO: Should support abstract methods, in which case no statement block
|
|
// should be provided
|
|
parseMethodAttributes();
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
// External shared functions must be ended with ';'
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
|
|
seg.scope = currentNs;
|
|
seg.type = SymbolType::Function;
|
|
seg.offset = t1.pos;
|
|
|
|
if (t1.type == ttEndStatement) {
|
|
parseToken(ttEndStatement);
|
|
return seg;
|
|
}
|
|
|
|
auto begin = t1.pos;
|
|
|
|
// We should just find the end of the statement block here.
|
|
t1 = superficiallyParseStatementBlock();
|
|
auto end = t1.pos;
|
|
seg.codes = _code.sliced(begin, end - begin + 1);
|
|
return seg;
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseFuncDef() {
|
|
CodeSegment seg;
|
|
sToken t1;
|
|
|
|
// Allow keywords 'external' and 'shared' before 'interface'
|
|
getToken(&t1);
|
|
while (identifierIs(t1, SHARED_TOKEN) || identifierIs(t1, EXTERNAL_TOKEN)) {
|
|
rewindTo(&t1);
|
|
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
getToken(&t1);
|
|
}
|
|
|
|
if (t1.type != ttFuncDef) {
|
|
rewindErrorTo(&t1);
|
|
return seg;
|
|
}
|
|
|
|
auto begin = t1.pos;
|
|
skipCodeBlock();
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
auto end = t1.pos;
|
|
|
|
// seg.name is empty
|
|
seg.scope = currentNs;
|
|
seg.offset = begin;
|
|
seg.type = SymbolType::FnDef;
|
|
seg.codes = _code.sliced(begin, end - begin);
|
|
return seg;
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseInterface() {
|
|
CodeSegment seg;
|
|
sToken t;
|
|
|
|
// Allow keywords 'external' and 'shared' before 'interface'
|
|
getToken(&t);
|
|
while (identifierIs(t, SHARED_TOKEN) || identifierIs(t, EXTERNAL_TOKEN)) {
|
|
rewindTo(&t);
|
|
parseIdentifier();
|
|
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
getToken(&t);
|
|
}
|
|
|
|
if (t.type != ttInterface) {
|
|
rewindErrorTo(&t);
|
|
return seg;
|
|
}
|
|
|
|
auto id = parseIdentifier();
|
|
seg.name = getSymbolString(id);
|
|
|
|
seg.scope = currentNs;
|
|
seg.type = SymbolType::Interface;
|
|
|
|
// External shared declarations are ended with ';'
|
|
getToken(&t);
|
|
seg.offset = t.pos;
|
|
if (t.type == ttEndStatement) {
|
|
rewindTo(&t);
|
|
parseToken(ttEndStatement);
|
|
return seg;
|
|
}
|
|
|
|
auto begin = t.pos;
|
|
rewindTo(&t);
|
|
t = superficiallyParseStatementBlock();
|
|
auto end = t.pos;
|
|
seg.codes = _code.sliced(begin, end - begin + 1);
|
|
|
|
return seg;
|
|
}
|
|
|
|
void QAsCodeParser::superficiallyParseVarInit() {
|
|
sToken t;
|
|
getToken(&t);
|
|
|
|
if (t.type == ttAssignment) {
|
|
getToken(&t);
|
|
sToken start = t;
|
|
rewindTo(&t);
|
|
|
|
// Find the end of the expression
|
|
int indentParan = 0;
|
|
int indentBrace = 0;
|
|
do {
|
|
getToken(&t);
|
|
if (t.type == ttOpenParenthesis) {
|
|
indentParan++;
|
|
} else if (t.type == ttCloseParenthesis) {
|
|
indentParan--;
|
|
} else if (t.type == ttStartStatementBlock) {
|
|
indentBrace++;
|
|
} else if (t.type == ttEndStatementBlock) {
|
|
indentBrace--;
|
|
} else if (t.type == ttNonTerminatedStringConstant) {
|
|
rewindErrorTo(&t);
|
|
break;
|
|
} else if (t.type == ttEnd) {
|
|
rewindErrorTo(&t);
|
|
break;
|
|
}
|
|
} while (indentParan || indentBrace ||
|
|
(t.type != ttListSeparator && t.type != ttEndStatement &&
|
|
t.type != ttEndStatementBlock));
|
|
rewindTo(&t);
|
|
} else if (t.type == ttOpenParenthesis) {
|
|
sToken start = t;
|
|
|
|
// Find the end of the argument list
|
|
int indent = 1;
|
|
while (indent) {
|
|
getToken(&t);
|
|
if (t.type == ttOpenParenthesis)
|
|
indent++;
|
|
else if (t.type == ttCloseParenthesis)
|
|
indent--;
|
|
else if (t.type == ttNonTerminatedStringConstant) {
|
|
rewindErrorTo(&t);
|
|
break;
|
|
} else if (t.type == ttEnd) {
|
|
rewindErrorTo(&start);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
rewindErrorTo(&t);
|
|
}
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol>
|
|
QAsCodeParser::parseDeclaration(const QByteArrayList &ns, bool isClassProp,
|
|
bool isGlobalVar) {
|
|
QList<QAsCodeParser::Symbol> ret;
|
|
Symbol sym;
|
|
|
|
sToken t;
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
|
|
// A class property can be preceeded by private
|
|
if (t.type == ttPrivate && isClassProp) {
|
|
parseToken(ttPrivate);
|
|
sym.vis = Visiblity::Private;
|
|
} else if (t.type == ttProtected && isClassProp) {
|
|
parseToken(ttProtected);
|
|
sym.vis = Visiblity::Protected;
|
|
}
|
|
|
|
// Parse data type
|
|
auto type = parseType(true, false, !isClassProp);
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
|
|
sym.type = type;
|
|
|
|
for (;;) {
|
|
// Parse identifier
|
|
auto id = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
|
|
sym.name = getSymbolString(id);
|
|
sym.offset = id.pos;
|
|
sym.symtype = QAsCodeParser::SymbolType::Variable;
|
|
|
|
if (isClassProp || isGlobalVar) {
|
|
// Only superficially parse the initialization RewindErrorTo for the
|
|
// class property
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
if (t.type == ttAssignment || t.type == ttOpenParenthesis) {
|
|
superficiallyParseVarInit();
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
}
|
|
} else {
|
|
// If next token is assignment, parse expression
|
|
getToken(&t);
|
|
if (t.type == ttOpenParenthesis || t.type == ttAssignment) {
|
|
rewindTo(&t);
|
|
superficiallyParseVarInit();
|
|
if (_isSyntaxError)
|
|
return ret;
|
|
} else {
|
|
rewindTo(&t);
|
|
}
|
|
}
|
|
|
|
// continue if list separator, else terminate with end statement
|
|
getToken(&t);
|
|
if (t.type == ttListSeparator) {
|
|
ret.append(sym);
|
|
continue;
|
|
} else {
|
|
ret.append(sym);
|
|
return ret;
|
|
}
|
|
}
|
|
UNREACHABLE_RETURN;
|
|
}
|
|
|
|
QList<QAsCodeParser::CodeSegment> QAsCodeParser::parseScript(bool inBlock) {
|
|
QList<CodeSegment> segs;
|
|
|
|
for (;;) {
|
|
while (!_isSyntaxError) {
|
|
sToken tStart;
|
|
getToken(&tStart);
|
|
|
|
// Optimize by skipping tokens 'shared', 'external', 'final',
|
|
// 'abstract' so they don't have to be checked in every condition
|
|
sToken t1 = tStart;
|
|
while (identifierIs(t1, SHARED_TOKEN) ||
|
|
identifierIs(t1, EXTERNAL_TOKEN) ||
|
|
identifierIs(t1, FINAL_TOKEN) ||
|
|
identifierIs(t1, ABSTRACT_TOKEN))
|
|
getToken(&t1);
|
|
rewindTo(&tStart);
|
|
|
|
if (t1.type == ttImport) {
|
|
CodeSegment seg;
|
|
seg.offset = t1.pos;
|
|
|
|
seg.type = SymbolType::Import;
|
|
segs << seg;
|
|
|
|
// import we don't support just skip
|
|
skipCodeBlock();
|
|
} else if (t1.type == ttEnum) // Handle enumerations
|
|
segs << parseEnumeration();
|
|
else if (t1.type == ttTypedef) // Handle primitive typedefs
|
|
segs << parseTypedef();
|
|
else if (t1.type == ttClass)
|
|
segs << parseClass();
|
|
else if (t1.type == ttMixin)
|
|
segs << parseMixin();
|
|
else if (t1.type == ttInterface)
|
|
segs << parseInterface();
|
|
else if (t1.type == ttFuncDef) {
|
|
segs << parseFuncDef();
|
|
} else if (t1.type == ttConst || t1.type == ttScope ||
|
|
t1.type == ttAuto || isDataType(t1)) {
|
|
// class properties parsing now are in deep parsing,
|
|
// so there is no need to parse there
|
|
if (isVarDecl()) {
|
|
auto begin = t1.pos;
|
|
skipCodeBlock();
|
|
getToken(&t1);
|
|
auto end = t1.pos;
|
|
|
|
CodeSegment seg;
|
|
// seg.name is empty
|
|
seg.offset = begin;
|
|
seg.scope = currentNs;
|
|
seg.type = SymbolType::Variable;
|
|
seg.codes = _code.sliced(begin, end - begin);
|
|
rewindTo(&t1);
|
|
|
|
segs.append(seg);
|
|
} else {
|
|
segs << parseFunction();
|
|
}
|
|
} else if (t1.type == ttEndStatement) {
|
|
// Ignore a semicolon by itself
|
|
getToken(&t1);
|
|
} else if (t1.type == ttNamespace)
|
|
parseNamespace();
|
|
else if (t1.type == ttEnd)
|
|
return segs;
|
|
else if (inBlock && t1.type == ttEndStatementBlock)
|
|
return segs;
|
|
else {
|
|
rewindErrorTo(&t1);
|
|
}
|
|
}
|
|
|
|
if (_isSyntaxError) {
|
|
skipCodeBlock();
|
|
_isSyntaxError = false;
|
|
}
|
|
}
|
|
UNREACHABLE_RETURN;
|
|
}
|
|
|
|
void QAsCodeParser::skipCodeBlock() {
|
|
// Search for either ';' or '{' or end
|
|
sToken t1;
|
|
getToken(&t1);
|
|
while (t1.type != ttEndStatement && t1.type != ttEnd &&
|
|
t1.type != ttStartStatementBlock)
|
|
getToken(&t1);
|
|
|
|
if (t1.type == ttStartStatementBlock) {
|
|
// Find the end of the block and skip nested blocks
|
|
int level = 1;
|
|
while (level > 0) {
|
|
getToken(&t1);
|
|
if (t1.type == ttStartStatementBlock)
|
|
level++;
|
|
if (t1.type == ttEndStatementBlock)
|
|
level--;
|
|
if (t1.type == ttEnd)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QByteArray QAsCodeParser::getSymbolString(const sToken &t) {
|
|
return QByteArray(_code.data() + t.pos, t.length);
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseMixin() {
|
|
sToken t;
|
|
getToken(&t);
|
|
|
|
if (t.type != ttMixin) {
|
|
rewindErrorTo(&t);
|
|
return {};
|
|
}
|
|
|
|
return parseClass();
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseClass() {
|
|
CodeSegment seg;
|
|
sToken t;
|
|
getToken(&t);
|
|
|
|
// Allow the keywords 'shared', 'abstract', 'final', and 'external' before
|
|
// 'class'
|
|
while (identifierIs(t, SHARED_TOKEN) || identifierIs(t, ABSTRACT_TOKEN) ||
|
|
identifierIs(t, FINAL_TOKEN) || identifierIs(t, EXTERNAL_TOKEN)) {
|
|
rewindTo(&t);
|
|
parseIdentifier();
|
|
getToken(&t);
|
|
}
|
|
|
|
if (t.type != ttClass) {
|
|
rewindErrorTo(&t);
|
|
return seg;
|
|
}
|
|
|
|
if (engine->ep.allowImplicitHandleTypes) {
|
|
// Parse 'implicit handle class' construct
|
|
getToken(&t);
|
|
|
|
if (t.type != ttHandle)
|
|
rewindTo(&t);
|
|
}
|
|
|
|
auto cls = parseIdentifier();
|
|
seg.name = getSymbolString(cls);
|
|
|
|
seg.scope = currentNs;
|
|
seg.type = SymbolType::Class;
|
|
|
|
// External shared declarations are ended with ';'
|
|
getToken(&t);
|
|
seg.offset = t.pos;
|
|
if (t.type == ttEndStatement) {
|
|
rewindTo(&t);
|
|
parseToken(ttEndStatement);
|
|
return seg;
|
|
}
|
|
|
|
rewindTo(&t);
|
|
auto begin = t.pos;
|
|
t = superficiallyParseStatementBlock();
|
|
auto end = t.pos;
|
|
seg.codes = _code.sliced(begin, end - begin + 1);
|
|
return seg;
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseTypedef() {
|
|
CodeSegment seg;
|
|
sToken token;
|
|
|
|
getToken(&token);
|
|
if (token.type != ttTypedef) {
|
|
rewindErrorTo(&token);
|
|
return seg;
|
|
}
|
|
|
|
auto begin = token.pos;
|
|
skipCodeBlock();
|
|
getToken(&token);
|
|
rewindTo(&token);
|
|
auto end = token.pos;
|
|
|
|
// seg.name is empty
|
|
seg.scope = currentNs;
|
|
seg.offset = begin;
|
|
seg.type = SymbolType::TypeDef;
|
|
seg.codes = _code.sliced(begin, end - begin);
|
|
return seg;
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseEnumeration() {
|
|
CodeSegment seg;
|
|
sToken token;
|
|
|
|
// Optional 'shared' and 'external' token
|
|
getToken(&token);
|
|
auto ebegin = token.pos;
|
|
while (identifierIs(token, SHARED_TOKEN) ||
|
|
identifierIs(token, EXTERNAL_TOKEN)) {
|
|
rewindTo(&token);
|
|
|
|
parseIdentifier();
|
|
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
getToken(&token);
|
|
}
|
|
|
|
// Check for enum
|
|
if (token.type != ttEnum) {
|
|
rewindErrorTo(&token);
|
|
return seg;
|
|
}
|
|
|
|
// Get the identifier
|
|
getToken(&token);
|
|
if (ttIdentifier != token.type) {
|
|
rewindErrorTo(&token);
|
|
return seg;
|
|
}
|
|
|
|
seg.name = getSymbolString(token);
|
|
seg.scope = currentNs;
|
|
seg.offset = ebegin;
|
|
seg.type = SymbolType::Enum;
|
|
|
|
// External shared declarations are ended with ';'
|
|
getToken(&token);
|
|
if (token.type == ttEndStatement) {
|
|
rewindTo(&token);
|
|
parseToken(ttEndStatement);
|
|
return seg;
|
|
}
|
|
|
|
auto begin = token.pos;
|
|
rewindTo(&token);
|
|
token = superficiallyParseStatementBlock();
|
|
auto end = token.pos;
|
|
seg.codes = _code.sliced(begin, end - begin + 1);
|
|
return seg;
|
|
}
|
|
|
|
void QAsCodeParser::parseNamespace() {
|
|
sToken t1;
|
|
QByteArrayList ns;
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttNamespace) {
|
|
rewindErrorTo(&t1);
|
|
}
|
|
|
|
auto id = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return;
|
|
|
|
ns.append(getSymbolString(id));
|
|
|
|
getToken(&t1);
|
|
while (t1.type == ttScope) {
|
|
auto id = parseIdentifier();
|
|
|
|
if (_isSyntaxError)
|
|
return;
|
|
|
|
ns.append(getSymbolString(id));
|
|
getToken(&t1);
|
|
}
|
|
|
|
if (t1.type != ttStartStatementBlock) {
|
|
rewindErrorTo(&t1);
|
|
return;
|
|
}
|
|
|
|
currentNs = ns;
|
|
sToken start = t1;
|
|
parseScript(true);
|
|
currentNs.clear();
|
|
|
|
if (!_isSyntaxError) {
|
|
getToken(&t1);
|
|
if (t1.type != ttEndStatementBlock) {
|
|
rewindErrorTo(&start);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol>
|
|
QAsCodeParser::parseGlobalVarDecls(const QByteArrayList &ns,
|
|
const QByteArray &code) {
|
|
_code = code;
|
|
return parseDeclaration(ns, false, true);
|
|
}
|
|
|
|
QAsCodeParser::CodeSegment QAsCodeParser::parseFunctionMethod(Visiblity &vis) {
|
|
CodeSegment seg;
|
|
|
|
sToken t1;
|
|
getToken(&t1);
|
|
|
|
vis = Visiblity::Public;
|
|
|
|
// A class method can start with 'private' or 'protected'
|
|
if (t1.type == ttPrivate) {
|
|
rewindTo(&t1);
|
|
parseToken(ttPrivate);
|
|
getToken(&t1);
|
|
vis = Visiblity::Private;
|
|
} else if (t1.type == ttProtected) {
|
|
rewindTo(&t1);
|
|
parseToken(ttProtected);
|
|
getToken(&t1);
|
|
vis = Visiblity::Protected;
|
|
}
|
|
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
// If it is a global function, or a method, except constructor and
|
|
// destructor, then the return type is parsed
|
|
sToken t2;
|
|
getToken(&t2);
|
|
rewindTo(&t1);
|
|
if (t1.type != ttBitNot && t2.type != ttOpenParenthesis) {
|
|
auto id = parseType(true);
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
seg.additonalInfos.append(id);
|
|
|
|
parseTypeMod(false);
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
}
|
|
|
|
// If this is a class destructor then it starts with ~, and no return type
|
|
// is declared
|
|
if (t1.type == ttBitNot) {
|
|
parseToken(ttBitNot);
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
}
|
|
|
|
auto iden = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
seg.name = getSymbolString(iden);
|
|
seg.offset = iden.pos;
|
|
|
|
auto params = parseParameterListContent();
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
if (seg.additonalInfos.isEmpty()) {
|
|
seg.additonalInfos.append(QByteArray());
|
|
}
|
|
if (params.isEmpty()) {
|
|
seg.additonalInfos.append(QByteArray());
|
|
} else {
|
|
QByteArrayList args;
|
|
for (auto &p : params) {
|
|
args.append(p.type + ' ' + p.name);
|
|
}
|
|
seg.additonalInfos.append(args.join(", "));
|
|
}
|
|
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
|
|
// Is the method a const?
|
|
if (t1.type == ttConst)
|
|
parseToken(ttConst);
|
|
|
|
// TODO: Should support abstract methods, in which case no statement block
|
|
// should be provided
|
|
parseMethodAttributes();
|
|
if (_isSyntaxError)
|
|
return seg;
|
|
|
|
// External shared functions must be ended with ';'
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
|
|
seg.scope = currentNs;
|
|
|
|
if (t1.type == ttEndStatement) {
|
|
parseToken(ttEndStatement);
|
|
return seg;
|
|
}
|
|
|
|
auto begin = t1.pos;
|
|
|
|
// We should just find the end of the statement block here.
|
|
t1 = superficiallyParseStatementBlock();
|
|
auto end = t1.pos;
|
|
seg.codes = _code.sliced(begin, end - begin + 1);
|
|
return seg;
|
|
}
|
|
|
|
QAsCodeParser::Symbol
|
|
QAsCodeParser::parseFuncDefContent(const QByteArrayList &ns,
|
|
const QByteArray &code) {
|
|
reset();
|
|
_code = code;
|
|
return parseFuncDefContent(ns);
|
|
}
|
|
|
|
QPair<QByteArrayList, QList<QAsCodeParser::Symbol>>
|
|
QAsCodeParser::parseClassContent(qsizetype offset, const QByteArrayList &ns,
|
|
const QByteArray &code) {
|
|
reset();
|
|
_code = code;
|
|
|
|
QByteArrayList inhertSyms;
|
|
QList<Symbol> syms;
|
|
|
|
sToken t;
|
|
getToken(&t);
|
|
|
|
// Optional list of interfaces that are being implemented and classes that
|
|
// are being inherited
|
|
if (t.type == ttColon) {
|
|
QByteArray inhertSym;
|
|
|
|
// assuming it as an interface
|
|
inhertSym = parseOptionalScope().join("::");
|
|
if (!inhertSym.isEmpty()) {
|
|
inhertSym += "::";
|
|
}
|
|
inhertSym += getSymbolString(parseIdentifier());
|
|
inhertSyms.append(inhertSym);
|
|
getToken(&t);
|
|
while (t.type == ttListSeparator) {
|
|
inhertSym = parseOptionalScope().join("::");
|
|
if (!inhertSym.isEmpty()) {
|
|
inhertSym += "::";
|
|
}
|
|
inhertSym += getSymbolString(parseIdentifier());
|
|
inhertSyms.append(inhertSym);
|
|
getToken(&t);
|
|
}
|
|
}
|
|
|
|
if (t.type != ttStartStatementBlock) {
|
|
rewindErrorTo(&t);
|
|
return {inhertSyms, syms};
|
|
}
|
|
|
|
// Parse properties
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
while (t.type != ttEndStatementBlock && t.type != ttEnd) {
|
|
// Is it a property or a method?
|
|
if (t.type == ttFuncDef) {
|
|
auto fndef = parseFuncDefContent(ns);
|
|
syms.append(fndef);
|
|
} else if (isFuncDecl(true)) {
|
|
Symbol sym;
|
|
auto fn = parseFunctionMethod(sym.vis);
|
|
|
|
// add function symbols
|
|
sym.symtype = SymbolType::Function;
|
|
sym.scope = fn.scope;
|
|
sym.offset = fn.offset;
|
|
sym.name = fn.name;
|
|
sym.type = fn.additonalInfos.at(0);
|
|
sym.additonalInfo = fn.additonalInfos.at(1);
|
|
syms.append(sym);
|
|
|
|
// deep parsing
|
|
if (offset >= fn.offset && offset < fn.end()) {
|
|
auto ss =
|
|
parseStatementBlock(fn.scope, fn.codes, offset - fn.offset);
|
|
syms.append(ss);
|
|
}
|
|
} else if (isVirtualPropertyDecl()) {
|
|
auto vp = parseVirtualPropertyDecl(true, false);
|
|
syms.append(vp);
|
|
} else if (isVarDecl()) {
|
|
auto decl = parseDeclaration(ns, true);
|
|
syms.append(decl);
|
|
} else if (t.type == ttEndStatement)
|
|
// Skip empty declarations
|
|
getToken(&t);
|
|
else {
|
|
rewindErrorTo(&t);
|
|
return {inhertSyms, syms};
|
|
}
|
|
|
|
if (_isSyntaxError)
|
|
return {inhertSyms, syms};
|
|
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
}
|
|
|
|
getToken(&t);
|
|
if (t.type != ttEndStatementBlock) {
|
|
rewindErrorTo(&t);
|
|
return {inhertSyms, syms};
|
|
}
|
|
|
|
return {inhertSyms, syms};
|
|
}
|
|
|
|
QPair<QByteArrayList, QList<QAsCodeParser::Symbol>>
|
|
QAsCodeParser::parseInterfaceContent(qsizetype offset, const QByteArrayList &ns,
|
|
const QByteArray &code) {
|
|
QByteArrayList inhertSyms;
|
|
QList<Symbol> syms;
|
|
|
|
sToken t;
|
|
|
|
getToken(&t);
|
|
// Can optionally have a list of interfaces that are inherited
|
|
if (t.type == ttColon) {
|
|
QByteArray inhertSym;
|
|
|
|
inhertSym = parseOptionalScope().join("::");
|
|
if (!inhertSym.isEmpty()) {
|
|
inhertSym += "::";
|
|
}
|
|
inhertSym += getSymbolString(parseIdentifier());
|
|
inhertSyms.append(inhertSym);
|
|
getToken(&t);
|
|
while (t.type == ttListSeparator) {
|
|
inhertSym = parseOptionalScope().join("::");
|
|
if (!inhertSym.isEmpty()) {
|
|
inhertSym += "::";
|
|
}
|
|
inhertSym += getSymbolString(parseIdentifier());
|
|
inhertSyms.append(inhertSym);
|
|
getToken(&t);
|
|
}
|
|
}
|
|
|
|
if (t.type != ttStartStatementBlock) {
|
|
rewindErrorTo(&t);
|
|
return {};
|
|
}
|
|
|
|
// Parse interface methods
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
while (t.type != ttEndStatementBlock && t.type != ttEnd) {
|
|
if (isVirtualPropertyDecl()) {
|
|
auto vp = parseVirtualPropertyDecl(true, true);
|
|
syms.append(vp);
|
|
} else if (t.type == ttEndStatement) {
|
|
// Skip empty declarations
|
|
getToken(&t);
|
|
} else {
|
|
// Parse the method signature
|
|
auto im = parseInterfaceMethod();
|
|
syms.append(im);
|
|
}
|
|
|
|
if (_isSyntaxError)
|
|
return {inhertSyms, syms};
|
|
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
}
|
|
|
|
getToken(&t);
|
|
if (t.type != ttEndStatementBlock) {
|
|
rewindErrorTo(&t);
|
|
return {inhertSyms, syms};
|
|
}
|
|
|
|
return {inhertSyms, syms};
|
|
}
|
|
|
|
QAsCodeParser::Symbol QAsCodeParser::parseInterfaceMethod() {
|
|
Symbol sym;
|
|
|
|
auto ret = parseType(true);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
sym.type = ret;
|
|
|
|
parseTypeMod(false);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
auto id = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
sym.name = getSymbolString(id);
|
|
sym.offset = id.pos;
|
|
|
|
auto args = parseParameterListContent();
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
sym.children = args;
|
|
|
|
// Parse an optional const after the method definition
|
|
sToken t1;
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
if (t1.type == ttConst)
|
|
parseToken(ttConst);
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttEndStatement) {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
|
|
sym.symtype = SymbolType::Function;
|
|
return sym;
|
|
}
|
|
|
|
QAsCodeParser::Symbol
|
|
QAsCodeParser::parseVirtualPropertyDecl(bool isMethod, bool isInterface) {
|
|
Symbol sym;
|
|
|
|
sToken t1, t2;
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
rewindTo(&t1);
|
|
|
|
// A class method can start with 'private' or 'protected'
|
|
if (isMethod && t1.type == ttPrivate) {
|
|
parseToken(ttPrivate);
|
|
sym.vis = Visiblity::Private;
|
|
} else if (isMethod && t1.type == ttProtected) {
|
|
parseToken(ttProtected);
|
|
sym.vis = Visiblity::Protected;
|
|
}
|
|
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
auto id = parseType(true);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
sym.type = id;
|
|
|
|
parseTypeMod(false);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
|
|
auto iden = parseIdentifier();
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
sym.name = getSymbolString(iden);
|
|
sym.offset = iden.pos;
|
|
|
|
getToken(&t1);
|
|
if (t1.type != ttStartStatementBlock) {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
|
|
for (;;) {
|
|
getToken(&t1);
|
|
|
|
CodeSegment seg;
|
|
|
|
if (identifierIs(t1, GET_TOKEN) || identifierIs(t1, SET_TOKEN)) {
|
|
rewindTo(&t1);
|
|
|
|
auto id = parseIdentifier();
|
|
auto name = getSymbolString(id);
|
|
seg.name = name;
|
|
|
|
if (isMethod) {
|
|
getToken(&t1);
|
|
rewindTo(&t1);
|
|
if (t1.type == ttConst)
|
|
parseToken(ttConst);
|
|
|
|
if (!isInterface) {
|
|
parseMethodAttributes();
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
}
|
|
}
|
|
|
|
if (!isInterface) {
|
|
getToken(&t1);
|
|
if (t1.type == ttStartStatementBlock) {
|
|
rewindTo(&t1);
|
|
superficiallyParseStatementBlock();
|
|
// TODO: support deep parsing ?
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
} else if (t1.type == ttEndStatement) {
|
|
rewindTo(&t1);
|
|
parseToken(ttEndStatement);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
} else {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
} else {
|
|
getToken(&t1);
|
|
if (t1.type == ttEndStatement) {
|
|
rewindTo(&t1);
|
|
parseToken(ttEndStatement);
|
|
if (_isSyntaxError)
|
|
return sym;
|
|
} else {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
}
|
|
} else if (t1.type == ttEndStatementBlock)
|
|
break;
|
|
else {
|
|
rewindErrorTo(&t1);
|
|
return sym;
|
|
}
|
|
}
|
|
|
|
sym.symtype = SymbolType::Variable;
|
|
return sym;
|
|
}
|
|
|
|
QList<QAsCodeParser::Symbol> QAsCodeParser::parseEnumerationContent(
|
|
const QByteArrayList &ns, const QByteArray &name, const QByteArray &code) {
|
|
reset();
|
|
_code = code;
|
|
|
|
QList<QAsCodeParser::Symbol> syms;
|
|
sToken token;
|
|
size_t eoff;
|
|
|
|
getToken(&token);
|
|
if (token.type == ttEndStatement) {
|
|
rewindTo(&token);
|
|
parseToken(ttEndStatement);
|
|
return syms;
|
|
}
|
|
|
|
// check for the start of the declaration block
|
|
if (token.type != ttStartStatementBlock) {
|
|
rewindTo(&token);
|
|
rewindErrorTo(&token);
|
|
return syms;
|
|
}
|
|
|
|
while (ttEnd != token.type) {
|
|
getToken(&token);
|
|
|
|
if (ttEndStatementBlock == token.type) {
|
|
rewindTo(&token);
|
|
break;
|
|
}
|
|
|
|
if (ttIdentifier != token.type) {
|
|
rewindErrorTo(&token);
|
|
return syms;
|
|
}
|
|
|
|
// Add the enum element
|
|
Symbol se;
|
|
se.type = "int";
|
|
se.symtype = SymbolType::Enum;
|
|
se.name = getSymbolString(token);
|
|
se.scope = ns;
|
|
se.scope.append(name);
|
|
syms.append(se);
|
|
|
|
if (token.type == ttAssignment) {
|
|
rewindTo(&token);
|
|
|
|
superficiallyParseVarInit();
|
|
|
|
if (_isSyntaxError) {
|
|
return syms;
|
|
}
|
|
|
|
// se.additonalInfo = getSymbolString(t);
|
|
}
|
|
|
|
getToken(&token);
|
|
if (ttListSeparator != token.type) {
|
|
rewindTo(&token);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check for the end of the declaration block
|
|
getToken(&token);
|
|
if (token.type != ttEndStatementBlock) {
|
|
rewindTo(&token);
|
|
rewindErrorTo(&token);
|
|
return syms;
|
|
}
|
|
|
|
return syms;
|
|
}
|
|
|
|
QAsCodeParser::Symbol
|
|
QAsCodeParser::parseTypedefContent(const QByteArrayList &ns,
|
|
const QByteArray &code) {
|
|
reset();
|
|
_code = code;
|
|
|
|
QAsCodeParser::Symbol sym;
|
|
sToken token;
|
|
|
|
getToken(&token);
|
|
auto begin = token.length;
|
|
if (token.type != ttTypedef) {
|
|
rewindErrorTo(&token);
|
|
return sym;
|
|
}
|
|
|
|
// Parse the base type
|
|
getToken(&token);
|
|
rewindTo(&token);
|
|
|
|
// Make sure it is a primitive type (except ttVoid)
|
|
if (!isPrimType(token.type) || token.type == ttVoid) {
|
|
rewindErrorTo(&token);
|
|
return sym;
|
|
}
|
|
|
|
auto r = parseRealType();
|
|
auto i = parseIdentifier();
|
|
|
|
// Check for the end of the typedef
|
|
getToken(&token);
|
|
if (token.type != ttEndStatement) {
|
|
rewindTo(&token);
|
|
rewindErrorTo(&token);
|
|
}
|
|
|
|
sym.name = getSymbolString(i);
|
|
sym.type = getSymbolString(r);
|
|
sym.symtype = SymbolType::TypeDef;
|
|
sym.offset = begin;
|
|
sym.scope = currentNs;
|
|
|
|
return sym;
|
|
}
|
|
|
|
bool QAsCodeParser::isVarDecl() {
|
|
// Set start point so that we can rewind
|
|
sToken t;
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
|
|
// A class property decl can be preceded by 'private' or 'protected'
|
|
sToken t1;
|
|
getToken(&t1);
|
|
if (t1.type != ttPrivate && t1.type != ttProtected)
|
|
rewindTo(&t1);
|
|
|
|
// A variable decl starts with the type
|
|
if (!findTokenAfterType(t1)) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
// Jump to the token after the type
|
|
rewindTo(&t1);
|
|
getToken(&t1);
|
|
|
|
// The declaration needs to have a name
|
|
if (t1.type != ttIdentifier) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
// It can be followed by an initialization
|
|
getToken(&t1);
|
|
if (t1.type == ttEndStatement || t1.type == ttAssignment ||
|
|
t1.type == ttListSeparator) {
|
|
rewindTo(&t);
|
|
return true;
|
|
}
|
|
if (t1.type == ttOpenParenthesis) {
|
|
// If the closing parenthesis is followed by a statement block,
|
|
// function decorator, or end-of-file, then treat it as a function.
|
|
// A function decl may have nested parenthesis so we need to check
|
|
// for this too.
|
|
int nest = 0;
|
|
while (t1.type != ttEnd) {
|
|
if (t1.type == ttOpenParenthesis)
|
|
nest++;
|
|
else if (t1.type == ttCloseParenthesis) {
|
|
nest--;
|
|
if (nest == 0)
|
|
break;
|
|
}
|
|
getToken(&t1);
|
|
}
|
|
|
|
if (t1.type == ttEnd) {
|
|
rewindTo(&t);
|
|
return false;
|
|
} else {
|
|
getToken(&t1);
|
|
rewindTo(&t);
|
|
if (t1.type == ttStartStatementBlock ||
|
|
t1.type == ttIdentifier || // function decorator
|
|
t1.type == ttEnd)
|
|
return false;
|
|
}
|
|
|
|
rewindTo(&t);
|
|
return true;
|
|
}
|
|
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isVirtualPropertyDecl() {
|
|
// Set start point so that we can rewind
|
|
sToken t;
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
|
|
// A class property decl can be preceded by 'private' or 'protected'
|
|
sToken t1;
|
|
getToken(&t1);
|
|
if (t1.type != ttPrivate && t1.type != ttProtected)
|
|
rewindTo(&t1);
|
|
|
|
// A variable decl starts with the type
|
|
if (!findTokenAfterType(t1)) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
// Move to the token after the type
|
|
rewindTo(&t1);
|
|
getToken(&t1);
|
|
|
|
// The decl must have an identifier
|
|
if (t1.type != ttIdentifier) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
// To be a virtual property it must also have a block for the get/set
|
|
// functions
|
|
getToken(&t1);
|
|
if (t1.type == ttStartStatementBlock) {
|
|
rewindTo(&t);
|
|
return true;
|
|
}
|
|
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isFuncDecl(
|
|
bool isMethod) { // Set start point so that we can rewind
|
|
sToken t;
|
|
getToken(&t);
|
|
rewindTo(&t);
|
|
|
|
if (isMethod) {
|
|
// A class method decl can be preceded by 'private' or 'protected'
|
|
sToken t1, t2;
|
|
getToken(&t1);
|
|
if (t1.type != ttPrivate && t1.type != ttProtected)
|
|
rewindTo(&t1);
|
|
|
|
// A class constructor starts with identifier followed by parenthesis
|
|
// A class destructor starts with the ~ token
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
rewindTo(&t1);
|
|
if ((t1.type == ttIdentifier && t2.type == ttOpenParenthesis) ||
|
|
t1.type == ttBitNot) {
|
|
rewindTo(&t);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// A function decl starts with a type
|
|
sToken t1;
|
|
if (!findTokenAfterType(t1)) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
// Move to the token after the type
|
|
rewindTo(&t1);
|
|
getToken(&t1);
|
|
|
|
// There can be an ampersand if the function returns a reference
|
|
if (t1.type == ttAmp) {
|
|
rewindTo(&t);
|
|
return true;
|
|
}
|
|
|
|
if (t1.type != ttIdentifier) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
getToken(&t1);
|
|
if (t1.type == ttOpenParenthesis) {
|
|
// If the closing parenthesis is not followed by a
|
|
// statement block then it is not a function.
|
|
// It's possible that there are nested parenthesis due to default
|
|
// arguments so this should be checked for.
|
|
int nest = 0;
|
|
getToken(&t1);
|
|
while ((nest || t1.type != ttCloseParenthesis) && t1.type != ttEnd) {
|
|
if (t1.type == ttOpenParenthesis)
|
|
nest++;
|
|
if (t1.type == ttCloseParenthesis)
|
|
nest--;
|
|
|
|
getToken(&t1);
|
|
}
|
|
|
|
if (t1.type == ttEnd)
|
|
return false;
|
|
else {
|
|
if (isMethod) {
|
|
// A class method can have a 'const' token after the parameter
|
|
// list
|
|
getToken(&t1);
|
|
if (t1.type != ttConst)
|
|
rewindTo(&t1);
|
|
}
|
|
|
|
// A function may also have any number of additional attributes
|
|
bool hasAttribs = false;
|
|
for (;;) {
|
|
getToken(&t1);
|
|
if (!identifierIs(t1, FINAL_TOKEN) &&
|
|
!identifierIs(t1, OVERRIDE_TOKEN) &&
|
|
!identifierIs(t1, EXPLICIT_TOKEN) &&
|
|
!identifierIs(t1, PROPERTY_TOKEN) &&
|
|
!identifierIs(t1, DELETE_TOKEN)) {
|
|
rewindTo(&t1);
|
|
break;
|
|
}
|
|
hasAttribs = true;
|
|
}
|
|
|
|
getToken(&t1);
|
|
rewindTo(&t);
|
|
|
|
// If the function has an attribute, e.g. delete, it can be
|
|
// terminated with a ; if no implementation is expected otherwise it
|
|
// must have a statement block
|
|
if ((t1.type == ttEndStatement && hasAttribs) ||
|
|
t1.type == ttStartStatementBlock)
|
|
return true;
|
|
}
|
|
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::findTokenAfterType(
|
|
sToken &nextToken) { // Set a rewind point
|
|
sToken t, t1;
|
|
getToken(&t);
|
|
|
|
// A type can start with a const
|
|
t1 = t;
|
|
if (t1.type == ttConst)
|
|
getToken(&t1);
|
|
|
|
sToken t2;
|
|
if (t1.type != ttAuto) {
|
|
// The type may be initiated with the scope operator
|
|
if (t1.type == ttScope)
|
|
getToken(&t1);
|
|
|
|
// The type may be preceded with a multilevel scope
|
|
getToken(&t2);
|
|
while (t1.type == ttIdentifier) {
|
|
if (t2.type == ttScope) {
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
continue;
|
|
} else if (t2.type == ttLessThan) {
|
|
// Template types can also be used as scope identifiers
|
|
rewindTo(&t2);
|
|
if (checkTemplateType(t1)) {
|
|
sToken t3;
|
|
getToken(&t3);
|
|
if (t3.type == ttScope) {
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
rewindTo(&t2);
|
|
}
|
|
|
|
// We don't validate if the identifier is an actual declared type at this
|
|
// moment as it may wrongly identify the statement as a non-declaration if
|
|
// the user typed the name incorrectly. The real type is validated in
|
|
// ParseDeclaration where a proper error message can be given.
|
|
if (!isPrimType(t1.type) && t1.type != ttIdentifier && t1.type != ttAuto) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
if (!checkTemplateType(t1)) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
|
|
// Object handles can be interleaved with the array brackets
|
|
// Even though declaring variables with & is invalid we'll accept
|
|
// it here to give an appropriate error message later
|
|
getToken(&t2);
|
|
while (t2.type == ttHandle || t2.type == ttAmp ||
|
|
t2.type == ttOpenBracket) {
|
|
if (t2.type == ttHandle) {
|
|
// A handle can optionally be read-only
|
|
sToken t3;
|
|
getToken(&t3);
|
|
if (t3.type != ttConst)
|
|
rewindTo(&t3);
|
|
} else if (t2.type == ttOpenBracket) {
|
|
getToken(&t2);
|
|
if (t2.type != ttCloseBracket) {
|
|
rewindTo(&t);
|
|
return false;
|
|
}
|
|
} else if (t2.type == ttAmp) {
|
|
// & can be followed by in, out, or inout
|
|
sToken t3;
|
|
getToken(&t3);
|
|
if (t3.type != ttIn && t3.type != ttOut && t3.type != ttInOut)
|
|
rewindTo(&t3);
|
|
}
|
|
|
|
getToken(&t2);
|
|
}
|
|
|
|
// Return the next token so the caller can jump directly to it if desired
|
|
nextToken = t2;
|
|
|
|
// Rewind to start point
|
|
rewindTo(&t);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool QAsCodeParser::isConstant(int tokenType) {
|
|
if (tokenType == ttIntConstant || tokenType == ttFloatConstant ||
|
|
tokenType == ttDoubleConstant || tokenType == ttStringConstant ||
|
|
tokenType == ttMultilineStringConstant ||
|
|
tokenType == ttHeredocStringConstant || tokenType == ttTrue ||
|
|
tokenType == ttFalse || tokenType == ttBitsConstant ||
|
|
tokenType == ttNull)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isOperator(int tokenType) {
|
|
if (tokenType == ttPlus || tokenType == ttMinus || tokenType == ttStar ||
|
|
tokenType == ttSlash || tokenType == ttPercent ||
|
|
tokenType == ttStarStar || tokenType == ttAnd || tokenType == ttOr ||
|
|
tokenType == ttXor || tokenType == ttEqual || tokenType == ttNotEqual ||
|
|
tokenType == ttLessThan || tokenType == ttLessThanOrEqual ||
|
|
tokenType == ttGreaterThan || tokenType == ttGreaterThanOrEqual ||
|
|
tokenType == ttAmp || tokenType == ttBitOr || tokenType == ttBitXor ||
|
|
tokenType == ttBitShiftLeft || tokenType == ttBitShiftRight ||
|
|
tokenType == ttBitShiftRightArith || tokenType == ttIs ||
|
|
tokenType == ttNotIs)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isPreOperator(int tokenType) {
|
|
if (tokenType == ttMinus || tokenType == ttPlus || tokenType == ttNot ||
|
|
tokenType == ttInc || tokenType == ttDec || tokenType == ttBitNot ||
|
|
tokenType == ttHandle)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isPostOperator(int tokenType) {
|
|
if (tokenType == ttInc || // post increment
|
|
tokenType == ttDec || // post decrement
|
|
tokenType == ttDot || // member access
|
|
tokenType == ttOpenBracket || // index operator
|
|
tokenType ==
|
|
ttOpenParenthesis) // argument list for call on function pointer
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::isAssignOperator(int tokenType) {
|
|
if (tokenType == ttAssignment || tokenType == ttAddAssign ||
|
|
tokenType == ttSubAssign || tokenType == ttMulAssign ||
|
|
tokenType == ttDivAssign || tokenType == ttModAssign ||
|
|
tokenType == ttPowAssign || tokenType == ttAndAssign ||
|
|
tokenType == ttOrAssign || tokenType == ttXorAssign ||
|
|
tokenType == ttShiftLeftAssign || tokenType == ttShiftRightLAssign ||
|
|
tokenType == ttShiftRightAAssign)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool QAsCodeParser::typeExist(const QString &t) {
|
|
Q_UNUSED(t);
|
|
// TODO: don't check
|
|
return true;
|
|
}
|
|
|
|
bool QAsCodeParser::checkTemplateType(const sToken &t) {
|
|
// Is this a template type?
|
|
auto type = getSymbolString(t);
|
|
if (engine->IsTemplateType(type)) {
|
|
// If the next token is a < then parse the sub-type too
|
|
sToken t1;
|
|
getToken(&t1);
|
|
if (t1.type != ttLessThan) {
|
|
rewindTo(&t1);
|
|
return true;
|
|
}
|
|
|
|
for (;;) {
|
|
// There might optionally be a 'const'
|
|
getToken(&t1);
|
|
if (t1.type == ttConst)
|
|
getToken(&t1);
|
|
|
|
// The type may be initiated with the scope operator
|
|
if (t1.type == ttScope)
|
|
getToken(&t1);
|
|
|
|
// There may be multiple levels of scope operators
|
|
sToken t2;
|
|
getToken(&t2);
|
|
while (t1.type == ttIdentifier && t2.type == ttScope) {
|
|
getToken(&t1);
|
|
getToken(&t2);
|
|
}
|
|
rewindTo(&t2);
|
|
|
|
// Now there must be a data type
|
|
if (!isDataType(t1))
|
|
return false;
|
|
|
|
if (!checkTemplateType(t1))
|
|
return false;
|
|
|
|
getToken(&t1);
|
|
|
|
// Is it a handle or array?
|
|
while (t1.type == ttHandle || t1.type == ttOpenBracket) {
|
|
if (t1.type == ttOpenBracket) {
|
|
getToken(&t1);
|
|
if (t1.type != ttCloseBracket)
|
|
return false;
|
|
} else if (t1.type == ttHandle) {
|
|
// after @ there can be a const
|
|
getToken(&t1);
|
|
if (t1.type != ttConst)
|
|
rewindTo(&t1);
|
|
}
|
|
|
|
getToken(&t1);
|
|
}
|
|
|
|
// Was this the last template subtype?
|
|
if (t1.type != ttListSeparator)
|
|
break;
|
|
}
|
|
|
|
// Accept >> and >>> tokens too. But then force the tokenizer to move
|
|
// only 1 character ahead (thus splitting the token in two).
|
|
if (_code[QString::size_type(t1.pos)] != '>')
|
|
return false;
|
|
else if (t1.length != 1) {
|
|
// We need to break the token, so that only the first character is
|
|
// parsed
|
|
setPos(t1.pos + 1);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
sToken QAsCodeParser::parseToken(int token) {
|
|
sToken t1;
|
|
|
|
getToken(&t1);
|
|
if (t1.type != token) {
|
|
rewindErrorTo(&t1);
|
|
}
|
|
|
|
return t1;
|
|
}
|
|
|
|
sToken QAsCodeParser::parseOneOf(int *tokens, int count) {
|
|
sToken t1;
|
|
|
|
getToken(&t1);
|
|
int n;
|
|
for (n = 0; n < count; n++) {
|
|
if (tokens[n] == t1.type)
|
|
break;
|
|
}
|
|
if (n == count) {
|
|
rewindErrorTo(&t1);
|
|
return t1;
|
|
}
|
|
|
|
return t1;
|
|
}
|