summaryrefslogtreecommitdiffstats
path: root/source/core/slang-command-options.cpp
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2023-04-29 09:24:26 -0400
committerGitHub <noreply@github.com>2023-04-29 09:24:26 -0400
commit19c0866b050a022406867aa650302f4efbf8e010 (patch)
treef5ed4e1f5d27865518daf81c7e861b4908186b23 /source/core/slang-command-options.cpp
parentc571bcb025009f9c662e8d631fa49dbfed560287 (diff)
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.
Diffstat (limited to 'source/core/slang-command-options.cpp')
-rw-r--r--source/core/slang-command-options.cpp842
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
+
+