diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2023-05-02 11:10:58 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-02 11:10:58 -0400 |
| commit | 29cb65585782f71a9c6fa1062eaa0b8de8359604 (patch) | |
| tree | cdd820f2c4a8b26933ba4f5057f92e84b8dc01eb /source | |
| parent | 19c0866b050a022406867aa650302f4efbf8e010 (diff) | |
Markdown CommandOptions (#2860)
* 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.
* WIP improvements around CommandOptions.
* Split out CommandOptionsWriter.
* Add links to options.
* Add command line options reference.
* Link to the reference command line information.
* Add quick links.
* Improvements around lookup.
Add categories to linking.
* Small additional fixes.
* Add LinkFlags control.
* Small text fixes.
* Fix typo.
* Fix typo.
* Fix typo.
* Add support for -g and -O using CommandOptions.
* Improve generated doc output/descriptions.
Remove options listed directly in documentation.
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/slang-command-options-writer.cpp | 712 | ||||
| -rw-r--r-- | source/core/slang-command-options-writer.h | 69 | ||||
| -rw-r--r-- | source/core/slang-command-options.cpp | 394 | ||||
| -rw-r--r-- | source/core/slang-command-options.h | 82 | ||||
| -rw-r--r-- | source/core/slang-string-util.cpp | 34 | ||||
| -rw-r--r-- | source/core/slang-string-util.h | 5 | ||||
| -rw-r--r-- | source/core/slang-type-text-util.cpp | 71 | ||||
| -rw-r--r-- | source/core/slang-type-text-util.h | 21 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 256 |
10 files changed, 1193 insertions, 453 deletions
diff --git a/source/core/slang-command-options-writer.cpp b/source/core/slang-command-options-writer.cpp new file mode 100644 index 000000000..afa46db64 --- /dev/null +++ b/source/core/slang-command-options-writer.cpp @@ -0,0 +1,712 @@ +// slang-command-options-writer.cpp + +#include "slang-command-options-writer.h" + +#include "slang-string-util.h" +#include "slang-char-util.h" +#include "slang-byte-encode-util.h" + +namespace Slang { + +namespace { // anonymous +typedef CommandOptionsWriter::Style Style; +} // anonymous + +static bool _isMarkdown(Style style) { return style == Style::Markdown || style == Style::NoLinkMarkdown; } +static bool _hasLinks(Style style) { return style == Style::Markdown; } + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! MarkdownCommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +class MarkdownCommandOptionsWriter : public CommandOptionsWriter +{ +public: + typedef CommandOptionsWriter Super; + + typedef uint32_t LinkFlags; + struct LinkFlag + { + enum Enum + { + Category = 0x1, + Option = 0x2, + + All = Category | Option, + }; + }; + + MarkdownCommandOptionsWriter(const Options& options): + Super(options) + { + } + +protected: + // CommandOptionsWriter + virtual void appendDescriptionForCategoryImpl(Index categoryIndex) SLANG_OVERRIDE; + virtual void appendDescriptionImpl() SLANG_OVERRIDE; + + void _appendParagraph(const UnownedStringSlice& text, LinkFlags flags = LinkFlag::All); + void _appendParagraph(const ConstArrayView<UnownedStringSlice>& words, LinkFlags flags = LinkFlag::All); + + void _appendMaybeLink(const UnownedStringSlice& word, LinkFlags linkFlags); + + void _appendText(const UnownedStringSlice& text); + void _appendDescriptionForCategory(Index categoryIndex); + UnownedStringSlice _getLinkName(CommandOptions::LookupKind kind, Index index); + UnownedStringSlice _getLinkName(const NameKey& key, Index index); + + void _appendQuickLinks(); + + bool m_hasLinks = false; + Dictionary<NameKey, StringSlicePool::Handle> m_linkMap; +}; + +void MarkdownCommandOptionsWriter::appendDescriptionForCategoryImpl(Index categoryIndex) +{ + // No point doing links for a single category + m_hasLinks = false; + _appendDescriptionForCategory(categoryIndex); +} + + +void MarkdownCommandOptionsWriter::appendDescriptionImpl() +{ + m_hasLinks = _hasLinks(m_options.style); + + if (m_hasLinks) + { + _appendQuickLinks(); + } + + // Go through categories in order + const auto& categories = m_commandOptions->getCategories(); + for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex) + { + _appendDescriptionForCategory(categoryIndex); + } +} + +static bool _needsMarkdownEscape(const UnownedStringSlice& text) +{ + for (auto c : text) + { + switch (c) + { + case '<': + case '>': + case '&': + case '[': + case ']': + { + return true; + } + default: break; + } + } + + return false; +} + +void _appendEscapedMarkdown(const UnownedStringSlice& text, StringBuilder& ioBuf) +{ + if (_needsMarkdownEscape(text)) + { + // Replace any < > & + for (auto c : text) + { + switch (c) + { + case '<': ioBuf << "<"; break; + case '>': ioBuf << ">"; break; + case '&': ioBuf << "&"; break; + case '[': ioBuf << "\\["; break; + case ']': ioBuf << "\\]"; break; + default: ioBuf << c; + } + } + } + else + { + ioBuf << text; + } +} + +void MarkdownCommandOptionsWriter::_appendQuickLinks() +{ + const auto& categories = m_commandOptions->getCategories(); + const auto count = categories.getCount(); + + m_builder << "## Quick Links\n\n"; + + for (Index categoryIndex = 0; categoryIndex < count; ++categoryIndex) + { + const auto& cat = categories[categoryIndex]; + + m_builder << "* ["; + _appendEscapedMarkdown(cat.name, m_builder); + m_builder << "](#" << _getLinkName(LookupKind::Category, categoryIndex) << ")\n"; + } + + m_builder << "\n"; +} + +void MarkdownCommandOptionsWriter::_appendParagraph(const UnownedStringSlice& text, LinkFlags linkFlags) +{ + List<UnownedStringSlice> words; + StringUtil::splitOnWhitespace(text, words); + _appendParagraph(words.getArrayView(), linkFlags); +} + +static bool _isEndPunctionation(char c) +{ + return c == '.' || c == ')' || c == ','; +} + +static bool _isStartPunctionation(char c) +{ + return c == '(' || c == ','; +} + +static UnownedStringSlice _trimPunctuation(const UnownedStringSlice& word) +{ + const char* start = word.begin(); + const char* end = word.end(); + + while (start < end && _isStartPunctionation(*start)) start++; + while (end > start && _isEndPunctionation(end[-1])) --end; + return UnownedStringSlice(start, end); +} + +void MarkdownCommandOptionsWriter::_appendMaybeLink(const UnownedStringSlice& inWord, LinkFlags linkFlags) +{ + if (linkFlags) + { + auto trimmedWord = _trimPunctuation(inWord); + + if (trimmedWord.getLength()) + { + Index index = -1; + NameKey nameKey; + + // Look for options + if (trimmedWord[0] == '-' && (linkFlags & LinkFlag::Option)) + { + index = m_commandOptions->findTargetIndexByName(LookupKind::Option, trimmedWord, &nameKey); + } + else if (trimmedWord[0] == '<' && trimmedWord[trimmedWord.getLength() - 1] == '>' && (linkFlags & LinkFlag::Category)) + { + index = m_commandOptions->findTargetIndexByName(LookupKind::Category, trimmedWord.subString(1, trimmedWord.getLength() - 2), &nameKey); + } + + if (index > 0) + { + // Append before the link + _appendEscapedMarkdown(UnownedStringSlice(inWord.begin(), trimmedWord.begin()), m_builder); + + // Make into a link + m_builder << "["; + _appendEscapedMarkdown(trimmedWord, m_builder); + m_builder << "](#" << _getLinkName(nameKey, index) << ")"; + + // Append after the link + _appendEscapedMarkdown(UnownedStringSlice(trimmedWord.end(), inWord.end()), m_builder); + return; + } + } + } + + _appendEscapedMarkdown(inWord, m_builder); +} + +void MarkdownCommandOptionsWriter::_appendParagraph(const ConstArrayView<UnownedStringSlice>& words, LinkFlags linkFlags) +{ + if (m_hasLinks && linkFlags) + { + for (auto word : words) + { + _appendMaybeLink(word, linkFlags); + m_builder << " "; + } + } + else + { + for (auto word : words) + { + _appendEscapedMarkdown(word, m_builder); + m_builder << " "; + } + } +} + +void MarkdownCommandOptionsWriter::_appendText(const UnownedStringSlice& text) +{ + List<UnownedStringSlice> lines; + StringUtil::calcLines(text, lines); + for (auto line : lines) + { + if (line.startsWith(toSlice(" "))) + { + // If prefixed means we want to display as is + m_builder << "> " << line << "\n"; + } + else + { + _appendParagraph(line); + m_builder << "\n\n"; + } + } +} + + +void MarkdownCommandOptionsWriter::_appendDescriptionForCategory(Index categoryIndex) +{ + auto& options = *m_commandOptions; + + const auto& categories = options.getCategories(); + const auto& category = categories[categoryIndex]; + + const bool isValue = (category.kind == CommandOptions::CategoryKind::Value); + + // Header + { + if (m_hasLinks) + { + // Output anchor + m_builder << "<a id=\"" << _getLinkName(LookupKind::Category, categoryIndex) << "\"></a>\n"; + } + + m_builder << "# " << category.name << "\n\n"; + + // If there is a description output, making \n split paragraphs + if (category.description.getLength() > 0) + { + _appendText(category.description); + } + } + + for (Index optionIndex = category.optionStartIndex; optionIndex < category.optionEndIndex; ++optionIndex) + { + const auto& option = options.getOptionAt(optionIndex); + + { + List<UnownedStringSlice> names; + StringUtil::split(option.names, ',', names); + + if (isValue) + { + m_builder << "* "; + // Output all the names + m_builder << "`"; + StringUtil::join(names.getBuffer(), names.getCount(), toSlice("`, `"), m_builder); + m_builder << "` "; + } + else + { + if (m_hasLinks) + { + m_builder << "<a id=\"" << _getLinkName(LookupKind::Option, optionIndex) << "\"></a>\n"; + } + + m_builder << "## "; + StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), m_builder); + m_builder << "\n"; + + if (option.usage.getLength()) + { + m_builder << "\n**"; + + if (m_hasLinks) + { + List<UnownedStringSlice> usedCategories; + options.splitUsage(option.usage, usedCategories); + + const char* cur = option.usage.begin(); + for (auto usedCategory : usedCategories) + { + _appendEscapedMarkdown(UnownedStringSlice(cur, usedCategory.begin()), m_builder); + + // Now do the link + const Index usedCategoryIndex = options.findCategoryByName(usedCategory); + + m_builder << "[" << usedCategory << "](#" << _getLinkName(LookupKind::Category, usedCategoryIndex) << ")"; + + cur = usedCategory.end(); + } + + _appendEscapedMarkdown(UnownedStringSlice(cur, option.usage.end()), m_builder); + } + else + { + _appendEscapedMarkdown(option.usage, m_builder); + } + + m_builder << "**\n\n"; + } + } + } + + if (option.description.getLength() > 0) + { + if (isValue) + { + m_builder << ": "; + _appendParagraph(option.description); + } + else + { + _appendText(option.description); + } + } + + m_builder << "\n"; + } + + m_builder << "\n"; +} + +UnownedStringSlice MarkdownCommandOptionsWriter::_getLinkName(const NameKey& key, Index index) +{ + if (auto ptr = m_linkMap.tryGetValue(key)) + { + return m_pool.getSlice(*ptr); + } + + UnownedStringSlice prefix = (key.kind == CommandOptions::LookupKind::Category) ? + m_commandOptions->getFirstNameForCategory(index) : + m_commandOptions->getFirstNameForOption(index); + prefix = prefix.trim('-'); + + if (prefix.getLength() == 0) + { + prefix = toSlice("id"); + } + + StringBuilder buf; + buf << prefix; + + const auto bufLen = buf.getLength(); + + for (Index i = 0; i < 1000; ++i) + { + buf.reduceLength(bufLen); + + if (i > 0) + { + buf << "-" << i; + } + + if (!m_pool.has(buf.getUnownedSlice())) + { + break; + } + } + + const auto handle = m_pool.add(buf.getUnownedSlice()); + m_linkMap.add(key, handle); + + return m_pool.getSlice(handle); +} + +UnownedStringSlice MarkdownCommandOptionsWriter::_getLinkName(CommandOptions::LookupKind kind, Index index) +{ + auto& options = *m_commandOptions; + + // Set up the name key + const auto key = (kind == LookupKind::Category) ? + options.getNameKeyForCategory(index) : + options.getNameKeyForOption(index); + + return _getLinkName(key, index); + +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TextCommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +class TextCommandOptionsWriter : public CommandOptionsWriter +{ +public: + typedef CommandOptionsWriter Super; + + TextCommandOptionsWriter(const Options& options) : + Super(options) + { + } +protected: + // CommandOptionsWriter + virtual void appendDescriptionForCategoryImpl(Index categoryIndex) SLANG_OVERRIDE; + virtual void appendDescriptionImpl() SLANG_OVERRIDE; + + void _appendText(Count indentCount, const UnownedStringSlice& text); + void _appendDescriptionForCategory(Index categoryIndex); +}; + +void TextCommandOptionsWriter::appendDescriptionForCategoryImpl(Index categoryIndex) +{ + _appendDescriptionForCategory(categoryIndex); +} + +void TextCommandOptionsWriter::appendDescriptionImpl() +{ + const auto& categories = m_commandOptions->getCategories(); + for (Index categoryIndex = 0; categoryIndex < categories.getCount(); ++categoryIndex) + { + _appendDescriptionForCategory(categoryIndex); + } +} + +void TextCommandOptionsWriter::_appendDescriptionForCategory(Index categoryIndex) +{ + auto& options = *m_commandOptions; + + 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_options.indent; + + if (option.usage.getLength()) + { + m_builder << option.usage; + } + else + { + List<UnownedStringSlice> names; + StringUtil::split(option.names, ',', names); + + _appendWrappedIndented(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_options.indent << m_options.indent; + + m_builder << "<" << usageCat.name << "> can be: "; + + List<UnownedStringSlice> optionNames; + options.getCategoryOptionNames(usageCategoryIndex, optionNames); + + _appendWrappedIndented(2, optionNames, toSlice(", ")); + + m_builder << "\n"; + } + } + } + + m_builder << "\n"; +} + +void TextCommandOptionsWriter::_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(); + } + + List<UnownedStringSlice> words; + + for (auto line : lines) + { + if (line.startsWith(toSlice(" "))) + { + // Append the line as is after the indent + _requireIndent(indentCount); + m_builder << line; + } + else if (line.trim().getLength() == 0) + { + } + else + { + words.clear(); + StringUtil::split(line, ' ', words); + + _requireIndent(indentCount); + _appendWrappedIndented(indentCount, words, toSlice(" ")); + } + + m_builder << "\n"; + } +} + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! CommandOptionsWriter !!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +typedef CommandOptionsWriter::Style Style; + +static const NamesDescriptionValue s_styleInfos[] = +{ + { ValueInt(Style::Text), "text", "Text suitable for output to a terminal" }, + { ValueInt(Style::Markdown), "markdown", "Markdown" }, + { ValueInt(Style::NoLinkMarkdown), "no-link-markdown", "Markdown without links" }, +}; + +/* static */ConstArrayView<NamesDescriptionValue> CommandOptionsWriter::getStyleInfos() +{ + return makeConstArrayView(s_styleInfos); +} + +CommandOptionsWriter::CommandOptionsWriter(const Options& options) : + m_pool(StringSlicePool::Style::Default), + m_options(options) +{ + m_options.indent = m_pool.addAndGetSlice(options.indent); +} + +/* static */RefPtr<CommandOptionsWriter> CommandOptionsWriter::create(const Options& options) +{ + if (_isMarkdown(options.style)) + { + return new MarkdownCommandOptionsWriter(options); + } + else + { + return new TextCommandOptionsWriter(options); + } +} + +void CommandOptionsWriter::appendDescriptionForCategory(CommandOptions* options, Index categoryIndex) +{ + m_commandOptions = options; + appendDescriptionForCategoryImpl(categoryIndex); + m_commandOptions = nullptr; +} + +void CommandOptionsWriter::appendDescription(CommandOptions* options) +{ + m_commandOptions = options; + appendDescriptionImpl(); + m_commandOptions = nullptr; +} + +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_options.indent); + } + } + } +} + +void CommandOptionsWriter::_appendWrappedIndented(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_options.lineLength) + { + m_builder.append("\n"); + + lineLength = indentCount * m_options.indent.getLength(); + + for (Index j = 0; j < indentCount; j++) + { + m_builder.append(m_options.indent); + } + } + + m_builder.append(slice); + if (i < count - 1) + { + m_builder.append(delimit); + } + + lineLength += sliceLength; + } +} + +} // namespace Slang + + diff --git a/source/core/slang-command-options-writer.h b/source/core/slang-command-options-writer.h new file mode 100644 index 000000000..eb9a2795f --- /dev/null +++ b/source/core/slang-command-options-writer.h @@ -0,0 +1,69 @@ +#ifndef SLANG_CORE_COMMAND_OPTIONS_WRITER_H +#define SLANG_CORE_COMMAND_OPTIONS_WRITER_H + +#include "slang-command-options.h" + +namespace Slang +{ + +class CommandOptionsWriter : public RefObject +{ +public: + typedef CommandOptions::CategoryKind CategoryKind; + typedef CommandOptions::NameKey NameKey; + typedef CommandOptions::LookupKind LookupKind; + + enum class Style + { + Text, ///< Suitable for output to a terminal + Markdown, ///< Markdown + NoLinkMarkdown, ///< Markdown without links + }; + + static ConstArrayView<NamesDescriptionValue> getStyleInfos(); + + struct Options + { + Style style = Style::Text; ///< The style + Index lineLength = 120; ///< The maximum amount of characters on a line + UnownedStringSlice indent = toSlice(" ");; + }; + + /// Append descirption for a category + void appendDescriptionForCategory(CommandOptions* options, Index categoryIndex); + /// Appends a description of all of the options + void appendDescription(CommandOptions* options); + + /// Get the builder that string is being written to + StringBuilder& getBuilder() { return m_builder; } + + static RefPtr<CommandOptionsWriter> create(const Options& options); + + +protected: + + /// Append descirption for a category + virtual void appendDescriptionForCategoryImpl(Index categoryIndex) = 0; + /// Appends a description of all of the options + virtual void appendDescriptionImpl() = 0; + + // Ctor, use create to create a writer + CommandOptionsWriter(const Options& options); + + /// Get the length of the current line in ascii chars/bytes + Count _getCurrentLineLength(); + + /// Indentation/wrapping + void _requireIndent(Count indentCount); + void _appendWrappedIndented(Count indentCount, List<UnownedStringSlice>& slices, const UnownedStringSlice& delimit); + + CommandOptions* m_commandOptions = nullptr; + + StringSlicePool m_pool; + StringBuilder m_builder; + Options m_options; +}; + +} // namespace Slang + +#endif diff --git a/source/core/slang-command-options.cpp b/source/core/slang-command-options.cpp index 4577bcb8e..5bbe59a0d 100644 --- a/source/core/slang-command-options.cpp +++ b/source/core/slang-command-options.cpp @@ -7,7 +7,37 @@ #include "slang-byte-encode-util.h" namespace Slang { - + +UnownedStringSlice CommandOptions::getFirstNameForOption(Index optionIndex) +{ + const auto& opt = m_options[optionIndex]; + return StringUtil::getAtInSplit(opt.names, ',', 0); +} + +UnownedStringSlice CommandOptions::getFirstNameForCategory(Index categoryIndex) +{ + const auto& cat = m_categories[categoryIndex]; + return cat.name; +} + +CommandOptions::NameKey CommandOptions::getNameKeyForOption(Index optionIndex) +{ + const auto& opt = m_options[optionIndex]; + const auto& cat = m_categories[opt.categoryIndex]; + NameKey key; + key.nameIndex = m_pool.findIndex(getFirstNameForOption(optionIndex)); + key.kind = (cat.kind == CategoryKind::Option) ? LookupKind::Option : makeLookupKind(opt.categoryIndex); + return key; +} + +CommandOptions::NameKey CommandOptions::getNameKeyForCategory(Index categoryIndex) +{ + NameKey key; + key.nameIndex = m_pool.findIndex(getFirstNameForCategory(categoryIndex)); + key.kind = LookupKind::Category; + return key; +} + SlangResult CommandOptions::_addName(LookupKind kind, const UnownedStringSlice& name, Index targetIndex) { NameKey nameKey; @@ -80,7 +110,16 @@ UnownedStringSlice CommandOptions::_addString(const UnownedStringSlice& slice) Index CommandOptions::_addOption(const UnownedStringSlice& name, const Option& inOption) { - return _addOption(&name, 1, inOption); + if (name.indexOf(',') < 0) + { + return _addOption(&name, 1, inOption); + } + else + { + List<UnownedStringSlice> names; + StringUtil::split(name, ',', names); + return _addOption(names.getBuffer(), names.getCount(), inOption); + } } Index CommandOptions::_addOption(const UnownedStringSlice* names, Count namesCount, const Option& inOption) @@ -386,7 +425,52 @@ void CommandOptions::setCategory(const char* name) m_currentCategoryIndex = -1; } -Index CommandOptions::findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name) const +Index CommandOptions::findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name, NameKey* outNameKey) const +{ + // Look up directly + { + auto index = _findTargetIndexByName(kind, name, outNameKey); + if (index >= 0) + { + return index; + } + } + + // Special case options, which can have prefix styles + if (kind == LookupKind::Option) + { + auto prefixSizes = m_prefixSizes; + + while (prefixSizes) + { + auto prefixSize = ByteEncodeUtil::calcMsb32(prefixSizes); + + if (prefixSize < name.getLength()) + { + // Look it up + const auto index = _findTargetIndexByName(kind, name.head(prefixSize), outNameKey); + 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; +} + +Index CommandOptions::_findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name, NameKey* outNameKey) 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 @@ -401,6 +485,10 @@ Index CommandOptions::findTargetIndexByName(LookupKind kind, const UnownedString if (auto ptr = m_nameMap.tryGetValue(key)) { + if (outNameKey) + { + *outNameKey = key; + } return *ptr; } @@ -447,66 +535,31 @@ Index CommandOptions::findOptionByCategoryUserValue(UserValue categoryUserValue, 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 + +void CommandOptions::appendCategoryOptionNames(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 +void CommandOptions::getCategoryOptionNames(Index categoryIndex, List<UnownedStringSlice>& outNames) const +{ + outNames.clear(); + appendCategoryOptionNames(categoryIndex, outNames); +} + +void CommandOptions::splitUsage(const UnownedStringSlice& usageSlice, List<UnownedStringSlice>& outSlices) const { - const auto* cur = slice.begin(); - const auto* end = slice.end(); + const auto* cur = usageSlice.begin(); + const auto* end = usageSlice.end(); while (cur < end) { @@ -522,16 +575,16 @@ void CommandOptions::findCategoryIndicesFromUsage(const UnownedStringSlice& slic { 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) + if (categoryIndex >= 0) { - outCategories.add(categoryIndex); + outSlices.add(categoryName); } } @@ -540,6 +593,22 @@ void CommandOptions::findCategoryIndicesFromUsage(const UnownedStringSlice& slic } } + +void CommandOptions::findCategoryIndicesFromUsage(const UnownedStringSlice& slice, List<Index>& outCategories) const +{ + List<UnownedStringSlice> categoryNames; + splitUsage(slice, categoryNames); + + for (auto name : categoryNames) + { + Index categoryIndex = findCategoryByName(name); + if (categoryIndex >= 0 && outCategories.indexOf(categoryIndex) < 0) + { + outCategories.add(categoryIndex); + } + } +} + Count CommandOptions::getOptionCountInRange(Index categoryIndex, UserValue start, UserValue nonInclEnd) const { const UserIndex startIndex = UserIndex(start); @@ -616,227 +685,6 @@ bool CommandOptions::hasContiguousUserValueRange(LookupKind kind, UserValue star 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 diff --git a/source/core/slang-command-options.h b/source/core/slang-command-options.h index a92d4d8f8..8b6d7b0ce 100644 --- a/source/core/slang-command-options.h +++ b/source/core/slang-command-options.h @@ -28,6 +28,20 @@ struct CommandOptions Base = 0, ///< Lookup via category index }; + /// A key type that uses the combination of the lookup kind and a name index. + /// Maps to a target index that could be a category or an option index. + struct NameKey + { + typedef NameKey ThisType; + + SLANG_FORCE_INLINE bool operator==(const ThisType& rhs) const { return kind == rhs.kind && nameIndex == rhs.nameIndex; } + SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + HashCode getHashCode() const { return combineHash(Slang::getHashCode(kind), Slang::getHashCode(nameIndex)); } + + LookupKind kind; ///< The kind of lookup + Index nameIndex; ///< The name index in the pool + }; + enum class CategoryKind { Option, ///< Command line option (like "-D") @@ -74,6 +88,16 @@ struct CommandOptions Flags flags = 0; ///< Flags about this option }; + /// Get the first name + UnownedStringSlice getFirstNameForOption(Index optionIndex); + /// Get the first name for the category + UnownedStringSlice getFirstNameForCategory(Index categoryIndex); + + /// Get a name key for an opton + NameKey getNameKeyForOption(Index optionIndex); + /// Get a name key for a category + NameKey getNameKeyForCategory(Index optionIndex); + /// 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 @@ -100,14 +124,14 @@ struct CommandOptions void addValuesWithAliases(const ConstArrayView<NameValue>& values); /// Get the target index based off the name and the kind - Index findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name) const; + Index findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name, NameKey* outNameKey = nullptr) 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; + Index findOptionByName(const UnownedStringSlice& name) const { return findTargetIndexByName(LookupKind::Option, name); } /// 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); } @@ -139,9 +163,14 @@ struct CommandOptions /// Find all of the categories in the usage slice void findCategoryIndicesFromUsage(const UnownedStringSlice& usageSlice, List<Index>& outCategories) const; + + /// Splits usage into category slices + void splitUsage(const UnownedStringSlice& usageSlice, List<UnownedStringSlice>& outSlices) const; + /// Get all the option names associated with a category index void getCategoryOptionNames(Index categoryIndex, List<UnownedStringSlice>& outNames) const; - + void appendCategoryOptionNames(Index categoryIndex, List<UnownedStringSlice>& outNames) const; + /// Set up a lookup kind from a category index static LookupKind makeLookupKind(Index categoryIndex) { return LookupKind(categoryIndex); } @@ -160,6 +189,7 @@ struct CommandOptions { } + protected: /// Returns name in the m_optionPool or -1 on error SlangResult _addOptionName(const UnownedStringSlice& name, Flags flags, Index targetIndex); SlangResult _addValueName(const UnownedStringSlice& name, Index categoryIndex, Index targetIndex); @@ -175,19 +205,7 @@ struct CommandOptions UnownedStringSlice _addString(const char* text); UnownedStringSlice _addString(const UnownedStringSlice& slice); - /// A key type that uses the combination of the lookup kind and a name index. - /// Maps to a target index that could be a category or an option index. - struct NameKey - { - typedef NameKey ThisType; - - SLANG_FORCE_INLINE bool operator==(const ThisType& rhs) const { return kind == rhs.kind && nameIndex == rhs.nameIndex; } - SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } - HashCode getHashCode() const { return combineHash(Slang::getHashCode(kind), Slang::getHashCode(nameIndex)); } - - LookupKind kind; ///< The kind of lookup - Index nameIndex; ///< The name index in the pool - }; + Index _findTargetIndexByName(LookupKind kind, const UnownedStringSlice& name, NameKey* outNameKey) const; struct UserValueKey { @@ -216,38 +234,6 @@ struct CommandOptions MemoryArena m_arena; ///< For other misc storage }; -struct CommandOptionsWriter -{ - typedef CommandOptions::CategoryKind CategoryKind; - - /// Append descirption for a category - void appendDescriptionForCategory(const CommandOptions& options, Index categoryIndex); - /// Appends a description of all of the options - void appendDescription(const CommandOptions& options); - - /// Get the builder that string is being written to - StringBuilder& getBuilder() { return m_builder; } - - /// Ctor - CommandOptionsWriter(): - m_indentSlice(toSlice(" ")) - { - } - - Count _getCurrentLineLength(); - - void _appendWithWrap(Count indentCount, List<UnownedStringSlice>& slices, const UnownedStringSlice& delimit); - void _appendWithWrap(Count indentCount, List<UnownedStringSlice>& lines); - void _requireIndent(Count indentCount); - void _appendText(Count indentCount, const UnownedStringSlice& text); - - - UnownedStringSlice m_indentSlice; - Count m_lineLength = 80; - - StringBuilder m_builder; -}; - } // namespace Slang #endif diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp index 7de6846d5..ac8a176ad 100644 --- a/source/core/slang-string-util.cpp +++ b/source/core/slang-string-util.cpp @@ -35,6 +35,34 @@ namespace Slang { return areAllEqual(slicesA, slicesB, equalFn); } +/* static */void StringUtil::appendSplitOnWhitespace(const UnownedStringSlice& in, List<UnownedStringSlice>& outSlices) +{ + const char* start = in.begin(); + const char* end = in.end(); + + // Skip any at the start + while (start < end && CharUtil::isWhitespace(*start)) start++; + + while (start < end) + { + // Find all the non white space in a run + const char* cur = start; + while (cur < end && !CharUtil::isWhitespace(*cur)) + { + cur++; + } + + // Add to output + outSlices.add(UnownedStringSlice(start, cur)); + + // Find the next start + start = cur + 1; + + // Skip the split + while (start < end && CharUtil::isWhitespace(*start)) start++; + } +} + /* static */void StringUtil::appendSplit(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& outSlices) { const char* start = in.begin(); @@ -113,6 +141,12 @@ namespace Slang { appendSplit(in, splitSlice, outSlices); } +/* static */void StringUtil::splitOnWhitespace(const UnownedStringSlice& in, List<UnownedStringSlice>& outSlices) +{ + outSlices.clear(); + appendSplitOnWhitespace(in, outSlices); +} + /* static */Index StringUtil::split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices) { Index index = 0; diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h index 2293acf8d..4b9a4d5d1 100644 --- a/source/core/slang-string-util.h +++ b/source/core/slang-string-util.h @@ -32,6 +32,8 @@ struct StringUtil static Index split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices); /// Splits into outSlices up to maxSlices. Returns SLANG_OK if of 'in' consumed. static SlangResult split(const UnownedStringSlice& in, char splitChar, Index maxSlices, UnownedStringSlice* outSlices, Index& outSlicesCount); + /// Splits on white space + static void splitOnWhitespace(const UnownedStringSlice& in, List<UnownedStringSlice>& slicesOut); /// Split in, by specified splitChar append into slices out /// Slices contents will directly address into in, so contents will only stay valid as long as in does. @@ -40,6 +42,9 @@ struct StringUtil /// Slices contents will directly address into in, so contents will only stay valid as long as in does. static void appendSplit(const UnownedStringSlice& in, const UnownedStringSlice& splitSlice, List<UnownedStringSlice>& slicesOut); + /// appends splits on white space + static void appendSplitOnWhitespace(const UnownedStringSlice& in, List<UnownedStringSlice>& slicesOut); + /// Append the joining of in items, separated by 'separator' onto out static void join(const List<String>& in, char separator, StringBuilder& out); static void join(const List<String>& in, const UnownedStringSlice& separator, StringBuilder& out); diff --git a/source/core/slang-type-text-util.cpp b/source/core/slang-type-text-util.cpp index 172426bba..6743800b3 100644 --- a/source/core/slang-type-text-util.cpp +++ b/source/core/slang-type-text-util.cpp @@ -7,9 +7,6 @@ namespace Slang { - - - namespace { // anonymous #define SLANG_SCALAR_TYPES(x) \ @@ -66,17 +63,16 @@ static const TypeTextUtil::CompileTargetInfo s_compileTargetInfos[] = { SLANG_HOST_HOST_CALLABLE, "", "host-host-callable", "Host callable for host execution" }, }; -static const NamesValue s_languageInfos[] = +static const NamesDescriptionValue s_languageInfos[] = { - { SLANG_SOURCE_LANGUAGE_C, "c,C" }, - { SLANG_SOURCE_LANGUAGE_CPP, "cpp,c++,C++,cxx" }, - { SLANG_SOURCE_LANGUAGE_SLANG, "slang" }, - { SLANG_SOURCE_LANGUAGE_GLSL, "glsl" }, - { SLANG_SOURCE_LANGUAGE_HLSL, "hlsl" }, - { SLANG_SOURCE_LANGUAGE_CUDA, "cu,cuda" }, + { SLANG_SOURCE_LANGUAGE_C, "c,C", "C language" }, + { SLANG_SOURCE_LANGUAGE_CPP, "cpp,c++,C++,cxx", "C++ language" }, + { SLANG_SOURCE_LANGUAGE_SLANG, "slang", "Slang language" }, + { SLANG_SOURCE_LANGUAGE_GLSL, "glsl", "GLSL language" }, + { SLANG_SOURCE_LANGUAGE_HLSL, "hlsl", "HLSL language" }, + { SLANG_SOURCE_LANGUAGE_CUDA, "cu,cuda", "CUDA" }, }; - static const NamesDescriptionValue s_compilerInfos[] = { { SLANG_PASS_THROUGH_NONE, "none", "Unknown" }, @@ -91,15 +87,14 @@ static const NamesDescriptionValue s_compilerInfos[] = { SLANG_PASS_THROUGH_LLVM, "llvm", "LLVM/Clang `slang-llvm`" }, }; -static const NameValue s_archiveTypeInfos[] = +static const NamesDescriptionValue s_archiveTypeInfos[] = { - { SLANG_ARCHIVE_TYPE_RIFF_DEFLATE, "riff-deflate"}, - { SLANG_ARCHIVE_TYPE_RIFF_LZ4, "riff-lz4"}, - { SLANG_ARCHIVE_TYPE_ZIP, "zip"}, - { SLANG_ARCHIVE_TYPE_RIFF, "riff"}, + { SLANG_ARCHIVE_TYPE_RIFF_DEFLATE, "riff-deflate", "Slang RIFF using deflate compression" }, + { SLANG_ARCHIVE_TYPE_RIFF_LZ4, "riff-lz4", "Slang RIFF using LZ4 compression" }, + { SLANG_ARCHIVE_TYPE_ZIP, "zip", "Zip file" }, + { SLANG_ARCHIVE_TYPE_RIFF, "riff", "Slang RIFF without compression" }, }; - static const NamesDescriptionValue s_debugInfoFormatInfos[] = { { SLANG_DEBUG_INFO_FORMAT_DEFAULT, "default-format", "Use the default debugging format for the target" }, @@ -134,14 +129,42 @@ static const NamesDescriptionValue s_floatingPointModes[] = "Default floating point mode" } }; +static const NamesDescriptionValue s_optimizationLevels[] = +{ + { SLANG_OPTIMIZATION_LEVEL_NONE, "0,none", "Disable all optimizations" }, + { SLANG_OPTIMIZATION_LEVEL_DEFAULT, "1,default", "Enable a default level of optimization.This is the default if no -o options are used." }, + { SLANG_OPTIMIZATION_LEVEL_HIGH, "2,high", "Enable aggressive optimizations for speed." }, + { SLANG_OPTIMIZATION_LEVEL_MAXIMAL, "3,maximal", "Enable further optimizations, which might have a significant impact on compile time, or involve unwanted tradeoffs in terms of code size." }, +}; + +static const NamesDescriptionValue s_debugLevels[] = +{ + { SLANG_DEBUG_INFO_LEVEL_NONE, "0,none", "Don't emit debug information at all." }, + { SLANG_DEBUG_INFO_LEVEL_MINIMAL, "1,minimal", "Emit as little debug information as possible, while still supporting stack traces." }, + { SLANG_DEBUG_INFO_LEVEL_STANDARD, "2,standard", "Emit whatever is the standard level of debug information for each target." }, + { SLANG_DEBUG_INFO_LEVEL_MAXIMAL, "3,maximal", "Emit as much debug information as possible for each target." }, +}; + +static const NamesDescriptionValue s_fileSystemTypes[] = +{ + { ValueInt(TypeTextUtil::FileSystemType::Default), "default", "Default fike system." }, + { ValueInt(TypeTextUtil::FileSystemType::LoadFile), "load-file", "Just implements loadFile interface, so will be wrapped with CacheFileSystem internally." }, + { ValueInt(TypeTextUtil::FileSystemType::Os), "os", "Use the OS based file system directly (without file system caching)" }, +}; + } // anonymous +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getFileSystemTypeInfos() +{ + return makeConstArrayView(s_fileSystemTypes); +} + /* static */ConstArrayView<TypeTextUtil::CompileTargetInfo> TypeTextUtil::getCompileTargetInfos() { return makeConstArrayView(s_compileTargetInfos); } -/* static */ConstArrayView<NamesValue> TypeTextUtil::getLanguageInfos() +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getLanguageInfos() { return makeConstArrayView(s_languageInfos); } @@ -151,7 +174,7 @@ static const NamesDescriptionValue s_floatingPointModes[] = return makeConstArrayView(s_compilerInfos); } -/* static */ConstArrayView<NameValue> TypeTextUtil::getArchiveTypeInfos() +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getArchiveTypeInfos() { return makeConstArrayView(s_archiveTypeInfos); } @@ -171,6 +194,16 @@ static const NamesDescriptionValue s_floatingPointModes[] = return makeConstArrayView(s_floatingPointModes); } +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getOptimizationLevelInfos() +{ + return makeConstArrayView(s_optimizationLevels); +} + +/* static */ConstArrayView<NamesDescriptionValue> TypeTextUtil::getDebugLevelInfos() +{ + return makeConstArrayView(s_debugLevels); +} + /* static */SlangArchiveType TypeTextUtil::findArchiveType(const UnownedStringSlice& slice) { return NameValueUtil::findValue(getArchiveTypeInfos(), slice, SLANG_ARCHIVE_TYPE_UNDEFINED); diff --git a/source/core/slang-type-text-util.h b/source/core/slang-type-text-util.h index 2249b1215..cf146fb46 100644 --- a/source/core/slang-type-text-util.h +++ b/source/core/slang-type-text-util.h @@ -13,6 +13,13 @@ namespace Slang /// Utility class to allow conversion of types (such as enums) to and from text types struct TypeTextUtil { + enum class FileSystemType + { + Default, + LoadFile, + Os, + }; + struct CompileTargetInfo { SlangCompileTarget target; ///< The target @@ -25,17 +32,23 @@ struct TypeTextUtil static ConstArrayView<CompileTargetInfo> getCompileTargetInfos(); /// Get the language infos - static ConstArrayView<NamesValue> getLanguageInfos(); + static ConstArrayView<NamesDescriptionValue> getLanguageInfos(); /// Get the compiler infos static ConstArrayView<NamesDescriptionValue> getCompilerInfos(); /// Get the archive type infos - static ConstArrayView<NameValue> getArchiveTypeInfos(); + static ConstArrayView<NamesDescriptionValue> getArchiveTypeInfos(); /// Get the debug format types static ConstArrayView<NamesDescriptionValue> getDebugInfoFormatInfos(); - + /// Get the debug levels + static ConstArrayView<NamesDescriptionValue> getDebugLevelInfos(); + /// Get the floating point modes static ConstArrayView<NamesDescriptionValue> getFloatingPointModeInfos(); - + // Get the line directive infos static ConstArrayView<NamesDescriptionValue> getLineDirectiveInfos(); + /// Get the optimization level info + static ConstArrayView<NamesDescriptionValue> getOptimizationLevelInfos(); + /// Get the file system type infos + static ConstArrayView<NamesDescriptionValue> getFileSystemTypeInfos(); /// Get the scalar type as text. static Slang::UnownedStringSlice getScalarTypeName(slang::TypeReflection::ScalarType scalarType); diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 8738c71cc..0b6494ad5 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -70,14 +70,12 @@ DIAGNOSTIC( 14, Error, unknownProfile, "unknown profile '$0'") DIAGNOSTIC( 15, Error, unknownStage, "unknown stage '$0'") DIAGNOSTIC( 16, Error, unknownPassThroughTarget, "unknown pass-through target '$0'") DIAGNOSTIC( 17, Error, unknownCommandLineOption, "unknown command-line option '$0'") -DIAGNOSTIC( 18, Error, unknownFileSystemOption, "unknown file-system option '$0'") DIAGNOSTIC( 19, Error, unknownSourceLanguage, "unknown source language '$0'") DIAGNOSTIC( 20, Error, entryPointsNeedToBeAssociatedWithTranslationUnits, "when using multiple source files, entry points must be specified after their corresponding source file(s)") DIAGNOSTIC( 22, Error, unknownDownstreamCompiler, "unknown downstream compiler '$0'") DIAGNOSTIC( 26, Error, unknownOptimiziationLevel, "unknown optimization level '$0'") -DIAGNOSTIC( 27, Error, unknownDebugInfoLevel, "unknown debug info level '$0'") DIAGNOSTIC( 28, Error, unableToGenerateCodeForTarget, "unable to generate code for target '$0'") diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index 6cb53496f..d6486ba18 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -32,7 +32,9 @@ #include "../core/slang-string-slice-pool.h" #include "../core/slang-char-util.h" -#include "../core/slang-command-options.h" +#include "../core/slang-name-value.h" + +#include "../core/slang-command-options-writer.h" #include <assert.h> @@ -49,6 +51,7 @@ enum class OptionKind DepFile, EntryPointName, Help, + HelpStyle, Include, Language, MatrixLayoutColumn, @@ -151,6 +154,10 @@ enum class ValueCategory Stage, LineDirectiveMode, DebugInfoFormat, + HelpStyle, + OptimizationLevel, + DebugLevel, + FileSystemType, CountOf, }; @@ -200,6 +207,18 @@ void initCommandOptions(CommandOptions& options) options.addCategory(CategoryKind::Value, "fp-mode", "Floating Point Mode", UserValue(ValueCategory::FloatingPointMode)); options.addValues(TypeTextUtil::getFloatingPointModeInfos()); + + options.addCategory(CategoryKind::Value, "help-style", "Help Style", UserValue(ValueCategory::HelpStyle)); + options.addValues(CommandOptionsWriter::getStyleInfos()); + + options.addCategory(CategoryKind::Value, "optimization-level", "Optimization Level", UserValue(ValueCategory::OptimizationLevel)); + options.addValues(TypeTextUtil::getOptimizationLevelInfos()); + + options.addCategory(CategoryKind::Value, "debug-level", "Debug Level", UserValue(ValueCategory::DebugLevel)); + options.addValues(TypeTextUtil::getDebugLevelInfos()); + + options.addCategory(CategoryKind::Value, "file-system-type", "File System Type", UserValue(ValueCategory::FileSystemType)); + options.addValues(TypeTextUtil::getFileSystemTypeInfos()); } /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! target !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ @@ -307,15 +326,20 @@ void initCommandOptions(CommandOptions& options) const Option generalOpts[] = { - { OptionKind::MacroDefine, "-D?...", "-D<name>[=<value>], -D <name>[=<value>]", "Insert a preprocessor macro." }, + { OptionKind::MacroDefine, "-D?...", "-D<name>[=<value>], -D <name>[=<value>]", + "Insert a preprocessor macro.\n" + "The space between - D and <name> is optional. If no <value> is specified, Slang will define the macro with an empty value." }, { OptionKind::DepFile, "-depfile", "-depfile <path>", "Save the source file dependency list in a file." }, { OptionKind::EntryPointName, "-entry", "-entry <name>", "Specify the name of an entry-point function.\n" + "When compiling from a single file, this defaults to main if you specify a stage using -stage.\n" "Multiple -entry options may be used in a single invocation. " + "When they do, the file associated with the entry point will be the first one found when searching to the left in the command line.\n" "If no -entry options are given, compiler will use [shader(...)] " "attributes to detect entry points."}, { OptionKind::EmitIr, "-emit-ir", nullptr, "Emit IR typically as a '.slang-module' when outputting to a container." }, { OptionKind::Help, "-h,-help,--help", "-h or -h <help-category>", "Print this message, or help in specified category." }, + { OptionKind::HelpStyle, "-help-style", "-help-style <help-style>", "Help formatting style" }, { OptionKind::Include, "-I?...", "-I<path>, -I <path>", "Add a path to be used in resolving '#include' " "and 'import' operations."}, @@ -336,10 +360,10 @@ void initCommandOptions(CommandOptions& options) { OptionKind::Profile, "-profile", "-profile <profile>[+<capability>...]", "Specify the shader profile for code generation.\n" "Accepted profiles are:\n" - " sm_{4_0,4_1,5_0,5_1,6_0,6_1,6_2,6_3,6_4,6_5,6_6}\n" - " glsl_{110,120,130,140,150,330,400,410,420,430,440,450,460}\n" + "* sm_{4_0,4_1,5_0,5_1,6_0,6_1,6_2,6_3,6_4,6_5,6_6}\n" + "* glsl_{110,120,130,140,150,330,400,410,420,430,440,450,460}\n" "Additional profiles that include -stage information:\n" - " {vs,hs,ds,gs,ps}_<version>\n" + "* {vs,hs,ds,gs,ps}_<version>\n" "See -capability for information on <capability>\n" "When multiple -target options are present, each -profile associates " "with the first -target to its left."}, @@ -350,7 +374,9 @@ void initCommandOptions(CommandOptions& options) "May be omitted if entry-point function has a [shader(...)] attribute; " "otherwise required for each -entry option."}, { OptionKind::Target, "-target", "-target <target>", "Specifies the format in which code should be generated."}, - { OptionKind::Version, "-v,-version", nullptr, "Display the build version."}, + { OptionKind::Version, "-v,-version", nullptr, + "Display the build version. This is the contents of git describe --tags.\n" + "It is typically only set from automated builds(such as distros available on github).A user build will by default be 'unknown'."}, { OptionKind::WarningsAsErrors, "-warnings-as-errors", "-warnings-as-errors all or -warnings-as-errors <id>[,<id>...]", "all - Treat all warnings as errors.\n" "<id>[,<id>...]: Treat specific warning ids as errors.\n"}, @@ -377,19 +403,17 @@ void initCommandOptions(CommandOptions& options) { OptionKind::DisableSpecialization, "-disable-specialization", nullptr, "Disables generics and specialization pass." }, { OptionKind::FloatingPointMode, "-fp-mode,-floating-point-mode", "-fp-mode <fp-mode>, -floating-point-mode <fp-mode>", "Control floating point optimizations"}, - { OptionKind::DebugInformation, "-g...", "-g, -g<N>, -g<debug-info-format>", + { OptionKind::DebugInformation, "-g...", "-g, -g<debug-info-format>, -g<debug-level>", "Include debug information in the generated code, where possible.\n" - "N is the amount of information, 0..3, unspecified means 2\n" + "<debug-level> is the amount of information, 0..3, unspecified means 2\n" "<debug-info-format> specifies a debugging info format\n" - "It is valid to have multiple -g options, such as a level and a <debug-info-format>" }, + "It is valid to have multiple -g options, such as a <debug-level> and a <debug-info-format>" }, { OptionKind::LineDirectiveMode, "-line-directive-mode", "-line-directive-mode <line-directive-mode>", "Sets how the `#line` directives should be produced. Available options are:\n" "If not specified, default behavior is to use C-style `#line` directives " "for HLSL and C/C++ output, and traditional GLSL-style `#line` directives " "for GLSL output." }, - { OptionKind::Optimization, "-O...", "-O<N>", - "Set the optimization level.\n" - "N is the amount of optimization, 0..3, default is 1" }, + { OptionKind::Optimization, "-O...", "-O<optimization-level>", "Set the optimization level."}, { OptionKind::Obfuscate, "-obfuscate", nullptr, "Remove all source file information from outputs." }, }; @@ -424,8 +448,9 @@ void initCommandOptions(CommandOptions& options) "to the downstream compiler. -X<compiler>... options -X. will pass *all* of the options " "inbetween the opening -X and -X. to the downstream compiler."}, { OptionKind::PassThrough, "-pass-through", "-pass-through <compiler>", - "Pass the input through mostly unmodified to the \n" - "existing compiler <compiler>." }, + "Pass the input through mostly unmodified to the " + "existing compiler <compiler>.\n" + "These are intended for debugging/testing purposes, when you want to be able to see what these existing compilers do with the \"same\" input and options"}, }; _addOptions(makeConstArrayView(downstreamOpts), options); @@ -470,9 +495,8 @@ void initCommandOptions(CommandOptions& options) { OptionKind::EmitSpirvDirectly, "-emit-spirv-directly", nullptr, "Generate SPIR-V output directly (otherwise through " "GLSL and using the glslang compiler)"}, - { OptionKind::FileSystem, "-file-system", "-file-system <fs>", - "Set the filesystem hook to use for a compile request.\n" - "Accepted file systems: default, load-file, os" }, + { OptionKind::FileSystem, "-file-system", "-file-system <file-system-type>", + "Set the filesystem hook to use for a compile request."}, { OptionKind::Heterogeneous, "-heterogeneous", nullptr, "Output heterogeneity-related code." }, { OptionKind::NoMangle, "-no-mangle", nullptr, "Do as little mangling of names as possible." } }; @@ -1087,11 +1111,11 @@ struct OptionsParser return SLANG_OK; } - SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, DiagnosticSink* sink, CommandOptions::UserValue& outValue) + SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, const UnownedStringSlice& name, DiagnosticSink* sink, CommandOptions::UserValue& outValue) { auto& cmdOptions = asInternal(session)->m_commandOptions; - const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), arg.value.getUnownedSlice()); + const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name); if (optionIndex < 0) { const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); @@ -1114,6 +1138,46 @@ struct OptionsParser outValue = cmdOptions.getOptionAt(optionIndex).userValue; return SLANG_OK; } + + SlangResult _getValue(ValueCategory valueCategory, const CommandLineArg& arg, DiagnosticSink* sink, CommandOptions::UserValue& outValue) + { + return _getValue(valueCategory, arg, arg.value.getUnownedSlice(), sink, outValue); + } + + SlangResult _getValue(const ConstArrayView<ValueCategory>& valueCategories, const CommandLineArg& arg, const UnownedStringSlice& name, DiagnosticSink* sink, ValueCategory& outCat, CommandOptions::UserValue& outValue) + { + auto& cmdOptions = asInternal(session)->m_commandOptions; + + for (auto valueCategory : valueCategories) + { + const auto optionIndex = cmdOptions.findOptionByCategoryUserValue(CommandOptions::UserValue(valueCategory), name); + if (optionIndex >= 0) + { + outCat = valueCategory; + outValue = cmdOptions.getOptionAt(optionIndex).userValue; + return SLANG_OK; + } + } + + List<UnownedStringSlice> names; + for (auto valueCategory : valueCategories) + { + const auto categoryIndex = cmdOptions.findCategoryByUserValue(CommandOptions::UserValue(valueCategory)); + SLANG_ASSERT(categoryIndex >= 0); + if (categoryIndex < 0) + { + return SLANG_FAIL; + } + cmdOptions.appendCategoryOptionNames(categoryIndex, names); + } + + StringBuilder buf; + StringUtil::join(names.getBuffer(), names.getCount(), toSlice(", "), buf); + + sink->diagnose(arg.loc, Diagnostics::unknownCommandLineValue, buf); + return SLANG_FAIL; + } + SlangResult _expectValue(ValueCategory valueCategory, CommandLineReader& reader, DiagnosticSink* sink, CommandOptions::UserValue& outValue) { CommandLineArg arg; @@ -1208,7 +1272,8 @@ struct OptionsParser // Get the options on the session CommandOptions& options = asInternal(session)->m_commandOptions; - + CommandOptionsWriter::Style helpStyle = CommandOptionsWriter::Style::Text; + auto frontEndReq = requestImpl->getFrontEndReq(); while (reader.hasArg()) @@ -1771,86 +1836,45 @@ struct OptionsParser UnownedStringSlice levelSlice = argValue.getUnownedSlice().tail(2); SlangOptimizationLevel level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; - const char c = levelSlice.getLength() == 1 ? levelSlice[0] : 0; - - switch (c) + if (levelSlice.getLength()) { - case '0': level = SLANG_OPTIMIZATION_LEVEL_NONE; break; - case '1': level = SLANG_OPTIMIZATION_LEVEL_DEFAULT; break; - case '2': level = SLANG_OPTIMIZATION_LEVEL_HIGH; break; - case '3': level = SLANG_OPTIMIZATION_LEVEL_MAXIMAL; break; - default: - { - sink->diagnose(arg.loc, Diagnostics::unknownOptimiziationLevel, arg.value); - return SLANG_FAIL; - } + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_getValue(ValueCategory::OptimizationLevel, arg, levelSlice, sink, value)); + level = SlangOptimizationLevel(value); } - + compileRequest->setOptimizationLevel(level); break; } case OptionKind::DebugInformation: { + auto name = argValue.getUnownedSlice().tail(2); + // Note: unlike with `-O` above, we have to consider that other // options might have names that start with `-g` and so cannot // just detect it as a prefix. - if (argValue == toSlice("-g")) + if (name.getLength() == 0) { // The default is standard compileRequest->setDebugInfoLevel(SLANG_DEBUG_INFO_LEVEL_STANDARD); } - else if (argValue.getLength() == 3 && argValue[2] >= '0' && argValue[2] <= '3') + else { - // Extract the digit into an index - const Index levelIndex = argValue[2] - '0'; - SLANG_ASSERT(levelIndex >= 0 && levelIndex <= 3); + CommandOptions::UserValue value; + ValueCategory valueCat; + ValueCategory valueCats[] = { ValueCategory::DebugLevel, ValueCategory::DebugInfoFormat }; + SLANG_RETURN_ON_FAIL(_getValue(makeConstArrayView(valueCats), arg, name, sink, valueCat, value)); - // Map indices to enum values - const SlangDebugInfoLevel levels[] = + if (valueCat == ValueCategory::DebugLevel) { - SLANG_DEBUG_INFO_LEVEL_NONE, - SLANG_DEBUG_INFO_LEVEL_MINIMAL, - SLANG_DEBUG_INFO_LEVEL_STANDARD, - SLANG_DEBUG_INFO_LEVEL_MAXIMAL - }; - - const auto level = levels[levelIndex]; - compileRequest->setDebugInfoLevel(level); - } - else - { - // Perhaps it's trying to specify a format - auto formatName = argValue.getUnownedSlice().tail(2); - - SlangDebugInfoFormat format; - if (SLANG_FAILED(TypeTextUtil::findDebugInfoFormat(formatName, format))) + const auto level = (SlangDebugInfoLevel)value; + compileRequest->setDebugInfoLevel(level); + } + else { - List<String> debugOptions; - - debugOptions.add(toSlice("-g")); - - for (Int i = 0; i <= 3; ++i) - { - StringBuilder buf; - buf << toSlice("-g") << i; - debugOptions.add(buf); - } - - for (Index i = 0; i < SLANG_DEBUG_INFO_FORMAT_COUNT_OF; ++i) - { - StringBuilder buf; - buf << toSlice("-g") << TypeTextUtil::getDebugInfoFormatName(SlangDebugInfoFormat(i)); - debugOptions.add(buf); - } - - StringBuilder buf; - StringUtil::join(debugOptions, toSlice(", "), buf); - - sink->diagnose(arg.loc, Diagnostics::unknownDebugOption, buf); - return SLANG_FAIL; + const auto debugFormat = (SlangDebugInfoFormat)value; + compileRequest->setDebugInfoFormat(debugFormat); } - - compileRequest->setDebugInfoFormat(format); } break; } @@ -1858,27 +1882,15 @@ struct OptionsParser case OptionKind::Obfuscate: requestImpl->getLinkage()->m_obfuscateCode = true; break; case OptionKind::FileSystem: { - CommandLineArg name; - SLANG_RETURN_ON_FAIL(reader.expectArg(name)); + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::FileSystemType, reader, sink, value)); + typedef TypeTextUtil::FileSystemType FileSystemType; - if (name.value == "default") - { - compileRequest->setFileSystem(nullptr); - } - else if (name.value == "load-file") + switch (FileSystemType(value)) { - // 'Simple' just implements loadFile interface, so will be wrapped with CacheFileSystem internally - compileRequest->setFileSystem(OSFileSystem::getLoadSingleton()); - } - else if (name.value == "os") - { - // 'Immutable' implements the ISlangFileSystemExt interface - and will be used directly - compileRequest->setFileSystem(OSFileSystem::getExtSingleton()); - } - else - { - sink->diagnose(name.loc, Diagnostics::unknownFileSystemOption, name.value); - return SLANG_FAIL; + case FileSystemType::Default: compileRequest->setFileSystem(nullptr); break; + case FileSystemType::LoadFile: compileRequest->setFileSystem(OSFileSystem::getLoadSingleton()); break; + case FileSystemType::Os: compileRequest->setFileSystem(OSFileSystem::getExtSingleton()); break; } break; } @@ -1950,9 +1962,15 @@ struct OptionsParser sink->diagnoseRaw(Severity::Note, session->getBuildTagString()); break; } + case OptionKind::HelpStyle: + { + CommandOptions::UserValue value; + SLANG_RETURN_ON_FAIL(_expectValue(ValueCategory::HelpStyle, reader, sink, value)); + helpStyle = CommandOptionsWriter::Style(value); + break; + } case OptionKind::Help: { - Index categoryIndex = -1; if (reader.hasArg()) @@ -1960,24 +1978,48 @@ struct OptionsParser auto catArg = reader.getArgAndAdvance(); categoryIndex = options.findCategoryByCaseInsensitiveName(catArg.value.getUnownedSlice()); - if (categoryIndex) + if (categoryIndex < 0) { sink->diagnose(catArg.loc, Diagnostics::unknownHelpCategory); return SLANG_FAIL; } } - CommandOptionsWriter writer; - auto& buf = writer.getBuilder(); + CommandOptionsWriter::Options writerOptions; + writerOptions.style = helpStyle; + + auto writer = CommandOptionsWriter::create(writerOptions); + + auto& buf = writer->getBuilder(); if (categoryIndex < 0) { - _appendUsageTitle(buf); - writer.appendDescription(options); + // If it's the text style we can inject usage at the top + if (helpStyle == CommandOptionsWriter::Style::Text) + { + _appendUsageTitle(buf); + } + else + { + // NOTE! We need this preamble because if we have links, + // we have to make sure the first thing in markdown *isn't* <> + + buf << "# Slang Command Line Options\n\n"; + buf << "*Usage:*\n"; + buf << "```\n"; + buf << "slangc [options...] [--] <input files>\n\n"; + buf << "# For help\n"; + buf << "slangc -h\n\n"; + buf << "# To generate this file\n"; + buf << "slangc -help-style markdown -h\n"; + buf << "```\n"; + } + + writer->appendDescription(&options); } else { - writer.appendDescriptionForCategory(options, categoryIndex); + writer->appendDescriptionForCategory(&options, categoryIndex); } sink->diagnoseRaw(Severity::Note, buf.getBuffer()); |
