diff options
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/slang-string.cpp | 12 | ||||
| -rw-r--r-- | source/core/slang-string.h | 7 | ||||
| -rw-r--r-- | source/slang/slang-check-decl.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-diagnostics.cpp | 164 | ||||
| -rw-r--r-- | source/slang/slang-diagnostics.h | 28 | ||||
| -rw-r--r-- | source/slang/slang-lexer.cpp | 36 | ||||
| -rw-r--r-- | source/slang/slang-lexer.h | 11 | ||||
| -rw-r--r-- | source/slang/slang-parser.cpp | 6 | ||||
| -rw-r--r-- | source/slang/slang-reflection-api.cpp | 5 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 20 |
10 files changed, 273 insertions, 20 deletions
diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp index 4b1ec4c84..a7374d8ba 100644 --- a/source/core/slang-string.cpp +++ b/source/core/slang-string.cpp @@ -362,6 +362,18 @@ namespace Slang } } + void String::appendRepeatedChar(char chr, Index count) + { + SLANG_ASSERT(count >= 0); + if (count > 0) + { + char* chars = prepareForAppend(count); + // Set all space to repeated chr. + ::memset(chars, chr, sizeof(char) * count); + appendInPlace(chars, count); + } + } + void String::appendChar(char c) { const auto oldLength = getLength(); diff --git a/source/core/slang-string.h b/source/core/slang-string.h index e57718d40..3fb184ac7 100644 --- a/source/core/slang-string.h +++ b/source/core/slang-string.h @@ -100,6 +100,10 @@ namespace Slang { return slice.m_begin >= m_begin && slice.m_end <= m_end; } + bool isMemoryContained(const char* pos) const + { + return pos >= m_begin && pos <= m_end; + } Index getLength() const { @@ -439,6 +443,9 @@ namespace Slang /// Append a character (to remove ambiguity with other integral types) void appendChar(char chr); + /// Append the specified char count times + void appendRepeatedChar(char chr, Index count); + String(const char* str) { append(str); diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 8c11e538d..a1c8369aa 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -1888,7 +1888,7 @@ namespace Slang // a temporary one. // DiagnosticSink* savedSink = m_shared->m_sink; - DiagnosticSink tempSink(savedSink->getSourceManager()); + DiagnosticSink tempSink(savedSink->getSourceManager(), nullptr); m_shared->m_sink = &tempSink; // With our temporary diagnostic sink soaking up any messages @@ -2177,7 +2177,7 @@ namespace Slang // of diagnostics more easily. // DiagnosticSink* savedSink = m_shared->m_sink; - DiagnosticSink tempSink(savedSink->getSourceManager()); + DiagnosticSink tempSink(savedSink->getSourceManager(), nullptr); m_shared->m_sink = &tempSink; // We start by constructing an expression that represents diff --git a/source/slang/slang-diagnostics.cpp b/source/slang/slang-diagnostics.cpp index 04ada6fc4..4b450c9a9 100644 --- a/source/slang/slang-diagnostics.cpp +++ b/source/slang/slang-diagnostics.cpp @@ -6,6 +6,7 @@ #include "../core/slang-memory-arena.h" #include "../core/slang-dictionary.h" #include "../core/slang-string-util.h" +#include "../core/slang-char-util.h" #include "../core/slang-name-convention-util.h" #include <assert.h> @@ -160,6 +161,160 @@ static void formatDiagnostic(const HumaneSourceLoc& humaneLoc, Diagnostic const& outBuilder << "\n"; } +static void _replaceTabWithSpaces(const UnownedStringSlice& slice, Int tabSize, StringBuilder& out) +{ + const char* start = slice.begin(); + const char*const end = slice.end(); + + const Index startLength = out.getLength(); + + for (const char* cur = start; cur < end; cur++) + { + if (*cur == '\t') + { + if (start < cur) + { + out.append(start, cur); + } + + // The amount of spaces we add depends on the current position. + const Index lastPosition = out.getLength() - startLength; + Index tabPosition = lastPosition; + + // Strip the tabPosition so it's back to the tab stop + // Special case if tabSize is a power of 2 + if ((tabSize & (tabSize - 1)) == 0) + { + tabPosition = tabPosition & ~Index(tabSize - 1); + } + else + { + tabPosition -= tabPosition % tabSize; + } + + // Move to next tab + tabPosition += tabSize; + + // The amount of spaces to simulate the tab + const Index spacesCount = tabPosition - lastPosition; + + // Add the spaces + out.appendRepeatedChar(' ', spacesCount); + + // Set the start at the first character past + start = cur + 1; + } + } + + if (start < end) + { + out.append(start, end); + } +} + +// Given multi-line text, and a position within the text (as a pointer into the memory of text) +// extract the line that contains pos +static UnownedStringSlice _extractLineContainingPosition(const UnownedStringSlice& text, const char* pos) +{ + SLANG_ASSERT(text.isMemoryContained(pos)); + + const char*const contentStart = text.begin(); + const char*const contentEnd = text.end(); + + // We want to determine the start of the line, and the end of the line + const char* start = pos; + for (; start > contentStart; --start) + { + const char c = *start; + if (c == '\n' || c == '\r') + { + // We want the character after, but we can only do this if not already at pos + start += int(start < pos); + break; + } + } + const char* end = pos; + for (; end < contentEnd; ++end) + { + const char c = *end; + if (c == '\n' || c == '\r') + { + break; + } + } + + return UnownedStringSlice(start, end); +} + +static void _sourceLocationNoteDiagnostic(SourceView* sourceView, SourceLoc sourceLoc, DiagnosticSink::SourceLocationLexer lexer, StringBuilder& sb) +{ + SourceFile* sourceFile = sourceView->getSourceFile(); + if (!sourceFile) + { + return; + } + + UnownedStringSlice content = sourceFile->getContent(); + + // Make sure the offset is within content. + // This is important because it's possible to have a 'SourceFile' that doesn't contain any content + // (for example when reconstructed via serialization with just line offsets, the actual source text 'content' isn't available). + const int offset = sourceView->getRange().getOffset(sourceLoc); + if (offset < 0 || offset >= content.getLength()) + { + return; + } + + // Work out the position of the SourceLoc in the source + const char*const pos = content.begin() + offset; + + UnownedStringSlice line = _extractLineContainingPosition(content, pos); + + // Trim any trailing white space + line = UnownedStringSlice(line.begin(), line.trim().end()); + + // TODO(JS): The tab size should ideally be configurable from command line. + // For now just go with 4. + const Index tabSize = 4; + + StringBuilder sourceLine; + StringBuilder caretLine; + + // First work out the sourceLine + _replaceTabWithSpaces(line, tabSize, sourceLine); + + // Now the caretLine which appears underneath the sourceLine + { + // Produce the text up to the caret position (at pos), taking into account tabs + _replaceTabWithSpaces(UnownedStringSlice(line.begin(), pos), tabSize, caretLine); + + // Now make all spaces + const Index length = caretLine.getLength(); + caretLine.Clear(); + caretLine.appendRepeatedChar(' ', length); + + // Add caret + caretLine << "^"; + + if (lexer) + { + UnownedStringSlice token = lexer(UnownedStringSlice(pos, line.end())); + + if (token.getLength() > 1) + { + caretLine.appendRepeatedChar('~', token.getLength() - 1); + } + } + } + + // We could have handling here for if the line is too long, that we surround the important section + // will ellipsis for example. + // For now we just output. + + sb << sourceLine << "\n"; + sb << caretLine << "\n"; +} + static void formatDiagnostic( DiagnosticSink* sink, Diagnostic const& diagnostic, @@ -214,7 +369,14 @@ static void formatDiagnostic( } } } - + + // We don't don't output source line information if this is a 'note' as a note is extra information for one + // of the other main severity types, and so the information should already be output on the initial line + if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::SourceLocationLine) && diagnostic.severity != Severity::Note) + { + _sourceLocationNoteDiagnostic(sourceView, sourceLoc, sink->getSourceLocationLexer(), sb); + } + if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::VerbosePath)) { auto actualHumaneLoc = sourceView->getHumaneLoc(diagnostic.loc, SourceLocType::Actual); diff --git a/source/slang/slang-diagnostics.h b/source/slang/slang-diagnostics.h index deec57b7c..05080989a 100644 --- a/source/slang/slang-diagnostics.h +++ b/source/slang/slang-diagnostics.h @@ -131,6 +131,7 @@ namespace Slang {} }; + class DiagnosticSink { public: @@ -140,10 +141,15 @@ namespace Slang { enum Enum: Flags { - VerbosePath = 0x1, ///< Will display a more verbose path (if available) - such as a canonical or absolute path + VerbosePath = 0x1, ///< Will display a more verbose path (if available) - such as a canonical or absolute path + SourceLocationLine = 0x2, ///< If set will display the location line if source is available }; }; + /// Used by diagnostic sink to be able to underline tokens. If not defined on the DiagnosticSink, + /// will only display a caret at the SourceLoc + typedef UnownedStringSlice(*SourceLocationLexer)(const UnownedStringSlice& text); + /// Get the total amount of errors that have taken place on this DiagnosticSink SLANG_FORCE_INLINE int getErrorCount() { return m_errorCount; } @@ -209,10 +215,22 @@ namespace Slang /// Test if flag is set bool isFlagSet(Flag::Enum flag) { return (m_flags & Flags(flag)) != 0; } + /// Get the (optional) diagnostic sink lexer. This is used to + /// improve quality of highlighting a locations token. If not set, will just have a single + /// character caret at location + SourceLocationLexer getSourceLocationLexer() const { return m_sourceLocationLexer; } + /// Ctor - DiagnosticSink(SourceManager* sourceManager) - : m_sourceManager(sourceManager) - {} + DiagnosticSink(SourceManager* sourceManager, SourceLocationLexer sourceLocationLexer) + : m_sourceManager(sourceManager), + m_sourceLocationLexer(sourceLocationLexer) + { + // If we have a source location lexer, we'll by default enable source location output + if (sourceLocationLexer) + { + setFlag(Flag::SourceLocationLine); + } + } // Public members @@ -231,6 +249,8 @@ namespace Slang // The source manager to use when mapping source locations to file+line info SourceManager* m_sourceManager = nullptr; + + SourceLocationLexer m_sourceLocationLexer; }; /// An `ISlangWriter` that writes directly to a diagnostic sink. diff --git a/source/slang/slang-lexer.cpp b/source/slang/slang-lexer.cpp index fe268223d..b0146c5b0 100644 --- a/source/slang/slang-lexer.cpp +++ b/source/slang/slang-lexer.cpp @@ -1339,4 +1339,40 @@ namespace Slang return tokenList; } } + + /* static */UnownedStringSlice Lexer::sourceLocationLexer(const UnownedStringSlice& in) + { + Lexer lexer; + + SourceManager sourceManager; + sourceManager.initialize(nullptr, nullptr); + + auto sourceFile = sourceManager.createSourceFileWithString(PathInfo::makeUnknown(), in); + auto sourceView = sourceManager.createSourceView(sourceFile, nullptr, SourceLoc::fromRaw(0)); + + DiagnosticSink sink(&sourceManager, nullptr); + + MemoryArena arena; + + RootNamePool rootNamePool; + NamePool namePool; + namePool.setRootNamePool(&rootNamePool); + + lexer.initialize(sourceView, &sink, &namePool, &arena); + + Token tok = lexer.lexToken(); + + if (tok.type == TokenType::Invalid) + { + return UnownedStringSlice(); + } + + const int offset = sourceView->getRange().getOffset(tok.loc); + + SLANG_ASSERT(offset >= 0 && offset <= in.getLength()); + SLANG_ASSERT(Index(offset + tok.charsCount) <= in.getLength()); + + return UnownedStringSlice(in.begin() + offset, in.begin() + offset + tok.charsCount); + } + } diff --git a/source/slang/slang-lexer.h b/source/slang/slang-lexer.h index d404296ff..957d05fec 100644 --- a/source/slang/slang-lexer.h +++ b/source/slang/slang-lexer.h @@ -106,6 +106,16 @@ namespace Slang ~Lexer(); + /// Runs the lexer to try and extract a single token, which is returned. + /// This can be used by the DiagnosticSink to be able to display more appropriate + /// information when displaying a source location - such as underscoring the + /// token at that location. + /// + /// NOTE! This function is relatively slow, and is designed for use around this specific + /// purpose. It does not return a token or a token type, because that information is + /// not needed by the DiagnosticSink. + static UnownedStringSlice sourceLocationLexer(const UnownedStringSlice& in); + Token lexToken(LexerFlags extraFlags = 0); TokenList lexAllTokens(); @@ -128,6 +138,7 @@ namespace Slang MemoryArena* m_memoryArena; }; + // Helper routines for extracting values from tokens String getStringLiteralTokenValue(Token const& token); String getFileNameTokenValue(Token const& token); diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index b15d215da..8ea51645a 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -1762,7 +1762,11 @@ namespace Slang TokenSpan tokenSpan; tokenSpan.m_begin = parser->tokenReader.m_cursor; tokenSpan.m_end = parser->tokenReader.m_end; - DiagnosticSink newSink(parser->sink->getSourceManager()); + + // Setup without diagnostic lexer, or SourceLocationLine output + // as this sink is just to *try* generic application + DiagnosticSink newSink(parser->sink->getSourceManager(), nullptr); + Parser newParser(*parser); newParser.sink = &newSink; diff --git a/source/slang/slang-reflection-api.cpp b/source/slang/slang-reflection-api.cpp index 2a3667295..dcfed07b0 100644 --- a/source/slang/slang-reflection-api.cpp +++ b/source/slang/slang-reflection-api.cpp @@ -718,7 +718,8 @@ SLANG_API SlangReflectionType * spReflection_FindTypeByName(SlangReflection * re // when type lookup fails. // Slang::DiagnosticSink sink( - programLayout->getTargetReq()->getLinkage()->getSourceManager()); + programLayout->getTargetReq()->getLinkage()->getSourceManager(), + Lexer::sourceLocationLexer); try { @@ -2560,7 +2561,7 @@ SLANG_API SlangReflectionType* spReflection_specializeType( auto linkage = programLayout->getProgram()->getLinkage(); - DiagnosticSink sink(linkage->getSourceManager()); + DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer); auto specializedType = linkage->specializeType(unspecializedType, specializationArgCount, (Type* const*) specializationArgs, &sink); diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index b711a5327..14f30c632 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -773,7 +773,7 @@ SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModule( { auto name = getNamePool()->getName(moduleName); - DiagnosticSink sink(getSourceManager()); + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); auto module = findOrImportModule(name, SourceLoc(), &sink); sink.getBlobIfNeeded(outDiagnostics); @@ -797,7 +797,7 @@ SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::createCompositeComponentType( return SLANG_OK; } - DiagnosticSink sink(getSourceManager()); + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); List<RefPtr<ComponentType>> childComponents; for( Int cc = 0; cc < componentTypeCount; ++cc ) @@ -834,7 +834,7 @@ SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::specializeType( typeArgs.add(asInternal(arg.type)); } - DiagnosticSink sink(getSourceManager()); + DiagnosticSink sink(getSourceManager(), Lexer::sourceLocationLexer); auto specializedType = specializeType(unspecializedType, typeArgs.getCount(), typeArgs.getBuffer(), &sink); sink.getBlobIfNeeded(outDiagnostics); @@ -1185,7 +1185,7 @@ Expr* Linkage::parseTermString(String typeStr, RefPtr<Scope> scope) Slang::SourceFile* srcFile = localSourceManager.createSourceFileWithString(PathInfo::makeTypeParse(), typeStr); // We'll use a temporary diagnostic sink - DiagnosticSink sink(&localSourceManager); + DiagnosticSink sink(&localSourceManager, nullptr); // RAII type to make make sure current SourceManager is restored after parse. // Use RAII - to make sure everything is reset even if an exception is thrown. @@ -1845,7 +1845,7 @@ BackEndCompileRequest::BackEndCompileRequest( EndToEndCompileRequest::EndToEndCompileRequest( Session* session) : m_session(session) - , m_sink(nullptr) + , m_sink(nullptr, Lexer::sourceLocationLexer) { RefPtr<ASTBuilder> astBuilder(new ASTBuilder(session->m_sharedASTBuilder, "EndToEnd::Linkage::astBuilder")); m_linkage = new Linkage(session, astBuilder, session->getBuiltinLinkage()); @@ -1856,7 +1856,7 @@ EndToEndCompileRequest::EndToEndCompileRequest( Linkage* linkage) : m_session(linkage->getSessionImpl()) , m_linkage(linkage) - , m_sink(nullptr) + , m_sink(nullptr, Lexer::sourceLocationLexer) { init(); } @@ -2627,7 +2627,7 @@ SLANG_NO_THROW slang::ProgramLayout* SLANG_MCALL ComponentType::getLayout( return nullptr; auto target = linkage->targets[targetIndex]; - DiagnosticSink sink(linkage->getSourceManager()); + DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer); auto programLayout = getTargetProgram(target)->getOrCreateLayout(&sink); sink.getBlobIfNeeded(outDiagnostics); @@ -2647,7 +2647,7 @@ SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::getEntryPointCode( auto targetProgram = getTargetProgram(target); - DiagnosticSink sink(linkage->getSourceManager()); + DiagnosticSink sink(linkage->getSourceManager(), Lexer::sourceLocationLexer); auto& entryPointResult = targetProgram->getOrCreateEntryPointResult(entryPointIndex, &sink); sink.getBlobIfNeeded(outDiagnostics); @@ -2698,7 +2698,7 @@ SLANG_NO_THROW SlangResult SLANG_MCALL ComponentType::specialize( slang::IComponentType** outSpecializedComponentType, ISlangBlob** outDiagnostics) { - DiagnosticSink sink(getLinkage()->getSourceManager()); + DiagnosticSink sink(getLinkage()->getSourceManager(), Lexer::sourceLocationLexer); // First let's check if the number of arguments given matches // the number of parameters that are present on this component type. @@ -3575,7 +3575,7 @@ void Session::addBuiltinSource( { SourceManager* sourceManager = getBuiltinSourceManager(); - DiagnosticSink sink(sourceManager); + DiagnosticSink sink(sourceManager, Lexer::sourceLocationLexer); RefPtr<FrontEndCompileRequest> compileRequest = new FrontEndCompileRequest( m_builtinLinkage, &sink); |
