yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
3aff764c2
master
1// slang-fiddle-main.cpp 2 3#include "core/slang-io.h" 4#include "slang-fiddle-diagnostics.h" 5#include "slang-fiddle-options.h" 6#include "slang-fiddle-scrape.h" 7#include "slang-fiddle-template.h" 8 9#if 0 10#include "compiler-core/slang-doc-extractor.h" 11#include "compiler-core/slang-name-convention-util.h" 12#include "compiler-core/slang-name.h" 13#include "compiler-core/slang-source-loc.h" 14#include "core/slang-file-system.h" 15#include "core/slang-list.h" 16#include "core/slang-secure-crt.h" 17#include "core/slang-string-slice-pool.h" 18#include "core/slang-string-util.h" 19#include "core/slang-string.h" 20#include "core/slang-writer.h" 21#include "slang-com-helper.h" 22 23#include <stdio.h> 24#include <stdlib.h> 25#include <string.h> 26#endif 27 28 29namespace fiddle 30{ 31using namespace Slang ; 32 33class InputFile :public RefObject 34{ 35public : 36String inputFileName ; 37 38RefPtr < SourceUnit > scrapedSourceUnit ; 39RefPtr < TextTemplateFile > textTemplateFile ; 40}; 41 42struct App 43{ 44public : 45App (SourceManager & sourceManager ,DiagnosticSink & sink ,NamePool & namePool ) 46 :sourceManager (sourceManager ),sink (sink ),namePool (namePool ) 47 { 48 } 49 50NamePool & namePool ; 51SourceManager & sourceManager ; 52DiagnosticSink & sink ; 53 54Options options ; 55 56List < RefPtr < InputFile >> inputFiles ; 57RefPtr < LogicalModule > logicalModule ; 58 59RefPtr < SourceUnit > parseSourceUnit (SourceView * inputSourceView ,String outputFileName ) 60 { 61return fiddle::parseSourceUnit ( 62inputSourceView , 63logicalModule , 64& namePool , 65& sink , 66& sourceManager , 67outputFileName ); 68 } 69 70RefPtr < TextTemplateFile > parseTextTemplate (SourceView * inputSourceView ) 71 { 72return fiddle::parseTextTemplateFile (inputSourceView ,& sink ); 73 } 74 75String getOutputFileName (String inputFileName ) {return inputFileName + ".fiddle" ; } 76 77void processInputFile (String const & inputFileName ) 78 { 79// The full path to the input and output is determined by the prefixes that 80// were specified via command-line arguments. 81// 82String inputPath = options .inputPathPrefix + inputFileName ; 83 84// We read the fill text of the file into memory as a single string, 85// so that we can easily parse it without need for I/O operations 86// along the way. 87// 88String inputText ; 89if (SLANG_FAILED (File ::readAllText (inputPath ,inputText ))) 90 { 91sink .diagnose (SourceLoc (), fiddle::Diagnostics ::couldNotReadInputFile ,inputPath ); 92return ; 93 } 94 95// Registering the input file with the `sourceManager` allows us 96// to get proper source locations for offsets within it. 97// 98PathInfo inputPathInfo = PathInfo ::makeFromString (inputPath ); 99SourceFile * inputSourceFile = 100sourceManager .createSourceFileWithString (inputPathInfo ,inputText ); 101SourceView * inputSourceView = 102sourceManager .createSourceView (inputSourceFile ,nullptr ,SourceLoc ()); 103 104auto inputFile = RefPtr (new InputFile ()); 105inputFile -> inputFileName = inputFileName ; 106 107// We are going to process the same input file in two different ways: 108// 109// - We will read the file using the C++-friendly `Lexer` type 110// from the Slang `compiler-core` library, in order to scrape 111// specially marked C++ declarations and process their contents. 112// 113// - We will also read the file as plain text, in order to find 114// ranges that represent templates to be processed with our 115// ad hoc Lua-based template engine. 116 117// We'll do the token-based parsing step first, and allow it 118// to return a `SourceUnit` that we can use to keep track 119// of the file. 120// 121auto sourceUnit = parseSourceUnit (inputSourceView ,getOutputFileName (inputFileName )); 122 123// Then we'll read the same file again looking for template 124// lines, and collect that information onto the same 125// object, so that we can emit the file back out again, 126// potentially with some of its content replaced. 127// 128auto textTemplateFile = parseTextTemplate (inputSourceView ); 129 130inputFile -> scrapedSourceUnit = sourceUnit ; 131inputFile -> textTemplateFile = textTemplateFile ; 132 133inputFiles .add (inputFile ); 134 } 135 136/// Generate a slug version of the given string. 137/// 138/// A *slug* is a version of a string that has 139/// a visible and obvious dependency on the input 140/// text, but that is massaged to conform to the 141/// constraints of names for some purpose. 142/// 143/// In our case, the constraints are to have an 144/// identifier that is suitable for use as a 145/// preprocessor macro. 146/// 147String generateSlug (String const & inputText ) 148 { 149StringBuilder builder ; 150int prev = -1 ; 151for (auto c :inputText ) 152 { 153// Ordinary alphabetic characters go 154// through as-is, but converted to 155// upper-case. 156// 157if (('A' <=c )&& (c <='Z' )) 158 { 159builder .appendChar (c ); 160 } 161else if (('a' <=c )&& (c <='z' )) 162 { 163builder .appendChar ((c - 'a' )+ 'A' ); 164 } 165else if (('0' <=c )&& (c <='9' )) 166 { 167// A digit can be passed through as-is, 168// except that we need to account for 169// the case where (somehow) the very 170// first character is a digit. 171if (prev == -1 ) 172builder .appendChar ('_' ); 173builder .appendChar (c ); 174 } 175else 176 { 177// We replace any other character with 178// an underscore (`_`), but we make 179// sure to collapse any sequence of 180// consecutive underscores, and to 181// ignore characters at the start of 182// the string that would turn into 183// underscores. 184// 185if (prev == -1 ) 186continue ; 187if (prev == '_' ) 188continue ; 189 190c = '_' ; 191builder .appendChar (c ); 192 } 193 194prev = c ; 195 } 196return builder .produceString (); 197 } 198 199void generateAndEmitFilesForInputFile (InputFile * inputFile ) 200 { 201// The output file wil name will be the input file 202// name, but with the suffix `.fiddle` appended to it. 203// 204auto inputFileName = inputFile -> inputFileName ; 205String outputFileName = getOutputFileName (inputFileName ); 206String outputFilePath = options .outputPathPrefix + outputFileName ; 207 208String inputFileSlug = generateSlug (inputFileName ); 209 210// We start the generated file with a header to warn 211// people against editing it by hand (not that doing 212// so will prevent by-hand edits, but its one of the 213// few things we can do). 214// 215StringBuilder builder ; 216builder .append ("// GENERATED CODE; DO NOT EDIT\n" ); 217builder .append ("//\n" ); 218 219builder .append ("// input file: " ); 220builder .append (inputFile -> inputFileName ); 221builder .append ("\n" ); 222 223// There are currently two kinds of generated code 224// we need to handle here: 225// 226// - The code that the scraping tool wants to inject 227// at each of the `FIDDLE(...)` macro invocation 228// sites. 229// 230// - The code that is generated from each of the 231// `FIDDLE TEMPLATE` constructs. 232// 233// We will emit both kinds of output to the same 234// file, to keep things easy-ish for the client. 235 236// The first kind of output is the content for 237// any `FIDDLE(...)` macro invocations. 238// 239if (hasAnyFiddleInvocations (inputFile -> scrapedSourceUnit )) 240 { 241 242builder .append ("\n// BEGIN FIDDLE SCRAPER OUTPUT\n" ); 243builder .append ("#ifndef " ); 244builder .append (inputFileSlug ); 245builder .append ("_INCLUDED\n" ); 246builder .append ("#define " ); 247builder .append (inputFileSlug ); 248builder .append ("_INCLUDED 1\n" ); 249builder .append ("#ifdef FIDDLE\n" ); 250builder .append ("#undef FIDDLE\n" ); 251builder .append ("#undef FIDDLEX\n" ); 252builder .append ("#undef FIDDLEY\n" ); 253builder .append ("#endif\n" ); 254builder .append ("#define FIDDLEY(ARG) FIDDLE_##ARG\n" ); 255builder .append ("#define FIDDLEX(ARG) FIDDLEY(ARG)\n" ); 256builder .append ("#define FIDDLE FIDDLEX(__LINE__)\n" ); 257 258emitSourceUnitMacros ( 259inputFile -> scrapedSourceUnit , 260builder , 261& sink , 262& sourceManager , 263logicalModule ); 264 265builder .append ("\n#endif\n" ); 266builder .append ("// END FIDDLE SCRAPER OUTPUT\n" ); 267 } 268 269if (inputFile -> textTemplateFile -> textTemplates .getCount ()!= 0 ) 270 { 271builder .append ("\n// BEGIN FIDDLE TEMPLATE OUTPUT:\n" ); 272builder .append ("#ifdef FIDDLE_GENERATED_OUTPUT_ID\n" ); 273 274generateTextTemplateOutputs ( 275options .inputPathPrefix + inputFileName , 276inputFile -> textTemplateFile , 277builder , 278& sink ); 279 280builder .append ("#undef FIDDLE_GENERATED_OUTPUT_ID\n" ); 281builder .append ("#endif\n" ); 282builder .append ("// END FIDDLE TEMPLATE OUTPUT\n" ); 283 } 284 285builder .append ("\n// END OF FIDDLE-GENERATED FILE\n" ); 286 287 288 { 289String outputFileContent = builder .produceString (); 290 291if (SLANG_FAILED (File ::writeAllTextIfChanged ( 292outputFilePath , 293outputFileContent .getUnownedSlice ()))) 294 { 295sink .diagnose ( 296SourceLoc (), 297 fiddle::Diagnostics ::couldNotWriteOutputFile , 298outputFilePath ); 299return ; 300 } 301 } 302 303// If we successfully wrote the output file and all of 304// its content, it is time to write out new text for 305// the *input* file, based on the template file. 306// 307 { 308String newInputFileContent = generateModifiedInputFileForTextTemplates ( 309outputFileName , 310inputFile -> textTemplateFile , 311& sink ); 312 313String inputFilePath = options .inputPathPrefix + inputFileName ; 314if (SLANG_FAILED (File ::writeAllTextIfChanged ( 315inputFilePath , 316newInputFileContent .getUnownedSlice ()))) 317 { 318sink .diagnose ( 319SourceLoc (), 320 fiddle::Diagnostics ::couldNotOverwriteInputFile , 321inputFilePath ); 322return ; 323 } 324 } 325 } 326 327void generateAndEmitFiles () 328 { 329for (auto inputFile :inputFiles ) 330generateAndEmitFilesForInputFile (inputFile ); 331 } 332 333void checkModule () { fiddle::checkModule (this -> logicalModule ,& sink ); } 334 335void execute (int argc ,char const * const * argv ) 336 { 337// We start by parsing any command-line options 338// that were specified. 339// 340options .parse (sink ,argc ,argv ); 341if (sink .getErrorCount ()) 342return ; 343 344// All of the code that get scraped will be 345// organized into a single logical module, 346// with no regard for what file each 347// declaration came from. 348// 349logicalModule = new LogicalModule (); 350 351// We iterate over the input paths specified on 352// the command line, to read each in and process 353// its text. 354// 355// This step both scans for declarations that 356// are to be scraped, and also reads the any 357// template spans. 358// 359for (auto inputPath :options .inputPaths ) 360 { 361processInputFile (inputPath ); 362 } 363if (sink .getErrorCount ()) 364return ; 365 366// In order to build up the data model of the 367// scraped declarations (such as what inherits 368// from what), we need to perform a minimal 369// amount of semantic checking here. 370// 371checkModule (); 372if (sink .getErrorCount ()) 373return ; 374 375 376// Before we go actually running any of the scripts 377// that make up the template files, we need to 378// put things into the environment that will allow 379// those scripts to find the things we've scraped... 380// 381registerScrapedStuffWithScript (logicalModule ); 382if (sink .getErrorCount ()) 383return ; 384 385 386// Once we've processed the data model, we 387// can generate the code that goes into 388// the corresponding output file, as well 389// as process any templates in the input 390// files. 391// 392generateAndEmitFiles (); 393if (sink .getErrorCount ()) 394return ; 395 } 396}; 397}// namespace fiddle 398 399#define DEBUG_FIDDLE_COMMAND_LINE 0 400 401#if DEBUG_FIDDLE_COMMAND_LINE 402#include <Windows.h> 403#endif 404 405int main (int argc ,char const * const * argv ) 406{ 407using namespace fiddle ; 408using namespace Slang ; 409 410ComPtr < ISlangWriter > writer (new FileWriter (stderr ,WriterFlag ::AutoFlush )); 411 412NamePool namePool ; 413 414SourceManager sourceManager ; 415sourceManager .initialize (nullptr ,nullptr ); 416 417DiagnosticSink sink (& sourceManager ,Lexer ::sourceLocationLexer ); 418sink .writer = writer ; 419 420#if DEBUG_FIDDLE_COMMAND_LINE 421fprintf (stderr ,"fiddle:" ); 422for (int i = 1 ;i < argc ;++ i ) 423 { 424fprintf (stderr ," %s" ,argv [i ]); 425 } 426fprintf (stderr ,"\n" ); 427 428wchar_t wideBuffer [1024 ]; 429GetCurrentDirectoryW (sizeof (wideBuffer ) /sizeof (wideBuffer [0 ]),wideBuffer ); 430 431// Convert to UTF-8 using String::fromWString 432String currentDir = String ::fromWString (wideBuffer ); 433fprintf (stderr ,"cwd: %s\n" ,currentDir .getBuffer ()); 434return 1 ; 435#endif 436 437try 438 { 439App app (sourceManager ,sink ,namePool ); 440app .execute (argc ,argv ); 441 } 442catch (...) 443 { 444sink .diagnose (SourceLoc (), fiddle::Diagnostics ::internalError ); 445return 1 ; 446 } 447return 0 ; 448}