summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2021-03-05 14:34:46 -0500
committerGitHub <noreply@github.com>2021-03-05 14:34:46 -0500
commit860d17b6876822ef7023fdce70c725d3f8be37b1 (patch)
tree1ed058603b789a59103886c8e1f77a3d4e69adb1 /source
parentdc7110858ecdb7c7567de360787b9adc4defa04a (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.h4
-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.h210
-rw-r--r--source/slang/slang-doc-mark-down.cpp477
-rw-r--r--source/slang/slang-doc-mark-down.h70
-rw-r--r--source/slang/slang-doc.h73
-rw-r--r--source/slang/slang.cpp12
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());
}
}
}