diff options
Diffstat (limited to 'source/core/slang-command-options.cpp')
| -rw-r--r-- | source/core/slang-command-options.cpp | 842 |
1 files changed, 842 insertions, 0 deletions
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<UnownedStringSlice> 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<NameValue>& values) +{ + for (const auto& value : values) + { + addValue(value.name, UserValue(value.value)); + } +} + +void CommandOptions::addValues(const ConstArrayView<NamesValue>& values) +{ + for (const auto& value : values) + { + addValue(value.names, UserValue(value.value)); + } +} + +void CommandOptions::addValues(const ConstArrayView<NamesDescriptionValue>& values) +{ + for (const auto& value : values) + { + addValue(value.names, value.description, UserValue(value.value)); + } +} + +void CommandOptions::addValuesWithAliases(const ConstArrayView<NameValue>& inValues) +{ + List<NameValue> values; + values.addRange(inValues.getBuffer(), inValues.getCount()); + + values.sort([](const NameValue& a, const NameValue& b) -> bool { return a.value < b.value; }); + + List<UnownedStringSlice> 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::Option> 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<UnownedStringSlice>& outNames) const +{ + outNames.clear(); + for (const auto& option : getOptionsForCategory(categoryIndex)) + { + StringUtil::appendSplit(option.names, ',', outNames); + } +} + +void CommandOptions::findCategoryIndicesFromUsage(const UnownedStringSlice& slice, List<Index>& 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<UnownedStringSlice> 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<Index> 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<UnownedStringSlice> 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<UnownedStringSlice> 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<UnownedStringSlice>& lines) +{ + List<UnownedStringSlice> 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<UnownedStringSlice>& 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 + + |
