yum-mirror/slang

Making it easier to work with shaders

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

aidanfnvFix capability generator to sort capabilities alphabetically within header groups (#7851)9adac4069

master
47.2 KiB1467 linesraw
1// capabilities-generator-main.cpp
2
3#include "../../source/compiler-core/slang-lexer.h"
4#include "../../source/compiler-core/slang-perfect-hash-codegen.h"
5#include "../../source/core/slang-file-system.h"
6#include "../../source/core/slang-io.h"
7#include "../../source/core/slang-secure-crt.h"
8#include "../../source/core/slang-string-util.h"
9#include "../../source/core/slang-uint-set.h"
10
11#include <stdio.h>
12
13using namespace Slang;
14
15namespace Diagnostics
16{
17#define DIAGNOSTIC(id, severity, name, messageFormat) \
18    const DiagnosticInfo name = {id, Severity::severity, #name, messageFormat};
19#include "slang-capability-diagnostic-defs.h"
20#undef DIAGNOSTIC
21} // namespace Diagnostics
22
23enum class CapabilityFlavor
24{
25    Normal,
26    Abstract,
27    Alias
28};
29
30struct CapabilityDef;
31
32struct CapabilityConjunctionExpr
33{
34    List<CapabilityDef*> atoms;
35    SourceLoc sourceLoc;
36};
37
38struct CapabilityDisjunctionExpr
39{
40    List<CapabilityConjunctionExpr> conjunctions;
41};
42
43struct SerializedArrayView
44{
45    Index first;
46    Index count;
47};
48
49struct CapabilitySharedContext
50{
51    CapabilityDef* ptrOfTarget = nullptr;
52    CapabilityDef* ptrOfStage = nullptr;
53};
54
55static void _removeFromOtherAtomsNotInThis(
56    HashSet<const CapabilityDef*> thisSet,
57    HashSet<const CapabilityDef*> otherSet,
58    List<const CapabilityDef*> atomsToRemove)
59{
60    atomsToRemove.clear();
61    atomsToRemove.reserve(otherSet.getCount());
62    for (auto keyAtom : otherSet)
63    {
64        if (thisSet.contains(keyAtom))
65            continue;
66        atomsToRemove.add(keyAtom);
67    }
68
69    for (auto atomToRemove : atomsToRemove)
70        otherSet.remove(atomToRemove);
71}
72
73enum class AutoDocHeaderGroup : UInt
74{
75    Targets = 0,
76    Stages,
77    Versions,
78    Extensions,
79    Compound,
80    Other,
81    Count,
82    Invalid,
83};
84
85UnownedStringSlice getHeaderNameFromAutoDocHeaderGroup(UInt headerGroup)
86{
87    switch (headerGroup)
88    {
89    case (UInt)AutoDocHeaderGroup::Targets:
90        return UnownedStringSlice("Targets");
91    case (UInt)AutoDocHeaderGroup::Stages:
92        return UnownedStringSlice("Stages");
93    case (UInt)AutoDocHeaderGroup::Extensions:
94        return UnownedStringSlice("Extensions");
95    case (UInt)AutoDocHeaderGroup::Versions:
96        return UnownedStringSlice("Versions");
97    case (UInt)AutoDocHeaderGroup::Compound:
98        return UnownedStringSlice("Compound Capabilities");
99    case (UInt)AutoDocHeaderGroup::Other:
100        return UnownedStringSlice("Other");
101    default:
102        SLANG_ASSERT("Unknown `AutoDocHeaderGroup`");
103        return UnownedStringSlice("");
104    }
105}
106
107UnownedStringSlice getHeaderDescriptionFromAutoDocHeaderGroup(UInt headerGroup)
108{
109    switch (headerGroup)
110    {
111    case (UInt)AutoDocHeaderGroup::Targets:
112        return UnownedStringSlice(
113            "Capabilities to specify code generation targets (`glsl`, `spirv`...)");
114    case (UInt)AutoDocHeaderGroup::Stages:
115        return UnownedStringSlice(
116            "Capabilities to specify code generation stages (`vertex`, `fragment`...)");
117    case (UInt)AutoDocHeaderGroup::Extensions:
118        return UnownedStringSlice("Capabilities to specify extensions (`GL_EXT`, `SPV_EXT`...)");
119    case (UInt)AutoDocHeaderGroup::Versions:
120        return UnownedStringSlice("Capabilities to specify versions of a code generation "
121                                  "target (`sm_5_0`, `GLSL_400`...)");
122    case (UInt)AutoDocHeaderGroup::Compound:
123        return UnownedStringSlice("Capabilities to specify capabilities created by other "
124                                  "capabilities (`raytracing`, `meshshading`...)");
125    case (UInt)AutoDocHeaderGroup::Other:
126        return UnownedStringSlice("Capabilities which may be deprecated");
127    default:
128        SLANG_ASSERT("Unknown `AutoDocHeaderGroup`");
129        return UnownedStringSlice("");
130    }
131}
132
133AutoDocHeaderGroup getAutoDocHeaderGroupFromTag(
134    DiagnosticSink* sink,
135    UnownedStringSlice headerGroupName,
136    SourceLoc loc)
137{
138    if (headerGroupName.caseInsensitiveEquals(UnownedStringSlice("Other")))
139        return AutoDocHeaderGroup::Other;
140    else if (headerGroupName.caseInsensitiveEquals(UnownedStringSlice("Target")))
141        return AutoDocHeaderGroup::Targets;
142    else if (headerGroupName.caseInsensitiveEquals(UnownedStringSlice("Stage")))
143        return AutoDocHeaderGroup::Stages;
144    else if (headerGroupName.caseInsensitiveEquals(UnownedStringSlice("EXT")))
145        return AutoDocHeaderGroup::Extensions;
146    else if (headerGroupName.caseInsensitiveEquals(UnownedStringSlice("Version")))
147        return AutoDocHeaderGroup::Versions;
148    else if (headerGroupName.caseInsensitiveEquals(UnownedStringSlice("Compound")))
149        return AutoDocHeaderGroup::Compound;
150    else
151    {
152        sink->diagnose(loc, Diagnostics::invalidDocCommentHeader, headerGroupName);
153        return AutoDocHeaderGroup::Invalid;
154    }
155}
156
157struct AutoDocInfo
158{
159    String comment;
160    AutoDocHeaderGroup headerGroup;
161
162    AutoDocInfo()
163    {
164        comment = {};
165        headerGroup = AutoDocHeaderGroup::Other;
166    }
167};
168
169struct CapabilityDef : public RefObject
170{
171public:
172    void operator=(const CapabilityDef& other)
173    {
174        this->name = other.name;
175        this->enumValue = other.enumValue;
176        this->expr = other.expr;
177        this->flavor = other.flavor;
178        this->rank = other.rank;
179        this->canonicalRepresentation = other.canonicalRepresentation;
180        this->serializedCanonicalRepresentation = other.serializedCanonicalRepresentation;
181        this->sourceLoc = other.sourceLoc;
182        this->keyAtomsPresent = other.keyAtomsPresent;
183        this->sharedContext = other.sharedContext;
184        this->docComment = other.docComment;
185    }
186
187    String name;
188    Index enumValue;
189    CapabilityDisjunctionExpr expr;
190    CapabilityFlavor flavor;
191    /// optional, 0 is default rank.
192    int rank = 0;
193    List<List<CapabilityDef*>> canonicalRepresentation;
194    SerializedArrayView serializedCanonicalRepresentation;
195    SourceLoc sourceLoc;
196    AutoDocInfo docComment;
197    /// Stores key atoms a CapabilityDef refers to.
198    /// Shared key atoms: key atoms shared between every individual set in a
199    /// canonicalRepresentation, added together.
200    HashSet<const CapabilityDef*> keyAtomsPresent;
201
202    CapabilitySharedContext* sharedContext;
203
204    CapabilityDef* getAbstractBase() const
205    {
206        if (flavor != CapabilityFlavor::Normal)
207            return nullptr;
208        if (expr.conjunctions.getCount() != 1)
209            return nullptr;
210        if (expr.conjunctions[0].atoms.getCount() == 0)
211            return nullptr;
212        if (expr.conjunctions[0].atoms[0]->flavor != CapabilityFlavor::Abstract)
213            return nullptr;
214        return expr.conjunctions[0].atoms[0];
215    }
216
217    void fillKeyAtomsPresentInCannonicalRepresentation()
218    {
219        HashSet<const CapabilityDef*> sharedKeyAtomsInCanonicalSet_target;
220        HashSet<const CapabilityDef*> sharedKeyAtomsInCanonicalSet_stage;
221        HashSet<const CapabilityDef*> keyAtomsFound;
222        List<const CapabilityDef*> atomsToRemove;
223        for (auto& canonicalSet : canonicalRepresentation)
224        {
225            bool alreadySetTarget = false;
226            bool alreadySetStage = false;
227            sharedKeyAtomsInCanonicalSet_target.clear();
228            sharedKeyAtomsInCanonicalSet_stage.clear();
229
230            // find key atoms all atoms in a canonical set share.
231            for (auto& atom : canonicalSet)
232            {
233                bool foundTarget = false;
234                bool foundStage = false;
235                for (auto otherkeyAtomsPresent : atom->keyAtomsPresent)
236                {
237                    auto base = otherkeyAtomsPresent->getAbstractBase();
238                    // add all `target` key atoms associated with atom in canonicalSet
239                    if (base == sharedContext->ptrOfTarget)
240                    {
241                        foundTarget = true;
242                        if (!alreadySetTarget)
243                            sharedKeyAtomsInCanonicalSet_target.add(otherkeyAtomsPresent);
244                    }
245                    // add all `stage` key atoms associated with atom in canonicalSet
246                    else if (base == sharedContext->ptrOfStage)
247                    {
248                        foundStage = true;
249                        if (!alreadySetTarget)
250                            sharedKeyAtomsInCanonicalSet_stage.add(otherkeyAtomsPresent);
251                    }
252                    // all key atoms associated with atom
253                    keyAtomsFound.add(otherkeyAtomsPresent);
254                }
255
256                // remove all not shared key atoms
257                if (foundTarget)
258                {
259                    alreadySetTarget = true;
260                    _removeFromOtherAtomsNotInThis(
261                        keyAtomsFound,
262                        sharedKeyAtomsInCanonicalSet_target,
263                        atomsToRemove);
264                }
265                if (foundStage)
266                {
267                    alreadySetStage = true;
268                    _removeFromOtherAtomsNotInThis(
269                        keyAtomsFound,
270                        sharedKeyAtomsInCanonicalSet_stage,
271                        atomsToRemove);
272                }
273                keyAtomsFound.clear();
274            }
275
276            // add all shared key atoms
277            for (auto keyAtom : sharedKeyAtomsInCanonicalSet_target)
278                this->keyAtomsPresent.add(keyAtom);
279            for (auto keyAtom : sharedKeyAtomsInCanonicalSet_stage)
280                this->keyAtomsPresent.add(keyAtom);
281        }
282        if (auto base = this->getAbstractBase())
283            keyAtomsPresent.add(this);
284    }
285};
286
287/// Advances through BlockComment/LineComment, otherwise, "advanceIf 'type' is the next token"
288enum class AdvanceOptions : UInt
289{
290    None = 0 << 0,
291    SkipComments = 1 << 0,
292};
293
294template<AdvanceOptions L, AdvanceOptions R>
295constexpr bool ContainsOption()
296{
297    return (UInt)L & (UInt)R;
298}
299
300static bool isInternalDef(RefPtr<CapabilityDef> def)
301{
302    return def->name.startsWith("_");
303}
304
305struct CapabilityDefParser
306{
307    CapabilityDefParser(Lexer* lexer, DiagnosticSink* sink, CapabilitySharedContext& sharedContext)
308        : m_lexer(lexer), m_sink(sink), m_sharedContext(sharedContext)
309    {
310    }
311
312    Lexer* m_lexer;
313    DiagnosticSink* m_sink;
314
315    Dictionary<String, CapabilityDef*> m_mapNameToCapability;
316    List<RefPtr<CapabilityDef>> m_defs;
317    CapabilitySharedContext& m_sharedContext;
318
319    TokenReader m_tokenReader;
320
321    template<AdvanceOptions advanceOptions>
322    bool advanceIf(TokenType type)
323    {
324        auto peekToken = m_tokenReader.peekTokenType();
325        if constexpr (ContainsOption<advanceOptions, AdvanceOptions::SkipComments>())
326        {
327            while (peekToken == TokenType::BlockComment || peekToken == TokenType::LineComment)
328            {
329                m_tokenReader.advanceToken();
330                peekToken = m_tokenReader.peekTokenType();
331            }
332        }
333        if (peekToken == type)
334        {
335            m_tokenReader.advanceToken();
336            return true;
337        }
338        return false;
339    }
340
341    template<AdvanceOptions advanceOptions>
342    SlangResult readToken(TokenType type, Token& nextToken)
343    {
344        nextToken = m_tokenReader.advanceToken();
345        if constexpr (ContainsOption<advanceOptions, AdvanceOptions::SkipComments>())
346        {
347            while (nextToken.type == TokenType::BlockComment ||
348                   nextToken.type == TokenType::LineComment)
349                nextToken = m_tokenReader.advanceToken();
350        }
351        if (nextToken.type != type)
352        {
353            m_sink->diagnose(
354                nextToken.loc,
355                Diagnostics::unexpectedTokenExpectedTokenType,
356                nextToken,
357                type);
358            return SLANG_FAIL;
359        }
360        return SLANG_OK;
361    }
362
363    template<AdvanceOptions advanceOptions>
364    SlangResult readToken(TokenType type)
365    {
366        Token nextToken;
367        return readToken<advanceOptions>(type, nextToken);
368    }
369
370    SlangResult parseConjunction(CapabilityConjunctionExpr& expr)
371    {
372        for (;;)
373        {
374            Token nameToken;
375            SLANG_RETURN_ON_FAIL(
376                readToken<AdvanceOptions::SkipComments>(TokenType::Identifier, nameToken));
377            CapabilityDef* def = nullptr;
378            if (m_mapNameToCapability.tryGetValue(nameToken.getContent(), def))
379            {
380                expr.atoms.add(def);
381            }
382            else
383            {
384                m_sink->diagnose(nameToken.loc, Diagnostics::undefinedIdentifier, nameToken);
385                return SLANG_FAIL;
386            }
387            if (!(advanceIf<AdvanceOptions::SkipComments>(TokenType::OpAdd)))
388                break;
389        }
390        return SLANG_OK;
391    }
392
393    SlangResult parseExpr(CapabilityDisjunctionExpr& expr)
394    {
395        for (;;)
396        {
397            CapabilityConjunctionExpr conjunction;
398            conjunction.sourceLoc = this->m_tokenReader.m_cursor->getLoc();
399            SLANG_RETURN_ON_FAIL(parseConjunction(conjunction));
400            expr.conjunctions.add(conjunction);
401            if (!advanceIf<AdvanceOptions::SkipComments>(TokenType::OpBitOr))
402                break;
403        }
404        return SLANG_OK;
405    }
406
407    void validateInternalAtomExternalAtomPair()
408    {
409        // All `_Internal` atoms must have an `External` atom.
410        // `External` atoms do not require to have an `_Internal` atom.
411        // The following behavior ensures that if we error with 'atom' instead of
412        // '_atom' a user may add the 'atom' capability to solve their error. This is
413        // important because '_Internal' will only be for 1 target, 'External' will alias
414        // to more than 1 target. We need to ensure users avoid 'Internal' when possible.
415
416        Dictionary<String, List<RefPtr<CapabilityDef>>> nameToInternalAndExternalAtom;
417        for (auto i : m_defs)
418        {
419            // 'abstract' atoms are not reported to a user and are ignored
420            if (i->flavor == CapabilityFlavor::Abstract)
421                continue;
422
423            // Try to pack `_atom` and `atom` into the same per key List
424            String name = i->name;
425            if (i->name.startsWith("_"))
426                name = name.subString(1, name.getLength() - 1);
427            nameToInternalAndExternalAtom[name].add(i);
428        }
429        for (auto i : nameToInternalAndExternalAtom)
430        {
431            SLANG_ASSERT(i.second.getCount() <= 2);
432            if (i.second.getCount() != 2)
433            {
434                // If we only have a '_Internal' atom inside our name list there is a missing
435                // 'External' atom
436                if (i.second[0]->name.startsWith("_"))
437                    m_sink->diagnose(
438                        i.second[0]->sourceLoc,
439                        Diagnostics::missingExternalInternalAtomPair,
440                        i.second[0]->name);
441            }
442        }
443    }
444
445    bool isLineSuccessive(HumaneSourceLoc above, HumaneSourceLoc below)
446    {
447        return above.line + 1 == below.line;
448    }
449
450    SlangResult parseDefs()
451    {
452        auto tokens = m_lexer->lexAllMarkupTokens();
453        m_tokenReader = TokenReader(tokens);
454        AutoDocInfo successiveComments = AutoDocInfo();
455        HumaneSourceLoc successiveCommentLine = {};
456
457        for (;;)
458        {
459            auto nextToken = m_tokenReader.advanceToken();
460
461            if (!isLineSuccessive(
462                    successiveCommentLine,
463                    m_lexer->m_sourceView->getHumaneLoc(nextToken.getLoc())))
464                successiveComments = AutoDocInfo();
465
466            RefPtr<CapabilityDef> def = new CapabilityDef();
467            def->sharedContext = &m_sharedContext;
468            def->flavor = CapabilityFlavor::Normal;
469            if (nextToken.getContent() == "alias")
470            {
471                def->flavor = CapabilityFlavor::Alias;
472            }
473            else if (nextToken.getContent() == "abstract")
474            {
475                def->flavor = CapabilityFlavor::Abstract;
476            }
477            else if (nextToken.getContent() == "def")
478            {
479                def->flavor = CapabilityFlavor::Normal;
480            }
481            else if (nextToken.type == TokenType::BlockComment)
482            {
483                // Do not auto-document
484                continue;
485            }
486            else if (nextToken.type == TokenType::LineComment)
487            {
488                // Auto-document if the preceeding token to an identifier is '///'
489                // complete rules described in `source\slang\slang-capabilities.capdef`
490                auto commentContent = nextToken.getContent();
491
492                // remove "//"
493                commentContent = commentContent.subString(2, commentContent.getLength() - 2);
494                if (commentContent.startsWith("/"))
495                {
496                    auto commentLine = m_lexer->m_sourceView->getHumaneLoc(nextToken.getLoc());
497
498                    // Reset the `successiveCommentLine` to our newest commentLine
499                    successiveCommentLine = commentLine;
500
501                    // remove "/" from "///"
502                    commentContent =
503                        commentContent.subString(1, commentContent.getLength() - 1).trim();
504
505                    // Check if we have a `[header]`
506                    if (commentContent.startsWith("["))
507                    {
508                        // Make a substring of `header]`
509                        auto consumedLeftBracketOfHeader =
510                            commentContent.subString(1, commentContent.getLength() - 1);
511                        // Find a `]` of `header]` if it exists
512                        auto indexOfHeaderEnd = consumedLeftBracketOfHeader.indexOf(']');
513                        if (indexOfHeaderEnd != -1)
514                        {
515                            // We found our `header`
516                            auto headerName =
517                                consumedLeftBracketOfHeader.subString(0, indexOfHeaderEnd);
518                            successiveComments.headerGroup = getAutoDocHeaderGroupFromTag(
519                                m_sink,
520                                headerName,
521                                nextToken.getLoc());
522                            continue;
523                        }
524                        // If we did not find a header this is a regular comment
525                    }
526                    successiveComments.comment.append("> ");
527                    successiveComments.comment.append(commentContent);
528                    successiveComments.comment.append("\n");
529                }
530                continue;
531            }
532            else if (nextToken.type == TokenType::EndOfFile)
533            {
534                break;
535            }
536            else
537            {
538                m_sink->diagnose(nextToken.loc, Diagnostics::unexpectedToken, nextToken);
539                return SLANG_FAIL;
540            }
541
542            Token nameToken;
543            SLANG_RETURN_ON_FAIL(
544                readToken<AdvanceOptions::SkipComments>(TokenType::Identifier, nameToken));
545            def->name = nameToken.getContent();
546
547            if (def->flavor == CapabilityFlavor::Normal)
548            {
549                if (advanceIf<AdvanceOptions::SkipComments>(TokenType::Colon))
550                {
551                    SLANG_RETURN_ON_FAIL(parseExpr(def->expr));
552                }
553                if (advanceIf<AdvanceOptions::SkipComments>(TokenType::OpAssign))
554                {
555                    Token rankToken;
556                    SLANG_RETURN_ON_FAIL(readToken<AdvanceOptions::SkipComments>(
557                        TokenType::IntegerLiteral,
558                        rankToken));
559                    def->rank = stringToInt(rankToken.getContent());
560                }
561                def->docComment = successiveComments;
562                if (def->docComment.comment.getLength() == 0 && !isInternalDef(def))
563                    m_sink->diagnose(nextToken.loc, Diagnostics::requiresDocComment, def->name);
564            }
565            else if (def->flavor == CapabilityFlavor::Alias)
566            {
567                SLANG_RETURN_ON_FAIL(readToken<AdvanceOptions::SkipComments>(TokenType::OpAssign));
568                SLANG_RETURN_ON_FAIL(parseExpr(def->expr));
569                def->docComment = successiveComments;
570                if (def->docComment.comment.getLength() == 0 && !isInternalDef(def))
571                    m_sink->diagnose(nextToken.loc, Diagnostics::requiresDocComment, def->name);
572            }
573            else if (def->flavor == CapabilityFlavor::Abstract)
574            {
575                if (advanceIf<AdvanceOptions::SkipComments>(TokenType::Colon))
576                {
577                    SLANG_RETURN_ON_FAIL(parseExpr(def->expr));
578                }
579            }
580            SLANG_RETURN_ON_FAIL(readToken<AdvanceOptions::SkipComments>(TokenType::Semicolon));
581            m_defs.add(def);
582            if (!m_mapNameToCapability.addIfNotExists(def->name, m_defs.getLast()))
583            {
584                m_sink->diagnose(nextToken.loc, Diagnostics::redefinition, def->name);
585                return SLANG_FAIL;
586            }
587
588            // set abstract atom identifiers
589            if (!m_sharedContext.ptrOfTarget && def->name.equals("target"))
590                m_sharedContext.ptrOfTarget = m_defs.getLast();
591            else if (!m_sharedContext.ptrOfStage && def->name.equals("stage"))
592                m_sharedContext.ptrOfStage = m_defs.getLast();
593
594            def->sourceLoc = nameToken.loc;
595        }
596        validateInternalAtomExternalAtomPair();
597        return SLANG_OK;
598    }
599};
600
601struct CapabilityConjunction
602{
603    HashSet<CapabilityDef*> atoms;
604
605    String toString() const
606    {
607        bool first = true;
608        String result = "[";
609        for (auto atom : atoms)
610        {
611            if (!first)
612            {
613                result.append(" + ");
614            }
615            first = false;
616            result.append(atom->name);
617        }
618        result.appendChar(']');
619        return result;
620    }
621
622    bool implies(const CapabilityConjunction& c) const
623    {
624        for (auto& atom : c.atoms)
625        {
626            if (!atoms.contains(atom))
627                return false;
628        }
629        return true;
630    }
631
632    const CapabilityDef* getAbstractAtom(CapabilityDef* defToFilterFor) const
633    {
634        for (auto* atom : this->atoms)
635        {
636            for (auto present : atom->keyAtomsPresent)
637            {
638                auto base = present->getAbstractBase();
639                if (base != defToFilterFor)
640                    continue;
641                return present;
642            }
643        }
644        return nullptr;
645    }
646
647    bool shareTargetAndStageAtom(
648        const CapabilityConjunction& other,
649        CapabilitySharedContext& context)
650    {
651        // shared target means thisTarget==otherTarget
652        // shared stage means either `nostage + ...` or `stage == stage`
653
654        const CapabilityDef* thisTarget = this->getAbstractAtom(context.ptrOfTarget);
655        const CapabilityDef* otherTarget = other.getAbstractAtom(context.ptrOfTarget);
656
657        if (thisTarget != otherTarget && thisTarget && otherTarget)
658            return false;
659
660        const CapabilityDef* thisStage = this->getAbstractAtom(context.ptrOfStage);
661        const CapabilityDef* otherStage = other.getAbstractAtom(context.ptrOfStage);
662
663        if (thisStage != otherStage && thisStage && otherStage)
664            return false;
665
666        return true;
667    }
668
669    bool isImpossible() const
670    {
671        // Keep a map from an abstract base to the concrete atom defined in this conjunction that
672        // implements the base.
673        Dictionary<CapabilityDef*, CapabilityDef*> abstractKV;
674
675        for (auto& atom : atoms)
676        {
677            auto abstractBase = atom->getAbstractBase();
678            if (!abstractBase)
679                continue;
680
681            // Have we already seen another concrete atom that implements the same abstract base of
682            // the current atom? If so, we have a conflict and the conjunction is impossible.
683            //
684            CapabilityDef* value = nullptr;
685            if (abstractKV.tryGetValue(abstractBase, value))
686            {
687                if (value != atom)
688                    return true;
689            }
690            else
691            {
692                abstractKV[abstractBase] = atom;
693            }
694        }
695        return false;
696    }
697};
698
699struct CapabilityDisjunction
700{
701    List<CapabilityConjunction> conjunctions;
702
703    void addConjunction(
704        DiagnosticSink* sink,
705        SourceLoc sourceLoc,
706        CapabilitySharedContext& context,
707        CapabilityConjunction& c)
708    {
709        if (c.isImpossible())
710            return;
711        bool cImpliesThis = false;
712        for (Index i = 0; i < conjunctions.getCount();)
713        {
714            // implied sets will be replaced
715            if (c.implies(conjunctions[i]))
716            {
717                cImpliesThis = true;
718                conjunctions.fastRemoveAt(i);
719            }
720            else
721                i++;
722        }
723        if (cImpliesThis)
724        {
725            conjunctions.add(_Move(c));
726            return;
727        }
728
729        for (Index i = 0; i < conjunctions.getCount();)
730        {
731            if (conjunctions[i].implies(c))
732            {
733                // subset is implied, we do not need to add it.
734                return;
735            }
736            else
737            {
738                // validate we are not creating a disjunction of same targets
739                if (conjunctions[i].shareTargetAndStageAtom(c, context))
740                {
741                    if (sink)
742                    {
743                        sink->diagnose(
744                            sourceLoc,
745                            Diagnostics::unionWithSameKeyAtomButNotSubset,
746                            conjunctions[i].toString(),
747                            c.toString());
748                        sink = nullptr;
749                    }
750                }
751                i++;
752            }
753        }
754        conjunctions.add(_Move(c));
755    }
756    void removeImplied()
757    {
758        for (Index i = 0; i < conjunctions.getCount(); i++)
759        {
760            for (Index ii = 0; ii < conjunctions.getCount(); ii++)
761            {
762                if (ii == i)
763                    continue;
764
765                if (!conjunctions[i].implies(conjunctions[ii]))
766                    continue;
767
768                if (i < ii)
769                {
770                    conjunctions.fastRemoveAt(ii);
771                }
772                else
773                {
774                    conjunctions.removeAt(ii);
775                    i--;
776                }
777                ii--;
778            }
779        }
780    }
781
782    void inclusiveJoinConjunction(
783        CapabilitySharedContext& context,
784        CapabilityConjunction& c,
785        List<CapabilityConjunction>& toAddAfter)
786    {
787        if (c.isImpossible())
788            return;
789        for (auto& conjunction : conjunctions)
790        {
791            if (conjunction.implies(c))
792                return;
793        }
794        for (Index i = 0; i < conjunctions.getCount();)
795        {
796            if (conjunctions[i].shareTargetAndStageAtom(c, context))
797            {
798                CapabilityConjunction toAddAfterSet;
799                for (auto atom : conjunctions[i].atoms)
800                    toAddAfterSet.atoms.add(atom);
801                for (auto atom : c.atoms)
802                    toAddAfterSet.atoms.add(atom);
803                toAddAfter.add(toAddAfterSet);
804                return;
805            }
806            else
807            {
808                i++;
809            }
810        }
811        conjunctions.add(_Move(c));
812    }
813
814    CapabilityDisjunction joinWith(
815        DiagnosticSink* sink,
816        SourceLoc sourceLoc,
817        CapabilitySharedContext& context,
818        const CapabilityDisjunction& other)
819    {
820        if (conjunctions.getCount() == 0)
821        {
822            return other;
823        }
824        if (other.conjunctions.getCount() == 0)
825        {
826            return *this;
827        }
828
829        CapabilityDisjunction result;
830
831        for (auto& thisC : conjunctions)
832        {
833            for (auto& thatC : other.conjunctions)
834            {
835                CapabilityConjunction newC;
836                for (auto atom : thisC.atoms)
837                    newC.atoms.add(atom);
838                for (auto atom : thatC.atoms)
839                    newC.atoms.add(atom);
840                result.addConjunction(sink, sourceLoc, context, newC);
841            }
842        }
843
844        // incompatible abstract atoms
845        if (result.conjunctions.getCount() == 0)
846            sink->diagnose(sourceLoc, Diagnostics::invalidJoinInGenerator);
847
848        return result;
849    }
850
851    List<List<CapabilityDef*>> canonicalize()
852    {
853        List<List<CapabilityDef*>> result;
854        for (auto& c : conjunctions)
855        {
856            List<CapabilityDef*> atoms;
857            for (auto& atom : c.atoms)
858                atoms.add(atom);
859            atoms.sort([](CapabilityDef* c1, CapabilityDef* c2)
860                       { return c1->enumValue < c2->enumValue; });
861            result.add(_Move(atoms));
862        }
863        result.sort(
864            [](const List<CapabilityDef*>& c1, const List<CapabilityDef*>& c2)
865            {
866                for (Index i = 0; i < Math::Min(c1.getCount(), c2.getCount()); i++)
867                {
868                    if (c1[i]->enumValue < c2[i]->enumValue)
869                        return true;
870                    else if (c1[i]->enumValue > c2[i]->enumValue)
871                        return false;
872                }
873                return c1.getCount() < c2.getCount();
874            });
875        return result;
876    }
877};
878
879CapabilityDisjunction getCanonicalRepresentation(CapabilityDef* def)
880{
881    CapabilityDisjunction result;
882    for (auto& c : def->canonicalRepresentation)
883    {
884        CapabilityConjunction conj;
885        for (auto& atom : c)
886            conj.atoms.add(atom);
887        result.conjunctions.add(conj);
888    }
889    return result;
890}
891
892CapabilityDisjunction evaluateConjunction(
893    DiagnosticSink* sink,
894    SourceLoc sourceLoc,
895    CapabilitySharedContext& context,
896    const List<CapabilityDef*>& atoms)
897{
898    CapabilityDisjunction result;
899    for (auto* def : atoms)
900    {
901        CapabilityDisjunction defCanonical = getCanonicalRepresentation(def);
902        result = result.joinWith(sink, sourceLoc, context, defCanonical);
903    }
904    return result;
905}
906
907void calcCanonicalRepresentation(
908    DiagnosticSink* sink,
909    CapabilityDef* def,
910    const List<CapabilityDef*>& mapEnumValueToDef)
911{
912    CapabilityDisjunction disjunction;
913    if (def->flavor == CapabilityFlavor::Normal)
914    {
915        CapabilityConjunction c;
916        c.atoms.add(def);
917        disjunction.conjunctions.add(c);
918    }
919    CapabilityDisjunction exprVal;
920    for (auto& c : def->expr.conjunctions)
921    {
922        CapabilityDisjunction evalD =
923            evaluateConjunction(sink, c.sourceLoc, *def->sharedContext, c.atoms);
924        List<CapabilityConjunction> toAddAfter;
925        for (auto& cc : evalD.conjunctions)
926        {
927            exprVal.inclusiveJoinConjunction(*def->sharedContext, cc, toAddAfter);
928        }
929        for (auto& i : toAddAfter)
930            exprVal.conjunctions.add(i);
931        if (toAddAfter.getCount() > 0)
932            exprVal.removeImplied();
933    }
934    disjunction = disjunction.joinWith(sink, def->sourceLoc, *def->sharedContext, exprVal);
935    def->canonicalRepresentation = disjunction.canonicalize();
936    def->fillKeyAtomsPresentInCannonicalRepresentation();
937}
938
939void calcCanonicalRepresentations(
940    DiagnosticSink* sink,
941    List<RefPtr<CapabilityDef>>& defs,
942    const List<CapabilityDef*>& mapEnumValueToDef)
943{
944    for (auto def : defs)
945        calcCanonicalRepresentation(sink, def, mapEnumValueToDef);
946}
947
948// Create a local UIntSet with data
949void outputLocalUIntSetBuffer(
950    const String& nameOfBuffer,
951    StringBuilder& resultBuilder,
952    UIntSet& set)
953{
954    resultBuilder << "    CapabilityAtomSet " << nameOfBuffer << ";\n";
955    resultBuilder << "    " << nameOfBuffer << ".resizeBackingBufferDirectly("
956                  << set.getBuffer().getCount() << ");\n";
957    for (Index i = 0; i < set.getBuffer().getCount(); i++)
958    {
959        resultBuilder << "    " << nameOfBuffer << ".addRawElement(UIntSet::Element("
960                      << set.getBuffer()[i] << "UL), " << i << "); \n";
961    }
962}
963
964// Create function to generate a UIntSet with initial data
965void outputUIntSetGenerator(
966    const String& nameOfGenerator,
967    StringBuilder& resultBuilder,
968    UIntSet& set)
969{
970    resultBuilder << "inline static CapabilityAtomSet " << nameOfGenerator << "()\n";
971    resultBuilder << "{\n";
972    auto nameOfBackingData = nameOfGenerator + "_data";
973    outputLocalUIntSetBuffer(nameOfBackingData, resultBuilder, set);
974    resultBuilder << "    return " << nameOfBackingData << ";\n";
975    resultBuilder << "}\n";
976}
977
978
979UIntSet atomSetToUIntSet(const List<CapabilityDef*>& atomSet)
980{
981    UIntSet set{};
982    // Last element is generally a larger number. Start from there to minimize reallocations.
983    for (Index i = atomSet.getCount() - 1; i >= 0; i--)
984        set.add(atomSet[i]->enumValue);
985    return set;
986}
987
988void printDocForCapabilityDef(
989    StringBuilder& sbDoc,
990    RefPtr<CapabilityDef> def,
991    List<StringBuilder>& sbDocSections)
992{
993    if (isInternalDef(def) || def->flavor == CapabilityFlavor::Abstract ||
994        def->docComment.headerGroup == AutoDocHeaderGroup::Invalid)
995        return;
996
997    auto& sbDocSection = sbDocSections[(UInt)def->docComment.headerGroup];
998    sbDocSection << "\n"
999                 << "`" << def->name << "`\n";
1000    sbDocSection << def->docComment.comment;
1001}
1002
1003List<StringBuilder> setupDocCommentHeaderStringBuilders()
1004{
1005    List<StringBuilder> sbDocSections;
1006    sbDocSections.setCount((UInt)AutoDocHeaderGroup::Count);
1007    for (UInt i = 0; i < (UInt)AutoDocHeaderGroup::Count; i++)
1008    {
1009        sbDocSections[i] << "\n"
1010                         << getHeaderNameFromAutoDocHeaderGroup(i) << "\n----------------------\n";
1011        sbDocSections[i] << "*" << getHeaderDescriptionFromAutoDocHeaderGroup(i) << "*\n";
1012    }
1013    return sbDocSections;
1014}
1015
1016/// "[Link Name](fileName#Link-Name)"
1017void addHyperLink(StringBuilder& sbDoc, UnownedStringSlice suffix)
1018{
1019    String suffixReformatted = "";
1020
1021    for (auto i : suffix)
1022    {
1023        if (i == ' ')
1024        {
1025            suffixReformatted.appendChar('-');
1026            continue;
1027        }
1028        suffixReformatted.appendChar(i);
1029    }
1030    sbDoc << "[" << suffix << "](#" << suffixReformatted.toLower() << ")";
1031}
1032
1033void setupDocumentationHeader(StringBuilder& sbDoc, const String& outPath)
1034{
1035    sbDoc << R"(
1036---
1037layout: user-guide
1038---
1039
1040Capability Atoms
1041============================
1042
1043### Sections:
1044
1045)";
1046
1047    // Hyper-Links
1048    for (UInt i = 0; i < (UInt)AutoDocHeaderGroup::Count; i++)
1049    {
1050        auto headerName = getHeaderNameFromAutoDocHeaderGroup(i);
1051        sbDoc << i + 1 << ". "; // "i. "
1052        addHyperLink(sbDoc, headerName);
1053        sbDoc << "\n";
1054    }
1055}
1056
1057SlangResult generateDocumentation(
1058    DiagnosticSink* sink,
1059    List<RefPtr<CapabilityDef>>& defs,
1060    StringBuilder& sbDoc,
1061    const String& outPath)
1062{
1063    setupDocumentationHeader(sbDoc, outPath);
1064
1065    List<StringBuilder> sbDocSections = setupDocCommentHeaderStringBuilders();
1066
1067    // Group capabilities by header group and sort alphabetically within each group
1068    List<List<RefPtr<CapabilityDef>>> capabilitiesByHeaderGroup;
1069    capabilitiesByHeaderGroup.setCount((UInt)AutoDocHeaderGroup::Count);
1070
1071    // Collect capabilities into their respective header groups
1072    for (auto def : defs)
1073    {
1074        if (!isInternalDef(def) && def->flavor != CapabilityFlavor::Abstract &&
1075            def->docComment.headerGroup != AutoDocHeaderGroup::Invalid)
1076        {
1077            capabilitiesByHeaderGroup[(UInt)def->docComment.headerGroup].add(def);
1078        }
1079    }
1080
1081    // Sort capabilities within each header group alphabetically by name
1082    for (auto& capabilitiesInGroup : capabilitiesByHeaderGroup)
1083    {
1084        capabilitiesInGroup.sort([](const RefPtr<CapabilityDef>& a, const RefPtr<CapabilityDef>& b)
1085                                 { return a->name < b->name; });
1086    }
1087
1088    // Add sorted capabilities to documentation sections
1089    for (UInt headerGroupIndex = 0; headerGroupIndex < (UInt)AutoDocHeaderGroup::Count;
1090         headerGroupIndex++)
1091    {
1092        for (auto def : capabilitiesByHeaderGroup[headerGroupIndex])
1093        {
1094            printDocForCapabilityDef(sbDoc, def, sbDocSections);
1095        }
1096    }
1097
1098    for (auto stringBuilder : sbDocSections)
1099        sbDoc << stringBuilder.toString();
1100    return 1;
1101}
1102SlangResult generateDefinitions(
1103    DiagnosticSink* sink,
1104    List<RefPtr<CapabilityDef>>& defs,
1105    StringBuilder& sbHeader,
1106    StringBuilder& sbCpp)
1107{
1108
1109    sbHeader << "enum class CapabilityAtom\n{\n";
1110    sbHeader << "    Invalid,\n";
1111    for (auto def : defs)
1112    {
1113        if (def->flavor == CapabilityFlavor::Normal)
1114        {
1115            sbHeader << "    " << def->name << ",\n";
1116        }
1117    }
1118    sbHeader << "    Count\n";
1119    sbHeader << "};\n";
1120
1121    CapabilityDef* firstAbstractDef = nullptr;
1122    CapabilityDef* firstAliasDef = nullptr;
1123    sbHeader << "enum class CapabilityName\n{\n";
1124    sbHeader << "    Invalid,\n";
1125    Index enumValueCounter = 1;
1126    List<CapabilityDef*> mapEnumValueToDef;
1127    mapEnumValueToDef.add(nullptr); // For Invalid.
1128    for (auto def : defs)
1129    {
1130        if (def->flavor == CapabilityFlavor::Normal)
1131        {
1132            def->enumValue = enumValueCounter;
1133            ++enumValueCounter;
1134            mapEnumValueToDef.add(def);
1135            sbHeader << "    " << def->name << " = (int)CapabilityAtom::" << def->name << ",\n";
1136        }
1137    }
1138    for (auto def : defs)
1139    {
1140        if (def->flavor == CapabilityFlavor::Abstract)
1141        {
1142            if (firstAbstractDef == nullptr)
1143                firstAbstractDef = def;
1144            def->enumValue = enumValueCounter;
1145            ++enumValueCounter;
1146            mapEnumValueToDef.add(def);
1147            sbHeader << "    " << def->name << ",\n";
1148        }
1149    }
1150    for (auto def : defs)
1151    {
1152        if (def->flavor == CapabilityFlavor::Alias)
1153        {
1154            if (firstAliasDef == nullptr)
1155                firstAliasDef = def;
1156            def->enumValue = enumValueCounter;
1157            ++enumValueCounter;
1158            mapEnumValueToDef.add(def);
1159            sbHeader << "    " << def->name << ",\n";
1160        }
1161    }
1162    sbHeader << "    Count\n";
1163    sbHeader << "};\n";
1164
1165    Index targetCount = 0;
1166    Index stageCount = 0;
1167
1168    UIntSet anyTargetAtomSet{};
1169    UIntSet anyStageAtomSet{};
1170    StringBuilder anyTargetUIntSetHash;
1171    StringBuilder anyStageUIntSetHash;
1172
1173    for (auto def : defs)
1174    {
1175        if (def->getAbstractBase() == def->sharedContext->ptrOfTarget)
1176        {
1177            targetCount++;
1178            anyTargetAtomSet.add(def->enumValue);
1179        }
1180        else if (def->getAbstractBase() == def->sharedContext->ptrOfStage)
1181        {
1182            stageCount++;
1183            anyStageAtomSet.add(def->enumValue);
1184        }
1185    }
1186    outputUIntSetGenerator(
1187        "generatorOf_kAnyTargetUIntSetBuffer",
1188        anyTargetUIntSetHash,
1189        anyTargetAtomSet);
1190    anyTargetUIntSetHash << "static CapabilityAtomSet kAnyTargetUIntSetBuffer = "
1191                            "generatorOf_kAnyTargetUIntSetBuffer();\n";
1192    sbCpp << anyTargetUIntSetHash;
1193
1194    outputUIntSetGenerator(
1195        "generatorOf_kAnyStageUIntSetBuffer",
1196        anyStageUIntSetHash,
1197        anyStageAtomSet);
1198    anyStageUIntSetHash << "static CapabilityAtomSet kAnyStageUIntSetBuffer = "
1199                           "generatorOf_kAnyStageUIntSetBuffer();\n";
1200    sbCpp << anyStageUIntSetHash;
1201
1202    sbHeader << "\nenum {\n";
1203    sbHeader << "    kCapabilityTargetCount = " << targetCount << ",\n";
1204    sbHeader << "    kCapabilityStageCount = " << stageCount << ",\n";
1205    sbHeader << "};\n\n";
1206
1207    calcCanonicalRepresentations(sink, defs, mapEnumValueToDef);
1208
1209    struct SerializedConjunction
1210    {
1211        SerializedConjunction() {}
1212        SerializedConjunction(const String& initFunctionName, UIntSet& data)
1213            : m_initFunctionName(initFunctionName), m_data(data)
1214        {
1215        }
1216        String m_initFunctionName;
1217        UIntSet m_data;
1218    };
1219    List<SerializedConjunction> serializedCapabilitesCache;
1220
1221    List<Index> serializedAtomDisjunctions;
1222    auto serializeConjunction = [&](const List<CapabilityDef*>& capabilities,
1223                                    CapabilityDef* parentDef,
1224                                    Index conjunctionNumber) -> Index
1225    {
1226        auto capabilitiesAsUIntSet = atomSetToUIntSet(capabilities);
1227        // Do we already have a serialized capability array that is the same the one we are trying
1228        // to serialize?
1229        for (Index i = 0; i < serializedCapabilitesCache.getCount(); i++)
1230        {
1231            auto& existingSet = serializedCapabilitesCache[i].m_data;
1232            if (existingSet == capabilitiesAsUIntSet)
1233            {
1234                return i;
1235            }
1236        }
1237        auto initName =
1238            "generatorOf_" + parentDef->name + "_conjunction" + String(conjunctionNumber);
1239        outputUIntSetGenerator(initName, sbCpp, capabilitiesAsUIntSet);
1240
1241        auto result = serializedCapabilitesCache.getCount();
1242        serializedCapabilitesCache.add(
1243            SerializedConjunction(initName + "()", capabilitiesAsUIntSet));
1244        return result;
1245    };
1246    auto serializeDisjunction = [&](const List<Index>& conjunctions) -> SerializedArrayView
1247    {
1248        SerializedArrayView result;
1249        result.first = serializedAtomDisjunctions.getCount();
1250        for (auto c : conjunctions)
1251        {
1252            serializedAtomDisjunctions.add(c);
1253        }
1254        result.count = conjunctions.getCount();
1255        return result;
1256    };
1257    for (auto def : defs)
1258    {
1259        List<Index> conjunctions;
1260        for (auto& c : def->canonicalRepresentation)
1261            conjunctions.add(serializeConjunction(c, def, conjunctions.getCount()));
1262        def->serializedCanonicalRepresentation = serializeDisjunction(conjunctions);
1263    }
1264
1265    sbCpp << "static CapabilityAtomSet kCapabilityArray[] = {\n";
1266    Index arrayIndex = 0;
1267    for (Index i = 0; i < serializedCapabilitesCache.getCount(); ++i)
1268    {
1269        sbCpp << "    " << serializedCapabilitesCache[i].m_initFunctionName << ",\n";
1270    }
1271    sbCpp << "};\n";
1272    sbCpp << "static CapabilityAtomSet* kCapabilityConjunctions[] = {\n";
1273    for (auto c : serializedAtomDisjunctions)
1274    {
1275        sbCpp << "    kCapabilityArray + " << c << ", \n";
1276    }
1277    sbCpp << "};\n";
1278
1279    sbCpp
1280        << "static const CapabilityAtomInfo kCapabilityNameInfos[int(CapabilityName::Count)] = {\n";
1281    for (auto* def : mapEnumValueToDef)
1282    {
1283        if (!def)
1284        {
1285            sbCpp
1286                << R"(    { UnownedStringSlice::fromLiteral("Invalid"), CapabilityNameFlavor::Concrete, CapabilityName::Invalid, 0, {nullptr, 0} },)"
1287                << "\n";
1288            continue;
1289        }
1290
1291        // name.
1292        sbCpp << "    { UnownedStringSlice::fromLiteral(\"" << def->name << "\"), ";
1293
1294        // flavor.
1295        switch (def->flavor)
1296        {
1297        case CapabilityFlavor::Normal:
1298            sbCpp << "CapabilityNameFlavor::Concrete";
1299            break;
1300        case CapabilityFlavor::Abstract:
1301            sbCpp << "CapabilityNameFlavor::Abstract";
1302            break;
1303        case CapabilityFlavor::Alias:
1304            sbCpp << "CapabilityNameFlavor::Alias";
1305            break;
1306        }
1307        sbCpp << ", ";
1308
1309        // abstract base.
1310        auto abstractBase = def->getAbstractBase();
1311        if (abstractBase)
1312        {
1313            sbCpp << "CapabilityName::" << abstractBase->name;
1314        }
1315        else
1316        {
1317            sbCpp << "CapabilityName::Invalid";
1318        }
1319        sbCpp << ", ";
1320
1321        // rank
1322        sbCpp << def->rank;
1323        sbCpp << ", ";
1324
1325        // canonnical representation.
1326        sbCpp << "{ kCapabilityConjunctions + " << def->serializedCanonicalRepresentation.first
1327              << ", " << def->serializedCanonicalRepresentation.count << "} },\n";
1328    }
1329
1330    sbCpp << "};\n";
1331
1332    sbCpp << "void freeCapabilityDefs()\n"
1333          << "{\n"
1334          << "    for (auto& cap : kCapabilityArray) { cap = CapabilityAtomSet(); }\n"
1335          << "    kAnyTargetUIntSetBuffer = CapabilityAtomSet();\n"
1336          << "    kAnyStageUIntSetBuffer = CapabilityAtomSet();\n"
1337          << "}\n";
1338    return SLANG_OK;
1339}
1340
1341
1342SlangResult parseDefFile(
1343    DiagnosticSink* sink,
1344    String inputPath,
1345    List<RefPtr<CapabilityDef>>& outDefs,
1346    CapabilitySharedContext& capabilitySharedContext)
1347{
1348    auto sourceManager = sink->getSourceManager();
1349
1350    String contents;
1351    SLANG_RETURN_ON_FAIL(File::readAllText(inputPath, contents));
1352    PathInfo pathInfo = PathInfo::makeFromString(inputPath);
1353    SourceFile* sourceFile = sourceManager->createSourceFileWithString(pathInfo, contents);
1354    SourceView* sourceView = sourceManager->createSourceView(sourceFile, nullptr, SourceLoc());
1355    Lexer lexer;
1356    NamePool namePool;
1357    lexer.initialize(sourceView, sink, &namePool, sourceManager->getMemoryArena());
1358
1359    CapabilityDefParser parser(&lexer, sink, capabilitySharedContext);
1360
1361    SLANG_RETURN_ON_FAIL(parser.parseDefs());
1362    outDefs = _Move(parser.m_defs);
1363    return SLANG_OK;
1364}
1365
1366void printDiagnostics(DiagnosticSink* sink)
1367{
1368    ComPtr<ISlangBlob> blob;
1369    sink->getBlobIfNeeded(blob.writeRef());
1370    if (blob)
1371    {
1372        fprintf(stderr, "%s", (const char*)blob->getBufferPointer());
1373    }
1374}
1375
1376void writeIfChanged(String fileName, String content)
1377{
1378    if (File::exists(fileName))
1379    {
1380        String existingContent;
1381        File::readAllText(fileName, existingContent);
1382        if (existingContent.getUnownedSlice().trim() == content.getUnownedSlice().trim())
1383            return;
1384    }
1385    File::writeAllText(fileName, content);
1386}
1387
1388int main(int argc, const char* const* argv)
1389{
1390    if (argc < 2)
1391    {
1392        fprintf(stderr, "Usage: %s\n", argc >= 1 ? argv[0] : "slang-capabilities-generator");
1393        return 1;
1394    }
1395    String targetDir, outDocPath;
1396    for (int i = 0; i < argc - 1; i++)
1397    {
1398        if (strcmp(argv[i], "--target-directory") == 0)
1399            targetDir = argv[i + 1];
1400        if (strcmp(argv[i], "--doc") == 0)
1401            outDocPath = argv[i + 1];
1402    }
1403
1404    String inPath = argv[1];
1405    if (targetDir.getLength() == 0)
1406        targetDir = Path::getParentDirectory(inPath);
1407
1408    auto outCppPath = Path::combine(targetDir, "slang-generated-capability-defs-impl.h");
1409    auto outHeaderPath = Path::combine(targetDir, "slang-generated-capability-defs.h");
1410    auto outLookupPath = Path::combine(targetDir, "slang-lookup-capability-defs.cpp");
1411    SourceManager sourceManager;
1412    sourceManager.initialize(nullptr, OSFileSystem::getExtSingleton());
1413    DiagnosticSink sink(&sourceManager, nullptr);
1414    List<RefPtr<CapabilityDef>> defs;
1415    CapabilitySharedContext capabilitySharedContext;
1416    if (SLANG_FAILED(parseDefFile(&sink, inPath, defs, capabilitySharedContext)))
1417    {
1418        printDiagnostics(&sink);
1419        return 1;
1420    }
1421
1422    StringBuilder sbHeader, sbCpp;
1423    if (SLANG_FAILED(generateDefinitions(&sink, defs, sbHeader, sbCpp)))
1424    {
1425        printDiagnostics(&sink);
1426        return 1;
1427    }
1428
1429    if (!File::exists(outDocPath))
1430    {
1431        sink.diagnose(
1432            SourceLoc(),
1433            Diagnostics::couldNotFindValidDocumentationOutputPath,
1434            outDocPath);
1435    }
1436
1437    StringBuilder sbDoc;
1438    if (SLANG_FAILED(generateDocumentation(&sink, defs, sbDoc, outDocPath)))
1439    {
1440        printDiagnostics(&sink);
1441        return 1;
1442    }
1443
1444    writeIfChanged(outHeaderPath, sbHeader.produceString());
1445    writeIfChanged(outCppPath, sbCpp.produceString());
1446    writeIfChanged(outDocPath, sbDoc.produceString());
1447
1448    List<String> opnames;
1449    for (auto def : defs)
1450    {
1451        opnames.add(def->name);
1452    }
1453
1454    if (SLANG_FAILED(writePerfectHashLookupCppFile(
1455            outLookupPath,
1456            opnames,
1457            "CapabilityName",
1458            "CapabilityName::",
1459            "slang-capability.h",
1460            &sink)))
1461    {
1462        printDiagnostics(&sink);
1463        return 1;
1464    }
1465    printDiagnostics(&sink);
1466    return 0;
1467}