// Copyright (C) 2016 Jochen Becher // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "classmembersedit.h" #include "qmt/model_ui/stereotypescontroller.h" #include "qmt/infrastructure/qmtassert.h" namespace qmt { class ClassMembersEdit::Cursor { public: explicit Cursor(const QString &text); bool isValid() const { return m_isValid; } bool atEnd() const { return m_pos == m_text.length(); } int position() const { return m_pos; } void setPosition(int pos); QString readWord(); bool skip(const QString &s); QString readUntil(const QString &delimiter); void unreadWord(); void skipUntilOrNewline(const QString &delimiter); QString readWordFromRight(); bool skipFromRight(const QString &s); bool searchOutsideOfBracketsFromRight(const QString &s); QString extractSubstr(int start, int stop); void skipWhitespaces(); void skipWhitespacesFromRight(); private: QString preparse(const QString &text); QString m_text; bool m_isValid = true; int m_pos = 0; int m_lastPos = -1; }; ClassMembersEdit::Cursor::Cursor(const QString &text) : m_text(preparse(text)) { } void ClassMembersEdit::Cursor::setPosition(int pos) { if (m_isValid) m_pos = pos; } QString ClassMembersEdit::Cursor::readWord() { skipWhitespaces(); QString word; if (m_isValid && m_pos < m_text.length()) { m_lastPos = m_pos; QChar c = m_text.at(m_pos); ++m_pos; if (c.isLetterOrNumber() || c == QLatin1Char('_')) { word = c; while (m_isValid && m_pos < m_text.length() && (m_text.at(m_pos).isLetterOrNumber() || m_text.at(m_pos) == QLatin1Char('_'))) { word += m_text.at(m_pos); ++m_pos; } } else { if (c == QLatin1Char('<') && m_pos < m_text.length() && m_text.at(m_pos) == QLatin1Char('<')) { ++m_pos; word = "<<"; } else if (c == QLatin1Char('>') && m_pos < m_text.length() && m_text.at(m_pos) == QLatin1Char('>')) { ++m_pos; word = ">>"; } else { word = c; } } } else { m_isValid = false; } return word; } bool ClassMembersEdit::Cursor::skip(const QString &s) { skipWhitespaces(); if (m_isValid && m_pos + s.length() <= m_text.length() && s.compare(m_text.mid(m_pos, s.length()), Qt::CaseInsensitive) == 0) { m_pos += s.length(); return true; } return false; } QString ClassMembersEdit::Cursor::readUntil(const QString &delimiter) { QString s; while (m_isValid) { if (m_pos >= m_text.length() || m_text.at(m_pos) == '\n') { m_isValid = false; return s; } if (m_pos + delimiter.length() <= m_text.length() && delimiter.compare(m_text.mid(m_pos, delimiter.length()), Qt::CaseInsensitive) == 0) { m_pos += delimiter.length(); return s; } s += m_text.at(m_pos); ++m_pos; } return s; } void ClassMembersEdit::Cursor::unreadWord() { if (!m_isValid) return; if (m_lastPos < 0) { m_isValid = false; return; } m_pos = m_lastPos; } void ClassMembersEdit::Cursor::skipUntilOrNewline(const QString &delimiter) { while (m_isValid) { if (m_pos >= m_text.length()) return; if (m_text.at(m_pos) == '\n') return; if (m_pos + delimiter.length() <= m_text.length() && delimiter.compare(m_text.mid(m_pos, delimiter.length()), Qt::CaseInsensitive) == 0) { m_pos += delimiter.length(); return; } ++m_pos; } } QString ClassMembersEdit::Cursor::readWordFromRight() { skipWhitespacesFromRight(); QString word; if (m_isValid && m_pos >= 0) { m_lastPos = m_pos; QChar c = m_text.at(m_pos); --m_pos; if (c.isLetterOrNumber() || c == QLatin1Char('_')) { word = c; while (m_isValid && m_pos >= 0 && (m_text.at(m_pos).isLetterOrNumber() || m_text.at(m_pos) == QLatin1Char('_'))) { word = m_text.at(m_pos) + word; --m_pos; } } else { if (c == QLatin1Char('<') && m_pos >= 0 && m_text.at(m_pos) == QLatin1Char('<')) { --m_pos; word = "<<"; } else if (c == QLatin1Char('>') && m_pos >= 0 && m_text.at(m_pos) == QLatin1Char('>')) { --m_pos; word = ">>"; } else { word = c; } } } else { m_isValid = false; } return word; } bool ClassMembersEdit::Cursor::skipFromRight(const QString &s) { skipWhitespacesFromRight(); if (m_isValid && m_pos - s.length() >= 0 && s.compare(m_text.mid(m_pos - s.length() + 1, s.length()), Qt::CaseInsensitive) == 0) { m_pos -= s.length(); return true; } return false; } bool ClassMembersEdit::Cursor::searchOutsideOfBracketsFromRight(const QString &s) { if (!m_isValid) return false; int startPos = m_pos; int lastPos = m_lastPos; int brackets = 0; for (;;) { const QString word = readWordFromRight(); if (!m_isValid || word == "\n") break; if (brackets == 0 && word == s) return true; else if (word == "(" || word == "[" || word == "{") ++brackets; else if (word == ")" || word == "]" || word == "}") --brackets; } m_isValid = true; m_pos = startPos; m_lastPos = lastPos; return false; } QString ClassMembersEdit::Cursor::extractSubstr(int start, int stop) { if (m_isValid && start >= 0 && start < m_text.length() && stop >= start && stop < m_text.length()) return m_text.mid(start, stop - start + 1); m_isValid = false; return QString(); } void ClassMembersEdit::Cursor::skipWhitespaces() { while (m_isValid && m_pos < m_text.length() && m_text.at(m_pos).isSpace() && m_text.at(m_pos) != '\n') ++m_pos; if (m_pos >= m_text.length()) m_isValid = false; } void ClassMembersEdit::Cursor::skipWhitespacesFromRight() { while (m_isValid && m_pos >= 0 && m_text.at(m_pos).isSpace() && m_text.at(m_pos) != '\n') --m_pos; if (m_pos < 0) m_isValid = false; } QString ClassMembersEdit::Cursor::preparse(const QString &text) { QString parsedText; if (!text.isEmpty()) { QChar lastChar = QLatin1Char(' '); bool inCComment = false; bool inCppComment = false; int braces = 0; for (QChar c : text) { if (!inCComment && !inCppComment && lastChar == QLatin1Char('/') && c == QLatin1Char('/')) { inCppComment = true; lastChar = QLatin1Char(' '); } else if (!inCComment && !inCppComment && lastChar == QLatin1Char('/') && c == QLatin1Char('*')) { inCComment = true; lastChar = QLatin1Char(' '); } else if (inCComment && !inCppComment && lastChar == QLatin1Char('*') && c == QLatin1Char('/')) { inCComment = false; lastChar = QLatin1Char(' '); } else if (!inCComment && inCppComment && c == QLatin1Char('\n')) { inCppComment = false; lastChar = QLatin1Char('\n'); } else if (inCComment || inCppComment) { lastChar = c; } else { if (c == QLatin1Char('(') || c == QLatin1Char('{') || c == QLatin1Char('[')) ++braces; else if (c == QLatin1Char(')') || c == QLatin1Char('}') || c == QLatin1Char(']')) --braces; else if (c == QLatin1Char('\n') && braces != 0) c = QLatin1Char(' '); parsedText += lastChar; lastChar = c; } } if (!inCComment && !inCppComment) parsedText += lastChar; } return parsedText; } class ClassMembersEdit::ClassMembersEditPrivate { public: bool m_isValid = true; QList m_members; }; ClassMembersEdit::ClassMembersEdit(QWidget *parent) : QPlainTextEdit(parent), d(new ClassMembersEditPrivate) { setTabChangesFocus(true); connect(this, &QPlainTextEdit::textChanged, this, &ClassMembersEdit::onTextChanged); } ClassMembersEdit::~ClassMembersEdit() { delete d; } QList ClassMembersEdit::members() const { return d->m_members; } void ClassMembersEdit::setMembers(const QList &members) { d->m_members = members; setPlainText(build(d->m_members)); } void ClassMembersEdit::reparse() { bool ok = false; QList members = parse(toPlainText(), &ok); if (ok != d->m_isValid) { d->m_isValid = ok; emit statusChanged(d->m_isValid); } if (ok) { if (members != d->m_members) { d->m_members = members; emit membersChanged(d->m_members); } setPlainText(build(members)); } } void ClassMembersEdit::onTextChanged() { bool ok = false; QList members = parse(toPlainText(), &ok); if (ok != d->m_isValid) { d->m_isValid = ok; emit statusChanged(d->m_isValid); } if (ok) { if (members != d->m_members) { d->m_members = members; emit membersChanged(d->m_members); } } } QString ClassMembersEdit::build(const QList &members) { MClassMember::Visibility currentVisibility = MClassMember::VisibilityUndefined; QString currentGroup; QString text; for (const MClassMember &member : members) { bool addNewline = false; bool addSpace = false; if (member.visibility() != currentVisibility) { if (member.visibility() != MClassMember::VisibilityUndefined) { QString vis; switch (member.visibility()) { case MClassMember::VisibilityUndefined: break; case MClassMember::VisibilityPublic: vis = "public:"; break; case MClassMember::VisibilityProtected: vis = "protected:"; break; case MClassMember::VisibilityPrivate: vis = "private:"; break; case MClassMember::VisibilitySignals: vis = "signals:"; break; case MClassMember::VisibilityPrivateSlots: vis = "private slots:"; break; case MClassMember::VisibilityProtectedSlots: vis = "protected slots:"; break; case MClassMember::VisibilityPublicSlots: vis = "public slots:"; break; } if (!text.isEmpty()) text += "\n"; text += vis; addNewline = true; addSpace = true; } currentVisibility = member.visibility(); } if (member.group() != currentGroup) { if (addSpace) text += " "; else if (!text.isEmpty()) text += "\n"; text += QString("[%1]").arg(member.group()); addNewline = true; currentGroup = member.group(); } if (addNewline) text += "\n"; if (!member.stereotypes().isEmpty()) { StereotypesController ctrl; text += QString("<<%1>> ").arg(ctrl.toString(member.stereotypes())); } if (member.properties() & MClassMember::PropertyQsignal) text += "signal "; if (member.properties() & MClassMember::PropertyQslot) text += "slot "; if (member.properties() & MClassMember::PropertyQinvokable) text += "invokable "; if (member.properties() & MClassMember::PropertyStatic) text += "static "; if (member.properties() & MClassMember::PropertyVirtual) text += "virtual "; if (member.properties() & MClassMember::PropertyConstexpr) text += "constexpr "; text += member.declaration(); if (member.properties() & MClassMember::PropertyConst) text += " const"; if (member.properties() & MClassMember::PropertyOverride) text += " override"; if (member.properties() & MClassMember::PropertyFinal) text += " final"; if (member.properties() & MClassMember::PropertyAbstract) text += " = 0"; text += ";\n"; } return text; } QList ClassMembersEdit::parse(const QString &text, bool *ok) { QMT_ASSERT(ok, return QList()); *ok = true; QList members; MClassMember member; MClassMember::Visibility currentVisibility = MClassMember::VisibilityUndefined; QString currentGroup; Cursor cursor(text); while (cursor.isValid() && *ok) { cursor.skipWhitespaces(); if (!cursor.isValid()) return members; member = MClassMember(); QString word = cursor.readWord().toLower(); for (;;) { if (word == "public") { currentVisibility = MClassMember::VisibilityPublic; word = cursor.readWord().toLower(); } else if (word == "protected") { currentVisibility = MClassMember::VisibilityProtected; word = cursor.readWord().toLower(); } else if (word == "private") { currentVisibility = MClassMember::VisibilityPrivate; word = cursor.readWord().toLower(); } else if (word == "signals") { currentVisibility = MClassMember::VisibilitySignals; word = cursor.readWord().toLower(); } else if (word == "slots") { switch (currentVisibility) { case MClassMember::VisibilityPrivate: currentVisibility = MClassMember::VisibilityPrivateSlots; break; case MClassMember::VisibilityProtected: currentVisibility = MClassMember::VisibilityProtectedSlots; break; case MClassMember::VisibilityPublic: currentVisibility = MClassMember::VisibilityPublicSlots; break; default: currentVisibility = MClassMember::VisibilityPrivateSlots; break; } word = cursor.readWord().toLower(); } else if (word == "[") { currentGroup = cursor.readUntil("]"); word = cursor.readWord().toLower(); } else if (word == "<<") { QString stereotypes = cursor.readUntil(">>"); StereotypesController ctrl; member.setStereotypes(ctrl.fromString(stereotypes)); word = cursor.readWord().toLower(); } else if (word == "static") { member.setProperties(member.properties() | MClassMember::PropertyStatic); word = cursor.readWord().toLower(); } else if (word == "virtual") { member.setProperties(member.properties() | MClassMember::PropertyVirtual); word = cursor.readWord().toLower(); } else if (word == "constexpr" || word == "q_decl_constexpr" || word == "q_decl_relaxed_constexpr") { member.setProperties(member.properties() | MClassMember::PropertyConstexpr); word = cursor.readWord().toLower(); } else if (word == "signal" || word == "q_signal") { member.setProperties(member.properties() | MClassMember::PropertyQsignal); word = cursor.readWord().toLower(); } else if (word == "slot" || word == "q_slot") { member.setProperties(member.properties() | MClassMember::PropertyQslot); word = cursor.readWord().toLower(); } else if (word == "invokable" || word == "q_invokable") { member.setProperties(member.properties() | MClassMember::PropertyQinvokable); word = cursor.readWord().toLower(); } else if (word == ":") { word = cursor.readWord().toLower(); } else { cursor.unreadWord(); break; } } member.setVisibility(currentVisibility); member.setGroup(currentGroup); if (word != "\n") { int declarationStart = cursor.position(); cursor.skipUntilOrNewline(";"); int nextLinePosition = cursor.position(); cursor.setPosition(nextLinePosition - 1); word = cursor.readWordFromRight().toLower(); if (word != ";") cursor.unreadWord(); int endPosition = cursor.position(); QString expr; if (cursor.searchOutsideOfBracketsFromRight("=")) expr = cursor.extractSubstr(cursor.position() + 2, endPosition).trimmed(); word = cursor.readWordFromRight().toLower(); for (;;) { // TODO ignore throw(), noexpect(*), if (word == "final" || word == "q_decl_final") { member.setProperties(member.properties() | MClassMember::PropertyFinal); word = cursor.readWordFromRight().toLower(); } else if (word == "override" || word == "q_decl_override") { member.setProperties(member.properties() | MClassMember::PropertyOverride); word = cursor.readWordFromRight().toLower(); } else if (word == "const") { member.setProperties(member.properties() | MClassMember::PropertyConst); word = cursor.readWordFromRight().toLower(); } else if (word == "noexpect") { // just ignore it word = cursor.readWordFromRight().toLower(); } else { cursor.unreadWord(); break; } } int declarationStop = cursor.position(); QString declaration = cursor.extractSubstr(declarationStart, declarationStop).trimmed(); if (!cursor.isValid()) break; if (!declaration.isEmpty()) { member.setDeclaration(declaration); // TODO recognize function pointer if (declaration.endsWith(")")) { member.setMemberType(MClassMember::MemberMethod); if (expr == "0") member.setProperties(member.properties() | MClassMember::PropertyAbstract); } else { member.setMemberType(MClassMember::MemberAttribute); } members.append(member); } cursor.setPosition(nextLinePosition); if (cursor.atEnd()) return members; cursor.skip("\n"); } else { word = cursor.readWord().toLower(); } } if (!cursor.isValid()) *ok = false; return members; } } // namespace qmt