#include "common.h" #ifdef HAVE_LSP #include "symbolmaps.h" #include "locate.h" #include namespace AsymptoteLsp { [[nodiscard]] bool isVirtualFile(std::string const& filename) { bool isSettings=filename=="settings"; #ifdef HAVE_LIBGSL bool isGSL=filename=="gsl" and settings::getSetting("gsl"); #else bool isGSL=false; #endif return isSettings || isGSL; } std::string getPlainFile() { return std::string((std::string) settings::locateFile("plain", true).c_str()); } positions::positions(filePos const& positionInFile) { add(positionInFile); } void positions::add(const filePos &positionInFile) { auto fileLoc=pos.find(positionInFile.first); if (fileLoc == pos.end()) { pos.emplace(positionInFile.first, std::vector{positionInFile.second}); } else { fileLoc->second.push_back(positionInFile.second); } } ostream& operator <<(std::ostream& os, const SymbolMaps& sym) { os << "var decs:" << endl; for (auto const& s : sym.varDec) { auto const& key=std::get<0>(s); auto const& value=std::get<1>(s); os << key << " " << value.pos.first << ":" << value.pos.second << endl; } return os; } optional SymbolMaps::searchSymbol(posInFile const& inputPos) { // FIXME: can be optimized by binary search. for (auto const& s : usageByLines) { auto const& pos=std::get<0>(s); auto const& syLit=std::get<1>(s); size_t endCharacter = pos.second + syLit.name.length() - 1; bool posMatches = pos.first == inputPos.first and pos.second <= inputPos.second and inputPos.second <= endCharacter; bool isOperator = syLit.name.find("operator ") == 0; if (posMatches and !isOperator) { posInFile endPos(pos.first, endCharacter + 1); return make_optional(std::make_tuple(syLit, pos, endPos)); } } return nullopt; } FunctionInfo& SymbolMaps::addFunDef( std::string const& funcName, posInFile const& position, std::string const& returnType) { auto fit=std::get<0>(funDec.emplace(std::piecewise_construct, std::forward_as_tuple(funcName), std::forward_as_tuple())); fit->second.emplace_back(funcName, position, returnType); return fit->second.back(); } std::pair, SymbolContext*> SymbolContext::searchSymbol(posInFile const& inputPos) { auto currCtxSym = symMap.searchSymbol(inputPos); if (currCtxSym.has_value()) { return make_pair(currCtxSym, this); } // else, not found in currCtx; for (auto& subContext : subContexts) { if (!posLt(inputPos, subContext->contextLoc)) { auto v=subContext->searchSymbol(inputPos); auto subCtxSym=std::get<0>(v);; auto ctx=std::get<1>(v); if (subCtxSym.has_value()) { return make_pair(subCtxSym, ctx); } } } return make_pair(nullopt, nullptr); } SymbolInfo const* SymbolContext::searchVarRaw(std::string const& symbol) const { // local variable declarations auto pt = symMap.varDec.find(symbol); if (pt != symMap.varDec.end()) { return &pt->second; } // otherwise, search parent. return parent != nullptr ? parent->searchVarRaw(symbol) : nullptr; } optional SymbolContext::searchVarDecl( std::string const& symbol, optional const& position) { auto pt = symMap.varDec.find(symbol); if (pt != symMap.varDec.end()) { auto line = std::get<0>(pt->second.pos); auto ch = std::get<1>(pt->second.pos); if ((not position.has_value()) or (!posLt(position.value(), pt->second.pos))) { return std::make_tuple(getFileName().value_or(""), pt->second.pos, std::make_pair(line, ch + symbol.length())); } } auto ptFn = symMap.funDec.find(symbol); if (ptFn != symMap.funDec.end() and !ptFn->second.empty()) { // FIXME: Right now, we have no way of knowing the exact position of the // start of where the function name is. As an example, we do not know // where the exact position of // real testFunction(...) { ... // ^ auto ptValue = ptFn->second[0]; if ((not position.has_value()) or (!posLt(position.value(), ptValue.pos))) { return std::make_tuple(getFileName().value_or(""), ptValue.pos, ptValue.pos); } } // otherwise, search parent. return parent != nullptr ? parent->searchVarDecl(symbol, position) : nullopt; } void SymbolContext::addPlainFile() { std::string plainFile = getPlainFile(); addEmptyExtRef(plainFile); } SymbolContext::SymbolContext(posInFile loc): fileLoc(nullopt), contextLoc(std::move(loc)), parent(nullptr) { addPlainFile(); } SymbolContext::SymbolContext(posInFile loc, std::string filename): fileLoc(std::move(filename)), contextLoc(std::move(loc)), parent(nullptr) { addPlainFile(); } optional SymbolContext::searchVarDeclFull(std::string const& symbol, optional const& position) { std::unordered_set searched; std::unordered_set symbolSearch { symbol }; auto returnVal = _searchVarFull( searched, symbolSearch, [&position](SymbolContext* ctx, std::string const& sym) { return ctx->searchVarDecl(sym, position); }, [](SymbolContext* ctx, std::string const& sym) { return ctx->searchVarDecl(sym); }, fnFromDeclCreateTrav); if (returnVal.has_value()) { return returnVal; } return _searchVarFull( symbol, [](SymbolContext* ctx, std::string const& sym) -> optional { for (auto const& unravelVal : ctx->extRefs.unraveledVals) { if (auto* pctx=ctx->searchStructCtxFull(unravelVal)) { auto retVal = pctx->searchVarDecl(sym); if (retVal.has_value()) { return retVal; } } } return nullopt; }); } std::list SymbolContext::getEmptyRefs() { std::list finalList; for (auto it = extRefs.extFileRefs.begin(); it != extRefs.extFileRefs.end(); it++) { if (it->second == nullptr) { // cerr << it->first << endl; finalList.emplace_back(it); } } for (auto& ctx : subContexts) { finalList.splice(finalList.end(), ctx->getEmptyRefs()); } return finalList; } optional SymbolContext::getFileName() const { if (fileLoc.has_value()) { return fileLoc; } else { return parent == nullptr ? fileLoc : parent->getFileName(); } } optional SymbolContext::searchVarSignatureFull(std::string const& symbol) { std::unordered_set searched; auto retVal = _searchVarFull( symbol, [](SymbolContext const* ctx, std::string const& sym) -> optional { if (auto const* info = ctx->searchVarRaw(sym)) { return info->signature(); } return nullopt; }, fnFromDeclCreateTrav); if (retVal.has_value()) { return retVal; } // else, // search every unravel values possible (not only here, but also in every includes + unravels as well). // If an unravel value match a struct, then see if symbol matches the struct. If yes, return otherwise skip. return _searchVarFull( symbol, [](SymbolContext* ctx, std::string const& sym) -> optional { if (SymbolInfo* info=ctx->searchVarUnravelStructRaw(sym)) { return info->signature(); } return nullopt; }); } SymbolInfo* SymbolContext::searchVarUnravelStructRaw(std::string const& symbol) { return searchVarUnravelStructRaw(symbol, nullopt); } SymbolInfo* SymbolContext::searchVarUnravelStructRaw(std::string const& symbol, optional const& position) { for (auto const& unravelVal : extRefs.unraveledVals) { if (auto* ctx=searchStructCtxFull(unravelVal)) { // ctx points to a struct. auto it=ctx->symMap.varDec.find(symbol); if (it != ctx->symMap.varDec.end()) { if (((not position.has_value()) or (!posLt(position.value(), it->second.pos)))) { return &it->second; } } } } return parent != nullptr ? parent->searchVarUnravelStructRaw(symbol) : nullptr; } std::list SymbolContext::searchFuncUnravelStruct(std::string const& symbol) { std::list finalList; for (auto const& unravelVal : extRefs.unraveledVals) { if (auto* ctx=searchStructCtxFull(unravelVal)) { // ctx points to a struct. auto it=ctx->symMap.funDec.find(symbol); if (it != ctx->symMap.funDec.end() and not it->second.empty()) { std::transform( it->second.begin(), it->second.end(), std::back_inserter(finalList), [](FunctionInfo const& fnInfo) { return fnInfo.signature(); }); } } } if (parent != nullptr) { finalList.splice(finalList.end(), parent->searchFuncUnravelStruct(symbol)); } return finalList; } std::list SymbolContext::searchFuncSignature(std::string const& symbol) { std::list funcSigs; auto pt = symMap.funDec.find(symbol); if (pt != symMap.funDec.end()) { std::transform(pt->second.begin(), pt->second.end(), std::back_inserter(funcSigs), [](FunctionInfo const& fnInfo) { return fnInfo.signature(); }); } if (parent != nullptr) { funcSigs.splice(funcSigs.end(), parent->searchFuncSignature(symbol)); } return funcSigs; } std::list SymbolContext::searchFuncSignatureFull(std::string const& symbol) { auto base_list = _searchAllVarFull( symbol, std::mem_fn(&SymbolContext::searchFuncSignature), fnFromDeclCreateTrav); base_list.splice(base_list.end(), _searchAllVarFull( symbol, std::mem_fn(&SymbolContext::searchFuncUnravelStruct))); return base_list; } optional SymbolContext::searchStructContext(std::string const& tyVal) const { auto stCtx = symMap.typeDecs.find(tyVal); if (stCtx != symMap.typeDecs.end()) { if (auto* stDec = dynamic_cast(stCtx->second.get())) { return make_optional(stDec->ctx); } } return parent != nullptr ? parent->searchStructContext(tyVal) : nullopt; }; optional SymbolContext::searchLitSignature(SymbolLit const& symbol) { if (symbol.scopes.empty()) { return searchVarSignatureFull(symbol.name); } else { auto ctx=std::get<0>(searchLitContext(symbol)); auto isStruct=std::get<1>(searchLitContext(symbol)); if (ctx) { if (isStruct) { // ctx is a struct declaration. should not search beyond this struct. auto varDec=ctx->symMap.varDec.find(symbol.name); return varDec != ctx->symMap.varDec.end() ? optional(varDec->second.signature()) : nullopt; } else { return ctx->searchVarSignatureFull(symbol.name); } } } return nullopt; } std::list SymbolContext::searchLitFuncSignature(SymbolLit const& symbol) { std::list signatures; if (symbol.scopes.empty()) { signatures.splice(signatures.end(), searchFuncSignatureFull(symbol.name)); } else { auto ctx=std::get<0>(searchLitContext(symbol)); auto isStruct=std::get<1>(searchLitContext(symbol)); if (ctx) { if (isStruct) { // ctx is a struct declaration. should not search beyond this struct. auto fnDecs=ctx->symMap.funDec.find(symbol.name); if (fnDecs != ctx->symMap.funDec.end()) { std::transform( fnDecs->second.begin(), fnDecs->second.end(), std::back_inserter(signatures), [&scopes=symbol.scopes](FunctionInfo const& fnInf) { return fnInf.signature(scopes); }); } } else { signatures.splice(signatures.end(), searchFuncSignatureFull(symbol.name)); } } } return signatures; } optional SymbolContext::searchLitPosition( SymbolLit const& symbol, optional const& position) { if (symbol.scopes.empty()) { return searchVarDeclFull(symbol.name, position); } else { auto ctx=std::get<0>(searchLitContext(symbol)); auto isStruct=std::get<1>(searchLitContext(symbol)); if (ctx) // ctx is a struct declaration. should not search beyond this struct. { if (isStruct) { auto varDec=ctx->symMap.varDec.find(symbol.name); if (varDec != ctx->symMap.varDec.end()) { auto line=std::get<0>(varDec->second.pos); auto ch=std::get<1>(varDec->second.pos); return std::make_tuple(ctx->getFileName().value_or(""), varDec->second.pos, std::make_pair(line, ch + symbol.name.length())); } } else { return searchVarDeclFull(symbol.name, position); } } return nullopt; } } std::list SymbolContext::searchLitFuncPositions(SymbolLit const& symbol, optional const& position) { if (symbol.scopes.empty()) { return searchFuncDeclsFull(symbol.name, position); } else { std::list fnDecls; auto v=searchLitContext(symbol); auto ctx=std::get<0>(v); auto isStruct=std::get<1>(v); if (ctx) // ctx is a struct declaration. should not search beyond this struct. { if (isStruct) { auto fnDec=ctx->symMap.funDec.find(symbol.name); if (fnDec != ctx->symMap.funDec.end()) { for (auto const& ptValue : fnDec->second) { fnDecls.emplace_back(getFileName().value_or(""), ptValue.pos, ptValue.pos); } } } else { return searchFuncDeclsFull(symbol.name, position); } } return fnDecls; } } SymbolContext* SymbolContext::searchStructCtxFull(std::string const& symbol) { std::unordered_set searched; optional tyInfo=_searchVarFull( symbol, [](SymbolContext const* ctx, std::string const& sym) -> optional { if (auto const* info = ctx->searchVarRaw(sym)) { return info->type; } return nullopt; }, fnFromDeclCreateTrav); if (not tyInfo.has_value()) { return nullptr; } return _searchVarFull( tyInfo.value(), std::mem_fn(&SymbolContext::searchStructContext)).value_or(nullptr); } optional SymbolContext::searchAccessDecls(std::string const& accessVal) { // fileIdPair includes accessVals auto src=extRefs.fileIdPair.find(accessVal); if (src != extRefs.fileIdPair.end()) { std::string& fileSrc=src->second; auto ctxMap=extRefs.extFileRefs.find(fileSrc); if (ctxMap != extRefs.extFileRefs.end() && ctxMap->second != nullptr) { return ctxMap->second; } } return parent != nullptr ? parent->searchAccessDecls(accessVal) : nullopt; } /** * Fully search context for source structs or source file in access * @param symbol Symbol to search * @return pair of [context, isStruct], where isStruct is true * if the context is a struct, and false if the context is a file from access declaration. * If context is nullptr, isStruct's return does not have defined behavior. */ std::pair SymbolContext::searchLitContext(SymbolLit const& symbol) { if (symbol.scopes.empty()) { // if the symbol is empty, then we can access every variable, not just those in the struct context // and hence should be treated as "not a struct", even if the variable is accessed in the struct. // for example, // int z; struct St { int x; int y=x+z; } // here, x and z can be accessed in the struct context. return std::make_pair(this, false); } else { std::vector scopes(symbol.scopes); bool isStruct=false; // search in struct auto* ctx=searchStructCtxFull(scopes.back()); if (ctx) { isStruct = true; scopes.pop_back(); } else { // search in access declarations ctx =_searchVarFull( scopes.back(), std::mem_fn(&SymbolContext::searchAccessDecls)).value_or(nullptr); if (ctx) { scopes.pop_back(); } } for (auto it = scopes.rbegin(); it != scopes.rend() and ctx != nullptr; it++) { // FIXME: Impelemnt scope searching // example: // varx.vary.varz => go into varx's type context (struct or external file), repeat. // struct and extfile handled very differently // get next variable declaration loc. // assumes struct, hence we do not search entire workspace SymbolContext* newCtx=nullptr; auto locVarDec = ctx->symMap.varDec.find(*it); if (locVarDec != ctx->symMap.varDec.end() and locVarDec->second.type.has_value()) { newCtx = ctx->_searchVarFull( locVarDec->second.type.value(), std::mem_fn(&SymbolContext::searchStructContext) ).value_or(nullptr); } // here, we searched for a struct context and did not find anything. // If we are in a struct context, we hit a dead end. // otherwise, we search for access declaration contexts. if (newCtx == nullptr) { if (isStruct) { return make_pair(nullptr, false); } else { ctx =_searchVarFull( *it, std::mem_fn(&SymbolContext::searchAccessDecls)).value_or(nullptr); } } else { isStruct = true; } ctx = newCtx; } return make_pair(ctx, isStruct); } } std::unordered_set SymbolContext::createTraverseSet() { std::unordered_set traverseSet(extRefs.includeVals); traverseSet.emplace(getPlainFile()); for (auto const& unravelVal : extRefs.unraveledVals) { auto it = extRefs.fileIdPair.find(unravelVal); if (it != extRefs.fileIdPair.end()) { traverseSet.emplace(it->second); } } return traverseSet; } SymbolContext* SymbolContext::getExternalRef(std::string const& symbol) { auto symRef = extRefs.extFileRefs.find(symbol); if (symRef != extRefs.extFileRefs.end()) { return symRef->second; } return parent != nullptr ? parent->getExternalRef(symbol) : nullptr; } std::list SymbolContext::searchFuncDecls(std::string const& symbol) { return searchFuncDecls(symbol, nullopt); } std::list SymbolContext::searchFuncDecls(std::string const& symbol, optional const& position) { std::list funcDecls; auto pt = symMap.funDec.find(symbol); if (pt != symMap.funDec.end()) { for (auto const& fnInfo : pt->second) { if ((not position.has_value()) or (!posLt(position.value(), fnInfo.pos))) { funcDecls.emplace_back(getFileName().value_or(""), fnInfo.pos, fnInfo.pos); } } } if (parent != nullptr) { funcDecls.splice(funcDecls.end(), parent->searchFuncDecls(symbol, position)); } return funcDecls; } std::list SymbolContext::searchFuncDeclsFull(std::string const& symbol, optional const& position) { std::unordered_set searched; auto retVal = _searchAllVarFull( searched, std::unordered_set { symbol }, [&position](SymbolContext* ctx, std::string const& symbol) { return ctx->searchFuncDecls(symbol, position); }, [](SymbolContext* ctx, std::string const& symbol) { return ctx->searchFuncDecls(symbol); }, fnFromDeclCreateTrav); auto retValStruct =_searchAllVarFull( symbol, [](SymbolContext* ctx, std::string const& sym) -> std::list { std::list finalList; for (auto const& unravelVal : ctx->extRefs.unraveledVals) { if (auto* pctx=ctx->searchStructCtxFull(unravelVal)) { finalList.splice(finalList.end(), pctx->searchFuncDecls(sym)); } } return finalList; }); retVal.splice(retVal.end(), std::move(retValStruct)); return retVal; } optional AddDeclContexts::searchVarDecl( std::string const& symbol, optional const& position) { auto pt = additionalDecs.find(symbol); if (pt != additionalDecs.end()) { auto line=std::get<0>(pt->second.pos); auto ch=std::get<1>(pt->second.pos); if ((not position.has_value()) or (!posLt(position.value(), pt->second.pos))) { return std::make_tuple(getFileName().value_or(""), pt->second.pos, std::make_pair(line, ch + symbol.length())); } } return SymbolContext::searchVarDecl(symbol, position); } SymbolInfo const* AddDeclContexts::searchVarRaw(std::string const& symbol) const { auto pt = additionalDecs.find(symbol); return pt != additionalDecs.end() ? &pt->second : SymbolContext::searchVarRaw(symbol); } bool SymbolInfo::operator==(SymbolInfo const& sym) const { return name==sym.name and type==sym.type and pos == sym.pos; } std::string SymbolInfo::signature() const { return type.value_or("") + " " + name + ";"; } std::string FunctionInfo::signature() const { return signature(std::vector()); } std::string FunctionInfo::signature(std::vector const& scopes) const { std::stringstream ss; ss << returnType << " "; for (auto it=scopes.crbegin(); it!=scopes.crend(); it++) { ss << *it << "."; } ss << name << "("; for (auto it = arguments.begin(); it != arguments.end(); it++) { auto const& argtype=std::get<0>(*it); auto const& argname=std::get<1>(*it); ss << argtype; if (argname.has_value()) { ss << " " << argname.value(); } if (std::next(it) != arguments.end()) { ss << ","; } if (std::next(it) != arguments.end() or restArgs.has_value()) { ss << " "; } } if (restArgs.has_value()) { auto const& v=restArgs.value(); auto const& argtype=std::get<0>(v); auto const& argname=std::get<1>(v); ss << "... " << argtype; if (argname.has_value()) { ss << " " << argname.value(); } } ss << ");"; return ss.str(); } } #endif