Raw File
source-loc.cpp
// source-loc.cpp
#include "source-loc.h"

#include "compiler.h"

#include "../core/slang-string-util.h"

namespace Slang {

/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceView !!!!!!!!!!!!!!!!!!!!!!!!!!!! */

const String PathInfo::getMostUniquePath() const
{
    switch (type)
    {
        case Type::Normal:      return canonicalPath;
        case Type::FoundPath:   return foundPath;
        default:                return "";
    }
}

/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceView !!!!!!!!!!!!!!!!!!!!!!!!!!!! */

int SourceView::findEntryIndex(SourceLoc sourceLoc) const
{
    if (!m_range.contains(sourceLoc))
    {
        return -1;
    }

    const auto rawValue = sourceLoc.getRaw();

    int hi = int(m_entries.Count());    
    // 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;
    }

    int lo = 0;
    while (lo + 1 < hi)
    {
        const int 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 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.Count() == 0 || (m_entries.Last().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 = m_sourceManager->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.Count() == 0 || (m_entries.Last().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.Count() == 0 || (m_entries.Count() && m_entries.Last().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 = _getPathInfo(pathHandle);
    return humaneLoc;
}

PathInfo SourceView::_getPathInfo(StringSlicePool::Handle pathHandle) const
{
    // If there is no override path, then just the source files path
    if (pathHandle == StringSlicePool::Handle(0))
    {
        return m_sourceFile->pathInfo;
    }
    else
    {
        // We don't have a full normal path (including 'canonical') so just go with FoundPath
        return PathInfo::makePath(m_sourceManager->getStringSlicePool().getSlice(pathHandle));
    }
}

PathInfo SourceView::getPathInfo(SourceLoc loc, SourceLocType type)
{
    if (type == SourceLocType::Actual)
    {
        return m_sourceFile->pathInfo;
    }

    const int entryIndex = findEntryIndex(loc);
    return _getPathInfo((entryIndex >= 0) ? m_entries[entryIndex].m_pathHandle : StringSlicePool::Handle(0));
}

/* !!!!!!!!!!!!!!!!!!!!!!! SourceFile !!!!!!!!!!!!!!!!!!!!!!!!!!!! */

const List<uint32_t>& 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.Count() == 0)
    {
        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.

                    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) <= content.size());

    // 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.
    int lo = 0;
    int hi = int(lineBreakOffsets.Count());

    while (lo + 1 < hi)
    {
        const int mid = (hi + lo) >> 1; 
        const uint32_t midOffset = lineBreakOffsets[mid];
        if (midOffset <= uint32_t(offset))
        {
            lo = mid;
        }
        else
        {
            hi = mid;
        }
    }

    return lo;
}

int SourceFile::calcColumnIndex(int lineIndex, int offset)
{
    const auto& lineBreakOffsets = getLineBreakOffsets();
    return offset - lineBreakOffsets[lineIndex];   
}

/* !!!!!!!!!!!!!!!!!!!!!!!!! SourceManager !!!!!!!!!!!!!!!!!!!!!!!!!!!! */

void SourceManager::initialize(
    SourceManager*  p)
{
    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;
}

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::createSourceFile(const PathInfo& pathInfo, ISlangBlob* contentBlob)
{
    char const* contentBegin = (char const*) contentBlob->getBufferPointer();
    UInt contentSize = contentBlob->getBufferSize();
    char const* contentEnd = contentBegin + contentSize;

    SourceFile* sourceFile = new SourceFile();
    sourceFile->pathInfo = pathInfo;
    sourceFile->contentBlob = contentBlob;
    sourceFile->content = UnownedStringSlice(contentBegin, contentEnd);
 
    return sourceFile;
}
 
SourceFile* SourceManager::createSourceFile(const PathInfo& pathInfo, const String& content)
{
    ComPtr<ISlangBlob> contentBlob = StringUtil::createStringBlob(content);
    return createSourceFile(pathInfo, contentBlob);
}

SourceView* SourceManager::createSourceView(SourceFile* sourceFile)
{
    SourceRange range = allocateSourceRange(sourceFile->content.size());
    SourceView* sourceView = new SourceView(this, sourceFile, range);
    m_sourceViews.Add(sourceView);

    return sourceView;
}

SourceView* SourceManager::findSourceView(SourceLoc loc) const
{
    int hi = int(m_sourceViews.Count());
    // 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
    int lo = 0;
    while (lo + 1 < hi)
    {
        int 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& canonicalPath) const
{
    RefPtr<SourceFile>* filePtr = m_sourceFiles.TryGetValue(canonicalPath);
    return (filePtr) ? filePtr->Ptr() : nullptr;
}

SourceFile* SourceManager::findSourceFileRecursively(const String& canonicalPath) const
{
    const SourceManager* manager = this;
    do 
    {
        SourceFile* sourceFile = manager->findSourceFile(canonicalPath);
        if (sourceFile)
        {
            return sourceFile;
        }
        manager = manager->m_parent;
    } while (manager);
    return nullptr;
}

void SourceManager::addSourceFile(const String& canonicalPath, SourceFile* sourceFile)
{
    SLANG_ASSERT(!findSourceFileRecursively(canonicalPath));
    m_sourceFiles.Add(canonicalPath, 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
back to top