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