diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-03-05 14:34:46 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-05 14:34:46 -0500 |
| commit | 860d17b6876822ef7023fdce70c725d3f8be37b1 (patch) | |
| tree | 1ed058603b789a59103886c8e1f77a3d4e69adb1 /source | |
| parent | dc7110858ecdb7c7567de360787b9adc4defa04a (diff) | |
Doc tooling improvements (#1734)
* #include an absolute path didn't work - because paths were taken to always be relative.
* Split out AST 'printing'.
* Replace listener with List<Section>
* Section -> Part.
* Kind -> Type Flags -> Kind for ASTPrinter::Part
* Improve comments around ASTPrinter.
* toString -> toText on Val derived types. toText appends to a StringBuilder.
* Added toSlice free function.
Added operator<< for Val derived types.
Use << where appropriate in doing toText.
* More work at mark down output.
* Fill in sourceloc for enum case.
Add more sophisticated location determination for EnumCase.
Refactored documentation output into DocMarkdownWriter.
* Improvements for sig output.
* Split up slang-doc into extractor and writer.
* WIP generic support for doc support.
* Some refactoring to make DocExtractor have potential to be used without Decls.
* Made doc extraction work without Decls.
* Output generic parameters.
* Add generic parameter extraction.
* Added writing variables.
* Add an interface test.
* Fix toArray.
* Support for extensions, and inheritance.
* Disable the doc test.
Co-authored-by: Tim Foley <tfoleyNV@users.noreply.github.com>
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-ast-support-types.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-doc-extractor.cpp (renamed from source/slang/slang-doc.cpp) | 734 | ||||
| -rw-r--r-- | source/slang/slang-doc-extractor.h | 210 | ||||
| -rw-r--r-- | source/slang/slang-doc-mark-down.cpp | 477 | ||||
| -rw-r--r-- | source/slang/slang-doc-mark-down.h | 70 | ||||
| -rw-r--r-- | source/slang/slang-doc.h | 73 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 12 |
7 files changed, 963 insertions, 617 deletions
diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index 6e4c90cc4..6697e878a 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -840,9 +840,9 @@ namespace Slang /// Returns true if non empty (equivalent to getCount() != 0 but faster) bool isNonEmpty() const { return !isEmpty(); } - List<RefPtr<T>> toArray() + List<T*> toList() { - List<RefPtr<T>> result; + List<T*> result; for (auto element : (*this)) { result.add(element); diff --git a/source/slang/slang-doc.cpp b/source/slang/slang-doc-extractor.cpp index c72250122..f44257877 100644 --- a/source/slang/slang-doc.cpp +++ b/source/slang/slang-doc-extractor.cpp @@ -1,5 +1,5 @@ // slang-doc.cpp -#include "slang-doc.h" +#include "slang-doc-extractor.h" #include "../core/slang-string-util.h" @@ -18,125 +18,6 @@ namespace Slang { ** This will need to display the decoration appropriately */ -/* Extracts 'markup' from comments in Slang source core. The comments are extracted and associated in declarations. The association -is held in DocMarkup type. The comment style follows the doxygen style */ -class DocMarkupExtractor -{ -public: - - typedef uint32_t MarkupFlags; - struct MarkupFlag - { - enum Enum : MarkupFlags - { - Before = 0x1, - After = 0x2, - IsMultiToken = 0x4, ///< Can use more than one token - IsBlock = 0x8, ///< - }; - }; - - // NOTE! Don't change order without fixing isBefore and isAfter - enum class MarkupType - { - None, - - BlockBefore, /// /** */ or /*! */. - LineBangBefore, /// //! Can be multiple lines - LineSlashBefore, /// /// Can be multiple lines - - BlockAfter, /// /*!< */ or /**< */ - LineBangAfter, /// //!< Can be multiple lines - LineSlashAfter, /// ///< Can be multiple lines - }; - - static bool isBefore(MarkupType type) { return Index(type) >= Index(MarkupType::BlockBefore) && Index(type) <= Index(MarkupType::LineSlashBefore); } - static bool isAfter(MarkupType type) { return Index(type) >= Index(MarkupType::BlockAfter); } - - struct IndexRange - { - SLANG_FORCE_INLINE Index getCount() const { return end - start; } - - Index start; - Index end; - }; - - enum class Location - { - None, ///< No defined location - Before, - AfterParam, ///< Can have trailing , or ) - AfterSemicolon, ///< Can have a trailing ; - AfterEnumCase, ///< Can have a , or before } - }; - - static bool isAfter(Location location) { return Index(location) >= Index(Location::AfterParam); } - static bool isBefore(Location location) { return location == Location::Before; } - - struct FoundMarkup - { - void reset() - { - location = Location::None; - type = MarkupType::None; - range = IndexRange { 0, 0 }; - } - - Location location = Location::None; - MarkupType type = MarkupType::None; - IndexRange range; - }; - - struct FindInfo - { - - SourceView* sourceView; ///< The source view the tokens were generated from - TokenList* tokenList; ///< The token list - Index declTokenIndex; ///< The token index location (where searches start from) - Index declLineIndex; ///< The line number for the decl - }; - - SlangResult extract(DocMarkup* doc, ModuleDecl* moduleDecl, SourceManager* sourceManager, DiagnosticSink* sink); - - static MarkupFlags getFlags(MarkupType type); - static MarkupType findMarkupType(const Token& tok); - static UnownedStringSlice removeStart(MarkupType type, const UnownedStringSlice& comment); - -protected: - /// returns SLANG_E_NOT_FOUND if not found, SLANG_OK on success else an error - SlangResult _findMarkup(const FindInfo& info, Location location, FoundMarkup& out); - - /// Locations are processed in order, and the first successful used. If found in another location will issue a warning. - /// returns SLANG_E_NOT_FOUND if not found, SLANG_OK on success else an error - SlangResult _findFirstMarkup(const FindInfo& info, const Location* locs, Index locCount, FoundMarkup& out, Index& outIndex); - - SlangResult _findMarkup(const FindInfo& info, const Location* locs, Index locCount, FoundMarkup& out); - - /// Given the decl, the token stream, and the decls tokenIndex, try to find some associated markup - SlangResult _findMarkup(const FindInfo& info, Decl* decl, FoundMarkup& out); - - /// Given a found markup location extracts the contents of the tokens into out - SlangResult _extractMarkup(const FindInfo& info, const FoundMarkup& foundMarkup, StringBuilder& out); - - /// Given a location, try to find the first token index that could potentially be markup - /// Will return -1 if not found - Index _findStartIndex(const FindInfo& info, Location location); - - /// True if the tok is 'on' lineIndex. Interpretation of 'on' depends on the markup type. - static bool _isTokenOnLineIndex(SourceView* sourceView, MarkupType type, const Token& tok, Index lineIndex); - - void _addDecl(Decl* decl); - void _addDeclRec(Decl* decl); - void _findDecls(ModuleDecl* moduleDecl); - - List<Decl*> m_decls; - - DocMarkup* m_doc; - ModuleDecl* m_moduleDecl; - SourceManager* m_sourceManager; - DiagnosticSink* m_sink; -}; - /* static */UnownedStringSlice DocMarkupExtractor::removeStart(MarkupType type, const UnownedStringSlice& comment) { switch (type) @@ -186,50 +67,6 @@ protected: return comment; } -void DocMarkupExtractor::_addDecl(Decl* decl) -{ - if (!decl->loc.isValid()) - { - return; - } - m_decls.add(decl); -} - -void DocMarkupExtractor::_addDeclRec(Decl* decl) -{ - // Just add. - // There may be things we don't want to add, but just add them all of now - _addDecl(decl); - -#if 0 - if (CallableDecl* callableDecl = as<CallableDecl>(decl)) - { - // For callables (like functions), - - m_decls.add(callableDecl); - } - else -#endif - - if (ContainerDecl* containerDecl = as<ContainerDecl>(decl)) - { - // Add the container - which could be a class, struct, enum, namespace, extension, generic etc. - // Now add what the container contains - for (Decl* childDecl : containerDecl->members) - { - _addDeclRec(childDecl); - } - } -} - -void DocMarkupExtractor::_findDecls(ModuleDecl* moduleDecl) -{ - for (Decl* decl : moduleDecl->members) - { - _addDeclRec(decl); - } -} - static Index _findTokenIndex(SourceLoc loc, const Token* toks, Index numToks) { // Use a binary search to find the token @@ -471,11 +308,10 @@ SlangResult DocMarkupExtractor::_extractMarkup(const FindInfo& info, const Found Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location location) { - Index openParensCount = 0; - Index openBracketCount = 0; + Index openCount = 0; const TokenList& toks = *info.tokenList; - const Index tokIndex = info.declTokenIndex; + const Index tokIndex = info.tokenIndex; Index direction = isBefore(location) ? -1 : 1; @@ -486,72 +322,87 @@ Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location locatio switch (tok.type) { - case TokenType::BlockComment: - case TokenType::LineComment: - { - if (openParensCount == 0 && openBracketCount == 0) - { - // Determine the markup type - const MarkupType markupType = findMarkupType(tok); - // If the location wanted is before and the markup is, we'll assume this is it - if (isBefore(location) && isBefore(markupType)) - { - return i; - } - // If we are looking for enum cases, and the markup is after, we'll assume this is it - if (location == Location::AfterEnumCase && isAfter(markupType)) - { - return i; - } - } - break; - } + case TokenType::LBracket: case TokenType::LParent: + case TokenType::OpLess: { - ++openParensCount; + openCount += direction; + if (openCount < 0) return -1; break; } case TokenType::RBracket: { - openBracketCount += Index(isBefore(location)); + openCount -= direction; + if (openCount < 0) return -1; break; } - case TokenType::LBracket: + case TokenType::OpGreater: { - openBracketCount -= Index(isBefore(location)); + if (location == Location::AfterGenericParam && openCount == 0) + { + return i + 1; + } + + openCount -= direction; + if (openCount < 0) return -1; + break; } case TokenType::RParent: { - if (openParensCount == 0 && - location == Location::AfterParam) + if (openCount == 0 && location == Location::AfterParam) { return i + 1; } - --openParensCount; - if (openParensCount < 0) + openCount -= direction; + if (openCount < 0) return -1; + break; + } + case TokenType::RBrace: + { + // If we haven't hit a candidate yet before hitting } it's not going to work + if (location == Location::Before || location == Location::AfterEnumCase) { - // Not found - or weird parens at least return -1; } break; } - case TokenType::Comma: + case TokenType::BlockComment: + case TokenType::LineComment: { - if (location == Location::AfterParam || location == Location::AfterEnumCase) + if (openCount == 0) { - return i + 1; + // Determine the markup type + const MarkupType markupType = findMarkupType(tok); + // If the location wanted is before and the markup is, we'll assume this is it + if (isBefore(location) && isBefore(markupType)) + { + return i; + } + // If we are looking for enum cases, and the markup is after, we'll assume this is it + if (isAfter(location) && isAfter(markupType)) + { + return i; + } } break; } - case TokenType::RBrace: + case TokenType::Comma: { - // If we haven't hit a candidate yet before hitting } it's not going to work - if (location == Location::Before || location == Location::AfterEnumCase) + if (openCount == 0) + { + if (location == Location::AfterParam || location == Location::AfterEnumCase || location == Location::AfterGenericParam) + { + return i + 1; + } + } + + if (location == Location::Before) { return -1; } + break; } case TokenType::Semicolon: @@ -561,8 +412,7 @@ Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location locatio { return -1; } - - if (openParensCount == 0 && location == Location::AfterSemicolon) + if (openCount == 0 && location == Location::AfterSemicolon) { return i + 1; } @@ -594,13 +444,12 @@ Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location locatio } } - SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, Location location, FoundMarkup& out) { out.reset(); const auto& toks = info.tokenList->m_tokens; - const Index tokIndex = info.declTokenIndex; + const Index tokIndex = info.tokenIndex; // The starting token index Index startIndex = _findStartIndex(info, location); @@ -734,75 +583,104 @@ SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, const Location return SLANG_OK; } -SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, Decl* decl, FoundMarkup& out) +/* static */DocMarkupExtractor::SearchStyle DocMarkupExtractor::getSearchStyle(Decl* decl) { if (auto enumCaseDecl = as<EnumCaseDecl>(decl)) { - Location locs[] = { Location::Before, Location::AfterEnumCase }; - return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + return SearchStyle::EnumCase; } if (auto paramDecl = as<ParamDecl>(decl)) { - Location locs[] = { Location::Before, Location::AfterParam }; - return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + return SearchStyle::Param; } else if (auto callableDecl = as<CallableDecl>(decl)) { - // We allow it defined before - return _findMarkup(info, Location::Before, out); + return SearchStyle::Function; } else if (as<VarDecl>(decl) || as<TypeDefDecl>(decl) || as<AssocTypeDecl>(decl)) { - Location locs[] = { Location::Before, Location::AfterSemicolon }; - return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + return SearchStyle::Variable; + } + else if (auto genericDecl = as<GenericDecl>(decl)) + { + return getSearchStyle(genericDecl->inner); + } + else if (as<GenericTypeParamDecl>(decl) || as<GenericValueParamDecl>(decl)) + { + return SearchStyle::GenericParam; } else { - // We'll only allow before - return _findMarkup(info, Location::Before, out); + // If can't determine just allow before + return SearchStyle::Before; } } -SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, SourceManager* sourceManager, DiagnosticSink* sink) +SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, SearchStyle searchStyle, FoundMarkup& out) { - m_doc = doc; - m_moduleDecl = moduleDecl; - m_sourceManager = sourceManager; - m_sink = sink; - - _findDecls(moduleDecl); + switch (searchStyle) + { + default: + case SearchStyle::None: + { + return SLANG_E_NOT_FOUND; + } + case SearchStyle::EnumCase: + { + Location locs[] = { Location::Before, Location::AfterEnumCase }; + return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + } + case SearchStyle::Param: + { + Location locs[] = { Location::Before, Location::AfterParam }; + return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + } + case SearchStyle::Before: + case SearchStyle::Function: + { + return _findMarkup(info, Location::Before, out); + } + case SearchStyle::Variable: + { + Location locs[] = { Location::Before, Location::AfterSemicolon }; + return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + } + case SearchStyle::GenericParam: + { + Location locs[] = { Location::Before, Location::AfterGenericParam }; + return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); + } + } +} +SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inputCount, SourceManager* sourceManager, DiagnosticSink* sink, List<SourceView*>& outViews, List<SearchItemOutput>& out) +{ struct Entry { typedef Entry ThisType; - bool operator<(const ThisType& rhs) const { return locOrOffset < rhs.locOrOffset; } - Index viewIndex; ///< The view/file index this loc is found in SourceLoc::RawValue locOrOffset; ///< Can be a loc or an offset into the file - Decl* decl; ///< The decl + SearchStyle searchStyle; ///< The search style when looking for an item + Index inputIndex; ///< The index to this item in the input }; List<Entry> entries; { - const Index count = m_decls.getCount(); - entries.setCount(count); - - for (Index i = 0; i < count; ++i) + entries.setCount(inputCount); + for (Index i = 0; i < inputCount; ++i) { + const auto& input = inputs[i]; Entry& entry = entries[i]; - auto decl = m_decls[i]; - entry.decl = decl; + entry.inputIndex = i; entry.viewIndex = -1; //< We don't know what file/view it's in - entry.locOrOffset = decl->loc.getRaw(); + entry.locOrOffset = input.sourceLoc.getRaw(); + entry.searchStyle = input.searchStyle; } } - // We hold one view per *SourceFile* - List<SourceView*> views; - // Sort them into loc order entries.sort([](Entry& a, Entry& b) { return a.locOrOffset < b.locOrOffset; }); @@ -817,19 +695,19 @@ SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, if (sourceView == nullptr || !sourceView->getRange().contains(loc)) { // Find the new view - sourceView = m_sourceManager->findSourceView(loc); + sourceView = sourceManager->findSourceView(loc); SLANG_ASSERT(sourceView); // We want only one view per SourceFile SourceFile* sourceFile = sourceView->getSourceFile(); // NOTE! The view found might be different than sourceView. - viewIndex = views.findFirstIndex([&](SourceView* currentView) -> bool { return currentView->getSourceFile() == sourceFile; }); + viewIndex = outViews.findFirstIndex([&](SourceView* currentView) -> bool { return currentView->getSourceFile() == sourceFile; }); if (viewIndex < 0) { - viewIndex = views.getCount(); - views.add(sourceView); + viewIndex = outViews.getCount(); + outViews.add(sourceView); } } @@ -857,12 +735,28 @@ SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, Index viewIndex = -1; SourceView* sourceView = nullptr; - for (auto& entry : entries) + const Int entryCount = entries.getCount(); + + out.setCount(entryCount); + + for (Index i = 0; i < entryCount; ++i) { + const auto& entry = entries[i]; + auto& dst = out[i]; + + dst.viewIndex = -1; + dst.inputIndex = entry.inputIndex; + + // If there isn't a mechanism to search with, just move on + if (entry.searchStyle == SearchStyle::None) + { + continue; + } + if (viewIndex != entry.viewIndex) { viewIndex = entry.viewIndex; - sourceView = views[viewIndex]; + sourceView = outViews[viewIndex]; // Make all memory free again memoryArena.reset(); @@ -875,6 +769,8 @@ SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, tokens = lexer.lexAllTokens(); } + dst.viewIndex = viewIndex; + // Get the offset within the source file const uint32_t offset = entry.locOrOffset; @@ -890,14 +786,14 @@ SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, if (tokenIndex >= 0 && lineIndex >= 0) { FindInfo findInfo; - findInfo.declTokenIndex = tokenIndex; - findInfo.declLineIndex = lineIndex; + findInfo.tokenIndex = tokenIndex; + findInfo.lineIndex = lineIndex; findInfo.tokenList = &tokens; findInfo.sourceView = sourceView; // Okay let's see if we extract some documentation then for this. FoundMarkup foundMarkup; - SlangResult res = _findMarkup(findInfo, entry.decl, foundMarkup); + SlangResult res = _findMarkup(findInfo, entry.searchStyle, foundMarkup); if (SLANG_SUCCEEDED(res)) { @@ -905,9 +801,9 @@ SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, StringBuilder buf; SLANG_RETURN_ON_FAIL(_extractMarkup(findInfo, foundMarkup, buf)); - // Add to the documentation - DocMarkup::Entry& docEntry = m_doc->addEntry(entry.decl); - docEntry.m_markup = buf; + // Save the extracted text in the output + dst.text = buf; + } else if (res != SLANG_E_NOT_FOUND) { @@ -916,333 +812,99 @@ SlangResult DocMarkupExtractor::extract(DocMarkup* doc, ModuleDecl* moduleDecl, } } } - - return SLANG_OK; -} -SlangResult DocMarkup::extract(ModuleDecl* moduleDecl, SourceManager* sourceManager, DiagnosticSink* sink) -{ - m_moduleDecl = moduleDecl; - - DocMarkupExtractor context; - return context.extract(this, moduleDecl, sourceManager, sink); + return SLANG_OK; } -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DocMarkDownWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ - - -struct DocMarkDownWriter +static void _addDeclRec(Decl* decl, List<Decl*>& outDecls) { - typedef ASTPrinter::Part Part; - typedef ASTPrinter::PartPair PartPair; - - struct Signature + if (decl == nullptr) { - Part returnType; - List<PartPair> params; - Part name; - }; - - void write(); - - void writeCallable(const DocMarkup::Entry& entry, CallableDecl* callable); - void writeEnum(const DocMarkup::Entry& entry, EnumDecl* enumDecl); - void writeAggType(const DocMarkup::Entry& entry, AggTypeDecl* aggTypeDecl); + return; + } - void writePreamble(const DocMarkup::Entry& entry); - void writeDescription(const DocMarkup::Entry& entry); - - DocMarkDownWriter(DocMarkup* markup, ASTBuilder* astBuilder): - m_markup(markup), - m_astBuilder(astBuilder) + // If we don't have a loc, we have no way of locating documentation. + if (decl->loc.isValid() || decl->nameAndLoc.loc.isValid()) { + outDecls.add(decl); } - - static void getSignature(const List<Part>& parts, Signature& outSig); - - template <typename T> - void _appendAsBullets(FilteredMemberList<T>& in); - - DocMarkup* m_markup; - ASTBuilder* m_astBuilder; - StringBuilder m_builder; -}; - -static void _appendAsSingleLine(const UnownedStringSlice& in, StringBuilder& out) -{ - List<UnownedStringSlice> lines; - StringUtil::calcLines(in, lines); - - // Ideally we'd remove any extraneous whitespace, but for now just join - StringUtil::join(lines.getBuffer(), lines.getCount(), ' ', out); -} - -template <typename T> -void DocMarkDownWriter::_appendAsBullets(FilteredMemberList<T>& list) -{ - auto& out = m_builder; - for (auto element : list) + else { - DocMarkup::Entry* paramEntry = m_markup->getEntry(element); - - out << "* "; - - Name* name = element->getName(); - if (name) - { - out << toSlice("_") << name->text << toSlice("_ "); - } - - if (paramEntry) - { - // Hmm, we'll want to make something multiline into a single line - _appendAsSingleLine(paramEntry->m_markup.getUnownedSlice(), out); - } - - out << "\n"; + SLANG_ASSERT(!"Decl without a location!"); } - out << toSlice("\n"); -} - -/* static */void DocMarkDownWriter::getSignature(const List<Part>& parts, Signature& outSig) -{ - const Index count = parts.getCount(); - for (Index i = 0; i < count; ++i) + if (GenericDecl* genericDecl = as<GenericDecl>(decl)) { - const auto& part = parts[i]; - switch (part.type) - { - case Part::Type::ParamType: - { - PartPair pair; - pair.first = part; - if (parts[i + 1].type == Part::Type::ParamName) - { - pair.second = parts[i + 1]; - i++; - } - outSig.params.add(pair); - break; - } - case Part::Type::ReturnType: - { - outSig.returnType = part; - break; - } - case Part::Type::DeclPath: - { - outSig.name = part; - break; - } - default: break; - } + _addDeclRec(genericDecl->inner, outDecls); } -} - -void DocMarkDownWriter::writeCallable(const DocMarkup::Entry& entry, CallableDecl* callableDecl) -{ - writePreamble(entry); - - auto& out = m_builder; - - StringBuilder sigBuffer; - List<ASTPrinter::Part> parts; - ASTPrinter printer(m_astBuilder, ASTPrinter::OptionFlag::ParamNames, &parts); - - printer.addDeclSignature(DeclRef<Decl>(callableDecl, nullptr)); - - Signature signature; - getSignature(parts, signature); - - const Index paramCount = signature.params.getCount(); - // Output the signature - { - // Extract the name - out << toSlice("# ") << printer.getPartSlice(signature.name) << toSlice("\n\n"); - - out << toSlice("## Signature \n"); - out << toSlice("```\n"); - out << printer.getPartSlice(signature.returnType) << toSlice(" "); - - out << printer.getPartSlice(signature.name); - - - if (paramCount > 0) - { - out << toSlice("(\n"); - - StringBuilder line; - for (Index i = 0; i < paramCount; ++i) - { - const auto& param = signature.params[i]; - line.Clear(); - // If we want to tab these over... we'll need to know how must space I have - line << " " << printer.getPartSlice(param.first); - - Index indent = 25; - if (line.getLength() < indent) - { - line.appendRepeatedChar(' ', indent - line.getLength()); - } - else - { - line.appendChar(' '); - } - - line << printer.getPartSlice(param.second); - if (i < paramCount - 1) - { - line << ",\n"; - } - - out << line; - } - - out << ");\n"; - } - else + if (ContainerDecl* containerDecl = as<ContainerDecl>(decl)) + { + // Add the container - which could be a class, struct, enum, namespace, extension, generic etc. + // Now add what the container contains + for (Decl* childDecl : containerDecl->members) { - out << toSlice("();\n"); + _addDeclRec(childDecl, outDecls); } - - out << "```\n\n"; - } - - // Only output params if there are any - if (paramCount) - { - out << "## Parameters\n\n"; - - auto params = callableDecl->getParameters(); - _appendAsBullets(params); } - - writeDescription(entry); } -void DocMarkDownWriter::writeEnum(const DocMarkup::Entry& entry, EnumDecl* enumDecl) +/* static */void DocMarkupExtractor::findDecls(ModuleDecl* moduleDecl, List<Decl*>& outDecls) { - writePreamble(entry); - - auto& out = m_builder; - - out << toSlice("# enum "); - Name* name = enumDecl->getName(); - if (name) + for (Decl* decl : moduleDecl->members) { - out << name->text; + _addDeclRec(decl, outDecls); } - out << toSlice("\n\n"); - - out << toSlice("## Values \n\n"); - - auto cases = enumDecl->getMembersOfType<EnumCaseDecl>(); - _appendAsBullets(cases); - - writeDescription(entry); } -void DocMarkDownWriter::writeAggType(const DocMarkup::Entry& entry, AggTypeDecl* aggTypeDecl) +SlangResult DocMarkupExtractor::extract(ModuleDecl* moduleDecl, SourceManager* sourceManager, DiagnosticSink* sink, DocMarkup* outDoc) { - writePreamble(entry); + List<Decl*> decls; + findDecls(moduleDecl, decls); - auto& out = m_builder; + const Index declsCount = decls.getCount(); - // This could be lots of different things - struct/class/extension/interface/.. + List<SearchItemInput> inputItems; + List<SearchItemOutput> outItems; - out << toSlice("# "); - if (as<StructDecl>(aggTypeDecl)) { - out << toSlice("struct "); - } - else if (as<ClassDecl>(aggTypeDecl)) - { - out << toSlice("class "); - } - else - { - out << toSlice("?"); - } - - Name* name = aggTypeDecl->getName(); - if (name) - { - out << name->text; - } - out << toSlice("\n\n"); - - out << "## Fields\n\n"; + inputItems.setCount(declsCount); - auto fields = aggTypeDecl->getMembersOfType<VarDecl>(); - _appendAsBullets(fields); + for (Index i = 0; i < declsCount; ++i) + { + Decl* decl = decls[i]; + auto& item = inputItems[i]; - writeDescription(entry); -} - -void DocMarkDownWriter::writePreamble(const DocMarkup::Entry& entry) -{ - SLANG_UNUSED(entry); - auto& out = m_builder; - - out << toSlice("\n"); - out.appendRepeatedChar('-', 80); - out << toSlice("\n"); -} + item.sourceLoc = decl->loc.isValid() ? decl->loc : decl->nameAndLoc.loc; + // Has to be valid to be lookupable + SLANG_ASSERT(item.sourceLoc.isValid()); + item.searchStyle = getSearchStyle(decl); + } -void DocMarkDownWriter::writeDescription(const DocMarkup::Entry& entry) -{ - auto& out = m_builder; + DocMarkupExtractor extractor; - out << toSlice("\n## Description\n\n"); - out << entry.m_markup; -} + List<SourceView*> views; + SLANG_RETURN_ON_FAIL(extractor.extract(inputItems.getBuffer(), declsCount, sourceManager, sink, views, outItems)); + } -void DocMarkDownWriter::write() -{ - for (const auto& entry : m_markup->getEntries()) + // Set back + for (Index i = 0; i < declsCount; ++i) { - NodeBase* node = entry.m_node; - Decl* decl = as<Decl>(node); - if (!decl) - { - continue; - } + const auto& outputItem = outItems[i]; + const auto& inputItem = inputItems[outputItem.inputIndex]; - // Skip these they will be output as part of their respective 'containers' - if (as<ParamDecl>(decl) || as<EnumCaseDecl>(decl)) - { - continue; - } - - if (CallableDecl* callableDecl = as<CallableDecl>(decl)) - { - writeCallable(entry, callableDecl); - } - else if (EnumDecl* enumDecl = as<EnumDecl>(decl)) + // If we don't know how to search add to the output + if (inputItem.searchStyle != SearchStyle::None) { - writeEnum(entry, enumDecl); - } - else if (AggTypeDecl* aggType = as<AggTypeDecl>(decl)) - { - writeAggType(entry, aggType); + Decl* decl = decls[outputItem.inputIndex]; + + // Add to the documentation + DocMarkup::Entry& docEntry = outDoc->addEntry(decl); + docEntry.m_markup = outputItem.text; } } -} - -/* static */SlangResult DocumentationUtil::writeMarkdown(DocMarkup* markup, ASTBuilder* astBuilder, StringBuilder& out) -{ - // The ASTBuilder is needed in order to be able to create ast types that can then be printed. - // It is *assumed* here, that them being transient on this temporary ASTBuilder, doesn't mutate - // any of the nodes from the ASTBuilder/s for the things being documented - - DocMarkDownWriter writer(markup, astBuilder); - writer.write(); - - Swap(out, writer.m_builder); - + return SLANG_OK; } diff --git a/source/slang/slang-doc-extractor.h b/source/slang/slang-doc-extractor.h new file mode 100644 index 000000000..ca03fc60b --- /dev/null +++ b/source/slang/slang-doc-extractor.h @@ -0,0 +1,210 @@ +// slang-doc.h +#ifndef SLANG_DOC_EXTRACTOR_H +#define SLANG_DOC_EXTRACTOR_H + +#include "../core/slang-basic.h" +#include "slang-ast-all.h" + +namespace Slang { + +/* Holds the documentation markup that is associated with each node (typically a decl) from a module */ +class DocMarkup : public RefObject +{ +public: + struct Entry + { + NodeBase* m_node; ///< The node this documentation is associated with + String m_markup; ///< The raw contents of of markup associated with the decoration + }; + + /// Adds an entry, returns the reference to pre-existing node if there is one + Entry& addEntry(NodeBase* base); + /// Gets an entry for a node. Returns nullptr if there is no markup. + Entry* getEntry(NodeBase* base); + + /// Get list of all of the entries in source order + const List<Entry>& getEntries() const { return m_entries; } + +protected: + + /// Map from AST nodes to documentation entries + Dictionary<NodeBase*, Index> m_entryMap; + /// All of the documentation entries in source order + List<Entry> m_entries; +}; + +// --------------------------------------------------------------------------- +SLANG_INLINE DocMarkup::Entry& DocMarkup::addEntry(NodeBase* base) +{ + const Index count = m_entries.getCount(); + const Index index = m_entryMap.GetOrAddValue(base, count); + + if (index == count) + { + Entry entry; + entry.m_node = base; + m_entries.add(entry); + } + return m_entries[index]; +} + +// --------------------------------------------------------------------------- +SLANG_INLINE DocMarkup::Entry* DocMarkup::getEntry(NodeBase* base) +{ + Index* indexPtr = m_entryMap.TryGetValue(base); + return (indexPtr) ? &m_entries[*indexPtr] : nullptr; +} + +/* Extracts 'markup' from comments in Slang source core. The comments are extracted and associated in declarations. The association +is held in DocMarkup type. The comment style follows the doxygen style */ +class DocMarkupExtractor +{ +public: + + typedef uint32_t MarkupFlags; + struct MarkupFlag + { + enum Enum : MarkupFlags + { + Before = 0x1, + After = 0x2, + IsMultiToken = 0x4, ///< Can use more than one token + IsBlock = 0x8, ///< + }; + }; + + // NOTE! Don't change order without fixing isBefore and isAfter + enum class MarkupType + { + None, + + BlockBefore, /// /** */ or /*! */. + LineBangBefore, /// //! Can be multiple lines + LineSlashBefore, /// /// Can be multiple lines + + BlockAfter, /// /*!< */ or /**< */ + LineBangAfter, /// //!< Can be multiple lines + LineSlashAfter, /// ///< Can be multiple lines + }; + + static bool isBefore(MarkupType type) { return Index(type) >= Index(MarkupType::BlockBefore) && Index(type) <= Index(MarkupType::LineSlashBefore); } + static bool isAfter(MarkupType type) { return Index(type) >= Index(MarkupType::BlockAfter); } + + struct IndexRange + { + SLANG_FORCE_INLINE Index getCount() const { return end - start; } + + Index start; + Index end; + }; + + enum class Location + { + None, ///< No defined location + Before, + AfterParam, ///< Can have trailing , or ) + AfterSemicolon, ///< Can have a trailing ; + AfterEnumCase, ///< Can have a , or before } + AfterGenericParam, ///< Can have trailing , or > + }; + + static bool isAfter(Location location) { return Index(location) >= Index(Location::AfterParam); } + static bool isBefore(Location location) { return location == Location::Before; } + + struct FoundMarkup + { + void reset() + { + location = Location::None; + type = MarkupType::None; + range = IndexRange{ 0, 0 }; + } + + Location location = Location::None; + MarkupType type = MarkupType::None; + IndexRange range; + }; + + enum SearchStyle + { + None, ///< Cannot be searched for + EnumCase, ///< An enum case + Param, ///< A parameter in a function/method + Variable, ///< A variable-like declaration + Before, ///< Only allows before + Function, ///< Function/method + GenericParam, ///< Generic parameter + }; + + /// An input search item + struct SearchItemInput + { + SourceLoc sourceLoc; + SearchStyle searchStyle; ///< The search style when looking for an item + }; + + /// The items will be in source order + struct SearchItemOutput + { + Index viewIndex; ///< Index into the array of views on the output + Index inputIndex; ///< The index to this item in the input + String text; ///< The found text + }; + + struct FindInfo + { + SourceView* sourceView; ///< The source view the tokens were generated from + TokenList* tokenList; ///< The token list + Index tokenIndex; ///< The token index location (where searches start from) + Index lineIndex; ///< The line number for the decl + }; + + /// Extracts documentation from the nodes held in the module using the source manager. Found documentation is placed + /// in outMarkup + static SlangResult extract(ModuleDecl* moduleDecl, SourceManager* sourceManager, DiagnosticSink* sink, DocMarkup* outMarkup); + + /// Extracts 'markup' doc information for the specified input items + /// The output is placed in out - with the items now in the source order *not* the order of the input items + /// The inputIndex on the output holds the input item index + /// The outViews holds the views specified in viewIndex in the output, which may be useful for determining where the documentation was placed in source + SlangResult extract(const SearchItemInput* inputItems, Index inputCount, SourceManager* sourceManager, DiagnosticSink* sink, List<SourceView*>& outViews, List<SearchItemOutput>& out); + + /// Given a module finds all the decls, and places in outDecls + static void findDecls(ModuleDecl* moduleDecl, List<Decl*>& outDecls); + + /// Given a decl determines the search style that is appropriate. Returns None if can't determine a suitable style + static SearchStyle getSearchStyle(Decl* decl); + + static MarkupFlags getFlags(MarkupType type); + static MarkupType findMarkupType(const Token& tok); + static UnownedStringSlice removeStart(MarkupType type, const UnownedStringSlice& comment); + +protected: + /// returns SLANG_E_NOT_FOUND if not found, SLANG_OK on success else an error + SlangResult _findMarkup(const FindInfo& info, Location location, FoundMarkup& out); + + /// Locations are processed in order, and the first successful used. If found in another location will issue a warning. + /// returns SLANG_E_NOT_FOUND if not found, SLANG_OK on success else an error + SlangResult _findFirstMarkup(const FindInfo& info, const Location* locs, Index locCount, FoundMarkup& out, Index& outIndex); + + SlangResult _findMarkup(const FindInfo& info, const Location* locs, Index locCount, FoundMarkup& out); + + /// Given the decl, the token stream, and the decls tokenIndex, try to find some associated markup + SlangResult _findMarkup(const FindInfo& info, SearchStyle searchStyle, FoundMarkup& out); + + /// Given a found markup location extracts the contents of the tokens into out + SlangResult _extractMarkup(const FindInfo& info, const FoundMarkup& foundMarkup, StringBuilder& out); + + /// Given a location, try to find the first token index that could potentially be markup + /// Will return -1 if not found + Index _findStartIndex(const FindInfo& info, Location location); + + /// True if the tok is 'on' lineIndex. Interpretation of 'on' depends on the markup type. + static bool _isTokenOnLineIndex(SourceView* sourceView, MarkupType type, const Token& tok, Index lineIndex); + + DiagnosticSink* m_sink; +}; + +} // namespace Slang + +#endif diff --git a/source/slang/slang-doc-mark-down.cpp b/source/slang/slang-doc-mark-down.cpp new file mode 100644 index 000000000..82de7052e --- /dev/null +++ b/source/slang/slang-doc-mark-down.cpp @@ -0,0 +1,477 @@ +// slang-doc-mark-down.cpp +#include "slang-doc-mark-down.h" + +#include "../core/slang-string-util.h" + +#include "slang-ast-builder.h" + +namespace Slang { + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DocMarkDownWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +template <typename T> +static void _getDecls(ContainerDecl* containerDecl, List<T*>& out) +{ + for (Decl* decl : containerDecl->members) + { + if (T* declAsType = as<T>(decl)) + { + out.add(declAsType); + } + } +} + +template <typename T> +static void _getDeclsOfType(ContainerDecl* containerDecl, List<Decl*>& out) +{ + for (Decl* decl : containerDecl->members) + { + if (as<T>(decl)) + { + out.add(decl); + } + } +} + +template <typename T> +static void _toList(FilteredMemberList<T>& list, List<Decl*>& out) +{ + for (Decl* decl : list) + { + out.add(decl); + } +} + +static void _appendAsSingleLine(const UnownedStringSlice& in, StringBuilder& out) +{ + List<UnownedStringSlice> lines; + StringUtil::calcLines(in, lines); + + // Ideally we'd remove any extraneous whitespace, but for now just join + StringUtil::join(lines.getBuffer(), lines.getCount(), ' ', out); +} + +void DocMarkDownWriter::_appendAsBullets(const List<Decl*>& in) +{ + auto& out = m_builder; + for (auto decl : in) + { + DocMarkup::Entry* paramEntry = m_markup->getEntry(decl); + + out << "* "; + + Name* name = decl->getName(); + if (name) + { + out << toSlice("_") << name->text << toSlice("_ "); + } + + if (paramEntry) + { + // Hmm, we'll want to make something multiline into a single line + _appendAsSingleLine(paramEntry->m_markup.getUnownedSlice(), out); + } + + out << "\n"; + } + + out << toSlice("\n"); +} + +template <typename T> +void DocMarkDownWriter::_appendAsBullets(FilteredMemberList<T>& list) +{ + List<Decl*> decls; + _toList(list, decls); + _appendAsBullets(decls); +} + +/* static */void DocMarkDownWriter::getSignature(const List<Part>& parts, Signature& outSig) +{ + const Index count = parts.getCount(); + for (Index i = 0; i < count; ++i) + { + const auto& part = parts[i]; + switch (part.type) + { + case Part::Type::ParamType: + { + PartPair pair; + pair.first = part; + if ((i + 1) < count && parts[i + 1].type == Part::Type::ParamName) + { + pair.second = parts[i + 1]; + i++; + } + outSig.params.add(pair); + break; + } + case Part::Type::ReturnType: + { + outSig.returnType = part; + break; + } + case Part::Type::DeclPath: + { + outSig.name = part; + break; + } + case Part::Type::GenericParamValue: + case Part::Type::GenericParamType: + { + Signature::GenericParam genericParam; + genericParam.name = part; + + if ((i + 1) < count && parts[i + 1].type == Part::Type::GenericParamValueType) + { + genericParam.type = parts[i + 1]; + i++; + } + + outSig.genericParams.add(genericParam); + break; + } + + default: break; + } + } +} + +void DocMarkDownWriter::writeVar(const DocMarkup::Entry& entry, VarDecl* varDecl) +{ + writePreamble(entry); + auto& out = m_builder; + + out << toSlice("# ") << varDecl->getName()->text << toSlice("\n\n"); + + // TODO(JS): The outputting of types this way isn't right - it doesn't handle int a[10] for example. + //ASTPrinter printer(m_astBuilder, ASTPrinter::OptionFlag::ParamNames); + + out << toSlice("```\n"); + out << varDecl->type << toSlice(" ") << varDecl << toSlice("\n"); + out << toSlice("```\n"); + + writeDescription(entry); +} + +void DocMarkDownWriter::writeCallable(const DocMarkup::Entry& entry, CallableDecl* callableDecl) +{ + writePreamble(entry); + + auto& out = m_builder; + + List<ASTPrinter::Part> parts; + ASTPrinter printer(m_astBuilder, ASTPrinter::OptionFlag::ParamNames, &parts); + + GenericDecl* genericDecl = as<GenericDecl>(callableDecl->parentDecl); + + if (genericDecl) + { + printer.addDeclSignature(DeclRef<Decl>(genericDecl, nullptr)); + } + else + { + printer.addDeclSignature(DeclRef<Decl>(callableDecl, nullptr)); + } + + Signature signature; + getSignature(parts, signature); + + const Index paramCount = signature.params.getCount(); + + // Output the signature + { + // Extract the name + out << toSlice("# ") << printer.getPartSlice(signature.name) << toSlice("\n\n"); + + out << toSlice("## Signature \n"); + out << toSlice("```\n"); + out << printer.getPartSlice(signature.returnType) << toSlice(" "); + + out << printer.getPartSlice(signature.name); + + if (signature.genericParams.getCount()) + { + out << toSlice("<"); + const Index count = signature.genericParams.getCount(); + for (Index i = 0; i < count; ++i) + { + const auto& genericParam = signature.genericParams[i]; + if (i > 0) + { + out << toSlice(", "); + } + out << printer.getPartSlice(genericParam.name); + + if (genericParam.type.type != Part::Type::None) + { + out << toSlice(" : "); + out << printer.getPartSlice(genericParam.type); + } + } + out << toSlice(">"); + } + + if (paramCount > 0) + { + out << toSlice("(\n"); + + StringBuilder line; + for (Index i = 0; i < paramCount; ++i) + { + const auto& param = signature.params[i]; + line.Clear(); + // If we want to tab these over... we'll need to know how must space I have + line << " " << printer.getPartSlice(param.first); + + Index indent = 25; + if (line.getLength() < indent) + { + line.appendRepeatedChar(' ', indent - line.getLength()); + } + else + { + line.appendChar(' '); + } + + line << printer.getPartSlice(param.second); + if (i < paramCount - 1) + { + line << ",\n"; + } + + out << line; + } + + out << ");\n"; + } + else + { + out << toSlice("();\n"); + } + + out << "```\n\n"; + } + + { + // The parameters, in order + List<Decl*> params; + + if (genericDecl) + { + for (Decl* decl : genericDecl->members) + { + if (as<GenericTypeParamDecl>(decl) || + as<GenericValueParamDecl>(decl)) + { + params.add(decl); + } + } + } + + for (ParamDecl* paramDecl : callableDecl->getParameters()) + { + params.add(paramDecl); + } + + if (params.getCount()) + { + out << "## Parameters\n\n"; + // We have generic params and regular parameters, in this list + _appendAsBullets(params); + } + } + + writeDescription(entry); +} + +void DocMarkDownWriter::writeEnum(const DocMarkup::Entry& entry, EnumDecl* enumDecl) +{ + writePreamble(entry); + + auto& out = m_builder; + + out << toSlice("# enum "); + Name* name = enumDecl->getName(); + if (name) + { + out << name->text; + } + out << toSlice("\n\n"); + + out << toSlice("## Values \n\n"); + + auto cases = enumDecl->getMembersOfType<EnumCaseDecl>(); + _appendAsBullets(cases); + + writeDescription(entry); +} + +void DocMarkDownWriter::_appendDerivedFrom(const UnownedStringSlice& prefix, AggTypeDeclBase* aggTypeDecl) +{ + auto& out = m_builder; + + List<InheritanceDecl*> inheritanceDecls; + _getDecls(aggTypeDecl, inheritanceDecls); + + const Index count = inheritanceDecls.getCount(); + if (count) + { + out << prefix; + for (Index i = 0; i < count; ++i) + { + InheritanceDecl* inheritanceDecl = inheritanceDecls[i]; + if (i > 0) + { + out << toSlice(", "); + } + out << inheritanceDecl->base; + } + } +} + +void DocMarkDownWriter::writeAggType(const DocMarkup::Entry& entry, AggTypeDeclBase* aggTypeDecl) +{ + writePreamble(entry); + + auto& out = m_builder; + + // This could be lots of different things - struct/class/extension/interface/.. + + out << toSlice("# "); + if (as<StructDecl>(aggTypeDecl)) + { + out << toSlice("struct "); + } + else if (as<ClassDecl>(aggTypeDecl)) + { + out << toSlice("class "); + } + else if (as<InterfaceDecl>(aggTypeDecl)) + { + out << toSlice("interface "); + } + else if (ExtensionDecl* extensionDecl = as<ExtensionDecl>(aggTypeDecl)) + { + out << toSlice("extension ") << extensionDecl->targetType; + _appendDerivedFrom(toSlice(" : "), extensionDecl); + } + else + { + out << toSlice("?"); + } + + Name* name = aggTypeDecl->getName(); + if (name) + { + out << name->text; + } + out << toSlice("\n\n"); + + { + List<InheritanceDecl*> inheritanceDecls; + _getDecls<InheritanceDecl>(aggTypeDecl, inheritanceDecls); + + if (inheritanceDecls.getCount()) + { + out << "*Derives from:* "; + + for (Index i = 0; i < inheritanceDecls.getCount(); ++i) + { + if (i > 0) + { + out << toSlice(", "); + } + out << inheritanceDecls[i]->base; + } + out << toSlice("\n\n"); + } + } + + { + List<Decl*> fields; + _getDeclsOfType<VarDecl>(aggTypeDecl, fields); + if (fields.getCount()) + { + out << "## Fields\n\n"; + _appendAsBullets(fields); + } + } + + { + List<Decl*> methods; + _getDeclsOfType<CallableDecl>(aggTypeDecl, methods); + if (methods.getCount()) + { + out << "## Methods\n\n"; + _appendAsBullets(methods); + } + } + + writeDescription(entry); +} + +void DocMarkDownWriter::writePreamble(const DocMarkup::Entry& entry) +{ + SLANG_UNUSED(entry); + auto& out = m_builder; + + out << toSlice("\n"); + out.appendRepeatedChar('-', 80); + out << toSlice("\n"); +} + + +void DocMarkDownWriter::writeDescription(const DocMarkup::Entry& entry) +{ + auto& out = m_builder; + + out << toSlice("\n## Description\n\n"); + out << entry.m_markup; +} + +void DocMarkDownWriter::writeDecl(const DocMarkup::Entry& entry, Decl* decl) +{ + // Skip these they will be output as part of their respective 'containers' + if (as<ParamDecl>(decl) || as<EnumCaseDecl>(decl)) + { + return; + } + + if (CallableDecl* callableDecl = as<CallableDecl>(decl)) + { + writeCallable(entry, callableDecl); + } + else if (EnumDecl* enumDecl = as<EnumDecl>(decl)) + { + writeEnum(entry, enumDecl); + } + else if (AggTypeDeclBase* aggType = as<AggTypeDeclBase>(decl)) + { + writeAggType(entry, aggType); + } + else if (VarDecl* varDecl = as<VarDecl>(decl)) + { + writeVar(entry, varDecl); + } + else if (as<GenericDecl>(decl)) + { + // We can ignore as inner decls will be picked up, and written + } +} + + +void DocMarkDownWriter::writeAll() +{ + for (const auto& entry : m_markup->getEntries()) + { + NodeBase* node = entry.m_node; + Decl* decl = as<Decl>(node); + if (decl) + { + writeDecl(entry, decl); + } + } +} + +} // namespace Slang diff --git a/source/slang/slang-doc-mark-down.h b/source/slang/slang-doc-mark-down.h new file mode 100644 index 000000000..e1728d18a --- /dev/null +++ b/source/slang/slang-doc-mark-down.h @@ -0,0 +1,70 @@ +// slang-doc-markdown.h +#ifndef SLANG_DOC_MARK_DOWN_H +#define SLANG_DOC_MARK_DOWN_H + +#include "slang-doc-extractor.h" +#include "slang-ast-print.h" + +namespace Slang { + +class ASTBuilder; + +struct DocMarkDownWriter +{ + typedef ASTPrinter::Part Part; + typedef ASTPrinter::PartPair PartPair; + + struct Signature + { + struct GenericParam + { + Part name; + Part type; + }; + + Part returnType; + List<PartPair> params; + List<GenericParam> genericParams; + Part name; + }; + + /// Write out all documentation to the output buffer + void writeAll(); + + void writeCallable(const DocMarkup::Entry& entry, CallableDecl* callable); + void writeEnum(const DocMarkup::Entry& entry, EnumDecl* enumDecl); + void writeAggType(const DocMarkup::Entry& entry, AggTypeDeclBase* aggTypeDecl); + void writeDecl(const DocMarkup::Entry& entry, Decl* decl); + void writeVar(const DocMarkup::Entry& entry, VarDecl* varDecl); + + void writePreamble(const DocMarkup::Entry& entry); + void writeDescription(const DocMarkup::Entry& entry); + + /// Get the output string + const StringBuilder& getOutput() const { return m_builder; } + + /// Ctor. + DocMarkDownWriter(DocMarkup* markup, ASTBuilder* astBuilder) : + m_markup(markup), + m_astBuilder(astBuilder) + { + } + + /// Given a list of ASTPrinter::Parts, works out the different parts of the sig + static void getSignature(const List<Part>& parts, Signature& outSig); + + template <typename T> + void _appendAsBullets(FilteredMemberList<T>& in); + void _appendAsBullets(const List<Decl*>& in); + + /// Appends prefix and the list of types derived from + void _appendDerivedFrom(const UnownedStringSlice& prefix, AggTypeDeclBase* aggTypeDecl); + + DocMarkup* m_markup; + ASTBuilder* m_astBuilder; + StringBuilder m_builder; +}; + +} // namespace Slang + +#endif diff --git a/source/slang/slang-doc.h b/source/slang/slang-doc.h deleted file mode 100644 index b2fd8c664..000000000 --- a/source/slang/slang-doc.h +++ /dev/null @@ -1,73 +0,0 @@ -// slang-doc.h -#ifndef SLANG_DOC_H -#define SLANG_DOC_H - -#include "../core/slang-basic.h" -#include "slang-ast-all.h" -#include "slang-ast-print.h" - -namespace Slang { - -/* Holds the documentation markup that is associated with each node (typically a decl) from a module */ -class DocMarkup : public RefObject -{ -public: - struct Entry - { - NodeBase* m_node; ///< The node this documentation is associated with - String m_markup; ///< The raw contents of of markup associated with the decoration - }; - - /// Adds an entry, returns the reference to pre-existing node if there is one - Entry& addEntry(NodeBase* base); - /// Gets an entry for a node. Returns nullptr if there is no markup. - Entry* getEntry(NodeBase* base); - - /// Get list of all of the entries in source order - const List<Entry>& getEntries() const { return m_entries; } - - /// Given a module extracts all the associated markup. - SlangResult extract(ModuleDecl* moduleDecl, SourceManager* sourceManager, DiagnosticSink* sink); - -protected: - - /// The module this information was extracted from - ModuleDecl* m_moduleDecl; - /// Map from AST nodes to documentation entries - Dictionary<NodeBase*, Index> m_entryMap; - /// All of the documentation entries in source order - List<Entry> m_entries; -}; - -// --------------------------------------------------------------------------- -SLANG_INLINE DocMarkup::Entry& DocMarkup::addEntry(NodeBase* base) -{ - const Index count = m_entries.getCount(); - const Index index = m_entryMap.GetOrAddValue(base, count); - - if (index == count) - { - Entry entry; - entry.m_node = base; - m_entries.add(entry); - } - return m_entries[index]; -} - -// --------------------------------------------------------------------------- -SLANG_INLINE DocMarkup::Entry* DocMarkup::getEntry(NodeBase* base) -{ - Index* indexPtr = m_entryMap.TryGetValue(base); - return (indexPtr) ? &m_entries[*indexPtr] : nullptr; -} - -class SharedASTBuilder; - -struct DocumentationUtil -{ - static SlangResult writeMarkdown(DocMarkup* markup, ASTBuilder* astBuilder, StringBuilder& out); -}; - -} // namespace Slang - -#endif diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 21f60c090..1820b24e5 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -30,7 +30,8 @@ #include "slang-serialize-ir.h" #include "slang-serialize-container.h" -#include "slang-doc.h" +#include "slang-doc-extractor.h" +#include "slang-doc-mark-down.h" #include "slang-check-impl.h" @@ -1808,8 +1809,7 @@ SlangResult FrontEndCompileRequest::executeActionsInner() for (TranslationUnitRequest* translationUnit : translationUnits) { RefPtr<DocMarkup> markup(new DocMarkup); - - markup->extract(translationUnit->getModuleDecl(), getSourceManager(), getSink()); + DocMarkupExtractor::extract(translationUnit->getModuleDecl(), getSourceManager(), getSink(), markup); // Hmm.. we can have multiple sourcefiles. So fir now we just pick the first, so as to come up with // a reasonable name @@ -1822,10 +1822,10 @@ SlangResult FrontEndCompileRequest::executeActionsInner() String fileName = Path::getFileNameWithoutExt(path); fileName.append(".md"); - StringBuilder buf; - DocumentationUtil::writeMarkdown(markup, astBuilder, buf); + DocMarkDownWriter writer(markup, astBuilder); + writer.writeAll(); - File::writeAllText(fileName, buf); + File::writeAllText(fileName, writer.getOutput()); } } } |
