// source-loc.cpp #include "source-loc.h" #include "compiler.h" #include "../core/slang-string-util.h" namespace Slang { /* !!!!!!!!!!!!!!!!!!!!!!!!! SourceView !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ const String PathInfo::getMostUniqueIdentity() const { switch (type) { case Type::Normal: return uniqueIdentity; case Type::FoundPath: case Type::FromString: { return foundPath; } default: return ""; } } /* !!!!!!!!!!!!!!!!!!!!!!!!! SourceView !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ int SourceView::findEntryIndex(SourceLoc sourceLoc) const { if (!m_range.contains(sourceLoc)) { return -1; } const auto rawValue = sourceLoc.getRaw(); Index hi = m_entries.getCount(); // If there are no entries, or it is in front of the first entry, then there is no associated entry if (hi == 0 || m_entries[0].m_startLoc.getRaw() > sourceLoc.getRaw()) { return -1; } Index lo = 0; while (lo + 1 < hi) { const Index mid = (hi + lo) >> 1; const Entry& midEntry = m_entries[mid]; SourceLoc::RawValue midValue = midEntry.m_startLoc.getRaw(); if (midValue <= rawValue) { // The location we seek is at or after this entry lo = mid; } else { // The location we seek is before this entry hi = mid; } } return int(lo); } void SourceView::addLineDirective(SourceLoc directiveLoc, StringSlicePool::Handle pathHandle, int line) { SLANG_ASSERT(pathHandle != StringSlicePool::Handle(0)); SLANG_ASSERT(m_range.contains(directiveLoc)); // Check that the directiveLoc values are always increasing SLANG_ASSERT(m_entries.getCount() == 0 || (m_entries.getLast().m_startLoc.getRaw() < directiveLoc.getRaw())); // Calculate the offset const int offset = m_range.getOffset(directiveLoc); // Get the line index in the original file const int lineIndex = m_sourceFile->calcLineIndexFromOffset(offset); Entry entry; entry.m_startLoc = directiveLoc; entry.m_pathHandle = pathHandle; // We also need to make sure that any lookups for line numbers will // get corrected based on this files location. // We assume the line number coming from the directive is a line number, NOT an index, so the correction needs + 1 // There is an additional + 1 because we want the NEXT line - ie the line after the #line directive, to the specified value // Taking both into account means +2 is correct 'fix' entry.m_lineAdjust = line - (lineIndex + 2); m_entries.add(entry); } void SourceView::addLineDirective(SourceLoc directiveLoc, const String& path, int line) { StringSlicePool::Handle pathHandle = getSourceManager()->getStringSlicePool().add(path.getUnownedSlice()); return addLineDirective(directiveLoc, pathHandle, line); } void SourceView::addDefaultLineDirective(SourceLoc directiveLoc) { SLANG_ASSERT(m_range.contains(directiveLoc)); // Check that the directiveLoc values are always increasing SLANG_ASSERT(m_entries.getCount() == 0 || (m_entries.getLast().m_startLoc.getRaw() < directiveLoc.getRaw())); // Well if there are no entries, or the last one puts it in default case, then we don't need to add anything if (m_entries.getCount() == 0 || (m_entries.getCount() && m_entries.getLast().isDefault())) { return; } Entry entry; entry.m_startLoc = directiveLoc; entry.m_lineAdjust = 0; // No line adjustment... we are going back to default entry.m_pathHandle = StringSlicePool::Handle(0); // Mark that there is no path, and that this is a 'default' SLANG_ASSERT(entry.isDefault()); m_entries.add(entry); } HumaneSourceLoc SourceView::getHumaneLoc(SourceLoc loc, SourceLocType type) { const int offset = m_range.getOffset(loc); // We need the line index from the original source file const int lineIndex = m_sourceFile->calcLineIndexFromOffset(offset); // TODO: we should really translate the byte index in the line // to deal with: // // - Non-ASCII characters, while might consume multiple bytes // // - Tab characters, which should really adjust how we report // columns (although how are we supposed to know the setting // that an IDE expects us to use when reporting locations?) const int columnIndex = m_sourceFile->calcColumnIndex(lineIndex, offset); HumaneSourceLoc humaneLoc; humaneLoc.column = columnIndex + 1; humaneLoc.line = lineIndex + 1; // Make up a default entry StringSlicePool::Handle pathHandle = StringSlicePool::Handle(0); // Only bother looking up the entry information if we want a 'Normal' lookup const int entryIndex = (type == SourceLocType::Nominal) ? findEntryIndex(loc) : -1; if (entryIndex >= 0) { const Entry& entry = m_entries[entryIndex]; // Adjust the line humaneLoc.line += entry.m_lineAdjust; // Get the pathHandle.. pathHandle = entry.m_pathHandle; } humaneLoc.pathInfo = _getPathInfoFromHandle(pathHandle); return humaneLoc; } PathInfo SourceView::_getPathInfo() const { if (m_viewPath.getLength()) { PathInfo pathInfo(m_sourceFile->getPathInfo()); pathInfo.foundPath = m_viewPath; return pathInfo; } else { return m_sourceFile->getPathInfo(); } } PathInfo SourceView::_getPathInfoFromHandle(StringSlicePool::Handle pathHandle) const { // If there is no override path, then just the source files path if (pathHandle == StringSlicePool::Handle(0)) { return _getPathInfo(); } else { return PathInfo::makePath(getSourceManager()->getStringSlicePool().getSlice(pathHandle)); } } PathInfo SourceView::getPathInfo(SourceLoc loc, SourceLocType type) { if (type == SourceLocType::Actual) { return _getPathInfo(); } const int entryIndex = findEntryIndex(loc); return _getPathInfoFromHandle((entryIndex >= 0) ? m_entries[entryIndex].m_pathHandle : StringSlicePool::Handle(0)); } /* !!!!!!!!!!!!!!!!!!!!!!! SourceFile !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ void SourceFile::setLineBreakOffsets(const uint32_t* offsets, UInt numOffsets) { m_lineBreakOffsets.clear(); m_lineBreakOffsets.addRange(offsets, numOffsets); } const List& SourceFile::getLineBreakOffsets() { // We now have a raw input file that we can search for line breaks. // We obviously don't want to do a linear scan over and over, so we will // cache an array of line break locations in the file. if (m_lineBreakOffsets.getCount() == 0) { UnownedStringSlice content = getContent(); char const* begin = content.begin(); char const* end = content.end(); char const* cursor = begin; // Treat the beginning of the file as a line break m_lineBreakOffsets.add(0); while (cursor != end) { int c = *cursor++; switch (c) { case '\r': case '\n': { // When we see a line-break character we need // to record the line break, but we also need // to deal with the annoying issue of encodings, // where a multi-byte sequence might encode // the line break. // Check to make sure that the EOF hasn't been reached. if (cursor != end) { int d = *cursor; if ((c ^ d) == ('\r' ^ '\n')) cursor++; } m_lineBreakOffsets.add(uint32_t(cursor - begin)); break; } default: break; } } // Note that we do *not* treat the end of the file as a line // break, because otherwise we would report errors like // "end of file inside string literal" with a line number // that points at a line that doesn't exist. } return m_lineBreakOffsets; } int SourceFile::calcLineIndexFromOffset(int offset) { SLANG_ASSERT(UInt(offset) <= getContentSize()); // Make sure we have the line break offsets const auto& lineBreakOffsets = getLineBreakOffsets(); // At this point we can assume the `lineBreakOffsets` array has been filled in. // We will use a binary search to find the line index that contains our // chosen offset. Index lo = 0; Index hi = lineBreakOffsets.getCount(); while (lo + 1 < hi) { const Index mid = (hi + lo) >> 1; const uint32_t midOffset = lineBreakOffsets[mid]; if (midOffset <= uint32_t(offset)) { lo = mid; } else { hi = mid; } } return int(lo); } int SourceFile::calcColumnIndex(int lineIndex, int offset) { const auto& lineBreakOffsets = getLineBreakOffsets(); return offset - lineBreakOffsets[lineIndex]; } /* !!!!!!!!!!!!!!!!!!!!!!!!! SourceFile !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ void SourceFile::setContents(ISlangBlob* blob) { const UInt contentSize = blob->getBufferSize(); SLANG_ASSERT(contentSize == m_contentSize); char const* contentBegin = (char const*)blob->getBufferPointer(); char const* contentEnd = contentBegin + contentSize; m_contentBlob = blob; m_content = UnownedStringSlice(contentBegin, contentEnd); } void SourceFile::setContents(const String& content) { ComPtr contentBlob = StringUtil::createStringBlob(content); setContents(contentBlob); } SourceFile::SourceFile(SourceManager* sourceManager, const PathInfo& pathInfo, size_t contentSize) : m_sourceManager(sourceManager), m_pathInfo(pathInfo), m_contentSize(contentSize) { } SourceFile::~SourceFile() { } String SourceFile::calcVerbosePath() const { ISlangFileSystemExt* fileSystemExt = getSourceManager()->getFileSystemExt(); if (fileSystemExt) { String canonicalPath; ComPtr canonicalPathBlob; if (SLANG_SUCCEEDED(fileSystemExt->getCanonicalPath(m_pathInfo.foundPath.getBuffer(), canonicalPathBlob.writeRef()))) { canonicalPath = StringUtil::getString(canonicalPathBlob); } if (canonicalPath.getLength() > 0) { return canonicalPath; } } return m_pathInfo.foundPath; } /* !!!!!!!!!!!!!!!!!!!!!!!!! SourceManager !!!!!!!!!!!!!!!!!!!!!!!!!!!! */ void SourceManager::initialize( SourceManager* p, ISlangFileSystemExt* fileSystemExt) { m_fileSystemExt = fileSystemExt; m_parent = p; if( p ) { // If we have a parent source manager, then we assume that all code at that level // has already been loaded, and it is safe to start our own source locations // right after those from the parent. // // TODO: more clever allocation in cases where that might not be reasonable m_startLoc = p->m_nextLoc; } else { // Location zero is reserved for an invalid location, // so we need to start reserving locations starting at 1. m_startLoc = SourceLoc::fromRaw(1); } m_nextLoc = m_startLoc; } SourceManager::~SourceManager() { for (auto item : m_sourceViews) { delete item; } for (auto item : m_sourceFiles) { delete item; } } UnownedStringSlice SourceManager::allocateStringSlice(const UnownedStringSlice& slice) { const UInt numChars = slice.size(); char* dst = (char*)m_memoryArena.allocate(numChars); ::memcpy(dst, slice.begin(), numChars); return UnownedStringSlice(dst, numChars); } SourceRange SourceManager::allocateSourceRange(UInt size) { // TODO: consider using atomics here SourceLoc beginLoc = m_nextLoc; SourceLoc endLoc = beginLoc + size; // We need to be able to represent the location that is *at* the end of // the input source, so the next available location for a new file // must be placed one after the end of this one. m_nextLoc = endLoc + 1; return SourceRange(beginLoc, endLoc); } SourceFile* SourceManager::createSourceFileWithSize(const PathInfo& pathInfo, size_t contentSize) { SourceFile* sourceFile = new SourceFile(this, pathInfo, contentSize); m_sourceFiles.add(sourceFile); return sourceFile; } SourceFile* SourceManager::createSourceFileWithString(const PathInfo& pathInfo, const String& contents) { SourceFile* sourceFile = new SourceFile(this, pathInfo, contents.getLength()); m_sourceFiles.add(sourceFile); sourceFile->setContents(contents); return sourceFile; } SourceFile* SourceManager::createSourceFileWithBlob(const PathInfo& pathInfo, ISlangBlob* blob) { SourceFile* sourceFile = new SourceFile(this, pathInfo, blob->getBufferSize()); m_sourceFiles.add(sourceFile); sourceFile->setContents(blob); return sourceFile; } SourceView* SourceManager::createSourceView(SourceFile* sourceFile, const PathInfo* pathInfo) { SourceRange range = allocateSourceRange(sourceFile->getContentSize()); SourceView* sourceView = nullptr; if (pathInfo && (pathInfo->foundPath.getLength() && sourceFile->getPathInfo().foundPath != pathInfo->foundPath)) { sourceView = new SourceView(sourceFile, range, &pathInfo->foundPath); } else { sourceView = new SourceView(sourceFile, range, nullptr); } m_sourceViews.add(sourceView); return sourceView; } SourceView* SourceManager::findSourceView(SourceLoc loc) const { Index hi = m_sourceViews.getCount(); // It must be in the range of this manager and have associated views for it to possibly be a hit if (!getSourceRange().contains(loc) || hi == 0) { return nullptr; } // If we don't have very many, we may as well just linearly search if (hi <= 8) { for (int i = 0; i < hi; ++i) { SourceView* view = m_sourceViews[i]; if (view->getRange().contains(loc)) { return view; } } return nullptr; } const SourceLoc::RawValue rawLoc = loc.getRaw(); // Binary chop to see if we can find the associated SourceUnit Index lo = 0; while (lo + 1 < hi) { Index mid = (hi + lo) >> 1; SourceView* midView = m_sourceViews[mid]; if (midView->getRange().contains(loc)) { return midView; } const SourceLoc::RawValue midValue = midView->getRange().begin.getRaw(); if (midValue <= rawLoc) { // The location we seek is at or after this entry lo = mid; } else { // The location we seek is before this entry hi = mid; } } // Check if low is actually a hit SourceView* view = m_sourceViews[lo]; return (view->getRange().contains(loc)) ? view : nullptr; } SourceView* SourceManager::findSourceViewRecursively(SourceLoc loc) const { // Start with this manager const SourceManager* manager = this; do { SourceView* sourceView = manager->findSourceView(loc); // If we found a hit we are done if (sourceView) { return sourceView; } // Try the parent manager = manager->m_parent; } while (manager); // Didn't find it return nullptr; } SourceFile* SourceManager::findSourceFile(const String& uniqueIdentity) const { SourceFile*const* filePtr = m_sourceFileMap.TryGetValue(uniqueIdentity); return (filePtr) ? *filePtr : nullptr; } SourceFile* SourceManager::findSourceFileRecursively(const String& uniqueIdentity) const { const SourceManager* manager = this; do { SourceFile* sourceFile = manager->findSourceFile(uniqueIdentity); if (sourceFile) { return sourceFile; } manager = manager->m_parent; } while (manager); return nullptr; } void SourceManager::addSourceFile(const String& uniqueIdentity, SourceFile* sourceFile) { SLANG_ASSERT(!findSourceFileRecursively(uniqueIdentity)); m_sourceFileMap.Add(uniqueIdentity, sourceFile); } HumaneSourceLoc SourceManager::getHumaneLoc(SourceLoc loc, SourceLocType type) { SourceView* sourceView = findSourceViewRecursively(loc); if (sourceView) { return sourceView->getHumaneLoc(loc, type); } else { return HumaneSourceLoc(); } } PathInfo SourceManager::getPathInfo(SourceLoc loc, SourceLocType type) { SourceView* sourceView = findSourceViewRecursively(loc); if (sourceView) { return sourceView->getPathInfo(loc, type); } else { return PathInfo::makeUnknown(); } } } // namespace Slang