yum-mirror/slang

Making it easier to work with shaders

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

Ellie HermaszewskaMove switch statement bodies to their own lines (#5493)b118451e3

master
17.1 KiB548 linesraw
1// parse-diagnostic-util.cpp
2
3#include "parse-diagnostic-util.h"
4
5#include "../../source/compiler-core/slang-artifact-associated-impl.h"
6#include "../../source/compiler-core/slang-artifact-diagnostic-util.h"
7#include "../../source/compiler-core/slang-downstream-compiler.h"
8#include "../../source/core/slang-byte-encode-util.h"
9#include "../../source/core/slang-char-util.h"
10#include "../../source/core/slang-hex-dump-util.h"
11#include "../../source/core/slang-string-util.h"
12#include "../../source/core/slang-type-text-util.h"
13#include "slang-com-helper.h"
14
15using namespace Slang;
16
17/* static */ SlangResult ParseDiagnosticUtil::parseGenericLine(
18    SliceAllocator& allocator,
19    const UnownedStringSlice& line,
20    List<UnownedStringSlice>& lineSlices,
21    ArtifactDiagnostic& outDiagnostic)
22{
23    /* e:\git\somewhere\tests\diagnostics\syntax-error-intrinsic.slang(13): error C2018:  unknown
24     * character '0x40' */
25    if (lineSlices.getCount() < 3)
26    {
27        return SLANG_FAIL;
28    }
29
30    {
31        const UnownedStringSlice severityAndCodeSlice = lineSlices[1].trim();
32        // Get the code
33        outDiagnostic.code =
34            allocator.allocate(StringUtil::getAtInSplit(severityAndCodeSlice, ' ', 1).trim());
35
36        const UnownedStringSlice severitySlice =
37            StringUtil::getAtInSplit(severityAndCodeSlice, ' ', 0);
38
39        outDiagnostic.severity = ArtifactDiagnostic::Severity::Error;
40        if (severitySlice == UnownedStringSlice::fromLiteral("warning"))
41        {
42            outDiagnostic.severity = ArtifactDiagnostic::Severity::Warning;
43        }
44        else if (severitySlice == UnownedStringSlice::fromLiteral("info"))
45        {
46            outDiagnostic.severity = ArtifactDiagnostic::Severity::Info;
47        }
48    }
49
50    // Get the location info
51    SLANG_RETURN_ON_FAIL(
52        ArtifactDiagnosticUtil::splitPathLocation(allocator, lineSlices[0], outDiagnostic));
53
54    outDiagnostic.text = allocator.allocate(lineSlices[2].begin(), line.end());
55    return SLANG_OK;
56}
57
58static SlangResult _getSlangDiagnosticSeverity(
59    const UnownedStringSlice& inText,
60    ArtifactDiagnostic::Severity& outSeverity,
61    Int& outCode)
62{
63    UnownedStringSlice text(inText.trim());
64
65    static const UnownedStringSlice prefixes[] = {
66        UnownedStringSlice::fromLiteral("note"),
67        UnownedStringSlice::fromLiteral("warning"),
68        UnownedStringSlice::fromLiteral("error"),
69        UnownedStringSlice::fromLiteral("fatal error"),
70        UnownedStringSlice::fromLiteral("internal error"),
71        UnownedStringSlice::fromLiteral("unknown error")};
72
73    Int index = -1;
74
75    for (Index i = 0; i < SLANG_COUNT_OF(prefixes); ++i)
76    {
77        const auto& prefix = prefixes[i];
78        if (text.startsWith(prefix))
79        {
80            index = i;
81            break;
82        }
83    }
84
85    switch (index)
86    {
87    case -1:
88        return SLANG_FAIL;
89    case 0:
90        outSeverity = ArtifactDiagnostic::Severity::Info;
91        break;
92    case 1:
93        outSeverity = ArtifactDiagnostic::Severity::Warning;
94        break;
95    default:
96        outSeverity = ArtifactDiagnostic::Severity::Error;
97        break;
98    }
99
100    outCode = 0;
101
102    UnownedStringSlice tail = text.tail(prefixes[index].getLength()).trim();
103    if (tail.getLength() > 0)
104    {
105        SLANG_RETURN_ON_FAIL(StringUtil::parseInt(tail, outCode));
106    }
107
108    return SLANG_OK;
109}
110
111static bool _isSlangDiagnostic(const UnownedStringSlice& line)
112{
113    /*
114    tests/diagnostics/accessors.slang(11): error 31101: accessors other than 'set' must not have
115    parameters
116    */
117
118    UnownedStringSlice initial = StringUtil::getAtInSplit(line, ':', 0);
119
120    // Handle if path has :
121    const Index typeIndex = (initial.getLength() == 1 && CharUtil::isAlpha(initial[0])) ? 2 : 1;
122    // Extract the type/code slice
123    UnownedStringSlice typeSlice = StringUtil::getAtInSplit(line, ':', typeIndex);
124
125    ArtifactDiagnostic::Severity type;
126    Int code;
127    return SLANG_SUCCEEDED(_getSlangDiagnosticSeverity(typeSlice, type, code));
128}
129
130/* static */ SlangResult ParseDiagnosticUtil::parseSlangLine(
131    SliceAllocator& allocator,
132    const UnownedStringSlice& line,
133    List<UnownedStringSlice>& lineSlices,
134    ArtifactDiagnostic& outDiagnostic)
135{
136    /*
137    tests/diagnostics/accessors.slang(11): error 31101: accessors other than 'set' must not have
138    parameters
139    */
140
141    // Can be larger than 3, because might be : in the actual error text
142    if (lineSlices.getCount() < 3)
143    {
144        return SLANG_FAIL;
145    }
146
147    SLANG_RETURN_ON_FAIL(
148        ArtifactDiagnosticUtil::splitPathLocation(allocator, lineSlices[0], outDiagnostic));
149    Int code;
150    SLANG_RETURN_ON_FAIL(_getSlangDiagnosticSeverity(lineSlices[1], outDiagnostic.severity, code));
151
152    if (code != 0)
153    {
154        StringBuilder buf;
155        buf << code;
156        outDiagnostic.code = allocator.allocate(buf);
157    }
158
159    outDiagnostic.text = allocator.allocate(lineSlices[2].begin(), line.end());
160    return SLANG_OK;
161}
162
163/* static */ SlangResult ParseDiagnosticUtil::splitDiagnosticLine(
164    const CompilerIdentity& compilerIdentity,
165    const UnownedStringSlice& line,
166    const UnownedStringSlice& linePrefix,
167    List<UnownedStringSlice>& outSlices)
168{
169    StringUtil::split(line, ':', outSlices);
170
171    // If we have a prefix (typically identifying the compiler), remove so same code can be used for
172    // output with prefixes and without
173    if (linePrefix.getLength())
174    {
175        SLANG_ASSERT(outSlices[0].startsWith(linePrefix));
176        outSlices.removeAt(0);
177    }
178
179    /*
180    glslang: ERROR: tests/diagnostics/syntax-error-intrinsic.slang:13: '@' : unexpected token
181    dxc: tests/diagnostics/syntax-error-intrinsic.slang:14:2: error: expected expression
182    fxc: tests/diagnostics/syntax-error-intrinsic.slang(14,2): error X3000: syntax error: unexpected
183    token '@' Visual Studio 14.0:
184    e:\git\somewhere\tests\diagnostics\syntax-error-intrinsic.slang(13): error C2018:  unknown
185    character '0x40' NVRTC 11.0: tests/diagnostics/syntax-error-intrinsic.slang(13): error :
186    unrecognized token tests/diagnostics/accessors.slang(11): error 31101: accessors other than
187    'set' must not have parameters
188    */
189
190    // The index where the path starts
191    const Int pathIndex = 0;
192
193    // Now we want to fix up a path as might have drive letter, and therefore :
194    // If this is the situation then we need to have a slice after the one at the index
195    if (outSlices.getCount() > pathIndex + 1)
196    {
197        const UnownedStringSlice pathStart = outSlices[pathIndex].trim();
198        if (pathStart.getLength() == 1 && CharUtil::isAlpha(pathStart[0]))
199        {
200            // Splice back together
201            outSlices[pathIndex] =
202                UnownedStringSlice(outSlices[pathIndex].begin(), outSlices[pathIndex + 1].end());
203            outSlices.removeAt(pathIndex + 1);
204        }
205    }
206
207    return SLANG_OK;
208}
209
210static SlangResult _findDownstreamCompiler(
211    const UnownedStringSlice& slice,
212    SlangPassThrough& outDownstreamCompiler)
213{
214    for (Index i = SLANG_PASS_THROUGH_NONE + 1; i < SLANG_PASS_THROUGH_COUNT_OF; ++i)
215    {
216        const SlangPassThrough downstreamCompiler = SlangPassThrough(i);
217        UnownedStringSlice name = TypeTextUtil::getPassThroughAsHumanText(downstreamCompiler);
218
219        if (slice.startsWith(name))
220        {
221            outDownstreamCompiler = downstreamCompiler;
222            return SLANG_OK;
223        }
224    }
225    return SLANG_FAIL;
226}
227
228/* static */ SlangResult ParseDiagnosticUtil::identifyCompiler(
229    const UnownedStringSlice& inText,
230    CompilerIdentity& outIdentity)
231{
232    outIdentity = CompilerIdentity();
233
234    // This might be overkill - we should be able to identify the compiler from the first line, of
235    // the diagnostics. Here, we go through each line trying to identify the compiler. For
236    // downstream compilers, the only way to identify unambiguously is via the compiler name prefix.
237    // For Slang we *assume* if there isn't such a prefix, and it 'looks like' a Slang diagnostic
238    // that it is
239
240    UnownedStringSlice text(inText), line;
241    while (StringUtil::extractLine(text, line))
242    {
243        UnownedStringSlice initial = StringUtil::getAtInSplit(line, ':', 0);
244
245        if (_isSlangDiagnostic(line))
246        {
247            outIdentity = CompilerIdentity::makeSlang();
248            return SLANG_OK;
249        }
250        else
251        {
252            SlangPassThrough downstreamCompiler;
253            // First entry that begins with a numeral indicates the version number
254            if (SLANG_SUCCEEDED(_findDownstreamCompiler(initial, downstreamCompiler)))
255            {
256                outIdentity = CompilerIdentity::make(downstreamCompiler);
257                return SLANG_OK;
258            }
259        }
260    }
261
262    return SLANG_FAIL;
263}
264
265/* static */ ParseDiagnosticUtil::LineParser ParseDiagnosticUtil::getLineParser(
266    const CompilerIdentity& compilerIdentity)
267{
268    switch (compilerIdentity.m_type)
269    {
270    case CompilerIdentity::Slang:
271        return &parseSlangLine;
272    case CompilerIdentity::DownstreamCompiler:
273        return &parseGenericLine;
274    default:
275        return nullptr;
276    }
277}
278
279static bool _isWhitespace(const UnownedStringSlice& slice)
280{
281    for (const char c : slice)
282    {
283        if (!CharUtil::isWhitespace(c))
284        {
285            return false;
286        }
287    }
288    return true;
289}
290
291/* static */ SlangResult ParseDiagnosticUtil::parseDiagnostics(
292    const UnownedStringSlice& inText,
293    IArtifactDiagnostics* diagnostics)
294{
295    if (_isWhitespace(inText))
296    {
297        // If it's empty, then there are no diagnostics to add.
298        return SLANG_OK;
299    }
300
301    CompilerIdentity compilerIdentity;
302    SLANG_RETURN_ON_FAIL(ParseDiagnosticUtil::identifyCompiler(inText, compilerIdentity));
303
304    UnownedStringSlice linePrefix;
305    if (compilerIdentity.m_type == CompilerIdentity::Type::DownstreamCompiler)
306    {
307        linePrefix = TypeTextUtil::getPassThroughAsHumanText(compilerIdentity.m_downstreamCompiler);
308    }
309    else
310    {
311        // For Slang there isn't *currently* a prefix ever used, but that might change in the future
312        // For now we assume no prefix.
313    }
314
315    return parseDiagnostics(inText, compilerIdentity, linePrefix, diagnostics);
316}
317
318/* static */ SlangResult ParseDiagnosticUtil::parseDiagnostics(
319    const UnownedStringSlice& inText,
320    const CompilerIdentity& compilerIdentity,
321    const UnownedStringSlice& linePrefix,
322    IArtifactDiagnostics* diagnostics)
323{
324    auto lineParser = getLineParser(compilerIdentity);
325    if (!lineParser)
326    {
327        return SLANG_FAIL;
328    }
329
330    List<UnownedStringSlice> splitLine;
331
332    SliceAllocator allocator;
333
334    UnownedStringSlice text(inText), line;
335    while (StringUtil::extractLine(text, line))
336    {
337        bool isValidSplit = false;
338        // And the first entry must contain the prefix, else assume it's a note
339        if (linePrefix.getLength() > 0 && line.startsWith(linePrefix))
340        {
341            // Try with the line prefix
342            isValidSplit =
343                SLANG_SUCCEEDED(splitDiagnosticLine(compilerIdentity, line, linePrefix, splitLine));
344        }
345
346        if (!isValidSplit)
347        {
348            // Try without the prefix, as some output output's only some lines with the prefix (GLSL
349            // for example)
350            isValidSplit = SLANG_SUCCEEDED(
351                splitDiagnosticLine(compilerIdentity, line, UnownedStringSlice(), splitLine));
352        }
353
354        // If we don't have a valid split then just assume it's a note
355        if (!isValidSplit)
356        {
357            diagnostics->maybeAddNote(asCharSlice(line));
358            continue;
359        }
360
361        ArtifactDiagnostic diagnostic;
362        diagnostic.severity = ArtifactDiagnostic::Severity::Error;
363        diagnostic.stage = ArtifactDiagnostic::Stage::Compile;
364        diagnostic.location.line = 0;
365
366        if (SLANG_SUCCEEDED(lineParser(allocator, line, splitLine, diagnostic)))
367        {
368            diagnostics->add(diagnostic);
369        }
370        else
371        {
372            // If couldn't parse, just add as a note
373            ArtifactDiagnosticUtil::maybeAddNote(line, diagnostics);
374        }
375    }
376
377    return SLANG_OK;
378}
379
380static UnownedStringSlice _getEquals(const UnownedStringSlice& in)
381{
382    Index equalsIndex = in.indexOf('=');
383    if (equalsIndex < 0)
384    {
385        return UnownedStringSlice();
386    }
387    return in.tail(equalsIndex + 1).trim();
388}
389
390static bool _isAtEnd(const UnownedStringSlice& text, const UnownedStringSlice& line)
391{
392    if (line != "}")
393    {
394        return false;
395    }
396    // We need to get the *next* line. If it is "}" then this isn't the final closing
397    UnownedStringSlice remaining(text);
398    UnownedStringSlice nextLine;
399    StringUtil::extractLine(remaining, nextLine);
400
401    return (nextLine != toSlice("}"));
402}
403
404/* static */ SlangResult ParseDiagnosticUtil::parseOutputInfo(
405    const UnownedStringSlice& inText,
406    OutputInfo& out)
407{
408    enum State
409    {
410        Normal,
411        InStdError,
412        InStdOut,
413    };
414
415    UnownedStringSlice resultCodePrefix = UnownedStringSlice::fromLiteral("result code");
416    UnownedStringSlice stdErrorPrefix = UnownedStringSlice::fromLiteral("standard error");
417    UnownedStringSlice stdOutputPrefix = UnownedStringSlice::fromLiteral("standard output");
418
419
420    List<UnownedStringSlice> lines;
421
422    State state = State::Normal;
423
424    UnownedStringSlice text(inText), line;
425    while (StringUtil::extractLine(text, line))
426    {
427        switch (state)
428        {
429        case State::Normal:
430            {
431                if (line.startsWith(resultCodePrefix))
432                {
433                    // Split past the equal
434                    const UnownedStringSlice valueSlice =
435                        _getEquals(line.tail(resultCodePrefix.getLength()));
436                    Int value;
437                    SLANG_RETURN_ON_FAIL(StringUtil::parseInt(valueSlice, value));
438                    out.resultCode = int(value);
439                }
440                else
441                {
442                    UnownedStringSlice* startsWith = nullptr;
443                    if (line.startsWith(stdErrorPrefix))
444                    {
445                        startsWith = &stdErrorPrefix;
446                    }
447                    else if (line.startsWith(stdOutputPrefix))
448                    {
449                        startsWith = &stdOutputPrefix;
450                    }
451
452                    if (startsWith)
453                    {
454                        // Clear the lines buffer
455                        lines.clear();
456
457                        UnownedStringSlice valueSlice =
458                            _getEquals(line.tail(startsWith->getLength()));
459                        if (!valueSlice.isChar('{'))
460                        {
461                            return SLANG_FAIL;
462                        }
463                        // Okay we now inside std out or std error, so update the state
464                        state =
465                            (startsWith == &stdErrorPrefix) ? State::InStdError : State::InStdOut;
466                    }
467                }
468                break;
469            }
470        case State::InStdError:
471        case State::InStdOut:
472            {
473                if (_isAtEnd(text, line))
474                {
475                    String& dst = (state == State::InStdError) ? out.stdError : out.stdOut;
476                    if (lines.getCount() > 0)
477                    {
478                        dst = UnownedStringSlice(lines[0].begin(), lines.getLast().end());
479                    }
480                    state = State::Normal;
481                }
482                else
483                {
484                    lines.add(line);
485                }
486            }
487        }
488    }
489
490    return (state == State::Normal) ? SLANG_OK : SLANG_FAIL;
491}
492
493
494/* static */ bool ParseDiagnosticUtil::areEqual(
495    const UnownedStringSlice& a,
496    const UnownedStringSlice& b,
497    EqualityFlags flags)
498{
499    auto diagsA = ArtifactDiagnostics::create();
500    auto diagsB = ArtifactDiagnostics::create();
501
502    SlangResult resA = ParseDiagnosticUtil::parseDiagnostics(a, diagsA);
503    SlangResult resB = ParseDiagnosticUtil::parseDiagnostics(b, diagsB);
504
505    /*
506        TODO(JS): In the past we needed special handling of the core module, when
507        in some builds the path contains the core module.
508
509        For now we don't seem to need this, this is for future reference, if there
510        is an issue with needing to specially handle this.
511
512       static const UnownedStringSlice coreModuleNames[] =
513        {
514            UnownedStringSlice::fromLiteral("core.meta.slang"),
515            UnownedStringSlice::fromLiteral("hlsl.meta.slang"),
516            UnownedStringSlice::fromLiteral("slang-core-module.cpp"),
517        };
518        */
519
520    // Must have both succeeded, and have the same amount of lines
521    if (SLANG_SUCCEEDED(resA) && SLANG_SUCCEEDED(resB) && diagsA->getCount() == diagsB->getCount())
522    {
523        const auto count = diagsA->getCount();
524        for (Index i = 0; i < count; ++i)
525        {
526            ArtifactDiagnostic diagA = *diagsA->getAt(i);
527            ArtifactDiagnostic diagB = *diagsB->getAt(i);
528
529            // Check if we need to ignore line numbers
530            if (flags & EqualityFlag::IgnoreLineNos)
531            {
532                const ArtifactDiagnostic::Location loc;
533
534                diagA.location = loc;
535                diagB.location = loc;
536            }
537
538            if (diagA != diagB)
539            {
540                return false;
541            }
542        }
543
544        return true;
545    }
546
547    return false;
548}