yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Theresa FoleyOrganize code better by splitting some big files (#7890)8ccd495d5

master
24.7 KiB872 linesraw
1// slang-diagnostic-sink.cpp
2#include "slang-diagnostic-sink.h"
3
4#include "../core/slang-char-util.h"
5#include "../core/slang-dictionary.h"
6#include "../core/slang-memory-arena.h"
7#include "../core/slang-string-util.h"
8#include "slang-core-diagnostics.h"
9#include "slang-name-convention-util.h"
10#include "slang-name.h"
11
12namespace Slang
13{
14
15void printDiagnosticArg(StringBuilder& sb, char const* str)
16{
17    sb << str;
18}
19
20void printDiagnosticArg(StringBuilder& sb, int32_t val)
21{
22    sb << val;
23}
24
25void printDiagnosticArg(StringBuilder& sb, uint32_t val)
26{
27    sb << val;
28}
29
30void printDiagnosticArg(StringBuilder& sb, int64_t val)
31{
32    sb << val;
33}
34
35void printDiagnosticArg(StringBuilder& sb, uint64_t val)
36{
37    sb << val;
38}
39
40void printDiagnosticArg(StringBuilder& sb, double val)
41{
42    sb << val;
43}
44
45void printDiagnosticArg(StringBuilder& sb, Slang::String const& str)
46{
47    sb << str;
48}
49
50void printDiagnosticArg(StringBuilder& sb, Slang::UnownedStringSlice const& str)
51{
52    sb.append(str);
53}
54
55
56void printDiagnosticArg(StringBuilder& sb, Name* name)
57{
58    sb << getText(name);
59}
60
61
62void printDiagnosticArg(StringBuilder& sb, TokenType tokenType)
63{
64    sb << TokenTypeToString(tokenType);
65}
66
67void printDiagnosticArg(StringBuilder& sb, Token const& token)
68{
69    sb << token.getContent();
70}
71
72SourceLoc getDiagnosticPos(Token const& token)
73{
74    return token.loc;
75}
76
77// Take the format string for a diagnostic message, along with its arguments, and turn it into a
78static void formatDiagnosticMessage(
79    StringBuilder& sb,
80    char const* format,
81    int argCount,
82    DiagnosticArg const* args)
83{
84    char const* spanBegin = format;
85    for (;;)
86    {
87        char const* spanEnd = spanBegin;
88        while (int c = *spanEnd)
89        {
90            if (c == '$')
91                break;
92            spanEnd++;
93        }
94
95        sb.append(spanBegin, int(spanEnd - spanBegin));
96        if (!*spanEnd)
97            return;
98
99        SLANG_ASSERT(*spanEnd == '$');
100        spanEnd++;
101        int d = *spanEnd++;
102        switch (d)
103        {
104        // A double dollar sign `$$` is used to emit a single `$`
105        case '$':
106            sb.append('$');
107            break;
108
109        // A single digit means to emit the corresponding argument.
110        // TODO: support more than 10 arguments, and add options
111        // to control formatting, etc.
112        case '0':
113        case '1':
114        case '2':
115        case '3':
116        case '4':
117        case '5':
118        case '6':
119        case '7':
120        case '8':
121        case '9':
122            {
123                int index = d - '0';
124                if (index >= argCount)
125                {
126                    // TODO(tfoley): figure out what a good policy will be for "panic" situations
127                    // like this
128                    SLANG_INVALID_OPERATION("too few arguments for diagnostic message");
129                }
130                else
131                {
132                    DiagnosticArg const& arg = args[index];
133                    arg.printFunc(sb, arg.data);
134                }
135            }
136            break;
137
138        default:
139            SLANG_INVALID_OPERATION("invalid diagnostic message format");
140            break;
141        }
142
143        spanBegin = spanEnd;
144    }
145}
146
147static void formatDiagnostic(
148    const HumaneSourceLoc& humaneLoc,
149    Diagnostic const& diagnostic,
150    DiagnosticSink::Flags flags,
151    StringBuilder& outBuilder)
152{
153    if (flags & DiagnosticSink::Flag::HumaneLoc)
154    {
155        outBuilder << humaneLoc.pathInfo.foundPath;
156        outBuilder << "(";
157        outBuilder << Int32(humaneLoc.line);
158        if (flags & DiagnosticSink::Flag::LanguageServer)
159        {
160            outBuilder << ", " << humaneLoc.column;
161        }
162        outBuilder << "): ";
163    }
164
165    outBuilder << getSeverityName(diagnostic.severity);
166
167    if ((flags & DiagnosticSink::Flag::LanguageServer) || diagnostic.ErrorID >= 0)
168    {
169        outBuilder << " ";
170        outBuilder << diagnostic.ErrorID;
171    }
172
173    outBuilder << ": ";
174    outBuilder << diagnostic.Message;
175    outBuilder << "\n";
176}
177
178static void _replaceTabWithSpaces(const UnownedStringSlice& slice, Int tabSize, StringBuilder& out)
179{
180    const char* start = slice.begin();
181    const char* const end = slice.end();
182
183    const Index startLength = out.getLength();
184
185    for (const char* cur = start; cur < end; cur++)
186    {
187        if (*cur == '\t')
188        {
189            if (start < cur)
190            {
191                out.append(start, cur);
192            }
193
194            // The amount of spaces we add depends on the current position.
195            const Index lastPosition = out.getLength() - startLength;
196            Index tabPosition = lastPosition;
197
198            // Strip the tabPosition so it's back to the tab stop
199            // Special case if tabSize is a power of 2
200            if ((tabSize & (tabSize - 1)) == 0)
201            {
202                tabPosition = tabPosition & ~Index(tabSize - 1);
203            }
204            else
205            {
206                tabPosition -= tabPosition % tabSize;
207            }
208
209            // Move to next tab
210            tabPosition += tabSize;
211
212            // The amount of spaces to simulate the tab
213            const Index spacesCount = tabPosition - lastPosition;
214
215            // Add the spaces
216            out.appendRepeatedChar(' ', spacesCount);
217
218            // Set the start at the first character past
219            start = cur + 1;
220        }
221    }
222
223    if (start < end)
224    {
225        out.append(start, end);
226    }
227}
228
229// Given multi-line text, and a position within the text (as a pointer into the memory of text)
230// extract the line that contains pos
231static UnownedStringSlice _extractLineContainingPosition(
232    const UnownedStringSlice& text,
233    const char* pos)
234{
235    SLANG_ASSERT(text.isMemoryContained(pos));
236
237    const char* const contentStart = text.begin();
238    const char* const contentEnd = text.end();
239
240    // We want to determine the start of the line, and the end of the line
241    const char* start = pos;
242    for (; start > contentStart; --start)
243    {
244        const char c = *start;
245        if (c == '\n' || c == '\r')
246        {
247            // We want the character after, but we can only do this if not already at pos
248            start += int(start < pos);
249            break;
250        }
251    }
252    const char* end = pos;
253    for (; end < contentEnd; ++end)
254    {
255        const char c = *end;
256        if (c == '\n' || c == '\r')
257        {
258            break;
259        }
260    }
261
262    return UnownedStringSlice(start, end);
263}
264
265static void _reduceLength(Index startIndex, const UnownedStringSlice& prefix, StringBuilder& ioBuf)
266{
267    StringBuilder buf;
268    buf << prefix;
269    buf.append(ioBuf.getUnownedSlice().tail(startIndex));
270    ioBuf = buf;
271}
272
273static void _sourceLocationNoteDiagnostic(
274    DiagnosticSink* sink,
275    SourceView* sourceView,
276    SourceLoc sourceLoc,
277    StringBuilder& sb)
278{
279    SourceFile* sourceFile = sourceView->getSourceFile();
280    if (!sourceFile)
281    {
282        return;
283    }
284
285    UnownedStringSlice content = sourceFile->getContent();
286
287    // Make sure the offset is within content.
288    // This is important because it's possible to have a 'SourceFile' that doesn't contain any
289    // content (for example when reconstructed via serialization with just line offsets, the actual
290    // source text 'content' isn't available).
291    const int offset = sourceView->getRange().getOffset(sourceLoc);
292    if (offset < 0 || offset >= content.getLength())
293    {
294        return;
295    }
296
297    // Work out the position of the SourceLoc in the source
298    const char* const pos = content.begin() + offset;
299
300    UnownedStringSlice line = _extractLineContainingPosition(content, pos);
301
302    // Trim any trailing white space
303    line = UnownedStringSlice(line.begin(), line.trim().end());
304
305    // TODO(JS): The tab size should ideally be configurable from command line.
306    // For now just go with 4.
307    const Index tabSize = 4;
308
309    StringBuilder sourceLine;
310    StringBuilder caretLine;
311
312    // First work out the sourceLine
313    _replaceTabWithSpaces(line, tabSize, sourceLine);
314
315    // Now the caretLine which appears underneath the sourceLine
316    {
317        // Produce the text up to the caret position (at pos), taking into account tabs
318        _replaceTabWithSpaces(UnownedStringSlice(line.begin(), pos), tabSize, caretLine);
319
320        // Now make all spaces
321        const Index length = caretLine.getLength();
322        caretLine.clear();
323        caretLine.appendRepeatedChar(' ', length);
324
325        Index caretIndex = caretLine.getLength();
326
327        // Add caret
328        caretLine << "^";
329
330        auto lexer = sink->getSourceLocationLexer();
331        if (lexer)
332        {
333            UnownedStringSlice token = lexer(UnownedStringSlice(pos, line.end()));
334
335            if (token.getLength() > 1)
336            {
337                caretLine.appendRepeatedChar('~', token.getLength() - 1);
338            }
339        }
340
341        const Index maxLength = sink->getSourceLineMaxLength();
342        if (maxLength > 0)
343        {
344            const UnownedStringSlice ellipsis = UnownedStringSlice::fromLiteral("...");
345            const UnownedStringSlice spaces = UnownedStringSlice::fromLiteral("   ");
346            SLANG_ASSERT(ellipsis.getLength() == spaces.getLength());
347
348            // We use the caretLine length if we have a lexer, because it will have underscores such
349            // that it's end is the end of the item at issue. If we don't have the lexer, we
350            // guesstimate using 1/4 of the maximum length
351            const Index endIndex = lexer ? caretLine.getLength() : (caretIndex + (maxLength / 4));
352
353            if (endIndex > maxLength)
354            {
355                const Index startIndex = endIndex - (maxLength - ellipsis.getLength());
356
357                _reduceLength(startIndex, ellipsis, sourceLine);
358                _reduceLength(startIndex, spaces, caretLine);
359            }
360
361            if (sourceLine.getLength() > maxLength)
362            {
363                StringBuilder buf;
364                buf.append(sourceLine.getUnownedSlice().head(maxLength - ellipsis.getLength()));
365                buf << ellipsis;
366                sourceLine = buf;
367            }
368        }
369    }
370
371    // We could have handling here for if the line is too long, that we surround the important
372    // section will ellipsis for example. For now we just output.
373
374    sb << sourceLine << "\n";
375    sb << caretLine << "\n";
376}
377
378// Output the length of the token at `sourceLoc`. This is used by language server.
379static void _tokenLengthNoteDiagnostic(
380    DiagnosticSink* sink,
381    SourceView* sourceView,
382    SourceLoc sourceLoc,
383    StringBuilder& sb)
384{
385    SourceFile* sourceFile = sourceView->getSourceFile();
386    if (!sourceFile)
387    {
388        return;
389    }
390
391    UnownedStringSlice content = sourceFile->getContent();
392
393    // Make sure the offset is within content.
394    // This is important because it's possible to have a 'SourceFile' that doesn't contain any
395    // content (for example when reconstructed via serialization with just line offsets, the actual
396    // source text 'content' isn't available).
397    const int offset = sourceView->getRange().getOffset(sourceLoc);
398    if (offset < 0 || offset >= content.getLength())
399    {
400        return;
401    }
402
403    // Work out the position of the SourceLoc in the source
404    const char* const pos = content.begin() + offset;
405
406    UnownedStringSlice line = _extractLineContainingPosition(content, pos);
407
408    // Trim any trailing white space
409    line = UnownedStringSlice(line.begin(), line.trim().end());
410
411    auto lexer = sink->getSourceLocationLexer();
412    if (lexer)
413    {
414        UnownedStringSlice token = lexer(UnownedStringSlice(pos, line.end()));
415
416        if (token.getLength() > 1)
417        {
418            sb << "^+" << token.getLength() << "\n";
419        }
420    }
421}
422
423static void formatDiagnostic(DiagnosticSink* sink, Diagnostic const& diagnostic, StringBuilder& sb)
424{
425    auto sourceManager = sink->getSourceManager();
426
427    SourceView* sourceView = nullptr;
428    HumaneSourceLoc humaneLoc;
429    const auto sourceLoc = diagnostic.loc;
430    {
431        if (sourceManager)
432        {
433            sourceView = sourceManager->findSourceViewRecursively(sourceLoc);
434            if (sourceView)
435            {
436                humaneLoc = sourceView->getHumaneLoc(sourceLoc);
437            }
438        }
439
440        formatDiagnostic(humaneLoc, diagnostic, sink->getFlags(), sb);
441
442        {
443            SourceView* currentView = sourceView;
444
445            while (currentView && currentView->getInitiatingSourceLoc().isValid() &&
446                   currentView->getSourceFile()->getPathInfo().type == PathInfo::Type::TokenPaste)
447            {
448                SourceView* initiatingView =
449                    sourceManager
450                        ? sourceManager->findSourceView(currentView->getInitiatingSourceLoc())
451                        : nullptr;
452                if (initiatingView == nullptr)
453                {
454                    break;
455                }
456
457                const DiagnosticInfo& diagnosticInfo = MiscDiagnostics::seeTokenPasteLocation;
458
459                // Turn the message format into a message. For the moment it assumes no parameters.
460                StringBuilder msg;
461                formatDiagnosticMessage(msg, diagnosticInfo.messageFormat, 0, nullptr);
462
463                // Set up the diagnostic.
464                Diagnostic initiationDiagnostic;
465                initiationDiagnostic.ErrorID = diagnosticInfo.id;
466                initiationDiagnostic.Message = msg.produceString();
467                initiationDiagnostic.loc = sourceView->getInitiatingSourceLoc();
468                initiationDiagnostic.severity = diagnosticInfo.severity;
469
470                // TODO(JS):
471                // Not 100%  clear what the best sourceLoc type is most useful here - we will go
472                // with default for now
473                HumaneSourceLoc pasteHumaneLoc =
474                    initiatingView->getHumaneLoc(sourceView->getInitiatingSourceLoc());
475
476                // Okay we should output where the token paste took place
477                formatDiagnostic(pasteHumaneLoc, initiationDiagnostic, sink->getFlags(), sb);
478
479                // Make the initiatingView the current view
480                currentView = initiatingView;
481            }
482        }
483    }
484
485    // If we are a language server, output additional token length info.
486    if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::LanguageServer))
487    {
488        _tokenLengthNoteDiagnostic(sink, sourceView, sourceLoc, sb);
489    }
490
491    if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::SourceLocationLine) &&
492        diagnostic.loc.isValid())
493    {
494        _sourceLocationNoteDiagnostic(sink, sourceView, sourceLoc, sb);
495    }
496
497    if (sourceView && sink->isFlagSet(DiagnosticSink::Flag::VerbosePath))
498    {
499        auto actualHumaneLoc = sourceView->getHumaneLoc(diagnostic.loc, SourceLocType::Actual);
500
501        // Look up the path verbosely (will get the canonical path if necessary)
502        actualHumaneLoc.pathInfo.foundPath = sourceView->getSourceFile()->calcVerbosePath();
503
504        // Only output if it's actually different
505        if (actualHumaneLoc.pathInfo.foundPath != humaneLoc.pathInfo.foundPath ||
506            actualHumaneLoc.line != humaneLoc.line || actualHumaneLoc.column != humaneLoc.column)
507        {
508            formatDiagnostic(actualHumaneLoc, diagnostic, sink->getFlags(), sb);
509        }
510    }
511}
512
513void DiagnosticSink::init(SourceManager* sourceManager, SourceLocationLexer sourceLocationLexer)
514{
515    m_errorCount = 0;
516    m_internalErrorLocsNoted = 0;
517
518    m_sourceManager = sourceManager;
519    m_sourceLocationLexer = sourceLocationLexer;
520    m_sourceLineMaxLength = 0;
521
522    m_flags = Flag::HumaneLoc;
523
524    // If we have a source location lexer, we'll by default enable source location output
525    if (sourceLocationLexer)
526    {
527        setFlag(Flag::SourceLocationLine);
528    }
529}
530
531void DiagnosticSink::reset()
532{
533    m_errorCount = 0;
534    m_internalErrorLocsNoted = 0;
535
536    outputBuffer.clear();
537}
538
539
540void DiagnosticSink::noteInternalErrorLoc(SourceLoc const& loc)
541{
542    // Don't consider invalid source locations.
543    if (!loc.isValid())
544        return;
545
546    if (m_parentSink)
547    {
548        m_parentSink->noteInternalErrorLoc(loc);
549    }
550
551    // If this is the first source location being noted,
552    // then emit a message to help the user isolate what
553    // code might have confused the compiler.
554    if (m_internalErrorLocsNoted == 0)
555    {
556        diagnose(loc, MiscDiagnostics::noteLocationOfInternalError);
557    }
558    m_internalErrorLocsNoted++;
559}
560
561SlangResult DiagnosticSink::getBlobIfNeeded(ISlangBlob** outBlob)
562{
563    // If the client doesn't want an output blob, there is nothing to do.
564    //
565    if (!outBlob)
566        return SLANG_OK;
567
568    // For outputBuffer to be valid and hold diagnostics, writer must not be set
569    SLANG_ASSERT(writer == nullptr);
570
571    // If there were no errors, and there was no diagnostic output, there is nothing to do.
572    if (getErrorCount() == 0 && outputBuffer.getLength() == 0)
573    {
574        return SLANG_OK;
575    }
576
577    Slang::ComPtr<ISlangBlob> blob = Slang::StringUtil::createStringBlob(outputBuffer);
578    *outBlob = blob.detach();
579
580    return SLANG_OK;
581}
582
583bool DiagnosticSink::diagnoseImpl(
584    DiagnosticInfo const& info,
585    const UnownedStringSlice& formattedMessage)
586{
587    if (info.severity >= Severity::Error)
588    {
589        m_errorCount++;
590    }
591
592    if (writer)
593    {
594        writer->write(formattedMessage.begin(), formattedMessage.getLength());
595    }
596    else
597    {
598        outputBuffer.append(formattedMessage);
599    }
600
601    if (m_parentSink)
602    {
603        m_parentSink->diagnoseImpl(info, formattedMessage);
604    }
605
606    if (info.severity >= Severity::Fatal)
607    {
608        // TODO: figure out a better policy for aborting compilation
609        std::string message(formattedMessage.begin(), formattedMessage.end());
610        SLANG_ABORT_COMPILATION(message.c_str());
611    }
612    return true;
613}
614
615Severity DiagnosticSink::getEffectiveMessageSeverity(
616    DiagnosticInfo const& info,
617    SourceLoc const& location)
618{
619    Severity effectiveSeverity = info.severity;
620
621    if (effectiveSeverity <= Severity::Warning && m_sourceWarningStateTracker)
622    {
623        effectiveSeverity = m_sourceWarningStateTracker->consumeWarningSeverity(
624            location,
625            info.id,
626            effectiveSeverity);
627    }
628
629    Severity* pSeverityOverride = m_severityOverrides.tryGetValue(info.id);
630
631    // See if there is an override
632    if (pSeverityOverride)
633    {
634        // Override the current severity, but don't allow lowering it if it's Error or Fatal
635        if (effectiveSeverity < Severity::Error || *pSeverityOverride >= effectiveSeverity)
636            effectiveSeverity = *pSeverityOverride;
637    }
638
639    if (isFlagSet(Flag::TreatWarningsAsErrors) && effectiveSeverity == Severity::Warning)
640        effectiveSeverity = Severity::Error;
641
642    return effectiveSeverity;
643}
644
645bool DiagnosticSink::diagnoseImpl(
646    SourceLoc const& pos,
647    DiagnosticInfo info,
648    int argCount,
649    DiagnosticArg const* args)
650{
651    // Override the severity in the 'info' structure to pass it further into formatDiagnostics
652    info.severity = getEffectiveMessageSeverity(info, pos);
653
654    if (info.severity == Severity::Disable)
655        return false;
656
657    StringBuilder messageBuilder;
658    {
659        StringBuilder sb;
660        formatDiagnosticMessage(sb, info.messageFormat, argCount, args);
661
662        Diagnostic diagnostic;
663        diagnostic.ErrorID = info.id;
664        diagnostic.Message = sb.produceString();
665        diagnostic.loc = pos;
666        diagnostic.severity = info.severity;
667
668        // If so, pass the error string along to them
669        formatDiagnostic(this, diagnostic, messageBuilder);
670    }
671
672    return diagnoseImpl(info, messageBuilder.getUnownedSlice());
673}
674
675void DiagnosticSink::diagnoseRaw(Severity severity, char const* message)
676{
677    return diagnoseRaw(severity, UnownedStringSlice(message));
678}
679
680void DiagnosticSink::diagnoseRaw(Severity severity, const UnownedStringSlice& message)
681{
682    if (severity >= Severity::Error)
683    {
684        m_errorCount++;
685    }
686
687    // Did the client supply a callback for us to use?
688    if (writer)
689    {
690        // If so, pass the error string along to them.
691        writer->write(message.begin(), message.getLength());
692    }
693    else
694    {
695        // If the user doesn't have a callback, then just
696        // collect our diagnostic messages into a buffer.
697        outputBuffer.append(message);
698    }
699
700    if (m_parentSink)
701    {
702        m_parentSink->diagnoseRaw(severity, message);
703    }
704
705    if (severity >= Severity::Fatal)
706    {
707        // TODO: figure out a better policy for aborting compilation
708        SLANG_ABORT_COMPILATION("");
709    }
710}
711
712void DiagnosticSink::overrideDiagnosticSeverity(
713    int diagnosticId,
714    Severity overrideSeverity,
715    const DiagnosticInfo* info)
716{
717    if (info)
718    {
719        SLANG_ASSERT(info->id == diagnosticId);
720
721        // If the override is the same as the default, we can just remove the override
722        if (info->severity == overrideSeverity)
723        {
724            m_severityOverrides.remove(diagnosticId);
725            return;
726        }
727    }
728
729    // Set the override
730    m_severityOverrides[diagnosticId] = overrideSeverity;
731}
732
733/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DiagnosticLookup
734 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
735
736Index DiagnosticsLookup::_findDiagnosticIndexByExactName(const UnownedStringSlice& slice) const
737{
738    const Index* indexPtr = m_nameMap.tryGetValue(slice);
739    return indexPtr ? *indexPtr : -1;
740}
741
742void DiagnosticsLookup::_addName(const char* name, Index diagnosticIndex)
743{
744    UnownedStringSlice nameSlice(name);
745    m_nameMap.add(nameSlice, diagnosticIndex);
746}
747
748void DiagnosticsLookup::addAlias(const char* name, const char* diagnosticName)
749{
750    const Index index = _findDiagnosticIndexByExactName(UnownedStringSlice(diagnosticName));
751    SLANG_ASSERT(index >= 0);
752    if (index >= 0)
753    {
754        _addName(name, index);
755    }
756}
757
758const DiagnosticInfo* DiagnosticsLookup::getDiagnosticById(Int id) const
759{
760    const auto indexPtr = m_idMap.tryGetValue(id);
761    return indexPtr ? m_diagnostics[*indexPtr] : nullptr;
762}
763
764const DiagnosticInfo* DiagnosticsLookup::findDiagnosticByExactName(
765    const UnownedStringSlice& slice) const
766{
767    const Index* indexPtr = m_nameMap.tryGetValue(slice);
768    return indexPtr ? m_diagnostics[*indexPtr] : nullptr;
769}
770
771const DiagnosticInfo* DiagnosticsLookup::findDiagnosticByName(const UnownedStringSlice& slice) const
772{
773    const auto convention = NameConventionUtil::inferConventionFromText(slice);
774    switch (convention)
775    {
776    case NameConvention::Invalid:
777        return nullptr;
778    case NameConvention::LowerCamel:
779        return findDiagnosticByExactName(slice);
780    default:
781        break;
782    }
783
784    StringBuilder buf;
785    NameConventionUtil::convert(getNameStyle(convention), slice, NameConvention::LowerCamel, buf);
786
787    return findDiagnosticByExactName(buf.getUnownedSlice());
788}
789
790Index DiagnosticsLookup::add(const DiagnosticInfo* info)
791{
792    // Check it's not already added
793    SLANG_ASSERT(m_diagnostics.indexOf(info) < 0);
794
795    const Index diagnosticIndex = m_diagnostics.getCount();
796    m_diagnostics.add(info);
797
798    _addName(info->name, diagnosticIndex);
799    m_idMap.addIfNotExists(info->id, diagnosticIndex);
800
801    return diagnosticIndex;
802}
803
804void DiagnosticsLookup::add(const DiagnosticInfo* const* infos, Index infosCount)
805{
806    for (Index i = 0; i < infosCount; ++i)
807    {
808        add(infos[i]);
809    }
810}
811
812DiagnosticsLookup::DiagnosticsLookup()
813    : m_arena(kArenaInitialSize)
814{
815}
816
817DiagnosticsLookup::DiagnosticsLookup(
818    const DiagnosticInfo* const* diagnostics,
819    Index diagnosticsCount)
820    : m_arena(kArenaInitialSize)
821{
822    // TODO: We should eventually have a more formal system for associating individual
823    // diagnostics, or groups of diagnostics, with user-exposed names for use when
824    // enabling/disabling warnings (or turning warnings into errors, etc.).
825    //
826    // For now we build a map from diagnostic name to it's entry.
827
828    add(diagnostics, diagnosticsCount);
829}
830
831void outputExceptionDiagnostic(
832    const AbortCompilationException& exception,
833    DiagnosticSink& sink,
834    slang::IBlob** outDiagnostics)
835{
836    sink.diagnoseRaw(Severity::Error, exception.Message.getUnownedSlice());
837    sink.getBlobIfNeeded(outDiagnostics);
838}
839
840void outputExceptionDiagnostic(
841    const Exception& exception,
842    DiagnosticSink& sink,
843    slang::IBlob** outDiagnostics)
844{
845    try
846    {
847        sink.diagnoseRaw(Severity::Internal, exception.Message.getUnownedSlice());
848    }
849    catch (const AbortCompilationException&)
850    {
851        // Catch and ignore the AbortCompilationException that diagnoseRaw throws
852        // for Internal severity to prevent exception leak from loadModule
853    }
854    sink.getBlobIfNeeded(outDiagnostics);
855}
856
857void outputExceptionDiagnostic(DiagnosticSink& sink, slang::IBlob** outDiagnostics)
858{
859    try
860    {
861        sink.diagnoseRaw(Severity::Fatal, "An unknown exception occurred");
862    }
863    catch (const AbortCompilationException&)
864    {
865        // Catch and ignore the AbortCompilationException that diagnoseRaw throws
866        // for Fatal severity to prevent exception leak from loadModule
867    }
868    sink.getBlobIfNeeded(outDiagnostics);
869}
870
871
872} // namespace Slang