From 19c0866b050a022406867aa650302f4efbf8e010 Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Sat, 29 Apr 2023 09:24:26 -0400 Subject: CommandOptions (#2856) * WIP CommandOptions * Fix some output issues. * Simplify word wrapping. * Add file extensions. * Change how lookup takes place. Add appendSplit functions to StringUtil. Make Categories hold the index range of their options. * Small improvement. * Lookup with partial option names. * Associate user values. * Encoding flags in the name. * Refactor setting up of command options. * Use CommandOptions in slang-options. * Remove old help text. * Cache the CommandOptions on the Session. * Range checking. Fix bug in the Options handling. * Extra checks for validity. * Get categories directly. * Slight improvements over output. * Added NameValue types. * Fix typo. Remove some now unused diagnostics. Fix diagnostic in testing, as output has changed. * Add minimal usage message. * Remove platform executable extension from diagnostics output. * Some improvements around getting names from NameValue types. * Improve some option descriptions. * Small fixes. --- build/visual-studio/core/core.vcxproj | 4 + build/visual-studio/core/core.vcxproj.filters | 12 + build/visual-studio/slang-rt/slang-rt.vcxproj | 4 + .../slang-rt/slang-rt.vcxproj.filters | 12 + source/core/slang-command-options.cpp | 842 ++++++++++++++ source/core/slang-command-options.h | 253 ++++ source/core/slang-name-value.cpp | 163 +++ source/core/slang-name-value.h | 76 ++ source/core/slang-string-util.cpp | 24 +- source/core/slang-string-util.h | 7 + source/core/slang-type-text-util.cpp | 292 +++-- source/core/slang-type-text-util.h | 31 +- source/slang/slang-capability.cpp | 9 + source/slang/slang-capability.h | 4 + source/slang/slang-compiler.cpp | 13 +- source/slang/slang-compiler.h | 4 + source/slang/slang-diagnostic-defs.h | 5 +- source/slang/slang-options.cpp | 1223 ++++++++++++-------- source/slang/slang-options.h | 4 + source/slang/slang-profile.h | 8 + source/slang/slang.cpp | 4 + .../unknown-line-directive-mode.slang.expected | 2 +- .../command-line/unknown-option.slang.expected | 3 + 23 files changed, 2345 insertions(+), 654 deletions(-) create mode 100644 source/core/slang-command-options.cpp create mode 100644 source/core/slang-command-options.h create mode 100644 source/core/slang-name-value.cpp create mode 100644 source/core/slang-name-value.h diff --git a/build/visual-studio/core/core.vcxproj b/build/visual-studio/core/core.vcxproj index 7799dd8f4..90fcfe8ef 100644 --- a/build/visual-studio/core/core.vcxproj +++ b/build/visual-studio/core/core.vcxproj @@ -278,6 +278,7 @@ + @@ -298,6 +299,7 @@ + @@ -342,6 +344,7 @@ + @@ -353,6 +356,7 @@ + diff --git a/build/visual-studio/core/core.vcxproj.filters b/build/visual-studio/core/core.vcxproj.filters index 8ad3e42d5..41d0fafe1 100644 --- a/build/visual-studio/core/core.vcxproj.filters +++ b/build/visual-studio/core/core.vcxproj.filters @@ -48,6 +48,9 @@ Header Files + + Header Files + Header Files @@ -108,6 +111,9 @@ Header Files + + Header Files + Header Files @@ -236,6 +242,9 @@ Source Files + + Source Files + Source Files @@ -269,6 +278,9 @@ Source Files + + Source Files + Source Files diff --git a/build/visual-studio/slang-rt/slang-rt.vcxproj b/build/visual-studio/slang-rt/slang-rt.vcxproj index 31627101c..41c44a051 100644 --- a/build/visual-studio/slang-rt/slang-rt.vcxproj +++ b/build/visual-studio/slang-rt/slang-rt.vcxproj @@ -290,6 +290,7 @@ + @@ -310,6 +311,7 @@ + @@ -355,6 +357,7 @@ + @@ -366,6 +369,7 @@ + diff --git a/build/visual-studio/slang-rt/slang-rt.vcxproj.filters b/build/visual-studio/slang-rt/slang-rt.vcxproj.filters index 9ae64d433..4fa4373ff 100644 --- a/build/visual-studio/slang-rt/slang-rt.vcxproj.filters +++ b/build/visual-studio/slang-rt/slang-rt.vcxproj.filters @@ -48,6 +48,9 @@ Header Files + + Header Files + Header Files @@ -108,6 +111,9 @@ Header Files + + Header Files + Header Files @@ -239,6 +245,9 @@ Source Files + + Source Files + Source Files @@ -272,6 +281,9 @@ Source Files + + Source Files + Source Files diff --git a/source/core/slang-command-options.cpp b/source/core/slang-command-options.cpp new file mode 100644 index 000000000..4577bcb8e --- /dev/null +++ b/source/core/slang-command-options.cpp @@ -0,0 +1,842 @@ +// slang-command-options.cpp + +#include "slang-command-options.h" + +#include "slang-string-util.h" +#include "slang-char-util.h" +#include "slang-byte-encode-util.h" + +namespace Slang { + +SlangResult CommandOptions::_addName(LookupKind kind, const UnownedStringSlice& name, Index targetIndex) +{ + NameKey nameKey; + nameKey.kind = kind; + nameKey.nameIndex = (Index)m_pool.add(name); + + if (m_nameMap.tryGetValueOrAdd(nameKey, targetIndex)) + { + SLANG_ASSERT(!"Option is already added!"); + return SLANG_FAIL; + } + return SLANG_OK; +} + +SlangResult CommandOptions::_addOptionName(const UnownedStringSlice& name, Flags flags, Index targetIndex) +{ + SLANG_RETURN_ON_FAIL(_addName(LookupKind::Option, name, targetIndex)); + + // Add to prefix flags + if (flags & (Flag::CanPrefix | Flag::IsPrefix)) + { + const auto length = name.getLength(); + SLANG_ASSERT(length < 32); + m_prefixSizes |= uint32_t(1) << length; + } + + return SLANG_OK; +} + +SlangResult CommandOptions::_addValueName(const UnownedStringSlice& name, Index categoryIndex, Index optionIndex) +{ + return _addName(LookupKind(categoryIndex), name, optionIndex); +} + +SlangResult CommandOptions::_addUserValue(LookupKind kind, UserValue userValue, Index targetIndex) +{ + // If it's invalid we don't need to add it + if (userValue == kInvalidUserValue) + { + return SLANG_OK; + } + + UserValueKey userValueKey; + userValueKey.kind = kind; + userValueKey.userValue = userValue; + + if (m_userValueMap.tryGetValueOrAdd(userValueKey, targetIndex)) + { + SLANG_ASSERT(!"UserValue is already used for this kind!"); + return SLANG_FAIL; + } + return SLANG_OK; +} + +UnownedStringSlice CommandOptions::_addString(const char* text) +{ + if (text == nullptr) + { + return UnownedStringSlice(); + } + return _addString(UnownedStringSlice(text)); +} + +UnownedStringSlice CommandOptions::_addString(const UnownedStringSlice& slice) +{ + const auto length = slice.getLength(); + const char* dst = m_arena.allocateString(slice.begin(), length); + return UnownedStringSlice(dst, length); +} + +Index CommandOptions::_addOption(const UnownedStringSlice& name, const Option& inOption) +{ + return _addOption(&name, 1, inOption); +} + +Index CommandOptions::_addOption(const UnownedStringSlice* names, Count namesCount, const Option& inOption) +{ + SLANG_ASSERT(namesCount > 0); + SLANG_ASSERT(inOption.categoryIndex >= 0); + + if (namesCount <= 0 || inOption.categoryIndex < 0) + { + return -1; + } + + auto& cat = m_categories[inOption.categoryIndex]; + + // If there are already options associated with this category, we have to be in the run of the last ones added + if (cat.optionStartIndex != cat.optionEndIndex) + { + // If we aren't at the end then this is an error + if (cat.optionEndIndex != m_options.getCount()) + { + return -1; + } + } + else + { + // Move to the end of the option list + cat.optionStartIndex = m_options.getCount(); + cat.optionEndIndex = cat.optionStartIndex; + } + + Option option(inOption); + + const Index optionIndex = m_options.getCount(); + + if (cat.kind == CategoryKind::Option) + { + for (Index i = 0; i < namesCount; ++i) + { + if (SLANG_FAILED(_addOptionName(names[i], inOption.flags, optionIndex))) + { + return -1; + } + } + if (SLANG_FAILED(_addUserValue(LookupKind::Option, inOption.userValue, optionIndex))) + { + return -1; + } + } + else + { + for (Index i = 0; i < namesCount; ++i) + { + _addValueName(names[i], inOption.categoryIndex, optionIndex); + } + if (SLANG_FAILED(_addUserValue(LookupKind(inOption.categoryIndex), inOption.userValue, optionIndex))) + { + return -1; + } + } + + if (namesCount == 1) + { + // We already have storage on the slice + option.names = m_pool.addAndGetSlice(names[0]); + } + else + { + // Put all of the names in the list + StringBuilder buf; + StringUtil::join(names, namesCount, ',', buf); + // Allocate storage no in the pool + option.names = _addString(buf.getUnownedSlice()); + } + + m_options.add(option); + + // Set the end index + cat.optionEndIndex = optionIndex + 1; + + return optionIndex; +} + +static void _handlePostFix(UnownedStringSlice& ioSlice, CommandOptions::Flags& ioFlags) +{ + if (ioSlice.endsWith(toSlice("..."))) + { + if (ioSlice.endsWith(toSlice("?..."))) + { + ioFlags |= CommandOptions::Flag::CanPrefix; + ioSlice = ioSlice.head(ioSlice.getLength() - 4); + } + else + { + ioFlags |= CommandOptions::Flag::IsPrefix; + ioSlice = ioSlice.head(ioSlice.getLength() - 3); + } + } +} + +void CommandOptions::add(const char* inName, const char* usage, const char* description, UserValue userValue) +{ + UnownedStringSlice nameSlice(inName); + + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.usage = _addString(usage); + option.description = _addString(UnownedStringSlice(description)); + option.userValue = userValue; + option.flags = 0; + + if (nameSlice.indexOf(',') >= 0) + { + List names; + StringUtil::split(nameSlice, ',', names); + + for (auto& name : names) + { + _handlePostFix(name, option.flags); + } + + _addOption(names.getBuffer(), names.getCount(), option); + } + else + { + _handlePostFix(nameSlice, option.flags); + + _addOption(&nameSlice, 1, option); + } +} + +void CommandOptions::add(const UnownedStringSlice* names, Count namesCount, const char* usage, const char* description, UserValue userValue, Flags flags) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.usage = _addString(usage); + option.description = _addString(UnownedStringSlice(description)); + option.flags = flags; + option.userValue = userValue; + + _addOption(names, namesCount, option); +} + +Index CommandOptions::_addValue(const UnownedStringSlice& name, const Option& inOption) +{ + SLANG_ASSERT(m_currentCategoryIndex >= 0); + SLANG_ASSERT(m_categories[m_currentCategoryIndex].kind == CategoryKind::Value); + + return _addOption(name, inOption); +} + +void CommandOptions::addValues(const ValuePair* pairs, Count pairsCount) +{ + for (auto& pair : makeConstArrayView(pairs, pairsCount)) + { + addValue(pair.name, pair.description); + } +} + +void CommandOptions::addValues(const ConstArrayView& values) +{ + for (const auto& value : values) + { + addValue(value.name, UserValue(value.value)); + } +} + +void CommandOptions::addValues(const ConstArrayView& values) +{ + for (const auto& value : values) + { + addValue(value.names, UserValue(value.value)); + } +} + +void CommandOptions::addValues(const ConstArrayView& values) +{ + for (const auto& value : values) + { + addValue(value.names, value.description, UserValue(value.value)); + } +} + +void CommandOptions::addValuesWithAliases(const ConstArrayView& inValues) +{ + List values; + values.addRange(inValues.getBuffer(), inValues.getCount()); + + values.sort([](const NameValue& a, const NameValue& b) -> bool { return a.value < b.value; }); + + List names; + + const Count count = values.getCount(); + Index i = 0; + while (i < count) + { + names.clear(); + + const auto value = values[i].value; + names.add(UnownedStringSlice(values[i++].name)); + + for (; i < count && values[i].value == value; ++i) + { + names.add(UnownedStringSlice(values[i].name)); + } + + addValue(names.getBuffer(), names.getCount(), UserValue(value)); + } +} + +void CommandOptions::addValue(const UnownedStringSlice& name, UserValue userValue) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.userValue = userValue; + _addValue(name, option); +} + +void CommandOptions::addValue(const UnownedStringSlice& name, const UnownedStringSlice& description, UserValue userValue) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.description = _addString(description); + option.userValue = userValue; + _addValue(name, option); +} + +void CommandOptions::addValue(const UnownedStringSlice* names, Count namesCount, UserValue userValue) +{ + Option option; + option.categoryIndex = m_currentCategoryIndex; + option.userValue = userValue; + + SLANG_ASSERT(m_currentCategoryIndex >= 0); + SLANG_ASSERT(m_categories[m_currentCategoryIndex].kind == CategoryKind::Value); + + _addOption(names, namesCount, option); +} + +void CommandOptions::addValue(const char* inName, const char* description, UserValue userValue) +{ + const UnownedStringSlice name(inName); + + if (description) + { + addValue(name, UnownedStringSlice(description), userValue); + } + else + { + addValue(name, userValue); + } +} + +void CommandOptions::addValue(const char* name, UserValue userValue) +{ + addValue(UnownedStringSlice(name), userValue); +} + +Index CommandOptions::addCategory(CategoryKind kind, const char* name, const char* description, UserValue userValue) +{ + const UnownedStringSlice nameSlice(name); + + const auto categoryIndex = m_categories.getCount(); + + if (SLANG_FAILED(_addName(LookupKind::Category, nameSlice, categoryIndex))) + { + return -1; + } + + if (userValue != kInvalidUserValue) + { + _addUserValue(LookupKind::Category, userValue, categoryIndex); + } + + Category cat; + cat.kind = kind; + cat.name = _addString(nameSlice); + cat.description = _addString(description); + cat.userValue = userValue; + + m_currentCategoryIndex = categoryIndex; + + m_categories.add(cat); + + return categoryIndex; +} + +void CommandOptions::setCategory(const char* name) +{ + const UnownedStringSlice nameSlice(name); + + for (Index i = 0; i < m_categories.getCount(); ++i) + { + auto& cat = m_categories[i]; + if (cat.name == nameSlice) + { + m_currentCategoryIndex = i; + return; + } + } + + SLANG_ASSERT(!"Category not found"); + + m_currentCategoryIndex = -1; +} + +Index CommandOptions::findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name) const +{ + const auto nameIndex = m_pool.findIndex(name); + // If the name isn't in the pool then there isn't a category with this name + if (nameIndex < 0) + { + return -1; + } + + NameKey key; + key.kind = kind; + key.nameIndex = nameIndex; + + if (auto ptr = m_nameMap.tryGetValue(key)) + { + return *ptr; + } + + return -1; +} + +Index CommandOptions::findTargetIndexByUserValue(LookupKind kind, UserValue userValue) const +{ + UserValueKey key; + key.kind = kind; + key.userValue = userValue; + + if (auto ptr = m_userValueMap.tryGetValue(key)) + { + return *ptr; + } + + return -1; +} + +Index CommandOptions::findCategoryByCaseInsensitiveName(const UnownedStringSlice& slice) const +{ + const Count count = m_categories.getCount(); + for (Index i = 0; i < count; ++i) + { + const auto& cat = m_categories[i]; + + if (cat.name.caseInsensitiveEquals(slice)) + { + return i; + } + } + return -1; +} + +Index CommandOptions::findOptionByCategoryUserValue(UserValue categoryUserValue, const UnownedStringSlice& name) const +{ + Index categoryIndex = findTargetIndexByUserValue(LookupKind::Category, categoryUserValue); + if (categoryIndex < 0) + { + return -1; + } + + return findValueByName(categoryIndex, name); +} + +Index CommandOptions::findOptionByName(const UnownedStringSlice& name) const +{ + { + auto index = findTargetIndexByName(LookupKind::Option, name); + if (index >= 0) + { + return index; + } + } + + // We need to search for partials + auto prefixSizes = m_prefixSizes; + + while (prefixSizes) + { + auto prefixSize = ByteEncodeUtil::calcMsb32(prefixSizes); + + if (prefixSize < name.getLength()) + { + // Look it up + const auto index = findTargetIndexByName(LookupKind::Option, name.head(prefixSize)); + if (index >= 0) + { + auto& option = m_options[index]; + + // If the option accepts prefixes, we return the index + if (option.flags & (Flag::CanPrefix | Flag::IsPrefix)) + { + return index; + } + } + } + + // Remove the bit + prefixSizes &= ~(uint32_t(1) << prefixSize); + } + + // Was not found + return -1; +} + +ConstArrayView CommandOptions::getOptionsForCategory(Index categoryIndex) const +{ + const auto& cat = m_categories[categoryIndex]; + return makeConstArrayView(m_options.getBuffer() + cat.optionStartIndex, cat.optionEndIndex - cat.optionStartIndex); +} + +void CommandOptions::getCategoryOptionNames(Index categoryIndex, List& outNames) const +{ + outNames.clear(); + for (const auto& option : getOptionsForCategory(categoryIndex)) + { + StringUtil::appendSplit(option.names, ',', outNames); + } +} + +void CommandOptions::findCategoryIndicesFromUsage(const UnownedStringSlice& slice, List& outCategories) const +{ + const auto* cur = slice.begin(); + const auto* end = slice.end(); + + while (cur < end) + { + // Find < + while (cur < end && *cur != '<') cur++; + + // If we found it look for the end + if (cur < end && *cur == '<') + { + ++cur; + auto start = cur; + while (cur < end && (CharUtil::isAlphaOrDigit(*cur) || *cur == '-' || *cur == '_') && *cur != '>') + { + cur++; + } + + // If we hit closing > we want to lookup + if (cur < end && *cur == '>') + { + const UnownedStringSlice categoryName(start, cur); + + Index categoryIndex = findCategoryByName(categoryName); + if (categoryIndex >= 0 && outCategories.indexOf(categoryIndex) < 0) + { + outCategories.add(categoryIndex); + } + } + + cur++; + } + } +} + +Count CommandOptions::getOptionCountInRange(Index categoryIndex, UserValue start, UserValue nonInclEnd) const +{ + const UserIndex startIndex = UserIndex(start); + const UserIndex endIndex = UserIndex(nonInclEnd); + + Count count = 0; + + for (auto& opt : getOptionsForCategory(categoryIndex)) + { + const auto val = opt.userValue; + if (val == kInvalidUserValue) + { + continue; + } + + const auto valIndex = UserIndex(val); + count += Index(valIndex >= startIndex && valIndex < endIndex); + } + + return count; +} + +Count CommandOptions::getOptionCountInRange(LookupKind kind, UserValue start, UserValue nonInclEnd) const +{ + Index count = 0; + + if (kind == LookupKind::Category) + { + const UserIndex startIndex = UserIndex(start); + const UserIndex endIndex = UserIndex(nonInclEnd); + + for (auto& cat : m_categories) + { + if (cat.userValue != kInvalidUserValue) + { + const auto valIndex = UserIndex(cat.userValue); + count += Index(valIndex >= startIndex && valIndex < endIndex); + } + } + } + if (kind == LookupKind::Option) + { + // If we are lookup up options, then we iterate over all option categories + const auto catCount = m_categories.getCount(); + for (Index categoryIndex = 0; categoryIndex < catCount; ++categoryIndex) + { + if (m_categories[categoryIndex].kind == CategoryKind::Option) + { + count += getOptionCountInRange(categoryIndex, start, nonInclEnd); + } + } + } + else if (Index(kind) >= 0) + { + // It's a regular category + count = getOptionCountInRange(Index(kind), start, nonInclEnd); + } + + return count; +} + + +bool CommandOptions::hasContiguousUserValueRange(LookupKind kind, UserValue start, UserValue nonInclEnd) const +{ + const Count rangeCount = Count(nonInclEnd) - Count(start); + SLANG_ASSERT(rangeCount >= 0); + + if (rangeCount <= 0) + { + return true; + } + + const Count count = getOptionCountInRange(kind, start, nonInclEnd); + return rangeCount == count; +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!! CommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +void CommandOptionsWriter::appendDescriptionForCategory(const CommandOptions& options, Index categoryIndex) +{ + const auto& categories = options.getCategories(); + + const auto& category = categories[categoryIndex]; + + // Header + { + const auto count = m_builder.getLength(); + if (category.kind == CategoryKind::Value) + { + m_builder << "<" << category.name << ">"; + } + else + { + m_builder << category.name; + } + + const auto length = m_builder.getLength() - count; + m_builder << "\n"; + + m_builder.appendRepeatedChar('=', length); + + m_builder << "\n\n"; + + // If there is a description output it + if (category.description.getLength() > 0) + { + _appendText(0, category.description); + m_builder << "\n"; + } + } + + for (auto& option : options.getOptionsForCategory(categoryIndex)) + { + m_builder << m_indentSlice; + + if (option.usage.getLength()) + { + m_builder << option.usage; + } + else + { + List names; + StringUtil::split(option.names, ',', names); + + _appendWithWrap(1, names, toSlice(", ")); + } + + if (option.description.getLength() == 0) + { + m_builder << "\n"; + continue; + } + + m_builder << ": "; + + _appendText(2, option.description); + + if (option.usage.getLength()) + { + List usageCategoryIndices; + options.findCategoryIndicesFromUsage(option.usage, usageCategoryIndices); + + for (auto usageCategoryIndex : usageCategoryIndices) + { + auto& usageCat = categories[usageCategoryIndex]; + + m_builder << m_indentSlice << m_indentSlice; + m_builder << "<" << usageCat.name << "> can be: "; + + List optionNames; + options.getCategoryOptionNames(usageCategoryIndex, optionNames); + + _appendWithWrap(2, optionNames, toSlice(", ")); + + m_builder << "\n"; + } + } + } + + m_builder << "\n"; +} + +void CommandOptionsWriter::appendDescription(const CommandOptions& options) +{ + // Go through categories in order + + const auto& categories = options.getCategories(); + + for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex) + { + appendDescriptionForCategory(options, categoryIndex); + } +} + +void CommandOptionsWriter::_appendText(Count indentCount, const UnownedStringSlice& text) +{ + List lines; + StringUtil::calcLines(text, lines); + + // Remove very last line if it's empty + if (lines.getCount() > 1 && lines.getLast().trim().getLength() == 0) + { + lines.removeLast(); + } + + _appendWithWrap(indentCount, lines); +} + +Count CommandOptionsWriter::_getCurrentLineLength() +{ + // Work out the current line length + const char* start = m_builder.begin(); + const char* cur = m_builder.end(); + + Count lineLength = 0; + + if (cur > start) + { + for (--cur; cur > start; --cur) + { + const auto c = *cur; + if (c == '\n' || c == '\r') + { + ++cur; + break; + } + } + + lineLength = Count(ptrdiff_t(m_builder.end() - cur)); + } + + return lineLength; +} + +void CommandOptionsWriter::_requireIndent(Count indentCount) +{ + const auto length = m_builder.getLength(); + if (length) + { + const auto c = m_builder[length - 1]; + if (c == '\n' || c == '\r') + { + for (Index j = 0; j < indentCount; j++) + { + m_builder.append(m_indentSlice); + } + } + } +} + +void CommandOptionsWriter::_appendWithWrap(Count indentCount, List& lines) +{ + List words; + + for (auto line : lines) + { + if (line.trim().getLength() == 0 || line.startsWith(toSlice(" "))) + { + // Append the line as is after the indent + _requireIndent(indentCount); + m_builder << line << "\n"; + } + else + { + words.clear(); + StringUtil::split(line, ' ', words); + + _requireIndent(indentCount); + + _appendWithWrap(indentCount, words, toSlice(" ")); + m_builder << "\n"; + } + } +} + + + +void CommandOptionsWriter::_appendWithWrap(Count indentCount, List& slices, const UnownedStringSlice& delimit) +{ + Count lineLength = _getCurrentLineLength(); + + const auto count = slices.getCount(); + + for (Index i = 0; i < count; ++i) + { + auto slice = slices[i]; + + auto sliceLength = slice.getLength(); + + if (i < count - 1) + { + sliceLength += delimit.getLength(); + } + + // If out of space onto the next line + if (lineLength + sliceLength > m_lineLength) + { + m_builder.append("\n"); + + lineLength = indentCount * m_indentSlice.getLength(); + + for (Index j = 0; j < indentCount; j++) + { + m_builder.append(m_indentSlice); + } + } + + m_builder.append(slice); + if (i < count - 1) + { + m_builder.append(delimit); + } + + lineLength += sliceLength; + } +} + +} // namespace Slang + + diff --git a/source/core/slang-command-options.h b/source/core/slang-command-options.h new file mode 100644 index 000000000..a92d4d8f8 --- /dev/null +++ b/source/core/slang-command-options.h @@ -0,0 +1,253 @@ +#ifndef SLANG_CORE_COMMAND_OPTIONS_H +#define SLANG_CORE_COMMAND_OPTIONS_H + +#include "slang-basic.h" +#include "slang-string-slice-pool.h" +#include "slang-name-value.h" + +namespace Slang +{ + +/* For convenience we encode within "names" flags. +"-D..." means that -D *must* be followed by the value +"-D?..." means that -D *can* be a prefix, or it might be followed with the arg +*/ + +struct CommandOptions +{ + typedef uint32_t Flags; + + typedef int32_t UserIndex; + enum class UserValue : UserIndex; + static const UserValue kInvalidUserValue = UserValue(0x80000000); + + enum class LookupKind : int32_t + { + Category = -2, ///< Lookup a category name + Option = -1, ///< Lookup an option name (all options use the same lookup index even if in different categories) + Base = 0, ///< Lookup via category index + }; + + enum class CategoryKind + { + Option, ///< Command line option (like "-D") + Value, ///< One of a set of values (such as an enum or some other kind of list of values) + }; + + struct ValuePair + { + const char* name; + const char* description; + }; + + struct Category + { + UserValue userValue = kInvalidUserValue; + + CategoryKind kind; + UnownedStringSlice name; + UnownedStringSlice description; + + // Holds the span that defines all of the options associated with the category + Index optionStartIndex = 0; + Index optionEndIndex = 0; + }; + + struct Flag + { + enum Enum : Flags + { + CanPrefix = 0x1, /// Allows -Dfsggf or -D fdsfsd + IsPrefix = 0x2, /// Is an option that can only be a prefix + }; + }; + + struct Option + { + UnownedStringSlice names; ///< Comma delimited list of names, first name is the default + UnownedStringSlice usage; ///< Describes usage, can be empty + UnownedStringSlice description; ///< A description of usage + + UserValue userValue = kInvalidUserValue; + + Index categoryIndex = -1; ///< Category this option belongs to + Flags flags = 0; ///< Flags about this option + }; + + /// Add a category + Index addCategory(CategoryKind kind, const char* name, const char* description, UserValue userValue = kInvalidUserValue); + /// Use an already known category. It's an error if the category isn't found + void setCategory(const char* name); + + void add(const char* name, const char* usage, const char* description, UserValue userValue = kInvalidUserValue); + void add(const UnownedStringSlice* names, Count namesCount, const char* usage, const char* description, UserValue userValue = kInvalidUserValue, Flags flags = 0); + + void addValue(const UnownedStringSlice& name, UserValue userValue = kInvalidUserValue); + void addValue(const UnownedStringSlice& name, const UnownedStringSlice& description, UserValue userValue = kInvalidUserValue); + void addValue(const char* name, const char* description, UserValue userValue = kInvalidUserValue); + void addValue(const char* name, UserValue userValue = kInvalidUserValue); + void addValue(const UnownedStringSlice* names, Count namesCount, UserValue userValue = kInvalidUserValue); + + /// Add values (without UserValue association) + void addValues(const ValuePair* pairs, Count pairsCount); + + /// Add values + void addValues(const ConstArrayView& values); + void addValues(const ConstArrayView& values); + void addValues(const ConstArrayView& values); + + /// Sometimes values are listed with *names* per value. This method will take into account the aliases + void addValuesWithAliases(const ConstArrayView& values); + + /// Get the target index based off the name and the kind + Index findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name) const; + /// Given a kind and a user value lookup the target index + Index findTargetIndexByUserValue(LookupKind kind, UserValue userValue) const; + + /// Finds the category by name or -1 if not found + Index findCategoryByName(const UnownedStringSlice& name) const { return findTargetIndexByName(LookupKind::Category, name); } + /// Finds the option index by name or -1 if not found + Index findOptionByName(const UnownedStringSlice& name) const; + /// Find the option index of a value, using it's category index and the name + Index findValueByName(Index categoryIndex, const UnownedStringSlice& name) const { return findTargetIndexByName(LookupKind(categoryIndex), name); } + + /// Get the category index from a user value + Index findCategoryByUserValue(UserValue userValue) const { return findTargetIndexByUserValue(LookupKind::Category, userValue); } + /// Can only get options + Index findOptionByUserValue(UserValue userValue) const { return findTargetIndexByUserValue(LookupKind::Option, userValue); } + /// Get a value associated with a category + Index findValueByUserValue(Index categoryIndex, UserValue userValue) const { return findTargetIndexByUserValue(LookupKind(categoryIndex), userValue); } + + /// Given a category user value, find the associated name + /// Returns -1 if not found + Index findOptionByCategoryUserValue(UserValue categoryUserValue, const UnownedStringSlice& name) const; + + /// Find a category by case insensitive name. Returns -1 if not found + Index findCategoryByCaseInsensitiveName(const UnownedStringSlice& slice) const; + + /// Given a category index returns all the options associated. + ConstArrayView