yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
f65d756bf
master
1#include "slang-json-source-map-util.h" 2 3#include "../core/slang-blob.h" 4#include "../core/slang-string-util.h" 5#include "slang-com-helper.h" 6#include "slang-json-native.h" 7 8namespace Slang 9{ 10 11/* 12Support for source maps. Source maps provide a standardized mechanism to associate a location in one 13output file with another. 14 15* [Source Map 16Proposal](https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1) 17* [Chrome Source Map post](https://developer.chrome.com/blog/sourcemaps/) 18* [Base64 VLQs in Source 19Maps](https://www.lucidchart.com/techblog/2019/08/22/decode-encoding-base64-vlqs-source-maps/) 20 21Example... 22 23{ 24"version" : 3, 25"file": "out.js", 26"sourceRoot": "", 27"sources": ["foo.js", "bar.js"], 28"sourcesContent": [null, null], 29"names": ["src", "maps", "are", "fun"], 30"mappings": "A,AAAB;;ABCDE;" 31} 32*/ 33 34namespace 35{// anonymous 36 37struct JSONSourceMap 38{ 39/// File version (always the first entry in the object) and must be a positive integer. 40int32_t version = 3 ; 41/// An optional name of the generated code that this source map is associated with. 42String file ; 43/// An optional source root, useful for relocating source files on a server or removing repeated 44/// values in the “sources” entry. This value is prepended to the individual entries in the 45/// “source” field. 46String sourceRoot ; 47/// A list of original sources used by the “mappings” entry. 48List < UnownedStringSlice > sources ; 49/// An optional list of source content, useful when the “source” can’t be hosted. The contents 50/// are listed in the same order as the sources in line 5. “null” may be used if some original 51/// sources should be retrieved by name. Because could be a string or nullptr, we use JSONValue 52/// to hold value. 53List < JSONValue > sourcesContent ; 54/// A list of symbol names used by the “mappings” entry. 55List < UnownedStringSlice > names ; 56/// A string with the encoded mapping data. 57UnownedStringSlice mappings ; 58 59static const StructRttiInfo g_rttiInfo ; 60}; 61 62}// namespace 63 64static const StructRttiInfo _makeJSONSourceMap_Rtti () 65{ 66JSONSourceMap obj ; 67 68StructRttiBuilder builder (& obj ,"SourceMap" ,nullptr ); 69 70builder .addField ("version" ,& obj .version ); 71builder .addField ("file" ,& obj .file ); 72builder .addField ("sourceRoot" ,& obj .sourceRoot ,StructRttiInfo ::Flag ::Optional ); 73builder .addField ("sources" ,& obj .sources ); 74builder .addField ("sourcesContent" ,& obj .sourcesContent ,StructRttiInfo ::Flag ::Optional ); 75builder .addField ("names" ,& obj .names ,StructRttiInfo ::Flag ::Optional ); 76builder .addField ("mappings" ,& obj .mappings ); 77 78return builder .make (); 79} 80/* static */ const StructRttiInfo JSONSourceMap ::g_rttiInfo = _makeJSONSourceMap_Rtti (); 81 82// Encode a 6 bit value to VLQ encoding 83static const unsigned char g_vlqEncodeTable []= 84"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ; 85 86struct VlqDecodeTable 87{ 88VlqDecodeTable () 89 { 90 ::memset (map ,-1 ,sizeof (map )); 91for (Index i = 0 ;i < SLANG_COUNT_OF (g_vlqEncodeTable );++ i ) 92 { 93map [g_vlqEncodeTable [i ]]= int8_t (i ); 94 } 95 } 96/// Returns a *negative* value if invalid 97SLANG_FORCE_INLINE int8_t operator[](unsigned char c )const 98 { 99return (c & ~char (0x7f )) ?-1 :map [c ]; 100 } 101 102int8_t map [128 ]; 103}; 104 105static const VlqDecodeTable g_vlqDecodeTable ; 106 107/* 108https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?hl=en_US&pli=1&pli=1# 109The VLQ is a Base64 value, where the most significant bit (the 6th bit) is used as the continuation 110bit, and the “digits” are encoded into the string least significant first, and where the least 111significant bit of the first digit is used as the sign bit. */ 112 113static SlangResult _decode (UnownedStringSlice & ioEncoded ,Index & out ) 114{ 115Index v = 0 ; 116 117const char * cur = ioEncoded .begin (); 118const char * end = ioEncoded .end (); 119 120 { 121Index shift = 0 ; 122Index decodeValue = 0 ; 123do 124 { 125// Must have a char to decode 126if (cur >=end ) 127 { 128return SLANG_FAIL ; 129 } 130 131decodeValue = g_vlqDecodeTable [* cur ++ ]; 132if (decodeValue < 0 ) 133 { 134return SLANG_FAIL ; 135 } 136 137v += (decodeValue & 0x1f ) <<shift ; 138 139shift += 5 ; 140 }while (decodeValue & 0x20 ); 141 } 142 143// Save out the remaining part 144ioEncoded = UnownedStringSlice (cur ,end ); 145 146// Handle negating 147out = (v & 1 ) ?- (v >>1 ) : (v >>1 ); 148return SLANG_OK ; 149} 150 151void _encode (Index v ,StringBuilder & out ) 152{ 153// Double to free up low bit to hold the sign 154v += v ; 155 156// We want to make v always positive to encode 157// we use the last bit to indicate negativity 158v = (v < 0 ) ? (1 - v ) :v ; 159 160// We'll use a simple buffer, so as to not have to constantly update he StringBuffer 161char dst [8 ]; 162char * cur = dst ; 163 164do 165 { 166const Index nextV = v >>5 ; 167const Index encodeValue = (v & 0x1f )+ (nextV ?0x20 :0 ); 168 169// Encode 5 bits, plus continuation bit 170char c = g_vlqEncodeTable [encodeValue ]; 171 172// Save the char 173* cur ++ = c ; 174 175v = nextV ; 176 }while (v ); 177 178out .append (dst ,cur ); 179} 180 181/* static */ SlangResult JSONSourceMapUtil ::decode ( 182JSONContainer * container , 183JSONValue root , 184DiagnosticSink * sink , 185SourceMap & outSourceMap ) 186{ 187outSourceMap .clear (); 188 189// Let's try and decode the JSON into native types to make this easier... 190RttiTypeFuncsMap typeMap = JSONNativeUtil ::getTypeFuncsMap (); 191 192// Convert to native 193JSONSourceMap native ; 194 { 195JSONToNativeConverter converter (container ,& typeMap ,sink ); 196 197// Convert to the native type 198SLANG_RETURN_ON_FAIL (converter .convert (root ,GetRttiInfo < JSONSourceMap > ::get (),& native )); 199 } 200 201outSourceMap .m_file = native .file ; 202outSourceMap .m_sourceRoot = native .sourceRoot ; 203 204const Count sourcesCount = native .sources .getCount (); 205 206// These should all be unique, but for simplicity, we build a table 207outSourceMap .m_sources .setCount (sourcesCount ); 208for (Index i = 0 ;i < sourcesCount ;++ i ) 209 { 210outSourceMap .m_sources [i ]= outSourceMap .m_slicePool .add (native .sources [i ]); 211 } 212 213Count sourcesContentCount = native .sourcesContent .getCount (); 214sourcesContentCount = std::min (sourcesContentCount ,sourcesCount ); 215 216outSourceMap .m_sourcesContent .setCount (sourcesContentCount ); 217for (auto & cur :outSourceMap .m_sourcesContent ) 218 { 219cur = StringSlicePool ::kNullHandle ; 220 } 221 222// Special case sourcesContent, because needs to be able to handle null or string 223for (Index i = 0 ;i < sourcesContentCount ;++ i ) 224 { 225auto value = native .sourcesContent [i ]; 226 227if (value .type != JSONValue ::Type ::Null ) 228 { 229if (value .getKind ()== JSONValue ::Kind ::String ) 230 { 231auto stringValue = container -> getString (value ); 232outSourceMap .m_sourcesContent [i ]= outSourceMap .m_slicePool .add (stringValue ); 233 } 234 } 235 } 236 237// Copy over the names 238 { 239const auto namesCount = native .names .getCount (); 240outSourceMap .m_names .setCount (namesCount ); 241 242for (Index i = 0 ;i < namesCount ;++ i ) 243 { 244outSourceMap .m_names [i ]= outSourceMap .m_slicePool .add (native .names [i ]); 245 } 246 } 247 248List < UnownedStringSlice > lines ; 249StringUtil ::split (native .mappings ,';' ,lines ); 250 251List < UnownedStringSlice > segments ; 252 253// Index into sources 254Index sourceFileIndex = 0 ; 255 256Index sourceLine = 0 ; 257Index sourceColumn = 0 ; 258Index nameIndex = 0 ; 259 260const Count linesCount = lines .getCount (); 261 262outSourceMap .m_lineStarts .setCount (linesCount + 1 ); 263 264for (Index generatedLine = 0 ;generatedLine < linesCount ;++ generatedLine ) 265 { 266const auto line = lines [generatedLine ]; 267 268outSourceMap .m_lineStarts [generatedLine ]= outSourceMap .m_lineEntries .getCount (); 269 270// If it's empty move to next line 271if (line .getLength ()== 0 ) 272 { 273continue ; 274 } 275 276// Split the line into segments 277segments .clear (); 278StringUtil ::split (line ,',' ,segments ); 279 280Index generatedColumn = 0 ; 281 282for (auto segment :segments ) 283 { 284Index colDelta ; 285SLANG_RETURN_ON_FAIL (_decode (segment ,colDelta )); 286 287generatedColumn += colDelta ; 288SLANG_ASSERT (generatedColumn >=0 ); 289 290// It can be 4 or 5 parts 291if (segment .getLength ()) 292 { 293/* If present, an zero-based index into the "sources" list. This field is a base 64 294VLQ relative to the previous occurrence of this field, unless this is the first 295occurrence of this field, in which case the whole value is represented. If 296present, the zero-based starting line in the original source represented. This 297field is a base 64 VLQ relative to the previous occurrence of this field, unless 298this is the first occurrence of this field, in which case the whole value is 299represented. Always present if there is a source field. If present, the 300zero-based starting column of the line in the source represented. This field is a 301base 64 VLQ relative to the previous occurrence of this field, unless this is the 302first occurrence of this field, in which case the whole value is represented. 303Always present if there is a source field. 304*/ 305 306Index sourceFileDelta ; 307Index sourceLineDelta ; 308Index sourceColumnDelta ; 309 310SLANG_RETURN_ON_FAIL (_decode (segment ,sourceFileDelta )); 311SLANG_RETURN_ON_FAIL (_decode (segment ,sourceLineDelta )); 312SLANG_RETURN_ON_FAIL (_decode (segment ,sourceColumnDelta )); 313 314sourceFileIndex += sourceFileDelta ; 315sourceLine += sourceLineDelta ; 316sourceColumn += sourceColumnDelta ; 317 318SLANG_ASSERT (sourceFileIndex >=0 ); 319SLANG_ASSERT (sourceLine >=0 ); 320SLANG_ASSERT (sourceColumn >=0 ); 321 322// 5 parts 323if (segment .getLength ()> 0 ) 324 { 325/* If present, the zero - based index into the "names" list associated with this 326segment. This field is a base 64 VLQ relative to the previous occurrence of this 327field, unless this is the first occurrence of this field, in which case the 328whole value is represented. 329*/ 330 331Index nameDelta ; 332SLANG_RETURN_ON_FAIL (_decode (segment ,nameDelta )); 333 334nameIndex += nameDelta ; 335SLANG_ASSERT (nameIndex >=0 ); 336 } 337 } 338 339SourceMap ::Entry entry ; 340entry .generatedColumn = generatedColumn ; 341entry .sourceColumn = sourceColumn ; 342entry .sourceLine = sourceLine ; 343entry .sourceFileIndex = sourceFileIndex ; 344entry .nameIndex = nameIndex ; 345 346outSourceMap .m_lineEntries .add (entry ); 347 } 348 } 349 350// Mark the end 351outSourceMap .m_lineStarts [linesCount ]= outSourceMap .m_lineEntries .getCount (); 352 353return SLANG_OK ; 354} 355 356SlangResult JSONSourceMapUtil ::encode ( 357const SourceMap & sourceMap , 358JSONContainer * container , 359DiagnosticSink * sink , 360JSONValue & outValue ) 361{ 362// Convert to native 363JSONSourceMap native ; 364 365native .file = sourceMap .m_file ; 366native .sourceRoot = sourceMap .m_sourceRoot ; 367 368// Copy over the sources 369 { 370const auto count = sourceMap .m_sources .getCount (); 371native .sources .setCount (count ); 372for (Index i = 0 ;i < count ;++ i ) 373 { 374native .sources [i ]= sourceMap .m_slicePool .getSlice (sourceMap .m_sources [i ]); 375 } 376 } 377 378// Copy out the sourcesContent, care is needed around handling null 379 { 380const auto count = sourceMap .m_sourcesContent .getCount (); 381native .sourcesContent .setCount (count ); 382for (Index i = 0 ;i < count ;++ i ) 383 { 384const auto srcValue = sourceMap .m_sourcesContent [i ]; 385 386const JSONValue dstValue = 387 (srcValue == StringSlicePool ::kNullHandle ) 388 ?native .sourcesContent [i ]= JSONValue ::makeNull () 389 :container -> createString (sourceMap .m_slicePool .getSlice (srcValue )); 390 391native .sourcesContent [i ]= dstValue ; 392 } 393 } 394 395// Copy out the names 396 { 397const auto count = sourceMap .m_names .getCount (); 398native .names .setCount (count ); 399for (Index i = 0 ;i < count ;++ i ) 400 { 401native .names [i ]= sourceMap .m_slicePool .getSlice (sourceMap .m_names [i ]); 402 } 403 } 404 405StringBuilder mappings ; 406 407// Do the encoding! 408 { 409const Count linesCount = sourceMap .getGeneratedLineCount (); 410 411Index sourceFileIndex = 0 ; 412 413Index sourceLine = 0 ; 414Index sourceColumn = 0 ; 415Index nameIndex = 0 ; 416 417for (Index i = 0 ;i < linesCount ;++ i ) 418 { 419// Add the semicolon to start the line 420if (i > 0 ) 421 { 422mappings .appendChar (';' ); 423 } 424 425const auto entries = sourceMap .getEntriesForLine (i ); 426const auto entriesCount = entries .getCount (); 427 428if (entriesCount == 0 ) 429 { 430continue ; 431 } 432 433// We reset the generated column index at the start of each new generated line 434Index generatedColumn = 0 ; 435 436for (Index j = 0 ;j < entriesCount ;++ j ) 437 { 438auto entry = entries [j ]; 439 440if (j > 0 ) 441 { 442mappings .appendChar (',' ); 443 } 444 445Index generatedDelta = entry .generatedColumn - generatedColumn ; 446generatedColumn = entry .generatedColumn ; 447 448_encode (generatedDelta ,mappings ); 449 450// See if there any other deltas we need to handle 451const Index sourceFileDelta = entry .sourceFileIndex - sourceFileIndex ; 452const Index sourceLineDelta = entry .sourceLine - sourceLine ; 453const Index sourceColumnDelta = entry .sourceColumn - sourceColumn ; 454const Index nameIndexDelta = entry .nameIndex - nameIndex ; 455 456if (sourceFileDelta || sourceLineDelta || sourceColumnDelta || nameIndex ) 457 { 458// Okay we have to encode all these deltae 459_encode (sourceFileDelta ,mappings ); 460_encode (sourceLineDelta ,mappings ); 461_encode (sourceColumnDelta ,mappings ); 462 463// Update these values 464sourceFileIndex = entry .sourceFileIndex ; 465sourceLine = entry .sourceLine ; 466sourceColumn = entry .sourceColumn ; 467 468if (nameIndexDelta ) 469 { 470_encode (nameIndexDelta ,mappings ); 471nameIndex = entry .nameIndex ; 472 } 473 } 474 } 475 } 476 } 477 478// Set the mappings 479native .mappings = mappings .getUnownedSlice (); 480 481// Write it out 482 { 483RttiTypeFuncsMap typeMap = JSONNativeUtil ::getTypeFuncsMap (); 484 485NativeToJSONConverter converter (container ,& typeMap ,sink ); 486SLANG_RETURN_ON_FAIL ( 487converter .convert (GetRttiInfo < JSONSourceMap > ::get (),& native ,outValue )); 488 } 489 490return SLANG_OK ; 491} 492 493/* static */ SlangResult JSONSourceMapUtil ::read (ISlangBlob * blob ,SourceMap & outSourceMap ) 494{ 495return read (blob ,nullptr ,outSourceMap ); 496} 497 498SlangResult JSONSourceMapUtil ::read ( 499ISlangBlob * blob , 500DiagnosticSink * parentSink , 501SourceMap & outSourceMap ) 502{ 503outSourceMap .clear (); 504 505SourceManager sourceManager ; 506sourceManager .initialize (nullptr ,nullptr ); 507DiagnosticSink sink (& sourceManager ,nullptr ); 508 509sink .setParentSink (parentSink ); 510 511RefPtr < JSONContainer > container = new JSONContainer (& sourceManager ); 512 513JSONValue rootValue ; 514 { 515// Now need to parse as JSON 516SourceFile * sourceFile = 517sourceManager .createSourceFileWithBlob (PathInfo ::makeUnknown (),blob ); 518SourceView * sourceView = sourceManager .createSourceView (sourceFile ,nullptr ,SourceLoc ()); 519 520JSONLexer lexer ; 521lexer .init (sourceView ,& sink ); 522 523JSONBuilder builder (container ); 524 525JSONParser parser ; 526SLANG_RETURN_ON_FAIL (parser .parse (& lexer ,sourceView ,& builder ,& sink )); 527 528rootValue = builder .getRootValue (); 529 } 530 531SLANG_RETURN_ON_FAIL (decode (container ,rootValue ,& sink ,outSourceMap )); 532 533return SLANG_OK ; 534} 535 536 537/* static */ SlangResult JSONSourceMapUtil ::write ( 538const SourceMap & sourceMap , 539ComPtr < ISlangBlob >& outBlob ) 540{ 541SourceManager sourceMapSourceManager ; 542sourceMapSourceManager .initialize (nullptr ,nullptr ); 543 544// Create a sink 545DiagnosticSink sourceMapSink (& sourceMapSourceManager ,nullptr ); 546 547SLANG_RETURN_ON_FAIL (write (sourceMap ,& sourceMapSink ,outBlob )); 548return SLANG_OK ; 549} 550 551/* static */ SlangResult JSONSourceMapUtil ::write ( 552const SourceMap & sourceMap , 553DiagnosticSink * sink , 554ComPtr < ISlangBlob >& outBlob ) 555{ 556auto sourceManager = sink -> getSourceManager (); 557 558// Write it out 559String json ; 560 { 561RefPtr < JSONContainer > jsonContainer (new JSONContainer (sourceManager )); 562 563JSONValue jsonValue ; 564 565SLANG_RETURN_ON_FAIL (JSONSourceMapUtil ::encode (sourceMap ,jsonContainer ,sink ,jsonValue )); 566 567// Convert into a string 568JSONWriter writer (JSONWriter ::IndentationStyle ::Allman ); 569jsonContainer -> traverseRecursively (jsonValue ,& writer ); 570 571json = writer .getBuilder (); 572 } 573 574outBlob = StringBlob ::moveCreate (json ); 575return SLANG_OK ; 576} 577 578}// namespace Slang