yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Gangzheng TongUse wide char version of Windows API (#8390)3aff764c2

master
14.0 KiB448 linesraw
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:
36    String inputFileName;
37
38    RefPtr<SourceUnit> scrapedSourceUnit;
39    RefPtr<TextTemplateFile> textTemplateFile;
40};
41
42struct App
43{
44public:
45    App(SourceManager& sourceManager, DiagnosticSink& sink, NamePool& namePool)
46        : sourceManager(sourceManager), sink(sink), namePool(namePool)
47    {
48    }
49
50    NamePool& namePool;
51    SourceManager& sourceManager;
52    DiagnosticSink& sink;
53
54    Options options;
55
56    List<RefPtr<InputFile>> inputFiles;
57    RefPtr<LogicalModule> logicalModule;
58
59    RefPtr<SourceUnit> parseSourceUnit(SourceView* inputSourceView, String outputFileName)
60    {
61        return fiddle::parseSourceUnit(
62            inputSourceView,
63            logicalModule,
64            &namePool,
65            &sink,
66            &sourceManager,
67            outputFileName);
68    }
69
70    RefPtr<TextTemplateFile> parseTextTemplate(SourceView* inputSourceView)
71    {
72        return fiddle::parseTextTemplateFile(inputSourceView, &sink);
73    }
74
75    String getOutputFileName(String inputFileName) { return inputFileName + ".fiddle"; }
76
77    void 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        //
82        String 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        //
88        String inputText;
89        if (SLANG_FAILED(File::readAllText(inputPath, inputText)))
90        {
91            sink.diagnose(SourceLoc(), fiddle::Diagnostics::couldNotReadInputFile, inputPath);
92            return;
93        }
94
95        // Registering the input file with the `sourceManager` allows us
96        // to get proper source locations for offsets within it.
97        //
98        PathInfo inputPathInfo = PathInfo::makeFromString(inputPath);
99        SourceFile* inputSourceFile =
100            sourceManager.createSourceFileWithString(inputPathInfo, inputText);
101        SourceView* inputSourceView =
102            sourceManager.createSourceView(inputSourceFile, nullptr, SourceLoc());
103
104        auto inputFile = RefPtr(new InputFile());
105        inputFile->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        //
121        auto 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        //
128        auto textTemplateFile = parseTextTemplate(inputSourceView);
129
130        inputFile->scrapedSourceUnit = sourceUnit;
131        inputFile->textTemplateFile = textTemplateFile;
132
133        inputFiles.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    ///
147    String generateSlug(String const& inputText)
148    {
149        StringBuilder builder;
150        int prev = -1;
151        for (auto c : inputText)
152        {
153            // Ordinary alphabetic characters go
154            // through as-is, but converted to
155            // upper-case.
156            //
157            if (('A' <= c) && (c <= 'Z'))
158            {
159                builder.appendChar(c);
160            }
161            else if (('a' <= c) && (c <= 'z'))
162            {
163                builder.appendChar((c - 'a') + 'A');
164            }
165            else 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.
171                if (prev == -1)
172                    builder.appendChar('_');
173                builder.appendChar(c);
174            }
175            else
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                //
185                if (prev == -1)
186                    continue;
187                if (prev == '_')
188                    continue;
189
190                c = '_';
191                builder.appendChar(c);
192            }
193
194            prev = c;
195        }
196        return builder.produceString();
197    }
198
199    void 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        //
204        auto inputFileName = inputFile->inputFileName;
205        String outputFileName = getOutputFileName(inputFileName);
206        String outputFilePath = options.outputPathPrefix + outputFileName;
207
208        String 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        //
215        StringBuilder builder;
216        builder.append("// GENERATED CODE; DO NOT EDIT\n");
217        builder.append("//\n");
218
219        builder.append("// input file: ");
220        builder.append(inputFile->inputFileName);
221        builder.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        //
239        if (hasAnyFiddleInvocations(inputFile->scrapedSourceUnit))
240        {
241
242            builder.append("\n// BEGIN FIDDLE SCRAPER OUTPUT\n");
243            builder.append("#ifndef ");
244            builder.append(inputFileSlug);
245            builder.append("_INCLUDED\n");
246            builder.append("#define ");
247            builder.append(inputFileSlug);
248            builder.append("_INCLUDED 1\n");
249            builder.append("#ifdef FIDDLE\n");
250            builder.append("#undef FIDDLE\n");
251            builder.append("#undef FIDDLEX\n");
252            builder.append("#undef FIDDLEY\n");
253            builder.append("#endif\n");
254            builder.append("#define FIDDLEY(ARG) FIDDLE_##ARG\n");
255            builder.append("#define FIDDLEX(ARG) FIDDLEY(ARG)\n");
256            builder.append("#define FIDDLE FIDDLEX(__LINE__)\n");
257
258            emitSourceUnitMacros(
259                inputFile->scrapedSourceUnit,
260                builder,
261                &sink,
262                &sourceManager,
263                logicalModule);
264
265            builder.append("\n#endif\n");
266            builder.append("// END FIDDLE SCRAPER OUTPUT\n");
267        }
268
269        if (inputFile->textTemplateFile->textTemplates.getCount() != 0)
270        {
271            builder.append("\n// BEGIN FIDDLE TEMPLATE OUTPUT:\n");
272            builder.append("#ifdef FIDDLE_GENERATED_OUTPUT_ID\n");
273
274            generateTextTemplateOutputs(
275                options.inputPathPrefix + inputFileName,
276                inputFile->textTemplateFile,
277                builder,
278                &sink);
279
280            builder.append("#undef FIDDLE_GENERATED_OUTPUT_ID\n");
281            builder.append("#endif\n");
282            builder.append("// END FIDDLE TEMPLATE OUTPUT\n");
283        }
284
285        builder.append("\n// END OF FIDDLE-GENERATED FILE\n");
286
287
288        {
289            String outputFileContent = builder.produceString();
290
291            if (SLANG_FAILED(File::writeAllTextIfChanged(
292                    outputFilePath,
293                    outputFileContent.getUnownedSlice())))
294            {
295                sink.diagnose(
296                    SourceLoc(),
297                    fiddle::Diagnostics::couldNotWriteOutputFile,
298                    outputFilePath);
299                return;
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        {
308            String newInputFileContent = generateModifiedInputFileForTextTemplates(
309                outputFileName,
310                inputFile->textTemplateFile,
311                &sink);
312
313            String inputFilePath = options.inputPathPrefix + inputFileName;
314            if (SLANG_FAILED(File::writeAllTextIfChanged(
315                    inputFilePath,
316                    newInputFileContent.getUnownedSlice())))
317            {
318                sink.diagnose(
319                    SourceLoc(),
320                    fiddle::Diagnostics::couldNotOverwriteInputFile,
321                    inputFilePath);
322                return;
323            }
324        }
325    }
326
327    void generateAndEmitFiles()
328    {
329        for (auto inputFile : inputFiles)
330            generateAndEmitFilesForInputFile(inputFile);
331    }
332
333    void checkModule() { fiddle::checkModule(this->logicalModule, &sink); }
334
335    void execute(int argc, char const* const* argv)
336    {
337        // We start by parsing any command-line options
338        // that were specified.
339        //
340        options.parse(sink, argc, argv);
341        if (sink.getErrorCount())
342            return;
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        //
349        logicalModule = 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        //
359        for (auto inputPath : options.inputPaths)
360        {
361            processInputFile(inputPath);
362        }
363        if (sink.getErrorCount())
364            return;
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        //
371        checkModule();
372        if (sink.getErrorCount())
373            return;
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        //
381        registerScrapedStuffWithScript(logicalModule);
382        if (sink.getErrorCount())
383            return;
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        //
392        generateAndEmitFiles();
393        if (sink.getErrorCount())
394            return;
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{
407    using namespace fiddle;
408    using namespace Slang;
409
410    ComPtr<ISlangWriter> writer(new FileWriter(stderr, WriterFlag::AutoFlush));
411
412    NamePool namePool;
413
414    SourceManager sourceManager;
415    sourceManager.initialize(nullptr, nullptr);
416
417    DiagnosticSink sink(&sourceManager, Lexer::sourceLocationLexer);
418    sink.writer = writer;
419
420#if DEBUG_FIDDLE_COMMAND_LINE
421    fprintf(stderr, "fiddle:");
422    for (int i = 1; i < argc; ++i)
423    {
424        fprintf(stderr, " %s", argv[i]);
425    }
426    fprintf(stderr, "\n");
427
428    wchar_t wideBuffer[1024];
429    GetCurrentDirectoryW(sizeof(wideBuffer) / sizeof(wideBuffer[0]), wideBuffer);
430
431    // Convert to UTF-8 using String::fromWString
432    String currentDir = String::fromWString(wideBuffer);
433    fprintf(stderr, "cwd: %s\n", currentDir.getBuffer());
434    return 1;
435#endif
436
437    try
438    {
439        App app(sourceManager, sink, namePool);
440        app.execute(argc, argv);
441    }
442    catch (...)
443    {
444        sink.diagnose(SourceLoc(), fiddle::Diagnostics::internalError);
445        return 1;
446    }
447    return 0;
448}