yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
3ed776159
master
1// slang-fiddle-template.cpp 2#include "slang-fiddle-template.h" 3 4#include "slang-fiddle-script.h" 5 6namespace fiddle 7{ 8struct TextTemplateParserBase 9{ 10protected : 11TextTemplateParserBase ( 12SourceView * inputSourceView , 13DiagnosticSink * sink , 14UnownedStringSlice source ) 15 :_inputSourceView (inputSourceView ) 16 ,_sink (sink ) 17 ,_cursor (source .begin ()) 18 ,_end (source .end ()) 19 { 20 } 21 22SourceView * _inputSourceView = nullptr ; 23DiagnosticSink * _sink = nullptr ; 24char const * _cursor = nullptr ; 25char const * _end = nullptr ; 26 27bool atEnd () {return _cursor == _end ; } 28 29UnownedStringSlice readLine () 30 { 31auto lineBegin = _cursor ; 32 33while (!atEnd ()) 34 { 35char const * lineEnd = _cursor ; 36switch (* _cursor ) 37 { 38default : 39_cursor ++ ; 40continue ; 41 42case '\r' : 43_cursor ++ ; 44if (* _cursor == '\n' ) 45_cursor ++ ; 46break ; 47 48case '\n' : 49_cursor ++ ; 50break ; 51 } 52 53return UnownedStringSlice (lineBegin ,lineEnd ); 54 } 55 56return UnownedStringSlice (lineBegin ,_end ); 57 } 58}; 59 60struct TextTemplateParser :TextTemplateParserBase 61{ 62public : 63TextTemplateParser ( 64SourceView * inputSourceView , 65DiagnosticSink * sink , 66UnownedStringSlice templateSource ) 67 :TextTemplateParserBase (inputSourceView ,sink ,templateSource ) 68 { 69 } 70 71char const * findScriptStmtLine (UnownedStringSlice line ) 72 { 73char const * lineCursor = line .begin (); 74char const * lineEnd = line .end (); 75while (lineCursor != lineEnd ) 76 { 77switch (* lineCursor ) 78 { 79default : 80return nullptr ; 81 82case ' ' : 83case '\t' : 84lineCursor ++ ; 85continue ; 86 87case '%' : 88return lineCursor ; 89 } 90 } 91return nullptr ; 92 } 93 94List < RefPtr < TextTemplateStmt >> stmts ; 95 96void addRaw (char const * rawBegin ,char const * rawEnd ) 97 { 98if (rawBegin == rawEnd ) 99return ; 100 101auto stmt = RefPtr (new TextTemplateRawStmt ()); 102stmt -> text = UnownedStringSlice (rawBegin ,rawEnd ); 103stmts .add (stmt ); 104 } 105 106void addScriptStmtLine (char const * sourceBegin ,char const * sourceEnd ) 107 { 108auto stmt = RefPtr (new TextTemplateScriptStmt ()); 109stmt -> scriptSource = UnownedStringSlice (sourceBegin ,sourceEnd ); 110stmts .add (stmt ); 111 } 112 113void addScriptSpliceExpr (char const * sourceBegin ,char const * sourceEnd ) 114 { 115auto stmt = RefPtr (new TextTemplateSpliceStmt ()); 116stmt -> scriptExprSource = UnownedStringSlice (sourceBegin ,sourceEnd ); 117stmts .add (stmt ); 118 } 119 120bool isIdentifierStartChar (int c ) 121 { 122return (('a' <=c )&& (c <='z' ))|| (('A' <=c )&& (c <='Z' ))|| (c == '_' ); 123 } 124 125bool isIdentifierChar (int c ) {return isIdentifierStartChar (c )|| (('0' <=c )&& (c <='9' )); } 126 127RefPtr < TextTemplateStmt > parseTextTemplateBody () 128 { 129bool isAtStartOfLine = true; 130bool isInScriptLine = false; 131int depthInSplice = 0 ; 132 133char const * currentLineBegin = _cursor ; 134char const * currentSpanBegin = _cursor ; 135while (!atEnd ()) 136 { 137char const * currentSpanEnd = _cursor ; 138 139bool wasAtStartOfLine = isAtStartOfLine ; 140isAtStartOfLine = false; 141 142int c = * _cursor ++ ; 143switch (c ) 144 { 145default : 146break ; 147 148case '\r' : 149if (* _cursor == '\n' ) 150 { 151_cursor ++ ; 152 } 153case '\n' : 154isAtStartOfLine = true; 155currentLineBegin = _cursor ; 156if (isInScriptLine ) 157 { 158addScriptStmtLine (currentSpanBegin ,currentSpanEnd ); 159isInScriptLine = false; 160currentSpanBegin = currentSpanEnd ; 161 } 162break ; 163 164case ' ' : 165case '\t' : 166isAtStartOfLine = wasAtStartOfLine ; 167break ; 168 169case '%' : 170if (wasAtStartOfLine && !depthInSplice ) 171 { 172addRaw (currentSpanBegin ,currentLineBegin ); 173isInScriptLine = true; 174currentSpanBegin = _cursor ; 175 } 176break ; 177 178case '$' : 179if (isInScriptLine ) 180continue ; 181if (depthInSplice ) 182SLANG_ABORT_COMPILATION ("fiddle encountered a '$' nested inside a splice" ); 183 184if (* _cursor == '(' ) 185 { 186_cursor ++ ; 187addRaw (currentSpanBegin ,currentSpanEnd ); 188depthInSplice = 1 ; 189currentSpanBegin = _cursor ; 190break ; 191 } 192else if (isIdentifierStartChar (* _cursor )) 193 { 194addRaw (currentSpanBegin ,currentSpanEnd ); 195 196auto spliceExprBegin = _cursor ; 197while (isIdentifierChar (* _cursor )) 198_cursor ++ ; 199auto spliceExprEnd = _cursor ; 200addScriptSpliceExpr (spliceExprBegin ,spliceExprEnd ); 201currentSpanBegin = _cursor ; 202break ; 203 } 204break ; 205 206case '(' : 207if (!depthInSplice ) 208continue ; 209depthInSplice ++ ; 210break ; 211 212case ')' : 213if (!depthInSplice ) 214continue ; 215depthInSplice -- ; 216if (depthInSplice == 0 ) 217 { 218addScriptSpliceExpr (currentSpanBegin ,currentSpanEnd ); 219currentSpanBegin = _cursor ; 220 } 221break ; 222 } 223 } 224addRaw (currentSpanBegin ,_end ); 225 226if (stmts .getCount ()== 1 ) 227return stmts [0 ]; 228else 229 { 230auto stmt = RefPtr (new TextTemplateSeqStmt ()); 231stmt -> stmts = stmts ; 232return stmt ; 233 } 234 } 235 236private : 237}; 238 239 240char const * templateStartMarker = "FIDDLE TEMPLATE" ; 241char const * outputStartMarker = "FIDDLE OUTPUT" ; 242char const * endMarker = "FIDDLE END" ; 243 244struct TextTemplateFileParser :TextTemplateParserBase 245{ 246public : 247TextTemplateFileParser (SourceView * inputSourceView ,DiagnosticSink * sink ) 248 :TextTemplateParserBase (inputSourceView ,sink ,inputSourceView -> getContent ()) 249 { 250 } 251 252RefPtr < TextTemplateFile > parseTextTemplateFile () 253 { 254auto textTemplateFile = RefPtr (new TextTemplateFile ()); 255textTemplateFile -> loc = _inputSourceView -> getRange ().begin ; 256textTemplateFile -> originalFileContent = _inputSourceView -> getContent (); 257while (!atEnd ()) 258 { 259auto textTemplate = parseOptionalTextTemplate (); 260if (textTemplate ) 261textTemplateFile -> textTemplates .add (textTemplate ); 262 } 263return textTemplateFile ; 264 } 265 266private : 267Count _templateCounter = 0 ; 268 269bool matches (UnownedStringSlice const & line ,char const * marker ) 270 { 271auto index = line .indexOf (UnownedTerminatedStringSlice (marker )); 272return index >=0 ; 273 } 274 275bool findMatchingLine (char const * marker ,UnownedStringSlice & outMatchingLine ) 276 { 277while (!atEnd ()) 278 { 279auto line = readLine (); 280if (!matches (line ,marker )) 281 { 282// TODO: If the line doesn't match the expected marker, 283// but it *does* match one of the other markers, then 284// we should consider it a probable error. 285 286continue ; 287 } 288 289outMatchingLine = line ; 290return true; 291 } 292return false; 293 } 294 295SourceLoc getLoc (char const * ptr ) 296 { 297auto offset = ptr - _inputSourceView -> getContent ().begin (); 298auto startLoc = _inputSourceView -> getRange ().begin ; 299auto loc = SourceLoc ::fromRaw (startLoc .getRaw ()+ offset ); 300return loc ; 301 } 302 303SourceLoc getLoc (UnownedStringSlice text ) {return getLoc (text .begin ()); } 304 305RefPtr < TextTemplateStmt > parseTextTemplateBody (UnownedStringSlice const & source ) 306 { 307TextTemplateParser parser (_inputSourceView ,_sink ,source ); 308return parser .parseTextTemplateBody (); 309 } 310 311RefPtr < TextTemplate > parseOptionalTextTemplate () 312 { 313// The idea is pretty simple; we scan through the source, one line at 314// a time, until we find a line that matches our template start pattern. 315// 316// If we *don't* find the start marker, then there must not be any 317// templates left. 318// 319UnownedStringSlice templateStartLine ; 320if (!findMatchingLine (templateStartMarker ,templateStartLine )) 321return nullptr ; 322 323char const * templateSourceBegin = _cursor ; 324 325// If we *do* find a start line for a template, then we will expect 326// to find the other two kinds of lines, to round things out. 327 328UnownedStringSlice outputStartLine ; 329if (!findMatchingLine (outputStartMarker ,outputStartLine )) 330 { 331// TODO: need to diagnose a problem here... 332_sink -> diagnose ( 333getLoc (templateStartLine ), 334 fiddle::Diagnostics ::expectedOutputStartMarker , 335outputStartMarker ); 336 } 337 338char const * templateSourceEnd = outputStartLine .begin (); 339 340char const * existingOutputBegin = _cursor ; 341 342UnownedStringSlice endLine ; 343if (!findMatchingLine (endMarker ,endLine )) 344 { 345// TODO: need to diagnose a problem here... 346_sink -> diagnose ( 347getLoc (templateStartLine ), 348 fiddle::Diagnostics ::expectedEndMarker , 349endMarker ); 350 } 351char const * existingOutputEnd = endLine .begin (); 352 353auto templateSource = UnownedStringSlice (templateSourceBegin ,templateSourceEnd ); 354auto templateBody = parseTextTemplateBody (templateSource ); 355 356auto textTemplate = RefPtr (new TextTemplate ()); 357textTemplate -> id = _templateCounter ++ ; 358textTemplate -> templateStartLine = templateStartLine ; 359textTemplate -> templateSource = templateSource ; 360textTemplate -> body = templateBody ; 361textTemplate -> outputStartLine = outputStartLine ; 362textTemplate -> existingOutputContent = 363UnownedStringSlice (existingOutputBegin ,existingOutputEnd ); 364textTemplate -> endLine = endLine ; 365return textTemplate ; 366 } 367}; 368 369struct TextTemplateScriptCodeEmitter 370{ 371public : 372TextTemplateScriptCodeEmitter (TextTemplateFile * templateFile ) 373 :_templateFile (templateFile ) 374 { 375 } 376 377String emitScriptCodeForTextTemplateFile () 378 { 379// We start by emitting the content of the template 380// file out as Lua code, so that we can evaluate 381// it all using the Lua VM. 382// 383// We go to some effort to make sure that the line 384// numbers in the generated Lua will match those 385// in the input. 386// 387 388char const * originalFileRawSpanStart = _templateFile -> originalFileContent .begin (); 389for (auto t :_templateFile -> textTemplates ) 390 { 391flushOriginalFileRawSpan (originalFileRawSpanStart ,t -> templateSource .begin ()); 392 393evaluateTextTemplate (t ); 394 395originalFileRawSpanStart = t -> outputStartLine .begin (); 396 } 397flushOriginalFileRawSpan ( 398originalFileRawSpanStart , 399_templateFile -> originalFileContent .end ()); 400 401return _builder .produceString (); 402 } 403 404private : 405TextTemplateFile * _templateFile = nullptr ; 406StringBuilder _builder ; 407 408void flushOriginalFileRawSpan (char const * begin ,char const * end ) 409 { 410if (begin == end ) 411return ; 412 413// TODO: implement the important stuff... 414_builder .append ("ORIGINAL [==[" ); 415_builder .append (UnownedStringSlice (begin ,end )); 416_builder .append ("]==]" ); 417 } 418 419void evaluateTextTemplate (TextTemplate * textTemplate ) 420 { 421// TODO: there really needs to be some framing around this... 422_builder .append ("TEMPLATE(function() " ); 423evaluateTextTemplateStmt (textTemplate -> body ); 424_builder .append (" end)" ); 425 } 426 427bool isEntirelyWhitespace (UnownedStringSlice const & text ) 428 { 429for (auto c :text ) 430 { 431switch (c ) 432 { 433default : 434return false; 435 436case ' ' : 437case '\t' : 438case '\r' : 439case '\n' : 440continue ; 441 } 442 } 443return true; 444 } 445 446void evaluateTextTemplateStmt (TextTemplateStmt * stmt ) 447 { 448if (auto seqStmt = as < TextTemplateSeqStmt > (stmt )) 449 { 450for (auto s :seqStmt -> stmts ) 451evaluateTextTemplateStmt (s ); 452 } 453else if (auto rawStmt = as < TextTemplateRawStmt > (stmt )) 454 { 455auto rawContent = rawStmt -> text ; 456if (isEntirelyWhitespace (rawContent )) 457 { 458_builder .append (rawContent ); 459 } 460else 461 { 462_builder .append ("RAW [==[" ); 463_builder .append (rawContent ); 464_builder .append ("]==]" ); 465 } 466 } 467else if (auto scriptStmt = as < TextTemplateScriptStmt > (stmt )) 468 { 469_builder .append (scriptStmt -> scriptSource ); 470_builder .append (" " ); 471 } 472else if (auto spliceStmt = as < TextTemplateSpliceStmt > (stmt )) 473 { 474_builder .append ("SPLICE(function()return(" ); 475_builder .append (spliceStmt -> scriptExprSource ); 476_builder .append (")end)" ); 477 } 478else 479 { 480SLANG_ABORT_COMPILATION ( 481"fiddle encountered an unknown construct when converting a text template to Lua" ); 482 } 483 } 484}; 485 486 487RefPtr < TextTemplateFile > parseTextTemplateFile (SourceView * inputSourceView ,DiagnosticSink * sink ) 488{ 489TextTemplateFileParser parser (inputSourceView ,sink ); 490return parser .parseTextTemplateFile (); 491} 492 493void generateTextTemplateOutputs ( 494String originalFileName , 495TextTemplateFile * file , 496StringBuilder & builder , 497DiagnosticSink * sink ) 498{ 499TextTemplateScriptCodeEmitter emitter (file ); 500String scriptCode = emitter .emitScriptCodeForTextTemplateFile (); 501 502String output = evaluateScriptCode (file -> loc ,originalFileName ,scriptCode ,sink ); 503 504builder .append (output ); 505builder .append ("\n" ); 506} 507 508String generateModifiedInputFileForTextTemplates ( 509String templateOutputFileName , 510TextTemplateFile * file , 511DiagnosticSink * sink ) 512{ 513// The basic idea here is that we need to emit most of 514// the body of the file exactly as it originally 515// appeared, and then only modifify the few lines 516// that represent the text template output. 517// 518// TODO(tfoley): We could also use this as an opportunity 519// to insert the `FIDDLE(...)` markers that the scraping 520// tool needs, but that is more work than makes sense 521// right now. 522 523StringBuilder builder ; 524 525 526char const * originalFileRawSpanStart = file -> originalFileContent .begin (); 527for (auto t :file -> textTemplates ) 528 { 529builder .append ( 530UnownedStringSlice (originalFileRawSpanStart ,t -> existingOutputContent .begin ())); 531 532builder .append ("#define FIDDLE_GENERATED_OUTPUT_ID " ); 533builder .append (t -> id ); 534builder .append ("\n" ); 535builder .append ("#include \"" ); 536for (auto c :templateOutputFileName ) 537 { 538switch (c ) 539 { 540case '"' : 541case '\\' : 542builder .appendChar ('\\' ); 543builder .appendChar (c ); 544break ; 545 546default : 547builder .appendChar (c ); 548break ; 549 } 550 } 551builder .append ("\"\n" ); 552originalFileRawSpanStart = t -> existingOutputContent .end (); 553 } 554builder .append (UnownedStringSlice (originalFileRawSpanStart ,file -> originalFileContent .end ())); 555 556return builder .produceString (); 557} 558 559}// namespace fiddle