https://github.com/shader-slang/slang
Tip revision: 5902acdabc4445a65741a7a6a3a95f223e301059 authored by Yong He on 23 January 2024, 07:19:40 UTC
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
[LSP] Fetch configs directly from didConfigurationChanged message. (#3478)
Tip revision: 5902acd
slang-emit-source-writer.cpp
// slang-emit-source-writer.cpp
#include "slang-emit-source-writer.h"
#include "../core/slang-char-encode.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, IBoxValue<SourceMap>* sourceMap)
{
m_sourceMap = sourceMap;
m_lineDirectiveMode = lineDirectiveMode;
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::emitUInt64(uint64_t value)
{
emit(value);
}
void SourceWriter::emitInt64(int64_t value)
{
emit(value);
}
void SourceWriter::emit(Int32 value)
{
char buffer[16];
sprintf(buffer, "%" PRId32, value);
emit(buffer);
}
void SourceWriter::emit(Int64 value)
{
char buffer[32];
sprintf(buffer, "%" PRId64, value);
emit(buffer);
}
void SourceWriter::emit(UInt32 value)
{
char buffer[32];
sprintf(buffer, "%" PRIu32, value);
emit(buffer);
}
void SourceWriter::emit(UInt64 value)
{
char buffer[32];
sprintf(buffer, "%" PRIu64, 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;
auto str = stream.str();
auto slice = UnownedStringSlice(str.c_str());
// Remove redundant trailing 0s.
if (slice.end() > slice.begin())
{
auto lastChar = slice.end() - 1;
while (lastChar > slice.begin() && *lastChar == '0')
lastChar--;
if (*lastChar == '.')
lastChar++;
if (lastChar > slice.end() - 1)
lastChar = slice.end() - 1;
slice = slice.subString(0, lastChar - slice.begin() + 1);
}
emit(slice);
}
void SourceWriter::advanceToSourceLocationIfValid(const SourceLoc& sourceLocation)
{
if (sourceLocation.isValid())
{
advanceToSourceLocation(sourceLocation);
}
}
void SourceWriter::advanceToSourceLocation(const SourceLoc& sourceLocation)
{
// If we don't have any line directives *and* we don't want to output
// source map, we can just ignore
if (getLineDirectiveMode() == LineDirectiveMode::None &&
m_sourceMap == nullptr)
{
// 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, SourceLocType::Emit);
// 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);
// If we have a source map update state
if (m_sourceMap)
{
_updateSourceMap(m_nextHumaneSourceLocation);
}
}
void SourceWriter::_emitLineDirectiveAndUpdateSourceLocation(const HumaneSourceLoc& sourceLocation)
{
_emitLineDirective(sourceLocation);
HumaneSourceLoc newLoc = sourceLocation;
newLoc.column = 1;
m_loc = newLoc;
}
void SourceWriter::_updateSourceMap(const HumaneSourceLoc& sourceLocation)
{
// Ignore invalid source locations
if (sourceLocation.line <= 0)
return;
auto sourceMap = m_sourceMap->getPtr();
// We need to work out the current column in the generated (ie being written) output
Index generatedLineIndex, generatedColumnIndex;
_calcLocation(generatedLineIndex, generatedColumnIndex);
// Advance to the current output line
sourceMap->advanceToLine(generatedLineIndex);
// Add the entry into the map, mapping back to the original source
SourceMap::Entry entry;
entry.init();
entry.sourceFileIndex = sourceMap->getSourceFileIndex(sourceLocation.pathInfo.getName().getUnownedSlice());
entry.sourceLine = sourceLocation.line - 1;
entry.sourceColumn = sourceLocation.column - 1;
entry.generatedColumn = generatedColumnIndex;
sourceMap->addEntry(entry);
}
void SourceWriter::_emitLineDirectiveIfNeeded(const HumaneSourceLoc& sourceLocation)
{
if (m_supressLineDirective)
return;
// 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::SourceMap:
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::SourceMap:
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");
}
void SourceWriter::_calcLocation(Index& outLineIndex, Index& outColumnIndex)
{
// If there are move chars we need to update
if (m_currentOutputOffset < m_builder.getLength())
{
const char* cur = m_builder.getBuffer() + m_currentOutputOffset;
const char* end = m_builder.end();
const char* start = cur;
while (cur < end)
{
// Reset start
start = cur;
// Look for the end of the line
while (*cur != '\n' && *cur != '\r' && cur < end)
{
cur++;
}
// If we are not at the total end then we must have hit a \n or \r
if (cur < end)
{
const auto c = *cur++;
// Next line
++m_currentLineIndex;
// Check the next char to see if it's part of a CR/LF combination
if (cur < end)
{
const auto d = *cur;
// If it is combination skip the next byte
cur += ((c ^ d) == ('\r' ^ '\n'));
}
// Calculate the offset to the start of this line
m_currentColumnIndex = 0;
start = cur;
}
}
// Set the current offset to the end
m_currentOutputOffset = m_builder.getLength();
// Get the bytes remaining on this line (which may not be complete)
const UnownedStringSlice lineRemaining(start, m_builder.end());
// Offset the column index in codepoints
m_currentColumnIndex += UTF8Util::calcCodePointCount(lineRemaining);
}
// Output the position
outColumnIndex = m_currentColumnIndex;
outLineIndex = m_currentLineIndex;
}
} // namespace Slang