/** * @brief ComponentsMaker::makeField * @return */ OptionalEntity ComponentsMaker::makeField(const Tokens &tokens) { Q_ASSERT(!tokens.isEmpty() && tokens[int(FieldGroupNames::Typename)]->isSingle() && !tokens[int(FieldGroupNames::Typename)]->token().isEmpty() && tokens[int(FieldGroupNames::Name)]->isSingle() && !tokens[int(FieldGroupNames::Name)]->token().isEmpty()); Q_ASSERT(checkCommonState()); // Make field with lhs keywords auto newField = std::make_shared<entity::Field>(); newField->setName(tokens[int(FieldGroupNames::Name)]->token()); if (!tokens[int(FieldGroupNames::LhsKeywords)]->token().isEmpty()) { auto keyword = utility::fieldKeywordFromString(tokens[int(FieldGroupNames::LhsKeywords)]->token()); Q_ASSERT(keyword != entity::FieldKeyword::Invalid); newField->addKeyword(keyword); } // Make type Tokens typeTokens(int(TypeGroups::GroupsCount)); std::copy(std::begin(tokens) + int(FieldGroupNames::ConstStatus), std::begin(tokens) + int(FieldGroupNames::Name), // do not include name std::begin(typeTokens) + int(TypeGroups::ConstStatus)); // add first group offset auto optionalType = makeType(typeTokens); if (!optionalType.errorMessage.isEmpty()) return {optionalType.errorMessage, nullptr}; Q_ASSERT(optionalType.resultEntity); newField->setTypeId(optionalType.resultEntity->id()); return {"", newField}; }
static bool isInStringHelper(const QTextCursor &cursor, Token *retToken = 0) { LanguageFeatures features; features.qtEnabled = false; features.qtKeywordsEnabled = false; features.qtMocRunEnabled = false; features.cxx11Enabled = true; features.c99Enabled = true; SimpleLexer tokenize; tokenize.setLanguageFeatures(features); const int prevState = BackwardsScanner::previousBlockState(cursor.block()) & 0xFF; const Tokens tokens = tokenize(cursor.block().text(), prevState); const unsigned pos = cursor.selectionEnd() - cursor.block().position(); if (tokens.isEmpty() || pos <= tokens.first().utf16charsBegin()) return false; if (pos >= tokens.last().utf16charsEnd()) { const Token tk = tokens.last(); return tk.isStringLiteral() && prevState > 0; } Token tk = tokenAtPosition(tokens, pos); if (retToken) *retToken = tk; return tk.isStringLiteral() && pos > tk.utf16charsBegin(); }
/** * @brief ComponentsMaker::makeMethod * @param tokens * @return */ OptionalEntity ComponentsMaker::makeMethod(const Tokens &tokens) { Q_ASSERT(!tokens.isEmpty() && tokens[int(MethodsGroupsNames::ReturnType)]->isMulti() && tokens[int(MethodsGroupsNames::Name)]->isSingle()); Q_ASSERT(checkCommonState()); entity::SharedMethod newMethod = std::make_shared<entity::ClassMethod>(); // Add Lhs auto lhsToken = tokens[int(MethodsGroupsNames::LhsKeywords)]; Q_ASSERT(!lhsToken->isEmpty() && lhsToken->isSingle()); for (auto &&w : lhsToken->token().split(QChar::Space, QString::SkipEmptyParts)) { entity::LhsIdentificator id = utility::methodLhsIdFromString(w); Q_ASSERT(id != entity::LhsIdentificator::None); newMethod->addLhsIdentificator(id); } // Add return type auto returnTypeToken = tokens[int(MethodsGroupsNames::ReturnType)]; Q_ASSERT(returnTypeToken->isMulti() && !returnTypeToken->isEmpty()); auto returnType = makeType(returnTypeToken->tokens()); if (!returnType.errorMessage.isEmpty()) return {returnType.errorMessage, nullptr}; newMethod->setReturnTypeId(returnType.resultEntity->id()); // Add name newMethod->setName(tokens[int(MethodsGroupsNames::Name)]->token()); // Add arguments auto argumentsToken = tokens[int(MethodsGroupsNames::Arguments)]; Q_ASSERT(argumentsToken->isEmpty() || argumentsToken->isMulti()); if (argumentsToken->isMulti()) { auto argumentsTokens = argumentsToken->tokens(); for (auto &&argumentToken : argumentsTokens) { Q_ASSERT(argumentToken->isMulti()); auto argSubTokens = argumentToken->tokens(); Q_ASSERT(argSubTokens.size() == int(Argument::GroupsCount)); Q_ASSERT(argSubTokens[int(Argument::Name)]->isSingle()); auto name = argSubTokens[int(Argument::Name)]->token(); Q_ASSERT(argSubTokens[int(Argument::Type)]->isMulti()); auto type = makeType(argSubTokens[int(Argument::Type)]->tokens()); if (!type.errorMessage.isEmpty()) return {tr("Wrong type of argument: %1. Error: %2.").arg( QString::number(argumentsTokens.indexOf(argumentToken)), type.errorMessage ), nullptr}; Q_ASSERT(type.resultEntity); newMethod->addParameter(name, type.resultEntity->id()); } } // Const auto constArgument = tokens[int(MethodsGroupsNames::Const)]; Q_ASSERT(!constArgument->isEmpty() && constArgument->isSingle()); const QString &token = constArgument->token(); if (!token.isEmpty()) { if (token == "const") newMethod->setConstStatus(true); else return {tr("Wrong const status token: %1.").arg(constArgument->token()), nullptr}; } // Rhs auto rhsArgument = tokens[int(MethodsGroupsNames::RhsKeywords)]; Q_ASSERT(!rhsArgument->isEmpty() && rhsArgument->isSingle()); const QString &rhsToken = rhsArgument->token(); if (!rhsToken.isEmpty()) { entity::RhsIdentificator rhsId = utility::methodRhsIdFromString(rhsToken); if (rhsId != entity::RhsIdentificator::None) newMethod->setRhsIdentificator(rhsId); else return {tr("Wrong rhs keyword: %1").arg(rhsToken), nullptr}; } return {"", newMethod}; }
OptionalEntity ComponentsMaker::makeProperty(const Tokens &tokens) { Q_ASSERT(!tokens.isEmpty() && tokens[int(PropGroupNames::Type)]->isSingle() && !tokens[int(PropGroupNames::Type)]->token().isEmpty() && tokens[int(PropGroupNames::Name)]->isSingle() && !tokens[int(PropGroupNames::Name)]->token().isEmpty()); Q_ASSERT(checkCommonState()); entity::SharedProperty newProperty = std::make_shared<entity::Property>(); newProperty->setTypeSearcher(m_Model->globalDatabase()); // Add name newProperty->setName(tokens[int(PropGroupNames::Name)]->token()); // Add type // Not support namespaces for now, so check only in global database. // Work with namespaces and custom types in project will be hard due to Qt meta-stuff. // And also required more detail work on current code generation functionality. TODO: implement! const entity::SharedScope &globasScope = m_Model->globalDatabase()->scope(common::ID::globalScopeID()); if (!globasScope) return {tr("Cannot find global scope."), nullptr}; const auto &typeName = tokens[int(PropGroupNames::Type)]->token(); auto type = globasScope->type(typeName); if (!type) return {tr("Wrong type: %1.").arg(typeName), nullptr}; newProperty->setTypeId(type->id()); // Member (supports only "m_" prefix for now) const auto &member = tokens[int(PropGroupNames::Member)]->token(); if (!member.isEmpty()) { const bool hasPrefix = member.startsWith("m_"); const auto name(hasPrefix ? QString(member).remove(0, 2) : member); const auto prefix(hasPrefix ? "m_" : ""); newProperty->addField(name).field()->setPrefix(prefix); newProperty->setMember(true); } // Common methods addCommon(tokens, PropGroupNames::Getter, &entity::Property::addGetter, newProperty); addCommon(tokens, PropGroupNames::Setter, &entity::Property::addSetter, newProperty); addCommon(tokens, PropGroupNames::Resetter, &entity::Property::addResetter, newProperty); addCommon(tokens, PropGroupNames::Notifier, &entity::Property::addNotifier, newProperty); // Revision Q_ASSERT(tokens[int(PropGroupNames::Revision)]->isSingle()); const auto &revision = tokens[int(PropGroupNames::Revision)]->token(); if (!revision.isEmpty()) { bool ok = false; int rev = revision.toInt(&ok); if (ok) newProperty->setRevision(rev); else return {tr("Wrong revision: %1.").arg(revision), nullptr}; } // Add designable and scriptable addDS(tokens, PropGroupNames::Designable, &entity::Property::addDesignableGetter, &entity::Property::setDesignable, newProperty); addDS(tokens, PropGroupNames::Scriptable, &entity::Property::addScriptableGetter, &entity::Property::setScriptable, newProperty); // Add stroed, user, const and final options addExtra(tokens, PropGroupNames::Stored, &entity::Property::setStored, newProperty); addExtra(tokens, PropGroupNames::User, &entity::Property::setUser, newProperty); addExtra(tokens, PropGroupNames::Constant, &entity::Property::setConstant, newProperty); addExtra(tokens, PropGroupNames::Final, &entity::Property::setFinal, newProperty); return {"", newProperty}; }
/** * @brief ComponentsMaker::makeType * @param tokens * @return */ OptionalEntity ComponentsMaker::makeType(const Tokens &tokens) { Q_ASSERT(!tokens.isEmpty() && tokens[int(TypeGroups::Typename)]->isSingle() && !tokens[int(TypeGroups::Typename)]->token().isEmpty()); // Check common type const QString &typeName = tokens[int(TypeGroups::Typename)]->token(); entity::SharedType type; if (!tokens[int(TypeGroups::Namespaces)]->token().isEmpty()) { // TODO: should be already splitted i.e. is not single Q_ASSERT(tokens[int(TypeGroups::Namespaces)]->isSingle()); auto names = tokens[int(TypeGroups::Namespaces)]->token() .split("::", QString::SkipEmptyParts); auto scope = m_Model->globalDatabase()->chainScopeSearch(names); if (!scope) scope = m_Model->currentProject()->database()->chainScopeSearch(names); if (scope) type = scope->type(typeName); } else { // First of all check in all scopes of global database const entity::ScopesList &scopes = m_Model->globalDatabase()->scopes(); range::find_if(scopes, [&](auto scope){ type = scope->type(typeName); return !!type; }); // If not found, try to check project database if (!type) { auto db = m_Model->currentProject()->database(); range::find_if(db->scopes(), [&](auto scope){ type = scope->type(typeName); return !!type; }); } } if (!type) return {tr("Wrong type: %1.").arg(typeName), nullptr}; // Check extra stuff entity::SharedExtendedType extType = std::make_shared<entity::ExtendedType>(); extType->setTypeId(type->id()); extType->setScopeId(m_Scope->id()); Q_ASSERT(tokens[int(TypeGroups::ConstStatus)]->isSingle()); extType->setConstStatus(!tokens[int(TypeGroups::ConstStatus)]->token().isEmpty()); Q_ASSERT(tokens[int(TypeGroups::PLC)]->isSingle()); if (!tokens[int(TypeGroups::PLC)]->token().isEmpty()) { QString plc = tokens[int(TypeGroups::PLC)]->token(); plc.remove(QChar::Space); if (plc.startsWith("const")) { extType->setConstStatus(true); plc.remove(0, 4); } while (!plc.isEmpty()) { if (plc.startsWith("const")) { plc.remove(0, 5); } else if (plc.startsWith("*const")) { extType->addPointerStatus(true); plc.remove(0, 6); } else if (plc.startsWith("*")) { extType->addPointerStatus(); plc.remove(0, 1); } else if (plc.startsWith("&")) { extType->addLinkStatus(); plc.remove(0, 1); } else { break; } } } // TODO: should be already splitted too Q_ASSERT(tokens[int(TypeGroups::TemplateArgs)]->isSingle()); if (!tokens[int(TypeGroups::TemplateArgs)]->token().isEmpty()) { QStringList arguments = tokens[int(TypeGroups::TemplateArgs)]->token() .remove(QChar::Space) .split(",", QString::SkipEmptyParts); entity::ScopesList scopes = m_Model->currentProject()->database()->scopes(); for (auto && s : m_Model->globalDatabase()->scopes()) scopes << s; // TODO: add namespaces, * and const for (auto &&name : arguments) { entity::SharedType t; range::find_if(scopes, [&](auto &&sc){ t = sc->type(name); return !!t; }); if (t) extType->addTemplateParameter(t->id()); else return {tr("Template parameter \"%1\" not found.").arg(name), nullptr}; } } if (extType->isConst() || !extType->templateParameters().isEmpty() || !extType->pl().isEmpty()) { const entity::TypesList &types = m_Scope->types(); auto it = range::find_if(types, [=](const entity::SharedType &type) { return extType->isEqual(*type, false); }); if (it == cend(types)) m_Model->addExistsType(m_Model->currentProject()->name(), m_Scope->id(), extType); return {"", extType}; } else { return {"", type}; } }
void Editor::triggerAutoComplete() { if( !d->autoCompleteEnabled ) return; // tokenize the expression (don't worry, this is very fast) // faster now that it uses flex. ;) int para = 0, curPos = 0; getCursorPosition( ¶, &curPos ); QString subtext = text().left( curPos ); Tokens tokens = Evaluator::scan( subtext ); if(!tokens.valid()) { kdWarning() << "invalid tokens.\n"; return; } if(tokens.isEmpty() || subtext.endsWith(" ")) return; Token lastToken = tokens[ tokens.count()-1 ]; // last token must be an identifier if( !lastToken.isIdentifier() ) return; QString id = lastToken.text(); if( id.isEmpty() ) return; // find matches in function names QStringList fnames = FunctionManager::instance()->functionList(FunctionManager::All); QStringList choices; for( unsigned i=0; i<fnames.count(); i++ ) if( fnames[i].startsWith( id, false ) ) { QString str = fnames[i]; ::Function* f = FunctionManager::instance()->function( str ); if( f && !f->description.isEmpty() ) str.append( ':' ).append( f->description ); choices.append( str ); } choices.sort(); // find matches in variables names QStringList vchoices; QStringList values = NumeralModel::instance()->valueNames(); for(QStringList::ConstIterator it = values.begin(); it != values.end(); ++it) if( (*it).startsWith( id, false ) ) { QString choice = NumeralModel::description(*it); if(choice.isEmpty()) choice = NumeralModel::instance()->value(*it).toString(); vchoices.append( QString("%1:%2").arg( *it, choice ) ); } vchoices.sort(); choices += vchoices; // no match, don't bother with completion if( !choices.count() ) return; // one match, complete it for the user if( choices.count()==1 ) { QString str = QStringList::split( ':', choices[0] )[0]; // single perfect match, no need to give choices. if(str == id.lower()) return; str = str.remove( 0, id.length() ); int para = 0, curPos = 0; getCursorPosition( ¶, &curPos ); blockSignals( true ); insert( str ); setSelection( 0, curPos, 0, curPos+str.length() ); blockSignals( false ); return; } // present the user with completion choices d->completion->showCompletion( choices ); }
void CellEditor::Private::updateActiveSubRegion(const Tokens &tokens) { // Index of the token, at which the text cursor is positioned. // For sub-regions it is the start range. currentToken = 0; if (tokens.isEmpty()) { selection->setActiveSubRegion(0, 0); // also set the active element return; } const int cursorPosition = textEdit->textCursor().position() - 1; // without '=' kDebug() << "cursorPosition:" << cursorPosition << "textLength:" << textEdit->toPlainText().length() - 1; uint rangeCounter = 0; // counts the ranges in the sub-region uint currentRange = 0; // range index denoting the current range int regionStart = 0; // range index denoting the sub-region start uint regionEnd = 0; // range index denoting the sub-region end enum { Anywhere, InRegion, BeyondCursor } state = Anywhere; Token token; Token::Type type; // Search the current range the text cursor is positioned to. // Determine the subregion start and end, in which the range is located. for (int i = 0; i < tokens.count(); ++i) { token = tokens[i]; type = token.type(); // If not in a subregion, we may already quit the loop here. if (state == Anywhere) { // Already beyond the cursor position? if (token.pos() > cursorPosition) { state = BeyondCursor; break; // for loop } } else if (state == InRegion) { // Loop to the end of the subregion. if (type == Token::Cell || type == Token::Range) { regionEnd = rangeCounter++; continue; // keep going until the referenced region ends } if (type == Token::Operator) { if (tokens[i].asOperator() == Token::Semicolon) { continue; // keep going until the referenced region ends } } state = Anywhere; continue; } // Can the token be replaced by a reference? switch (type) { case Token::Cell: case Token::Range: if (state == Anywhere) { currentToken = i; regionStart = rangeCounter; state = InRegion; } regionEnd = rangeCounter; // length = 1 currentRange = ++rangeCounter; // point behind the last continue; case Token::Unknown: case Token::Boolean: case Token::Integer: case Token::Float: case Token::String: case Token::Error: // Set the active sub-region start to the next range but // with a length of 0, which results in inserting a new range // to the selection on calling Selection::initialize() or // Selection::update(). currentToken = i; regionStart = rangeCounter; // position of the next range regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; continue; case Token::Operator: case Token::Identifier: continue; } } // Cursor not reached? I.e. the cursor is placed at the last token's end. if (state == Anywhere) { token = tokens.last(); type = token.type(); // Check the last token. // It was processed, but maybe a reference can be placed behind it. // Check, if the token can be replaced by a reference. switch (type) { case Token::Operator: // Possible to place a reference behind the operator? switch (token.asOperator()) { case Token::Plus: case Token::Minus: case Token::Asterisk: case Token::Slash: case Token::Caret: case Token::LeftPar: case Token::Semicolon: case Token::Equal: case Token::NotEqual: case Token::Less: case Token::Greater: case Token::LessEqual: case Token::GreaterEqual: case Token::Intersect: case Token::Union: // Append new references by pointing behind the last. currentToken = tokens.count(); regionStart = rangeCounter; regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; break; case Token::InvalidOp: case Token::RightPar: case Token::Comma: case Token::Ampersand: case Token::Percent: case Token::CurlyBra: case Token::CurlyKet: case Token::Pipe: // reference cannot be placed behind break; } break; case Token::Unknown: case Token::Boolean: case Token::Integer: case Token::Float: case Token::String: case Token::Identifier: case Token::Error: // currentToken = tokens.count() - 1; // already set // Set the active sub-region start to the end of the selection // with a length of 0, which results in appending a new range // to the selection on calling Selection::initialize() or // Selection::update(). regionStart = rangeCounter; regionEnd = rangeCounter - 1; // length = 0 currentRange = rangeCounter; break; case Token::Cell: case Token::Range: // currentToken = tokens.count() - 1; // already set // Set the last range as active one. It is not a sub-region, // otherwise the state would have been InRegion. regionStart = rangeCounter - 1; regionEnd = rangeCounter - 1; // length = 1 currentRange = rangeCounter; // point behind the last break; } } const int regionLength = regionEnd - regionStart + 1; kDebug() << "currentRange:" << currentRange << "regionStart:" << regionStart << "regionEnd:" << regionEnd << "regionLength:" << regionLength; selection->setActiveSubRegion(regionStart, regionLength, currentRange); }