https://github.com/shader-slang/slang
Raw File
Tip revision: 129faf8c4af4a57b7f1c71749f45b31aebfab038 authored by Tim Foley on 26 March 2021, 17:53:58 UTC
Append proper suffixes to 16-bit literals for GLSL (#1767)
Tip revision: 129faf8
slang-emit-source-writer.cpp
// slang-emit-source-writer.cpp
#include "slang-emit-source-writer.h"

// Disable warnings about sprintf
#ifdef _WIN32
#   pragma warning(disable:4996)
#endif

// Note: using C++ stdio just to get a locale-independent
// way to format floating-point values.
//
// TODO: Go ahead and implement the Dragon4 algorithm so
// that we can print floating-point values to arbitrary
// precision as needed.
#include <sstream>

namespace Slang {

SourceWriter::SourceWriter(SourceManager* sourceManager, LineDirectiveMode lineDirectiveMode)
{
    m_lineDirectiveMode = lineDirectiveMode;
    this->m_sourceManager = sourceManager;
}

String SourceWriter::getContentAndClear()
{
    String content(getContent());
    clearContent();
    return content;
}

void SourceWriter::emitRawTextSpan(char const* textBegin, char const* textEnd)
{
    // TODO(tfoley): Need to make "corelib" not use `int` for pointer-sized things...
    auto len = textEnd - textBegin;
    m_builder.Append(textBegin, len);
}

void SourceWriter::emitRawText(char const* text)
{
    emitRawTextSpan(text, text + strlen(text));
}

void SourceWriter::_emitTextSpan(char const* textBegin, char const* textEnd)
{
    // Don't change anything given an empty string
    if (textBegin == textEnd)
        return;

    // If the source location has changed in a way that required update,
    // do it now!
    _flushSourceLocationChange();

    // Note: we don't want to emit indentation on a line that is empty.
    // The logic in `Emit(textBegin, textEnd)` below will have broken
    // the text into lines, so we can simply check if a line consists
    // of just a newline.
    if (m_isAtStartOfLine && *textBegin != '\n')
    {
        // We are about to emit text (other than a newline)
        // at the start of a line, so we will emit the proper
        // amount of indentation to keep things looking nice.
        m_isAtStartOfLine = false;
        for (Int ii = 0; ii < m_indentLevel; ++ii)
        {
            char const* indentString = "    ";
            size_t indentStringSize = strlen(indentString);
            emitRawTextSpan(indentString, indentString + indentStringSize);

            // We will also update our tracking location, just in
            // case other logic needs it.
            //
            // TODO: We may need to have a switch that controls whether
            // we are in "pretty-printing" mode or "follow the locations
            // in the original code" mode.
            m_loc.column += indentStringSize;
        }
    }

    // Emit the raw text
    emitRawTextSpan(textBegin, textEnd);

    // Update our logical position
    auto len = int(textEnd - textBegin);
    m_loc.column += len;
}

void SourceWriter::indent()
{
    m_indentLevel++;
}

void SourceWriter::dedent()
{
    m_indentLevel--;
}

void SourceWriter::emitChar(char c)
{
    emit(&c, &c + 1);
}

void SourceWriter::emit(char const* textBegin, char const* textEnd)
{
    char const* spanBegin = textBegin;
    char const* spanEnd = spanBegin;
    for (;;)
    {
        if (spanEnd == textEnd)
        {
            // We have a whole range of text waiting to be flushed
            _emitTextSpan(spanBegin, spanEnd);
            return;
        }

        auto c = *spanEnd++;

        if (c == '\n')
        {
            // At the end of a line, we need to update our tracking
            // information on code positions
            _emitTextSpan(spanBegin, spanEnd);
            m_loc.line++;
            m_loc.column = 1;
            m_isAtStartOfLine = true;

            // Start a new span for emit purposes
            spanBegin = spanEnd;
        }
    }
}

void SourceWriter::emit(char const* text)
{
    emit(text, text + strlen(text));
}

void SourceWriter::emit(const String& text)
{
    emit(text.begin(), text.end());
}

void SourceWriter::emit(const UnownedStringSlice& text)
{
    emit(text.begin(), text.end());
}

void SourceWriter::emit(Name* name)
{
    emit(getText(name));
}

void SourceWriter::emit(const NameLoc& nameAndLoc)
{
    advanceToSourceLocationIfValid(nameAndLoc.loc);
    emit(getText(nameAndLoc.name));
}

void SourceWriter::emit(const StringSliceLoc& nameAndLoc)
{
    advanceToSourceLocationIfValid(nameAndLoc.loc);
    emit(nameAndLoc.name);
}

void SourceWriter::emitName(Name* name, const SourceLoc& locIn)
{
    advanceToSourceLocationIfValid(locIn);
    emit(name);
}

void SourceWriter::emitName(const NameLoc& nameAndLoc)
{
    emitName(nameAndLoc.name, nameAndLoc.loc);
}

void SourceWriter::emitName(const StringSliceLoc& nameAndLoc)
{
    emit(nameAndLoc);
}

void SourceWriter::emitName(Name* name)
{
    emitName(name, SourceLoc());
}

void SourceWriter::emit(IntegerLiteralValue value)
{
    char buffer[32];
    sprintf(buffer, "%lld", (long long int)value);
    emit(buffer);
}

void SourceWriter::emit(UInt value)
{
    char buffer[32];
    sprintf(buffer, "%llu", (unsigned long long)(value));
    emit(buffer);
}

void SourceWriter::emitUInt64(uint64_t value)
{
    char buffer[32];
    sprintf(buffer, "%llu", (unsigned long long)(value));
    emit(buffer);
}

void SourceWriter::emitInt64(int64_t value)
{
    char buffer[32];
    sprintf(buffer, "%lld", (long long int)value);
    emit(buffer);
}

void SourceWriter::emit(int value)
{
    char buffer[16];
    sprintf(buffer, "%d", value);
    emit(buffer);
}

void SourceWriter::emit(double value)
{
    // There are a few different requirements here that we need to deal with:
    //
    // 1) We need to print something that is valid syntax in the target language
    //    (this means that hex floats are off the table for now)
    //
    // 2) We need our printing to be independent of the current global locale in C,
    //    so that we don't depend on the application leaving it as the default,
    //    and we also don't revert any changes they make.
    //    (this means that `sprintf` and friends are off the table)
    //
    // 3) We need to be sure that floating-point literals specified by the user will
    //    "round-trip" and turn into the same value when parsed back in. This means
    //    that we need to print a reasonable number of digits of precision.
    //
    // For right now, the easiest option that can balance these is to use
    // the C++ standard library `iostream`s, because they support an explicit locale,
    // and can (hopefully) print floating-point numbers accurately.
    //
    // Eventually, the right move here would be to implement proper floating-point
    // number formatting ourselves, but that would require extensive testing to
    // make sure we get it right.

    std::ostringstream stream;
    stream.imbue(std::locale::classic());
    stream.setf(std::ios::fixed, std::ios::floatfield);
    stream.precision(20);
    stream << value;

    emit(stream.str().c_str());
}

void SourceWriter::advanceToSourceLocationIfValid(const SourceLoc& sourceLocation)
{
    if (sourceLocation.isValid())
    {
        advanceToSourceLocation(sourceLocation);
    }
}

void SourceWriter::advanceToSourceLocation(const SourceLoc& sourceLocation)
{
    if (getLineDirectiveMode() == LineDirectiveMode::None)
    {
        // Ignore if we aren't outputting directives 
        return;
    }
    
    if (!sourceLocation.isValid())
    {
        // If it's not valid, we will just keep the current location.

        // The question now is if we want to trigger outputting the source location again. We do so if
        // * The nextSourceLoc is valid
        // * The line number on the output is different from that location
        m_needToUpdateSourceLocation = m_needToUpdateSourceLocation || (m_nextSourceLoc.isValid() && m_nextHumaneSourceLocation.line != m_loc.line);
        return;
    }

    // Even if the source location is the same, we still want to trigger output, as long
    // as we have a valid line location.
    if (sourceLocation == m_nextSourceLoc)
    {
        // This is important because we can end up with many instructions with the same source location - for example
        // when we have [__unsafeForceInlineEarly] all the inlined instructions get the same location.
        // When we output lines of source, the target sources line numbers change, therefore we need to
        // output  the same #line directive multiple times.
     
        m_needToUpdateSourceLocation = m_needToUpdateSourceLocation || (m_nextHumaneSourceLocation.line > 0);
        return;
    }

    // Workout the humane source location.
    const HumaneSourceLoc humaneSourceLoc = getSourceManager()->getHumaneLoc(sourceLocation);

    // If the location is valid, mark need to update, and the new location
    if (humaneSourceLoc.line > 0)
    {
        m_needToUpdateSourceLocation = true;        
        m_nextHumaneSourceLocation = humaneSourceLoc;
    }

    // Either way set this as the current source location.
    m_nextSourceLoc = sourceLocation;
}

void SourceWriter::_flushSourceLocationChange()
{
    if (!m_needToUpdateSourceLocation)
        return;

    // Note: the order matters here, because trying to update
    // the source location may involve outputting text that
    // advances the location, and outputting text is what
    // triggers this flush operation.
    m_needToUpdateSourceLocation = false;
    _emitLineDirectiveIfNeeded(m_nextHumaneSourceLocation);
}

void SourceWriter::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation)
{
    _emitLineDirective(sourceLocation);

    HumaneSourceLoc newLoc = sourceLocation;
    newLoc.column = 1;

    m_loc = newLoc;
}

void SourceWriter::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation)
{
    // Don't do any of this work if the user has requested that we
    // not emit line directives.
    auto mode = getLineDirectiveMode();
    switch (mode)
    {
        case LineDirectiveMode::None:
            return;

        case LineDirectiveMode::Default:
        default:
            break;
    }

    // Ignore invalid source locations
    if (sourceLocation.line <= 0)
        return;

    // If we are currently emitting code at a source location with
    // a differnet file or line, *or* if the source location is
    // somehow later on the line than what we want to emit,
    // then we need to emit a new `#line` directive.
    if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath
        || sourceLocation.line != m_loc.line
        || sourceLocation.column < m_loc.column)
    {
        // Special case: if we are in the same file, and within a small number
        // of lines of the target location, then go ahead and output newlines
        // to get us caught up.
        enum { kSmallLineCount = 3 };
        auto lineDiff = sourceLocation.line - m_loc.line;
        if (sourceLocation.pathInfo.foundPath == m_loc.pathInfo.foundPath
            && sourceLocation.line > m_loc.line
            && lineDiff <= kSmallLineCount)
        {
            for (int ii = 0; ii < lineDiff; ++ii)
            {
                emit("\n");
            }
            SLANG_RELEASE_ASSERT(sourceLocation.line == m_loc.line);
        }
        else
        {
            // Go ahead and output a `#line` directive to get us caught up
            _emitLineDirectiveAndUpdateSourceLocation(sourceLocation);
        }
    }
}

void SourceWriter::_emitLineDirective(const HumaneSourceLoc& sourceLocation)
{
    emitRawText("\n#line ");

    char buffer[16];
    sprintf(buffer, "%llu", (unsigned long long)sourceLocation.line);
    emitRawText(buffer);

    // Only emit the path part of a `#line` directive if needed
    if (sourceLocation.pathInfo.foundPath != m_loc.pathInfo.foundPath)
    {
        emitRawText(" ");

        auto mode = getLineDirectiveMode();
        switch (mode)
        {
            default:
            case LineDirectiveMode::None:
                SLANG_UNEXPECTED("should not be trying to emit '#line' directive");
                return;
            case LineDirectiveMode::GLSL:
            {
                auto path = sourceLocation.pathInfo.foundPath;

                // GLSL doesn't support the traditional form of a `#line` directive without
                // an extension. Rather than depend on that extension we will output
                // a directive in the traditional GLSL fashion.
                //
                // TODO: Add some kind of configuration where we require the appropriate
                // extension and then emit a traditional line directive.

                int id = 0;
                if (!m_mapGLSLSourcePathToID.TryGetValue(path, id))
                {
                    id = m_glslSourceIDCount++;
                    m_mapGLSLSourcePathToID.Add(path, id);
                }

                sprintf(buffer, "%d", id);
                emitRawText(buffer);
                break;
            }
            case LineDirectiveMode::Default:
            case LineDirectiveMode::Standard:
            {
                // The simple case is to emit the path for the current source
                // location. We need to be a little bit careful with this,
                // because the path might include backslash characters if we
                // are on Windows, and we want to canonicalize those over
                // to forward slashes.
                //
                // TODO: Canonicalization like this should be done centrally
                // in a module that tracks source files.

                emitRawText("\"");
                const auto& path = sourceLocation.pathInfo.foundPath;
                for (auto c : path)
                {
                    char charBuffer[] = { c, 0 };
                    switch (c)
                    {
                        default:
                            emitRawText(charBuffer);
                            break;

                            // The incoming file path might use `/` and/or `\\` as
                            // a directory separator. We want to canonicalize this.
                            //
                            // TODO: should probably canonicalize paths to not use backslash somewhere else
                            // in the compilation pipeline...
                        case '\\':
                            emitRawText("/");
                            break;
                    }
                }
                emitRawText("\"");
                break;
            }
        }
    }

    emitRawText("\n");
}

} // namespace Slang
back to top