forked from qt-creator/qt-creator
We re-use the moc parser for this purpose. Change-Id: Ib0ef4f727d1f0b862a202a95a3ae9c551cb502a5 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: David Schulz <david.schulz@qt.io>
428 lines
13 KiB
C++
428 lines
13 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2021 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of Qt Creator.
|
|
**
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "clangdqpropertyhighlighter.h"
|
|
|
|
#include "moc/parser.h"
|
|
#include "moc/preprocessor.h"
|
|
#include "moc/utils.h"
|
|
|
|
#include <utils/textutils.h>
|
|
|
|
#include <QVersionNumber>
|
|
|
|
using namespace TextEditor;
|
|
|
|
namespace ClangCodeModel {
|
|
namespace Internal {
|
|
|
|
class QPropertyHighlighter::Private
|
|
{
|
|
public:
|
|
QByteArray lexemUntil(Token target);
|
|
|
|
void highlightProperty();
|
|
|
|
const QTextDocument *document;
|
|
QString input;
|
|
int position;
|
|
Preprocessor pp;
|
|
Parser parser;
|
|
TextEditor::HighlightingResults results;
|
|
|
|
private:
|
|
void highlightType();
|
|
void highlightAttributes();
|
|
void highlightRevision();
|
|
bool until(Token target);
|
|
void skipCxxAttributes();
|
|
void addResult(TextStyle textStyle, int symbolOffset = 0);
|
|
};
|
|
|
|
QPropertyHighlighter::QPropertyHighlighter(const QTextDocument *doc, const QString &input,
|
|
int position)
|
|
: d(new Private)
|
|
{
|
|
d->document = doc;
|
|
d->input = input;
|
|
d->position = position;
|
|
|
|
d->pp.macros["Q_MOC_RUN"];
|
|
d->pp.macros["__cplusplus"];
|
|
|
|
// Don't stumble over GCC extensions
|
|
Macro dummyVariadicFunctionMacro;
|
|
dummyVariadicFunctionMacro.isFunction = true;
|
|
dummyVariadicFunctionMacro.isVariadic = true;
|
|
dummyVariadicFunctionMacro.arguments += Symbol(0, PP_IDENTIFIER, "__VA_ARGS__");
|
|
d->pp.macros["__attribute__"] = dummyVariadicFunctionMacro;
|
|
d->pp.macros["__declspec"] = dummyVariadicFunctionMacro;
|
|
}
|
|
|
|
QPropertyHighlighter::~QPropertyHighlighter() { delete d; }
|
|
|
|
const HighlightingResults QPropertyHighlighter::highlight()
|
|
{
|
|
try {
|
|
d->highlightProperty();
|
|
return d->results;
|
|
} catch (const MocParseException &) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
void QPropertyHighlighter::Private::highlightProperty()
|
|
{
|
|
parser.symbols = pp.preprocessed({}, input.toUtf8());
|
|
parser.next(Q_PROPERTY_TOKEN);
|
|
parser.next(LPAREN);
|
|
highlightType();
|
|
parser.next();
|
|
addResult(C_FIELD);
|
|
highlightAttributes();
|
|
}
|
|
|
|
void QPropertyHighlighter::Private::highlightType()
|
|
{
|
|
for (;;) {
|
|
skipCxxAttributes();
|
|
switch (parser.next()) {
|
|
case SIGNED:
|
|
case UNSIGNED:
|
|
case CONST_TOKEN:
|
|
case VOLATILE:
|
|
addResult(C_KEYWORD);
|
|
continue;
|
|
case Q_MOC_COMPAT_TOKEN:
|
|
case Q_INVOKABLE_TOKEN:
|
|
case Q_SCRIPTABLE_TOKEN:
|
|
case Q_SIGNALS_TOKEN:
|
|
case Q_SLOTS_TOKEN:
|
|
case Q_SIGNAL_TOKEN:
|
|
case Q_SLOT_TOKEN:
|
|
return;
|
|
case NOTOKEN:
|
|
return;
|
|
default:
|
|
parser.prev();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
skipCxxAttributes();
|
|
parser.test(ENUM) || parser.test(CLASS) || parser.test(STRUCT);
|
|
for(;;) {
|
|
skipCxxAttributes();
|
|
switch (parser.next()) {
|
|
case IDENTIFIER:
|
|
addResult(C_TYPE);
|
|
break;
|
|
case CHAR_TOKEN:
|
|
case SHORT_TOKEN:
|
|
case INT_TOKEN:
|
|
case LONG_TOKEN:
|
|
addResult(C_KEYWORD);
|
|
// preserve '[unsigned] long long', 'short int', 'long int', 'long double'
|
|
if (parser.test(LONG_TOKEN) || parser.test(INT_TOKEN) || parser.test(DOUBLE)) {
|
|
parser.prev();
|
|
continue;
|
|
}
|
|
break;
|
|
case FLOAT_TOKEN:
|
|
case DOUBLE:
|
|
case VOID_TOKEN:
|
|
case BOOL_TOKEN:
|
|
case AUTO:
|
|
addResult(C_KEYWORD);
|
|
break;
|
|
case NOTOKEN:
|
|
return;
|
|
default:
|
|
parser.prev();
|
|
break;
|
|
}
|
|
if (parser.test(LANGLE)) {
|
|
if (results.size() < 2) {
|
|
// '<' cannot start a type
|
|
return;
|
|
}
|
|
until(RANGLE); // TODO: Highlight template parameter?
|
|
}
|
|
if (parser.test(SCOPE)) {
|
|
addResult(C_PUNCTUATION);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
while (parser.test(CONST_TOKEN) || parser.test(VOLATILE) || parser.test(SIGNED)
|
|
|| parser.test(UNSIGNED) || parser.test(STAR) || parser.test(AND)
|
|
|| parser.test(ANDAND)) {
|
|
TextStyle textStyle = parser.test(CONST_TOKEN) || parser.test(VOLATILE)
|
|
? C_KEYWORD
|
|
: parser.test(SIGNED) || parser.test(UNSIGNED)
|
|
? C_TYPE
|
|
: C_PUNCTUATION;
|
|
addResult(textStyle);
|
|
}
|
|
}
|
|
|
|
void QPropertyHighlighter::Private::highlightAttributes()
|
|
{
|
|
auto checkIsFunction = [&](const QByteArray &def, const char *name) {
|
|
if (def.endsWith(')')) {
|
|
QByteArray msg = "Providing a function for ";
|
|
msg += name;
|
|
msg += " in a property declaration is not be supported in Qt 6.";
|
|
parser.error(msg.constData());
|
|
}
|
|
};
|
|
|
|
while (parser.test(IDENTIFIER)) {
|
|
const QByteArray l = parser.lexem();
|
|
if (l[0] == 'C' && l == "CONSTANT") {
|
|
addResult(C_KEYWORD);
|
|
continue;
|
|
} else if (l[0] == 'F' && l == "FINAL") {
|
|
addResult(C_KEYWORD);
|
|
continue;
|
|
} else if (l[0] == 'N' && l == "NAME") {
|
|
addResult(C_KEYWORD);
|
|
parser.next(IDENTIFIER);
|
|
addResult(C_FIELD);
|
|
continue;
|
|
} else if (l[0] == 'R' && l == "REQUIRED") {
|
|
addResult(C_KEYWORD);
|
|
continue;
|
|
} else if (l[0] == 'R' && l == "REVISION" && parser.test(LPAREN)) {
|
|
parser.prev();
|
|
highlightRevision();
|
|
continue;
|
|
}
|
|
|
|
QByteArray v, v2;
|
|
if (parser.test(LPAREN)) {
|
|
v = lexemUntil(RPAREN);
|
|
v = v.mid(1, v.length() - 2); // removes the '(' and ')'
|
|
} else if (parser.test(INTEGER_LITERAL)) {
|
|
v = parser.lexem();
|
|
if (l != "REVISION")
|
|
parser.error(1);
|
|
} else {
|
|
addResult(C_KEYWORD);
|
|
parser.next(IDENTIFIER);
|
|
addResult(C_FUNCTION);
|
|
v = parser.lexem();
|
|
if (parser.test(LPAREN))
|
|
v2 = lexemUntil(RPAREN);
|
|
else if (v != "true" && v != "false")
|
|
v2 = "()";
|
|
}
|
|
switch (l[0]) {
|
|
case 'M':
|
|
if (l != "MEMBER")
|
|
parser.error(2);
|
|
break;
|
|
case 'R':
|
|
if (l == "READ")
|
|
break;
|
|
if (l == "RESET")
|
|
break;
|
|
if (l == "REVISION") {
|
|
parser.prev();
|
|
highlightRevision();
|
|
break;
|
|
}
|
|
parser.error(2);
|
|
break;
|
|
case 'S':
|
|
if (l == "SCRIPTABLE") {
|
|
checkIsFunction(v + v2, "SCRIPTABLE");
|
|
} else if (l == "STORED") {
|
|
checkIsFunction(v + v2, "STORED");
|
|
} else
|
|
parser.error(2);
|
|
break;
|
|
case 'W': if (l != "WRITE") parser.error(2);
|
|
break;
|
|
case 'B': if (l != "BINDABLE") parser.error(2);
|
|
break;
|
|
case 'D': if (l != "DESIGNABLE") parser.error(2);
|
|
checkIsFunction(v + v2, "DESIGNABLE");
|
|
break;
|
|
case 'N': if (l != "NOTIFY") parser.error(2);
|
|
break;
|
|
case 'U': if (l != "USER") parser.error(2);
|
|
checkIsFunction(v + v2, "USER");
|
|
break;
|
|
default:
|
|
parser.error(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QPropertyHighlighter::Private::highlightRevision()
|
|
{
|
|
addResult(C_KEYWORD);
|
|
QByteArray revisionString;
|
|
const bool hasParen = parser.test(LPAREN);
|
|
if (hasParen) {
|
|
revisionString = lexemUntil(RPAREN);
|
|
revisionString.remove(0, 1);
|
|
revisionString.chop(1);
|
|
revisionString.replace(',', '.');
|
|
} else {
|
|
parser.next(INTEGER_LITERAL);
|
|
revisionString = parser.lexem();
|
|
}
|
|
QVersionNumber version = QVersionNumber::fromString(QString::fromUtf8(revisionString));
|
|
if (version.isNull() || version.segmentCount() > 2)
|
|
parser.error("Invalid revision");
|
|
}
|
|
|
|
QByteArray QPropertyHighlighter::Private::lexemUntil(Token target)
|
|
{
|
|
int from = parser.index;
|
|
until(target);
|
|
QByteArray s;
|
|
while (from <= parser.index) {
|
|
QByteArray n = parser.symbols.at(from++-1).lexem();
|
|
if (s.size() && n.size()) {
|
|
char prev = s.at(s.size()-1);
|
|
char next = n.at(0);
|
|
if ((is_ident_char(prev) && is_ident_char(next))
|
|
|| (prev == '<' && next == ':')
|
|
|| (prev == '>' && next == '>'))
|
|
s += ' ';
|
|
}
|
|
s += n;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
bool QPropertyHighlighter::Private::until(Token target)
|
|
{
|
|
int braceCount = 0;
|
|
int brackCount = 0;
|
|
int parenCount = 0;
|
|
int angleCount = 0;
|
|
if (parser.index) {
|
|
switch(parser.symbols.at(parser.index-1).token) {
|
|
case LBRACE: ++braceCount; break;
|
|
case LBRACK: ++brackCount; break;
|
|
case LPAREN: ++parenCount; break;
|
|
case LANGLE: ++angleCount; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
//when searching commas within the default argument, we should take care of template depth (anglecount)
|
|
// unfortunatelly, we do not have enough semantic information to know if '<' is the operator< or
|
|
// the beginning of a template type. so we just use heuristics.
|
|
int possible = -1;
|
|
|
|
while (parser.index < parser.symbols.size()) {
|
|
Token t = parser.symbols.at(parser.index++).token;
|
|
switch (t) {
|
|
case LBRACE: ++braceCount; break;
|
|
case RBRACE: --braceCount; break;
|
|
case LBRACK: ++brackCount; break;
|
|
case RBRACK: --brackCount; break;
|
|
case LPAREN: ++parenCount; break;
|
|
case RPAREN: --parenCount; break;
|
|
case LANGLE:
|
|
if (parenCount == 0 && braceCount == 0)
|
|
++angleCount;
|
|
break;
|
|
case RANGLE:
|
|
if (parenCount == 0 && braceCount == 0)
|
|
--angleCount;
|
|
break;
|
|
case GTGT:
|
|
if (parenCount == 0 && braceCount == 0) {
|
|
angleCount -= 2;
|
|
t = RANGLE;
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
if (t == target
|
|
&& braceCount <= 0
|
|
&& brackCount <= 0
|
|
&& parenCount <= 0
|
|
&& (target != RANGLE || angleCount <= 0)) {
|
|
if (target != COMMA || angleCount <= 0)
|
|
return true;
|
|
possible = parser.index;
|
|
}
|
|
|
|
if (target == COMMA && t == EQ && possible != -1) {
|
|
parser.index = possible;
|
|
return true;
|
|
}
|
|
|
|
if (braceCount < 0 || brackCount < 0 || parenCount < 0
|
|
|| (target == RANGLE && angleCount < 0)) {
|
|
--parser.index;
|
|
break;
|
|
}
|
|
|
|
if (braceCount <= 0 && t == SEMIC) {
|
|
// Abort on semicolon. Allow recovering bad template parsing (QTBUG-31218)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (target == COMMA && angleCount != 0 && possible != -1) {
|
|
parser.index = possible;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void QPropertyHighlighter::Private::skipCxxAttributes()
|
|
{
|
|
int rewind = parser.index;
|
|
if (parser.test(LBRACK) && parser.test(LBRACK) && until(RBRACK) && parser.test(RBRACK))
|
|
return;
|
|
parser.index = rewind;
|
|
}
|
|
|
|
void QPropertyHighlighter::Private::addResult(TextStyle textStyle, int symbolOffset)
|
|
{
|
|
const Symbol &s = parser.symbol_lookup(symbolOffset);
|
|
int line, column;
|
|
Utils::Text::convertPosition(document, position + s.from, &line, &column);
|
|
if (line > 0 && column > 0) {
|
|
TextStyles styles;
|
|
styles.mainStyle = textStyle;
|
|
results << HighlightingResult(line, column, s.len, styles);
|
|
}
|
|
}
|
|
|
|
} // namespace Internal
|
|
} // namespace ClangCodeModel
|