yum-mirror/slang

Making it easier to work with shaders

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

aidanfnvMake Command Line Reference readthedocs compatible (#7048)39c9e25f6

master
18.9 KiB756 linesraw
1// slang-command-options.cpp
2
3#include "slang-command-options.h"
4
5#include "slang-byte-encode-util.h"
6#include "slang-char-util.h"
7#include "slang-string-util.h"
8
9namespace Slang
10{
11
12UnownedStringSlice CommandOptions::getFirstNameForOption(Index optionIndex)
13{
14    const auto& opt = m_options[optionIndex];
15    return StringUtil::getAtInSplit(opt.names, ',', 0);
16}
17
18UnownedStringSlice CommandOptions::getFirstNameForCategory(Index categoryIndex)
19{
20    const auto& cat = m_categories[categoryIndex];
21    return cat.name;
22}
23
24CommandOptions::NameKey CommandOptions::getNameKeyForOption(Index optionIndex)
25{
26    const auto& opt = m_options[optionIndex];
27    const auto& cat = m_categories[opt.categoryIndex];
28    NameKey key;
29    key.nameIndex = m_pool.findIndex(getFirstNameForOption(optionIndex));
30    key.kind =
31        (cat.kind == CategoryKind::Option) ? LookupKind::Option : makeLookupKind(opt.categoryIndex);
32    return key;
33}
34
35CommandOptions::NameKey CommandOptions::getNameKeyForCategory(Index categoryIndex)
36{
37    NameKey key;
38    key.nameIndex = m_pool.findIndex(getFirstNameForCategory(categoryIndex));
39    key.kind = LookupKind::Category;
40    return key;
41}
42
43SlangResult CommandOptions::_addName(
44    LookupKind kind,
45    const UnownedStringSlice& name,
46    Index targetIndex)
47{
48    NameKey nameKey;
49    nameKey.kind = kind;
50    nameKey.nameIndex = (Index)m_pool.add(name);
51
52    if (m_nameMap.tryGetValueOrAdd(nameKey, targetIndex))
53    {
54        SLANG_ASSERT(!"Option is already added!");
55        return SLANG_FAIL;
56    }
57    return SLANG_OK;
58}
59
60SlangResult CommandOptions::_addOptionName(
61    const UnownedStringSlice& name,
62    Flags flags,
63    Index targetIndex)
64{
65    SLANG_RETURN_ON_FAIL(_addName(LookupKind::Option, name, targetIndex));
66
67    // Add to prefix flags
68    if (flags & (Flag::CanPrefix | Flag::IsPrefix))
69    {
70        const auto length = name.getLength();
71        SLANG_ASSERT(length < 32);
72        m_prefixSizes |= uint32_t(1) << length;
73    }
74
75    return SLANG_OK;
76}
77
78SlangResult CommandOptions::_addValueName(
79    const UnownedStringSlice& name,
80    Index categoryIndex,
81    Index optionIndex)
82{
83    return _addName(LookupKind(categoryIndex), name, optionIndex);
84}
85
86SlangResult CommandOptions::_addUserValue(LookupKind kind, UserValue userValue, Index targetIndex)
87{
88    // If it's invalid we don't need to add it
89    if (userValue == kInvalidUserValue)
90    {
91        return SLANG_OK;
92    }
93
94    UserValueKey userValueKey;
95    userValueKey.kind = kind;
96    userValueKey.userValue = userValue;
97
98    if (m_userValueMap.tryGetValueOrAdd(userValueKey, targetIndex))
99    {
100        SLANG_ASSERT(!"UserValue is already used for this kind!");
101        return SLANG_FAIL;
102    }
103    return SLANG_OK;
104}
105
106UnownedStringSlice CommandOptions::_addString(const char* text)
107{
108    if (text == nullptr)
109    {
110        return UnownedStringSlice();
111    }
112    return _addString(UnownedStringSlice(text));
113}
114
115UnownedStringSlice CommandOptions::_addString(const UnownedStringSlice& slice)
116{
117    const auto length = slice.getLength();
118    const char* dst = m_arena.allocateString(slice.begin(), length);
119    return UnownedStringSlice(dst, length);
120}
121
122Index CommandOptions::_addOption(const UnownedStringSlice& name, const Option& inOption)
123{
124    if (name.indexOf(',') < 0)
125    {
126        return _addOption(&name, 1, inOption);
127    }
128    else
129    {
130        List<UnownedStringSlice> names;
131        StringUtil::split(name, ',', names);
132        return _addOption(names.getBuffer(), names.getCount(), inOption);
133    }
134}
135
136Index CommandOptions::_addOption(
137    const UnownedStringSlice* names,
138    Count namesCount,
139    const Option& inOption)
140{
141    SLANG_ASSERT(namesCount > 0);
142    SLANG_ASSERT(inOption.categoryIndex >= 0);
143
144    if (namesCount <= 0 || inOption.categoryIndex < 0)
145    {
146        return -1;
147    }
148
149    auto& cat = m_categories[inOption.categoryIndex];
150
151    // If there are already options associated with this category, we have to be in the run of the
152    // last ones added
153    if (cat.optionStartIndex != cat.optionEndIndex)
154    {
155        // If we aren't at the end then this is an error
156        if (cat.optionEndIndex != m_options.getCount())
157        {
158            return -1;
159        }
160    }
161    else
162    {
163        // Move to the end of the option list
164        cat.optionStartIndex = m_options.getCount();
165        cat.optionEndIndex = cat.optionStartIndex;
166    }
167
168    Option option(inOption);
169
170    const Index optionIndex = m_options.getCount();
171
172    if (cat.kind == CategoryKind::Option)
173    {
174        for (Index i = 0; i < namesCount; ++i)
175        {
176            if (SLANG_FAILED(_addOptionName(names[i], inOption.flags, optionIndex)))
177            {
178                return -1;
179            }
180        }
181        if (SLANG_FAILED(_addUserValue(LookupKind::Option, inOption.userValue, optionIndex)))
182        {
183            return -1;
184        }
185    }
186    else
187    {
188        for (Index i = 0; i < namesCount; ++i)
189        {
190            _addValueName(names[i], inOption.categoryIndex, optionIndex);
191        }
192        if (SLANG_FAILED(
193                _addUserValue(LookupKind(inOption.categoryIndex), inOption.userValue, optionIndex)))
194        {
195            return -1;
196        }
197    }
198
199    if (namesCount == 1)
200    {
201        // We already have storage on the slice
202        option.names = m_pool.addAndGetSlice(names[0]);
203    }
204    else
205    {
206        // Put all of the names in the list
207        StringBuilder buf;
208        StringUtil::join(names, namesCount, ',', buf);
209        // Allocate storage no in the pool
210        option.names = _addString(buf.getUnownedSlice());
211    }
212
213    m_options.add(option);
214
215    // Set the end index
216    cat.optionEndIndex = optionIndex + 1;
217
218    return optionIndex;
219}
220
221static void _handlePostFix(UnownedStringSlice& ioSlice, CommandOptions::Flags& ioFlags)
222{
223    if (ioSlice.endsWith(toSlice("...")))
224    {
225        if (ioSlice.endsWith(toSlice("?...")))
226        {
227            ioFlags |= CommandOptions::Flag::CanPrefix;
228            ioSlice = ioSlice.head(ioSlice.getLength() - 4);
229        }
230        else
231        {
232            ioFlags |= CommandOptions::Flag::IsPrefix;
233            ioSlice = ioSlice.head(ioSlice.getLength() - 3);
234        }
235    }
236}
237
238void CommandOptions::add(
239    const char* inName,
240    const char* usage,
241    const char* description,
242    UserValue userValue)
243{
244    UnownedStringSlice nameSlice(inName);
245
246    Option option;
247    option.categoryIndex = m_currentCategoryIndex;
248    option.usage = _addString(usage);
249    option.description = _addString(UnownedStringSlice(description));
250    option.userValue = userValue;
251    option.flags = 0;
252
253    if (nameSlice.indexOf(',') >= 0)
254    {
255        List<UnownedStringSlice> names;
256        StringUtil::split(nameSlice, ',', names);
257
258        for (auto& name : names)
259        {
260            _handlePostFix(name, option.flags);
261        }
262
263        _addOption(names.getBuffer(), names.getCount(), option);
264    }
265    else
266    {
267        _handlePostFix(nameSlice, option.flags);
268
269        _addOption(&nameSlice, 1, option);
270    }
271}
272
273void CommandOptions::add(
274    const UnownedStringSlice* names,
275    Count namesCount,
276    const char* usage,
277    const char* description,
278    UserValue userValue,
279    Flags flags)
280{
281    Option option;
282    option.categoryIndex = m_currentCategoryIndex;
283    option.usage = _addString(usage);
284    option.description = _addString(UnownedStringSlice(description));
285    option.flags = flags;
286    option.userValue = userValue;
287
288    _addOption(names, namesCount, option);
289}
290
291Index CommandOptions::_addValue(const UnownedStringSlice& name, const Option& inOption)
292{
293    SLANG_ASSERT(m_currentCategoryIndex >= 0);
294    SLANG_ASSERT(m_categories[m_currentCategoryIndex].kind == CategoryKind::Value);
295
296    return _addOption(name, inOption);
297}
298
299void CommandOptions::addValues(const ValuePair* pairs, Count pairsCount)
300{
301    for (auto& pair : makeConstArrayView(pairs, pairsCount))
302    {
303        addValue(pair.name, pair.description);
304    }
305}
306
307void CommandOptions::addValues(const ConstArrayView<NameValue>& values)
308{
309    for (const auto& value : values)
310    {
311        addValue(value.name, UserValue(value.value));
312    }
313}
314
315void CommandOptions::addValues(const ConstArrayView<NamesValue>& values)
316{
317    for (const auto& value : values)
318    {
319        addValue(value.names, UserValue(value.value));
320    }
321}
322
323void CommandOptions::addValues(const ConstArrayView<NamesDescriptionValue>& values)
324{
325    for (const auto& value : values)
326    {
327        addValue(value.names, value.description, UserValue(value.value));
328    }
329}
330
331void CommandOptions::addValuesWithAliases(const ConstArrayView<NameValue>& inValues)
332{
333    List<NameValue> values;
334    values.addRange(inValues.getBuffer(), inValues.getCount());
335
336    // Use stable_sort to preserve the original order for names with the same value.
337    std::stable_sort(
338        values.begin(),
339        values.end(),
340        [](const NameValue& a, const NameValue& b) -> bool { return a.value < b.value; });
341
342    List<UnownedStringSlice> names;
343
344    const Count count = values.getCount();
345    Index i = 0;
346    while (i < count)
347    {
348        names.clear();
349
350        const auto value = values[i].value;
351        names.add(UnownedStringSlice(values[i++].name));
352
353        // For all names with the same value, preserve their original order (primary first)
354        for (; i < count && values[i].value == value; ++i)
355        {
356            names.add(UnownedStringSlice(values[i].name));
357        }
358
359        addValue(names.getBuffer(), names.getCount(), UserValue(value));
360    }
361}
362
363void CommandOptions::addValue(const UnownedStringSlice& name, UserValue userValue)
364{
365    Option option;
366    option.categoryIndex = m_currentCategoryIndex;
367    option.userValue = userValue;
368    _addValue(name, option);
369}
370
371void CommandOptions::addValue(
372    const UnownedStringSlice& name,
373    const UnownedStringSlice& description,
374    UserValue userValue)
375{
376    Option option;
377    option.categoryIndex = m_currentCategoryIndex;
378    option.description = _addString(description);
379    option.userValue = userValue;
380    _addValue(name, option);
381}
382
383void CommandOptions::addValue(
384    const UnownedStringSlice* names,
385    Count namesCount,
386    UserValue userValue)
387{
388    Option option;
389    option.categoryIndex = m_currentCategoryIndex;
390    option.userValue = userValue;
391
392    SLANG_ASSERT(m_currentCategoryIndex >= 0);
393    SLANG_ASSERT(m_categories[m_currentCategoryIndex].kind == CategoryKind::Value);
394
395    _addOption(names, namesCount, option);
396}
397
398void CommandOptions::addValue(const char* inName, const char* description, UserValue userValue)
399{
400    const UnownedStringSlice name(inName);
401
402    if (description)
403    {
404        addValue(name, UnownedStringSlice(description), userValue);
405    }
406    else
407    {
408        addValue(name, userValue);
409    }
410}
411
412void CommandOptions::addValue(const char* name, UserValue userValue)
413{
414    addValue(UnownedStringSlice(name), userValue);
415}
416
417Index CommandOptions::addCategory(
418    CategoryKind kind,
419    const char* name,
420    const char* description,
421    UserValue userValue)
422{
423    const UnownedStringSlice nameSlice(name);
424
425    const auto categoryIndex = m_categories.getCount();
426
427    if (SLANG_FAILED(_addName(LookupKind::Category, nameSlice, categoryIndex)))
428    {
429        return -1;
430    }
431
432    if (userValue != kInvalidUserValue)
433    {
434        _addUserValue(LookupKind::Category, userValue, categoryIndex);
435    }
436
437    Category cat;
438    cat.kind = kind;
439    cat.name = _addString(nameSlice);
440    cat.description = _addString(description);
441    cat.userValue = userValue;
442
443    m_currentCategoryIndex = categoryIndex;
444
445    m_categories.add(cat);
446
447    return categoryIndex;
448}
449
450void CommandOptions::setCategory(const char* name)
451{
452    const UnownedStringSlice nameSlice(name);
453
454    for (Index i = 0; i < m_categories.getCount(); ++i)
455    {
456        auto& cat = m_categories[i];
457        if (cat.name == nameSlice)
458        {
459            m_currentCategoryIndex = i;
460            return;
461        }
462    }
463
464    SLANG_ASSERT(!"Category not found");
465
466    m_currentCategoryIndex = -1;
467}
468
469Index CommandOptions::findTargetIndexByName(
470    LookupKind kind,
471    const UnownedStringSlice& name,
472    NameKey* outNameKey) const
473{
474    // Look up directly
475    {
476        auto index = _findTargetIndexByName(kind, name, outNameKey);
477        if (index >= 0)
478        {
479            return index;
480        }
481    }
482
483    // Special case options, which can have prefix styles
484    if (kind == LookupKind::Option)
485    {
486        auto prefixSizes = m_prefixSizes;
487
488        while (prefixSizes)
489        {
490            auto prefixSize = ByteEncodeUtil::calcMsb32(prefixSizes);
491
492            if (prefixSize < name.getLength())
493            {
494                // Look it up
495                const auto index = _findTargetIndexByName(kind, name.head(prefixSize), outNameKey);
496                if (index >= 0)
497                {
498                    auto& option = m_options[index];
499
500                    // If the option accepts prefixes, we return the index
501                    if (option.flags & (Flag::CanPrefix | Flag::IsPrefix))
502                    {
503                        return index;
504                    }
505                }
506            }
507
508            // Remove the bit
509            prefixSizes &= ~(uint32_t(1) << prefixSize);
510        }
511    }
512
513    // Was not found
514    return -1;
515}
516
517Index CommandOptions::_findTargetIndexByName(
518    LookupKind kind,
519    const UnownedStringSlice& name,
520    NameKey* outNameKey) const
521{
522    const auto nameIndex = m_pool.findIndex(name);
523    // If the name isn't in the pool then there isn't a category with this name
524    if (nameIndex < 0)
525    {
526        return -1;
527    }
528
529    NameKey key;
530    key.kind = kind;
531    key.nameIndex = nameIndex;
532
533    if (auto ptr = m_nameMap.tryGetValue(key))
534    {
535        if (outNameKey)
536        {
537            *outNameKey = key;
538        }
539        return *ptr;
540    }
541
542    return -1;
543}
544
545Index CommandOptions::findTargetIndexByUserValue(LookupKind kind, UserValue userValue) const
546{
547    UserValueKey key;
548    key.kind = kind;
549    key.userValue = userValue;
550
551    if (auto ptr = m_userValueMap.tryGetValue(key))
552    {
553        return *ptr;
554    }
555
556    return -1;
557}
558
559Index CommandOptions::findCategoryByCaseInsensitiveName(const UnownedStringSlice& slice) const
560{
561    const Count count = m_categories.getCount();
562    for (Index i = 0; i < count; ++i)
563    {
564        const auto& cat = m_categories[i];
565
566        if (cat.name.caseInsensitiveEquals(slice))
567        {
568            return i;
569        }
570    }
571    return -1;
572}
573
574Index CommandOptions::findOptionByCategoryUserValue(
575    UserValue categoryUserValue,
576    const UnownedStringSlice& name) const
577{
578    Index categoryIndex = findTargetIndexByUserValue(LookupKind::Category, categoryUserValue);
579    if (categoryIndex < 0)
580    {
581        return -1;
582    }
583
584    return findValueByName(categoryIndex, name);
585}
586
587ConstArrayView<CommandOptions::Option> CommandOptions::getOptionsForCategory(
588    Index categoryIndex) const
589{
590    const auto& cat = m_categories[categoryIndex];
591    return makeConstArrayView(
592        m_options.getBuffer() + cat.optionStartIndex,
593        cat.optionEndIndex - cat.optionStartIndex);
594}
595
596
597void CommandOptions::appendCategoryOptionNames(
598    Index categoryIndex,
599    List<UnownedStringSlice>& outNames) const
600{
601    for (const auto& option : getOptionsForCategory(categoryIndex))
602    {
603        StringUtil::appendSplit(option.names, ',', outNames);
604    }
605}
606
607void CommandOptions::getCategoryOptionNames(Index categoryIndex, List<UnownedStringSlice>& outNames)
608    const
609{
610    outNames.clear();
611    appendCategoryOptionNames(categoryIndex, outNames);
612}
613
614void CommandOptions::splitUsage(
615    const UnownedStringSlice& usageSlice,
616    List<UnownedStringSlice>& outSlices) const
617{
618    const auto* cur = usageSlice.begin();
619    const auto* end = usageSlice.end();
620
621    while (cur < end)
622    {
623        // Find <
624        while (cur < end && *cur != '<')
625            cur++;
626
627        // If we found it look for the end
628        if (cur < end && *cur == '<')
629        {
630            ++cur;
631            auto start = cur;
632            while (cur < end && (CharUtil::isAlphaOrDigit(*cur) || *cur == '-' || *cur == '_') &&
633                   *cur != '>')
634            {
635                cur++;
636            }
637
638            // If we hit closing > we want to lookup
639            if (cur < end && *cur == '>')
640            {
641                const UnownedStringSlice categoryName(start, cur);
642
643                Index categoryIndex = findCategoryByName(categoryName);
644                if (categoryIndex >= 0)
645                {
646                    outSlices.add(categoryName);
647                }
648            }
649
650            cur++;
651        }
652    }
653}
654
655
656void CommandOptions::findCategoryIndicesFromUsage(
657    const UnownedStringSlice& slice,
658    List<Index>& outCategories) const
659{
660    List<UnownedStringSlice> categoryNames;
661    splitUsage(slice, categoryNames);
662
663    for (auto name : categoryNames)
664    {
665        Index categoryIndex = findCategoryByName(name);
666        if (categoryIndex >= 0 && outCategories.indexOf(categoryIndex) < 0)
667        {
668            outCategories.add(categoryIndex);
669        }
670    }
671}
672
673Count CommandOptions::getOptionCountInRange(
674    Index categoryIndex,
675    UserValue start,
676    UserValue nonInclEnd) const
677{
678    const UserIndex startIndex = UserIndex(start);
679    const UserIndex endIndex = UserIndex(nonInclEnd);
680
681    Count count = 0;
682
683    for (auto& opt : getOptionsForCategory(categoryIndex))
684    {
685        const auto val = opt.userValue;
686        if (val == kInvalidUserValue)
687        {
688            continue;
689        }
690
691        const auto valIndex = UserIndex(val);
692        count += Index(valIndex >= startIndex && valIndex < endIndex);
693    }
694
695    return count;
696}
697
698Count CommandOptions::getOptionCountInRange(LookupKind kind, UserValue start, UserValue nonInclEnd)
699    const
700{
701    Index count = 0;
702
703    if (kind == LookupKind::Category)
704    {
705        const UserIndex startIndex = UserIndex(start);
706        const UserIndex endIndex = UserIndex(nonInclEnd);
707
708        for (auto& cat : m_categories)
709        {
710            if (cat.userValue != kInvalidUserValue)
711            {
712                const auto valIndex = UserIndex(cat.userValue);
713                count += Index(valIndex >= startIndex && valIndex < endIndex);
714            }
715        }
716    }
717    if (kind == LookupKind::Option)
718    {
719        // If we are lookup up options, then we iterate over all option categories
720        const auto catCount = m_categories.getCount();
721        for (Index categoryIndex = 0; categoryIndex < catCount; ++categoryIndex)
722        {
723            if (m_categories[categoryIndex].kind == CategoryKind::Option)
724            {
725                count += getOptionCountInRange(categoryIndex, start, nonInclEnd);
726            }
727        }
728    }
729    else if (Index(kind) >= 0)
730    {
731        // It's a regular category
732        count = getOptionCountInRange(Index(kind), start, nonInclEnd);
733    }
734
735    return count;
736}
737
738
739bool CommandOptions::hasContiguousUserValueRange(
740    LookupKind kind,
741    UserValue start,
742    UserValue nonInclEnd) const
743{
744    const Count rangeCount = Count(nonInclEnd) - Count(start);
745    SLANG_ASSERT(rangeCount >= 0);
746
747    if (rangeCount <= 0)
748    {
749        return true;
750    }
751
752    const Count count = getOptionCountInRange(kind, start, nonInclEnd);
753    return rangeCount == count;
754}
755
756} // namespace Slang