yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
73e9987c9
master
1// main.cpp 2 3#include "../../source/core/slang-io.h" 4#include "../../source/core/slang-list.h" 5#include "../../source/core/slang-secure-crt.h" 6#include "../../source/core/slang-string-util.h" 7#include "../../source/core/slang-string.h" 8 9#include <stdio.h> 10#include <stdlib.h> 11#include <string.h> 12 13using namespace Slang ; 14 15typedef Slang ::UnownedStringSlice StringSpan ; 16 17struct Node 18{ 19enum class Flavor 20 { 21text ,// Ordinary text to write to output 22escape ,// Meta-level code (statements) 23splice ,// Meta-level expression to splice into output 24 }; 25 26// What sort of node is this? 27Flavor flavor ; 28 29// The text of this node for `Flavor::text` 30StringSpan span ; 31 32// The body of this node for other flavors 33Node * body = nullptr ; 34 35// The next node in the document 36Node * next = nullptr ; 37 38Node ()= default ; 39 ~Node () 40 { 41if (body ) 42delete body ; 43if (next ) 44delete next ; 45 } 46}; 47 48// Information about a source file 49struct SourceFile :public RefObject 50{ 51String inputPath ; 52String linePath ;///< The path to this file for #line output 53 54StringSpan text ; 55Node * node = nullptr ; 56SourceFile ()= default ; 57 ~SourceFile () 58 { 59if (text .begin ()) 60free ((void * )text .begin ()); 61 62// To avoid deep recursion in the Node destructor, 63// we delete the first level of the node tree iteratively. 64while (node ) 65 { 66Node * next = node -> next ; 67node -> next = nullptr ; 68delete node ; 69node = next ; 70 } 71 } 72}; 73 74void addNode (Node **& ioLink ,Node ::Flavor flavor ,char const * spanBegin ,char const * spanEnd ) 75{ 76Node * node = new Node (); 77node -> flavor = flavor ; 78node -> span = StringSpan (spanBegin ,spanEnd ); 79node -> next = nullptr ; 80 81* ioLink = node ; 82ioLink = & node -> next ; 83} 84 85void addNode (Node **& ioLink ,Node ::Flavor flavor ,Node * body ) 86{ 87Node * node = new Node (); 88node -> flavor = flavor ; 89node -> body = body ; 90node -> next = nullptr ; 91 92* ioLink = node ; 93ioLink = & node -> next ; 94} 95 96bool isAlpha (int c ) 97{ 98return ((c >='a' )&& (c <='z' ))|| ((c >='A' )&& (c <='Z' ))|| (c == '_' ); 99} 100 101void addTextSpan (Node **& ioLink ,char const * spanBegin ,char const * spanEnd ) 102{ 103// Don't add an empty text span. 104if (spanBegin == spanEnd ) 105return ; 106 107addNode (ioLink ,Node ::Flavor ::text ,spanBegin ,spanEnd ); 108} 109 110void addSpliceSpan (Node **& ioLink ,Node * body ) 111{ 112addNode (ioLink ,Node ::Flavor ::splice ,body ); 113} 114 115void addEscapeSpan (Node **& ioLink ,Node * body ) 116{ 117addNode (ioLink ,Node ::Flavor ::escape ,body ); 118} 119 120void addEscapeSpan (Node **& ioLink ,char const * spanBegin ,char const * spanEnd ) 121{ 122Node * body = nullptr ; 123Node ** link = & body ; 124 125addTextSpan (link ,spanBegin ,spanEnd ); 126 127return addEscapeSpan (ioLink ,body ); 128} 129 130bool isIdentifierChar (int c ) 131{ 132if (c >='a' && c <='z' ) 133return true; 134if (c >='A' && c <='Z' ) 135return true; 136if (c == '_' ) 137return true; 138 139return false; 140} 141 142struct Reader 143{ 144char const * cursor ; 145char const * end ; 146}; 147 148int peek (Reader const & reader ) 149{ 150if (reader .cursor == reader .end ) 151return EOF ; 152 153return * reader .cursor ; 154} 155 156int get (Reader & reader ) 157{ 158if (reader .cursor == reader .end ) 159return -1 ; 160 161return * reader .cursor ++ ; 162} 163 164void handleNewline (Reader & reader ,int c ) 165{ 166int d = peek (reader ); 167if ((c ^d )== ('\r' ^'\n' )) 168 { 169get (reader ); 170 } 171} 172 173bool isHorizontalSpace (int c ) 174{ 175return (c == ' ' )|| (c == '\t' ); 176} 177 178void skipHorizontalSpace (Reader & reader ) 179{ 180while (isHorizontalSpace (peek (reader ))) 181get (reader ); 182} 183 184void skipOptionalNewline (Reader & reader ) 185{ 186switch (peek (reader )) 187 { 188default : 189break ; 190 191case '\r' : 192case '\n' : 193 { 194int c = get (reader ); 195handleNewline (reader ,c ); 196 } 197break ; 198 } 199} 200 201typedef unsigned int NodeReadFlags ; 202enum 203{ 204kNodeReadFlag_AllowEscape = 1 <<0 , 205}; 206 207template < bool onlyReadFirstOpenChar > 208Node * readBody (Reader & reader ,NodeReadFlags flags ,char openChar ,int openCount ,char closeChar ) 209{ 210while (peek (reader )== openChar ) 211 { 212get (reader ); 213openCount ++ ; 214 215// This case allows parsing `myFunc($((int)val))` correctly, else we parse 216// body as `int)val`, causing non obvious segfault. 217if constexpr (onlyReadFirstOpenChar ) 218break ; 219 } 220 221Node * nodes = nullptr ; 222Node ** link = & nodes ; 223 224bool atStartOfLine = true; 225int depth = 0 ; 226 227char const * spanBegin = reader .cursor ; 228char const * lineBegin = reader .cursor ; 229for (;;) 230 { 231int c = get (reader ); 232 233switch (c ) 234 { 235default : 236atStartOfLine = false; 237break ; 238 239case EOF : 240 { 241addTextSpan (link ,spanBegin ,reader .cursor ); 242return nodes ; 243 } 244 245case '{' : 246case '(' : 247if (c == openChar ) 248 { 249depth ++ ; 250 } 251atStartOfLine = false; 252break ; 253 254case ')' : 255case '}' : 256if (c == closeChar ) 257 { 258char const * spanEnd = reader .cursor - 1 ; 259 260if (openCount == 1 ) 261 { 262if (depth == 0 ) 263 { 264// We are at the end of the body. 265addTextSpan (link ,spanBegin ,spanEnd ); 266return nodes ; 267 } 268 269depth -- ; 270 } 271else 272 { 273// Count how many closing chars are stacked up 274 275int closeCount = 1 ; 276while (peek (reader )== closeChar ) 277 { 278get (reader ); 279closeCount ++ ; 280 } 281 282if (closeCount == openCount ) 283 { 284// We are at the end of the body. 285addTextSpan (link ,spanBegin ,spanEnd ); 286return nodes ; 287 } 288 } 289 } 290atStartOfLine = false; 291break ; 292 293 294case ' ' : 295case '\t' : 296break ; 297 298case '\r' : 299case '\n' : 300 { 301addTextSpan (link ,spanBegin ,reader .cursor ); 302 303handleNewline (reader ,c ); 304 305lineBegin = reader .cursor ; 306spanBegin = reader .cursor ; 307atStartOfLine = true; 308 } 309break ; 310 311case '$' : 312 { 313// If this is the start of a splice, then 314// the end of the preceding raw-text space 315// will be the byte before `$` 316char const * spanEnd = reader .cursor - 1 ; 317 318if (peek (reader )== '(' ) 319 { 320// This appears to be an expression splice. 321// 322// We must end the preceding span. 323// 324addTextSpan (link ,spanBegin ,spanEnd ); 325 326Node * body = readBody < true> (reader ,0 ,'(' ,0 ,')' ); 327 328addSpliceSpan (link ,body ); 329 330spanBegin = reader .cursor ; 331atStartOfLine = false; 332 } 333else if (peek (reader )== '{' ) 334 { 335// This is the start of a block-structured escape, which will 336// end at a matching `}`. 337 338addTextSpan (link ,spanBegin ,lineBegin ); 339 340Node * body = readBody < false> (reader ,0 ,'{' ,0 ,'}' ); 341 342addEscapeSpan (link ,body ); 343 344spanBegin = reader .cursor ; 345atStartOfLine = false; 346 } 347else if (atStartOfLine && peek (reader )== ':' ) 348 { 349// This is a statement escape, which will 350// continue to the end of the line. 351// 352// The spliced text begins *after* the `:` 353get (reader ); 354char const * spliceBegin = reader .cursor ; 355 356// The preceding text span will end at the 357// start of this line. 358addTextSpan (link ,spanBegin ,lineBegin ); 359 360// Any indentation on this line will be ignored. 361 362// Read up to end of line. 363for (;;) 364 { 365int c = get (reader ); 366switch (c ) 367 { 368default : 369continue ; 370 371case EOF : 372break ; 373 374case '\r' : 375case '\n' : 376handleNewline (reader ,c ); 377break ; 378 } 379 380break ; 381 } 382 383addEscapeSpan (link ,spliceBegin ,reader .cursor ); 384 385spanBegin = reader .cursor ; 386lineBegin = reader .cursor ; 387 } 388else if (atStartOfLine && isIdentifierChar (peek (reader ))) 389 { 390// This is a statement splice, which will use a {}-enclosed 391// body for the template to generate. 392 393// Consume an optional identifier 394while (isIdentifierChar (peek (reader ))) 395get (reader ); 396 397// Consume optional horizontal space 398skipHorizontalSpace (reader ); 399 400// Consume an optional `()`-enclosed block (strip 401// all but the outer-most `()`. 402 403// optional space/newline/space before `{` 404skipHorizontalSpace (reader ); 405skipOptionalNewline (reader ); 406skipHorizontalSpace (reader ); 407 408throw 99 ; 409 } 410else 411 { 412// Doesn't seem to be a splice at all, just 413// a literal `$` in the output. 414atStartOfLine = false; 415 } 416 } 417break ; 418 } 419 } 420} 421 422Node * readInput (char const * inputBegin ,char const * inputEnd ) 423{ 424Reader reader ; 425reader .cursor = inputBegin ; 426reader .end = inputEnd ; 427 428return readBody < false> (reader ,kNodeReadFlag_AllowEscape ,-2 ,0 ,-2 ); 429} 430 431void emitRaw (FILE * stream ,char const * begin ,char const * end ) 432{ 433// We will write the raw text to our output file. 434 435// TODO: need to output `#line` directives as well 436 437fputs ("sb << \"" ,stream ); 438for (char const * cc = begin ;cc != end ;++ cc ) 439 { 440int c = * cc ; 441switch (c ) 442 { 443case '\\' : 444fputs ("\\\\" ,stream ); 445break ; 446 447case '\r' : 448break ; 449case '\t' : 450fputs ("\\t" ,stream ); 451break ; 452case '\"' : 453fputs ("\\\"" ,stream ); 454break ; 455case '\n' : 456fputs ("\\n\";\n" ,stream ); 457fputs ("sb << \"" ,stream ); 458break ; 459 460default : 461if ((c >=32 )&& (c <=126 )) 462 { 463fputc (c ,stream ); 464 } 465else 466 { 467assert (false); 468 } 469 } 470 } 471fprintf (stream ,"\";\n" ); 472} 473 474void emitCode (FILE * stream ,char const * begin ,char const * end ) 475{ 476for (auto cc = begin ;cc != end ;++ cc ) 477 { 478if (* cc == '\r' ) 479continue ; 480 481fputc (* cc ,stream ); 482 } 483} 484 485void emit (FILE * stream ,char const * text ) 486{ 487fprintf (stream ,"%s" ,text ); 488} 489 490void emit (FILE * stream ,StringSpan const & span ) 491{ 492fprintf (stream ,"%.*s" ,int (span .end ()- span .begin ()),span .begin ()); 493} 494 495bool isASCIIPrintable (int c ) 496{ 497return (c >=0x20 )&& (c <=0x7E ); 498} 499 500void emitStringLiteralText (FILE * stream ,StringSpan const & span ) 501{ 502char const * cursor = span .begin (); 503char const * end = span .end (); 504 505while (cursor != end ) 506 { 507int c = * cursor ++ ; 508switch (c ) 509 { 510case '\r' : 511case '\n' : 512fprintf (stream ,"\\n" ); 513break ; 514 515case '\t' : 516fprintf (stream ,"\\t" ); 517break ; 518 519case ' ' : 520fprintf (stream ," " ); 521break ; 522 523case '"' : 524fprintf (stream ,"\\\"" ); 525break ; 526 527case '\\' : 528fprintf (stream ,"\\\\" ); 529break ; 530 531default : 532if (isASCIIPrintable (c )) 533 { 534fprintf (stream ,"%c" ,c ); 535 } 536else 537 { 538fprintf (stream ,"%03u" ,c ); 539 } 540break ; 541 } 542 } 543} 544 545void emitSimpleText (FILE * stream ,StringSpan const & span ) 546{ 547UnownedStringSlice content (span ),line ; 548while (StringUtil ::extractLine (content ,line )) 549 { 550// Write the line 551fwrite (line .begin (),1 ,line .getLength (),stream ); 552 553// Specially handle the 'final line', excluding an empty line after \n. 554// We can detect, as if input ends with 'cr/lf' combination, content.begin == span.end(), 555// else if content.begin() == nullptr. 556if (content .begin ()== nullptr || content .begin ()== span .end ()) 557 { 558break ; 559 } 560 561fprintf (stream ,"\n" ); 562 } 563} 564 565void emitCodeNodes (FILE * stream ,Node * node ) 566{ 567for (auto nn = node ;nn ;nn = nn -> next ) 568 { 569switch (nn -> flavor ) 570 { 571case Node ::Flavor ::text : 572emitSimpleText (stream ,nn -> span ); 573emit (stream ,"\n" ); 574break ; 575 576default : 577throw "unexpected" ; 578break ; 579 } 580 } 581} 582 583// Given line starts and a location, find the line number. Returns -1 if not found 584static Index _findLineIndex (const List < UnownedStringSlice >& lineBreaks ,const char * location ) 585{ 586if (location == nullptr ) 587 { 588return -1 ; 589 } 590 591// Use a binary chop to find the associated line 592Index lo = 0 ; 593Index hi = lineBreaks .getCount (); 594 595while (lo + 1 < hi ) 596 { 597const auto mid = (hi + lo ) >>1 ; 598const auto midOffset = lineBreaks [mid ].begin (); 599if (midOffset <=location ) 600 { 601lo = mid ; 602 } 603else 604 { 605hi = mid ; 606 } 607 } 608 609return lo ; 610} 611 612void emitTemplateNodes (SourceFile * sourceFile ,FILE * stream ,Node * node ) 613{ 614// Work out 615List < UnownedStringSlice > lineBreaks ; 616StringUtil ::calcLines (sourceFile -> text ,lineBreaks ); 617 618Node * prev = nullptr ; 619for (auto nn = node ;nn ;prev = nn ,nn = nn -> next ) 620 { 621// If we transition from escape to text, insert line number directive 622bool enable = true; 623if (enable && prev && prev -> flavor == Node ::Flavor ::escape && 624nn -> flavor == Node ::Flavor ::text ) 625 { 626// Find the line 627Index lineIndex = _findLineIndex (lineBreaks ,nn -> span .begin ()); 628// If found, output the directive 629if (lineIndex >=0 ) 630 { 631StringBuilder buf ; 632buf <<"SLANG_RAW(\"#line " << (lineIndex + 1 ) <<" \\\"" <<sourceFile -> linePath 633 <<"\\\"\")\n" ; 634 635emit (stream ,buf .getUnownedSlice ()); 636 } 637 } 638 639switch (nn -> flavor ) 640 { 641case Node ::Flavor ::text : 642emit (stream ,"SLANG_RAW(\"" ); 643emitStringLiteralText (stream ,nn -> span ); 644emit (stream ,"\")\n" ); 645break ; 646 647case Node ::Flavor ::splice : 648emit (stream ,"SLANG_SPLICE(" ); 649emitCodeNodes (stream ,nn -> body ); 650emit (stream ,")\n" ); 651break ; 652 653case Node ::Flavor ::escape : 654emitCodeNodes (stream ,nn -> body ); 655break ; 656 } 657 } 658} 659 660void usage (char const * appName ) 661{ 662fprintf (stderr ,"usage: %s [FILE]... [--target-directory FILE]\n" ,appName ); 663} 664 665SlangResult readAllText (char const * fileName ,String & outString ) 666{ 667FILE * f ; 668fopen_s (& f ,fileName ,"rb" ); 669if (!f ) 670 { 671outString = "" ; 672return SLANG_FAIL ; 673 } 674else 675 { 676fseek (f ,0 ,SEEK_END ); 677auto size = ftell (f ); 678 679StringRepresentation * stringRep = 680StringRepresentation ::createWithCapacityAndLength (size ,size ); 681outString = String (stringRep ); 682 683char * buffer = stringRep -> getData (); 684 685// Seems unnecessary 686// memset(buffer, 0, size); 687 688fseek (f ,0 ,SEEK_SET ); 689size_t readCount = fread (buffer ,sizeof (char ),size ,f ); 690fclose (f ); 691 692return (readCount == size ) ?SLANG_OK :SLANG_FAIL ; 693 } 694} 695 696void writeAllText (char const * srcFileName ,char const * fileName ,const char * content ) 697{ 698FILE * f = nullptr ; 699fopen_s (& f ,fileName ,"wb" ); 700if (!f ) 701 { 702printf ("%s(0): error G0001: cannot write file %s\n" ,srcFileName ,fileName ); 703 } 704else 705 { 706fwrite (content ,1 ,strlen (content ),f ); 707fclose (f ); 708 } 709} 710 711#define PARSE_HANDLER (NAME ) Node* NAME(StringSpan const& text) 712 713typedef PARSE_HANDLER ((* ParseHandler )); 714 715PARSE_HANDLER (parseTemplateFile ) 716{ 717// Read a template node! 718return readInput (text .begin (),text .end ()); 719} 720 721PARSE_HANDLER (parseCxxFile ) 722{ 723// TODO: "scrape" the source file for metadata 724return nullptr ; 725} 726 727PARSE_HANDLER (parseUnknownFile ) 728{ 729// Don't process files we don't know how to handle. 730return nullptr ; 731} 732 733 734Node * parseSourceFile (SourceFile * file ) 735{ 736auto path = file -> inputPath ; 737auto text = file -> text ; 738 739static const struct 740 { 741char const * extension ; 742ParseHandler handler ; 743 }kHandlers []= { 744 {".meta.slang" ,& parseTemplateFile }, 745 {".meta.cpp" ,& parseTemplateFile }, 746 {".cpp" ,& parseCxxFile }, 747 {"" ,& parseUnknownFile }, 748 }; 749 750for (auto hh :kHandlers ) 751 { 752if (path .endsWith (hh .extension )) 753 { 754return hh .handler (text ); 755 } 756 } 757 758return nullptr ; 759} 760 761 762SourceFile * parseSourceFile (const String & path ) 763{ 764FILE * inputStream ; 765fopen_s (& inputStream ,path .getBuffer (),"rb" ); 766if (!inputStream ) 767 { 768fprintf (stderr ,"unable to read input file: %s\n" ,path .getBuffer ()); 769return nullptr ; 770 } 771fseek (inputStream ,0 ,SEEK_END ); 772size_t inputSize = ftell (inputStream ); 773fseek (inputStream ,0 ,SEEK_SET ); 774 775char * input = (char * )malloc (inputSize + 1 ); 776if (fread (input ,inputSize ,1 ,inputStream )!= 1 ) 777 { 778fprintf (stderr ,"unable to read input file: %s\n" ,path .getBuffer ()); 779return nullptr ; 780 } 781input [inputSize ]= 0 ; 782 783char const * inputEnd = input + inputSize ; 784StringSpan span = StringSpan (input ,inputEnd ); 785 786SourceFile * sourceFile = new SourceFile (); 787 788sourceFile -> inputPath = path ; 789 790// We use the fileName as the line path, as the path as passed to the command could contain a 791// complicated depending on the project location. 792sourceFile -> linePath = Path ::getFileName (path ); 793 794sourceFile -> text = span ; 795 796Node * node = parseSourceFile (sourceFile ); 797 798sourceFile -> node = node ; 799 800fclose (inputStream ); 801return sourceFile ; 802} 803 804List < RefPtr < SourceFile >> gSourceFiles ; 805 806int main (int argc ,const char * const * argv ) 807{ 808// Parse command-line arguments. 809List < String > inputPaths ; 810String outputDir ; 811char const * appName = "slang-generate" ; 812 813 { 814const char * const * argCursor = argv ; 815const char * const * argEnd = argv + argc ; 816// Copy the app name 817if (argCursor != argEnd ) 818 { 819appName = * argCursor ++ ; 820 } 821// Parse arguments 822for (;argCursor != argEnd ;++ argCursor ) 823 { 824const auto arg = UnownedStringSlice (* argCursor ); 825if (arg == "--target-directory" ) 826 { 827argCursor ++ ; 828if (argCursor == argEnd ) 829 { 830usage (appName ); 831fprintf (stderr ,"--target-directory expects an argument\n" ); 832exit (1 ); 833 } 834outputDir = Path ::simplify (UnownedStringSlice (* argCursor )); 835 } 836else 837 { 838// We simplify here because doing so also means paths separators are set to / 839// and that makes path emitting work correctly 840inputPaths .add (Path ::simplify (arg )); 841 } 842 } 843 } 844 845if (inputPaths .getCount ()== 0 ) 846 { 847usage (appName ); 848exit (1 ); 849 } 850 851// Read each input file and process it according 852// to the type of treatment it requires. 853for (auto & inputPath :inputPaths ) 854 { 855SourceFile * sourceFile = parseSourceFile (inputPath ); 856gSourceFiles .add (sourceFile ); 857 } 858 859for (auto sourceFile :gSourceFiles ) 860 { 861if (!sourceFile ) 862 { 863fprintf (stderr ,"failed to parse source files\n" ); 864exit (1 ); 865 } 866 } 867 868// Once all inputs have been read, we can start 869// to produce output files by expanding templates. 870for (auto sourceFile :gSourceFiles ) 871 { 872auto inputPath = sourceFile -> inputPath ; 873auto node = sourceFile -> node ; 874 875// write output to a temporary file first 876StringBuilder outputPath ; 877outputPath <<inputPath <<".temp.h" ; 878 879FILE * outputStream ; 880fopen_s (& outputStream ,outputPath .getBuffer (),"w" ); 881if (!outputStream ) 882 { 883fprintf (stderr ,"unable to open file for writing: %s.\n" ,outputPath .getBuffer ()); 884exit (1 ); 885 } 886 887emitTemplateNodes (sourceFile ,outputStream ,node ); 888 889fclose (outputStream ); 890 891// update final output only when content has changed 892StringBuilder outputPathFinal ; 893if (outputDir .getLength ()) 894outputPathFinal <<outputDir <<"/" <<Slang ::Path ::getFileName (inputPath ) <<".h" ; 895else 896outputPathFinal <<inputPath <<".h" ; 897 898String allTextOld ,allTextNew ; 899readAllText (outputPathFinal .getBuffer (),allTextOld ); 900readAllText (outputPath .getBuffer (),allTextNew ); 901if (allTextOld != allTextNew ) 902 { 903writeAllText ( 904inputPath .getBuffer (), 905outputPathFinal .getBuffer (), 906allTextNew .getBuffer ()); 907 } 908remove (outputPath .getBuffer ()); 909 } 910 911return 0 ; 912}