diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-03-11 17:08:08 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-11 17:08:08 -0500 |
| commit | 5bcb342962634e9c36fe399a822e685bb2eb8d76 (patch) | |
| tree | 7f621e2932e3b7eb6d1c5121f382bdd23a8f5855 /source | |
| parent | 4b74f994bf94217f174cf0fb02ed94abe62e9d7c (diff) | |
stdlib documentation (#1745)
* #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.
* Added flags to compileStdLib.
* More work around handling generics in markdown output.
* More improvements around associated type handling.
* List method names only once.
Output in/out/inout/const
* Fix namespace printing.
* WIP summarizing doc output.
* Small fixes and improvements for doc output.
* Output all stdlib in single doc file.
* Remove compile flags from addBuiltinSource.
* Find only unique signatures.
First pass at trying to get requirements.
* First pass at requirements for stdlib docs.
* Remove __ function/methods
* Added Target Availability
* Add markup access.
Make sections of stdlib hidden.
* MarkdownAccess -> Visibility
Add isVisible methods
Use ASTPrinter to print decl name.
* Add current stdlib doc output.
* Disable doc test for now.
* Fix clang issue.
* Don't use bullets and numbering , just use numbering.
* Put methods in source order.
* Fix bad-operator-call.slang test that fails because it now outputs out parameters as such.
* Refactor MarkDownWriter to separate 'extraction' from output.
* Fix typo around @ lines.
* Fix issue with extracting 'before' when preceeded by complex attributes/modifiers.
* Fix handling of generics with the same name.
* Work around for having overloading with generics - we don't want to output generic params as part of name.
* Remove generic paramters from name.
* Simplify handling of outputting overridable names.
Diffstat (limited to 'source')
| -rw-r--r-- | source/core/slang-list.h | 24 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 40 | ||||
| -rw-r--r-- | source/slang/slang-api.cpp | 2 | ||||
| -rw-r--r-- | source/slang/slang-ast-print.cpp | 229 | ||||
| -rw-r--r-- | source/slang/slang-ast-print.h | 17 | ||||
| -rwxr-xr-x | source/slang/slang-compiler.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-doc-extractor.cpp | 88 | ||||
| -rw-r--r-- | source/slang/slang-doc-extractor.h | 13 | ||||
| -rw-r--r-- | source/slang/slang-doc-mark-down.cpp | 860 | ||||
| -rw-r--r-- | source/slang/slang-doc-mark-down.h | 62 | ||||
| -rw-r--r-- | source/slang/slang-options.cpp | 13 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 39 |
12 files changed, 1179 insertions, 210 deletions
diff --git a/source/core/slang-list.h b/source/core/slang-list.h index 42820b492..08d9aa773 100644 --- a/source/core/slang-list.h +++ b/source/core/slang-list.h @@ -30,6 +30,8 @@ namespace Slang static const Index kInitialCount = 16; public: + typedef List ThisType; + List() : m_buffer(nullptr), m_count(0), m_capacity(0) { @@ -185,6 +187,28 @@ namespace Slang const T* getBuffer() const { return m_buffer; } T* getBuffer() { return m_buffer; } + bool operator==(const ThisType& rhs) const + { + if (&rhs == this) + { + return true; + } + const Index count = getCount(); + if (count != rhs.getCount()) + { + return false; + } + for (Index i = 0; i < count; ++i) + { + if ((*this)[i] != rhs[i]) + { + return false; + } + } + return true; + } + SLANG_FORCE_INLINE bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + void insert(Index idx, const T& val) { insertRange(idx, &val, 1); } void insertRange(Index idx, const T* vals, Index n) diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index 39ee702c6..6b20dbc3b 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -27,12 +27,12 @@ syntax globallycoherent : GloballyCoherentModifier; /// syntax pervertex : PerVertexModifier; -// A type that can be used as an operand for builtins +/// A type that can be used as an operand for builtins [sealed] [builtin] interface __BuiltinType {} -// A type that can be used for arithmetic operations +/// A type that can be used for arithmetic operations [sealed] [builtin] interface __BuiltinArithmeticType : __BuiltinType @@ -41,28 +41,28 @@ interface __BuiltinArithmeticType : __BuiltinType __init(int value); } - /// A type that can be used for logical/bitwsie operations +/// A type that can be used for logical/bitwise operations [sealed] [builtin] interface __BuiltinLogicalType : __BuiltinType {} -// A type that logically has a sign (positive/negative/zero) +/// A type that logically has a sign (positive/negative/zero) [sealed] [builtin] interface __BuiltinSignedArithmeticType : __BuiltinArithmeticType {} -// A type that can represent integers +/// A type that can represent integers [sealed] [builtin] interface __BuiltinIntegerType : __BuiltinArithmeticType {} -// A type that can represent non-integers +/// A type that can represent non-integers [sealed] [builtin] interface __BuiltinRealType : __BuiltinSignedArithmeticType {} -// A type that uses a floating-point representation +/// A type that uses a floating-point representation [sealed] [builtin] interface __BuiltinFloatingPointType : __BuiltinRealType @@ -74,6 +74,8 @@ interface __BuiltinFloatingPointType : __BuiltinRealType static This getPi(); } +//@ hidden: + // A type resulting from an `enum` declaration. [builtin] __magic_type(EnumTypeType) @@ -406,6 +408,8 @@ for (int tt = 0; tt < kTypeCount; ++tt) // Declare additional built-in generic types }}}} +//@ public: + __generic<T> __intrinsic_type($(kIROp_ConstantBufferType)) __magic_type(ConstantBuffer) @@ -421,6 +425,8 @@ __intrinsic_type($(kIROp_ParameterBlockType)) __magic_type(ParameterBlockType) struct ParameterBlock {} +//@ hidden: + ${{{{ static const char* kComponentNames[]{ "x", "y", "z", "w" }; @@ -575,6 +581,8 @@ ${{{{ }}}} +//@ public: + /// Sampling state for filtered texture fetches. __magic_type(SamplerState, $(int(SamplerStateFlavor::SamplerState))) __intrinsic_type($(kIROp_SamplerStateType)) @@ -1554,6 +1562,11 @@ for (int tt = 0; tt < kBaseTextureTypeCount; ++tt) } } +}}}} + +//@ hidden: + +${{{{ for (auto op : intrinsicUnaryOps) { @@ -1933,6 +1946,8 @@ ${{{{ }}}} +//@ public: + // Bit cast __generic<T, U> [__unsafeForceInlineEarly] @@ -1941,12 +1956,13 @@ T bit_cast(U value); // Specialized function +/// Given a string returns an integer hash of that string. __intrinsic_op int getStringHash(String string); -// Use will produce a syntax error in downstream compiler -// Useful for testing diagnostics around compilation errors of downstream compiler -// It 'returns' an int so can be used in expressions without the front end complaining. +/// Use will produce a syntax error in downstream compiler +/// Useful for testing diagnostics around compilation errors of downstream compiler +/// It 'returns' an int so can be used in expressions without the front end complaining. __target_intrinsic(hlsl, " @ ") __target_intrinsic(glsl, " @ ") __target_intrinsic(cuda, " @ ") @@ -1967,6 +1983,8 @@ void endInvocationInterlock() {} // Operators to apply to `enum` types +//@ hidden: + __generic<E : __EnumType> __intrinsic_op($(kIROp_Eql)) bool operator==(E left, E right); @@ -1975,6 +1993,8 @@ __generic<E : __EnumType> __intrinsic_op($(kIROp_Neq)) bool operator!=(E left, E right); +//@ public: + // Binding Attributes __attributeTarget(DeclBase) diff --git a/source/slang/slang-api.cpp b/source/slang/slang-api.cpp index 251a3980b..bb0e2e174 100644 --- a/source/slang/slang-api.cpp +++ b/source/slang/slang-api.cpp @@ -98,7 +98,7 @@ SLANG_API SlangResult slang_createGlobalSession( if (tryLoadStdLibFromCache(globalSession, cacheFilename, dllTimestamp) != SLANG_OK) { // Compile std lib from embeded source. - SLANG_RETURN_ON_FAIL(globalSession->compileStdLib()); + SLANG_RETURN_ON_FAIL(globalSession->compileStdLib(0)); // Store the compiled stdlib to cache file. trySaveStdLibToCache(globalSession, cacheFilename, dllTimestamp); diff --git a/source/slang/slang-ast-print.cpp b/source/slang/slang-ast-print.cpp index 29c034659..3ffb481c6 100644 --- a/source/slang/slang-ast-print.cpp +++ b/source/slang/slang-ast-print.cpp @@ -34,29 +34,40 @@ void ASTPrinter::addVal(Val* val) val->toText(m_builder); } -void ASTPrinter::_addDeclName(Decl* decl) +/* static */void ASTPrinter::appendDeclName(Decl* decl, StringBuilder& out) { if (as<ConstructorDecl>(decl)) { - m_builder << "init"; + out << "init"; } else if (as<SubscriptDecl>(decl)) { - m_builder << "subscript"; + out << "subscript"; } else { - m_builder << getText(decl->getName()); + out << getText(decl->getName()); } } +void ASTPrinter::_addDeclName(Decl* decl) +{ + appendDeclName(decl, m_builder); +} + +void ASTPrinter::addOverridableDeclPath(const DeclRef<Decl>& declRef) +{ + ScopePart scopePart(this, Part::Type::DeclPath); + _addDeclPathRec(declRef, 0); +} + void ASTPrinter::addDeclPath(const DeclRef<Decl>& declRef) { ScopePart scopePart(this, Part::Type::DeclPath); - _addDeclPathRec(declRef); + _addDeclPathRec(declRef, 1); } -void ASTPrinter::_addDeclPathRec(const DeclRef<Decl>& declRef) +void ASTPrinter::_addDeclPathRec(const DeclRef<Decl>& declRef, Index depth) { auto& sb = m_builder; @@ -74,8 +85,48 @@ void ASTPrinter::_addDeclPathRec(const DeclRef<Decl>& declRef) // Depending on what the parent is, we may want to format things specially if (auto aggTypeDeclRef = parentDeclRef.as<AggTypeDecl>()) { - _addDeclPathRec(aggTypeDeclRef); - sb << "."; + _addDeclPathRec(aggTypeDeclRef, depth + 1); + sb << toSlice("."); + } + else if (auto namespaceDeclRef = parentDeclRef.as<NamespaceDecl>()) + { + _addDeclPathRec(namespaceDeclRef, depth + 1); + // Hmm, it could be argued that we follow the . as seen in AggType as is followed in some other languages + // like Java. + // That it is useful to have a distinction between something that is a member/method and something that is + // in a scope (such as a namespace), and is something that has returned to later languages probably for that + // reason (Slang accepts . or ::). So for now this is follows the :: convention. + // + // It could be argued them that the previous '.' use should vary depending on that distinction. + + sb << toSlice("::"); + } + else if (auto extensionDeclRef = parentDeclRef.as<ExtensionDecl>()) + { + ExtensionDecl* extensionDecl = as<ExtensionDecl>(parentDeclRef.getDecl()); + Type* type = extensionDecl->targetType.type; + addType(type); + sb << toSlice("."); + } + else if (auto moduleDecl = as<ModuleDecl>(parentDeclRef.getDecl())) + { + Name* moduleName = moduleDecl->getName(); + if ((m_optionFlags & OptionFlag::ModuleName) && moduleName) + { + // Use to say in modules scope + sb << moduleName->text << toSlice("::"); + } + } + + // If this decl is the module, we only output it's name if that feature is enabled + if (ModuleDecl* moduleDecl = as<ModuleDecl>(declRef.getDecl())) + { + Name* moduleName = moduleDecl->getName(); + if ((m_optionFlags & OptionFlag::ModuleName) && moduleName) + { + sb << moduleName->text; + } + return; } _addDeclName(declRef.getDecl()); @@ -85,41 +136,91 @@ void ASTPrinter::_addDeclPathRec(const DeclRef<Decl>& declRef) if (parentGenericDeclRef) { auto genSubst = as<GenericSubstitution>(declRef.substitutions.substitutions); - SLANG_RELEASE_ASSERT(genSubst); - SLANG_RELEASE_ASSERT(genSubst->genericDecl == parentGenericDeclRef.getDecl()); + if (genSubst) + { + SLANG_RELEASE_ASSERT(genSubst); + SLANG_RELEASE_ASSERT(genSubst->genericDecl == parentGenericDeclRef.getDecl()); - // If the name we printed previously was an operator - // that ends with `<`, then immediately printing the - // generic arguments inside `<...>` may cause it to - // be hard to parse the operator name visually. - // - // We thus include a space between the declaration name - // and its generic arguments in this case. - // - if (sb.endsWith("<")) + // If the name we printed previously was an operator + // that ends with `<`, then immediately printing the + // generic arguments inside `<...>` may cause it to + // be hard to parse the operator name visually. + // + // We thus include a space between the declaration name + // and its generic arguments in this case. + // + if (sb.endsWith("<")) + { + sb << " "; + } + + sb << "<"; + bool first = true; + for (auto arg : genSubst->args) + { + // When printing the representation of a specialized + // generic declaration we don't want to include the + // argument values for subtype witnesses since these + // do not correspond to parameters of the generic + // as the user sees it. + // + if (as<Witness>(arg)) + continue; + + if (!first) sb << ", "; + addVal(arg); + first = false; + } + sb << ">"; + } + else if (depth > 0) { - sb << " "; + // Write out the generic parameters (only if the depth allows it) + addGenericParams(parentGenericDeclRef); } + } +} - sb << "<"; - bool first = true; - for (auto arg : genSubst->args) +void ASTPrinter::addGenericParams(const DeclRef<GenericDecl>& genericDeclRef) +{ + auto& sb = m_builder; + + sb << "<"; + bool first = true; + for (auto paramDeclRef : getMembers(genericDeclRef)) + { + if (auto genericTypeParam = paramDeclRef.as<GenericTypeParamDecl>()) { - // When printing the representation of a specialized - // generic declaration we don't want to include the - // argument values for subtype witnesses since these - // do not correspond to parameters of the generic - // as the user sees it. - // - if (as<Witness>(arg)) - continue; + if (!first) sb << ", "; + first = false; + { + ScopePart scopePart(this, Part::Type::GenericParamType); + sb << getText(genericTypeParam.getName()); + } + } + else if (auto genericValParam = paramDeclRef.as<GenericValueParamDecl>()) + { if (!first) sb << ", "; - addVal(arg); first = false; + + { + ScopePart scopePart(this, Part::Type::GenericParamValue); + sb << getText(genericValParam.getName()); + } + + sb << ":"; + + { + ScopePart scopePart(this, Part::Type::GenericParamValueType); + addType(getType(m_astBuilder, genericValParam)); + } + } + else + { } - sb << ">"; } + sb << ">"; } void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) @@ -140,6 +241,29 @@ void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) { ScopePart scopePart(this, Part::Type::ParamType); + + // Seems these apply to parameters/VarDeclBase and are not part of the 'type' + // but seems more appropriate to put in the Type Part + + if (paramDecl->hasModifier<InOutModifier>()) + { + sb << toSlice("inout "); + } + else if (paramDecl->hasModifier<OutModifier>()) + { + sb << toSlice("out "); + } + else if (paramDecl->hasModifier<InModifier>()) + { + sb << toSlice("in "); + } + + // And this to params/variables (not the type) + if (paramDecl->hasModifier<ConstModifier>()) + { + sb << toSlice("const "); + } + addType(getType(m_astBuilder, paramDeclRef)); } @@ -161,42 +285,7 @@ void ASTPrinter::addDeclParams(const DeclRef<Decl>& declRef) } else if (auto genericDeclRef = declRef.as<GenericDecl>()) { - sb << "<"; - bool first = true; - for (auto paramDeclRef : getMembers(genericDeclRef)) - { - if (auto genericTypeParam = paramDeclRef.as<GenericTypeParamDecl>()) - { - if (!first) sb << ", "; - first = false; - - { - ScopePart scopePart(this, Part::Type::GenericParamType); - sb << getText(genericTypeParam.getName()); - } - } - else if (auto genericValParam = paramDeclRef.as<GenericValueParamDecl>()) - { - if (!first) sb << ", "; - first = false; - - { - ScopePart scopePart(this, Part::Type::GenericParamValue); - sb << getText(genericValParam.getName()); - } - - sb << ":"; - - { - ScopePart scopePart(this, Part::Type::GenericParamValueType); - addType(getType(m_astBuilder, genericValParam)); - } - } - else - { - } - } - sb << ">"; + addGenericParams(genericDeclRef); addDeclParams(DeclRef<Decl>(getInner(genericDeclRef), genericDeclRef.substitutions)); } @@ -265,7 +354,7 @@ void ASTPrinter::addDeclResultType(const DeclRef<Decl>& inDeclRef) return index >= 0 ? getPart(slice, parts[index]) : UnownedStringSlice(); } -UnownedStringSlice ASTPrinter::getPart(Part::Type partType) const +UnownedStringSlice ASTPrinter::getPartSlice(Part::Type partType) const { return m_parts ? getPart(partType, getSlice(), *m_parts) : UnownedStringSlice(); } diff --git a/source/slang/slang-ast-print.h b/source/slang/slang-ast-print.h index 7fb818898..1c4c20e28 100644 --- a/source/slang/slang-ast-print.h +++ b/source/slang/slang-ast-print.h @@ -15,7 +15,8 @@ public: { enum Enum : OptionFlags { - ParamNames = 0x1, ///< If set will output parameter names + ParamNames = 0x01, ///< If set will output parameter names + ModuleName = 0x02, ///< Writes out module names }; }; @@ -112,6 +113,9 @@ public: /// Add the path to the declaration including the declaration name void addDeclPath(const DeclRef<Decl>& declRef); + /// Add the path such that it encapsulates all overridable decls (ie is without terminal generic parameters) + void addOverridableDeclPath(const DeclRef<Decl>& declRef); + /// Add just the parameters from a declaration. /// Will output the generic parameters (if it's a generic) in <> before the parameters () void addDeclParams(const DeclRef<Decl>& declRef); @@ -126,15 +130,20 @@ public: /// Add the signature for the decl void addDeclSignature(const DeclRef<Decl>& declRef); + /// Add generic parameters + void addGenericParams(const DeclRef<GenericDecl>& genericDeclRef); + /// Get the specified part type. Returns empty slice if not found - UnownedStringSlice getPart(Part::Type partType) const; + UnownedStringSlice getPartSlice(Part::Type partType) const; /// Get the slice for a part UnownedStringSlice getPartSlice(const Part& part) const { return getPart(getSlice(), part); } - + /// Gets the specified part type static UnownedStringSlice getPart(const UnownedStringSlice& slice, const Part& part) { return UnownedStringSlice(slice.begin() + part.start, slice.begin() + part.end); } static UnownedStringSlice getPart(Part::Type partType, const UnownedStringSlice& slice, const List<Part>& parts); + static void appendDeclName(Decl* decl, StringBuilder& out); + /// Ctor ASTPrinter(ASTBuilder* astBuilder, OptionFlags optionFlags = 0, List<Part>* parts = nullptr): m_astBuilder(astBuilder), @@ -148,7 +157,7 @@ public: protected: - void _addDeclPathRec(const DeclRef<Decl>& declRef); + void _addDeclPathRec(const DeclRef<Decl>& declRef, Index depth); void _addDeclName(Decl* decl); OptionFlags m_optionFlags; ///< Flags controlling output diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 5eedb4db6..0eb6f992d 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -2176,7 +2176,7 @@ namespace Slang SLANG_NO_THROW SlangResult SLANG_MCALL checkCompileTargetSupport(SlangCompileTarget target) override; SLANG_NO_THROW SlangResult SLANG_MCALL checkPassThroughSupport(SlangPassThrough passThrough) override; - SLANG_NO_THROW SlangResult SLANG_MCALL compileStdLib() override; + SLANG_NO_THROW SlangResult SLANG_MCALL compileStdLib(slang::CompileStdLibFlags flags) override; SLANG_NO_THROW SlangResult SLANG_MCALL loadStdLib(const void* stdLib, size_t stdLibSizeInBytes) override; SLANG_NO_THROW SlangResult SLANG_MCALL saveStdLib(SlangArchiveType archiveType, ISlangBlob** outBlob) override; diff --git a/source/slang/slang-doc-extractor.cpp b/source/slang/slang-doc-extractor.cpp index f44257877..3951e4977 100644 --- a/source/slang/slang-doc-extractor.cpp +++ b/source/slang/slang-doc-extractor.cpp @@ -322,6 +322,7 @@ Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location locatio switch (tok.type) { + case TokenType::LBrace: case TokenType::LBracket: case TokenType::LParent: case TokenType::OpLess: @@ -396,11 +397,11 @@ Index DocMarkupExtractor::_findStartIndex(const FindInfo& info, Location locatio { return i + 1; } - } - if (location == Location::Before) - { - return -1; + if (location == Location::Before) + { + return -1; + } } break; @@ -636,6 +637,9 @@ SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, SearchStyle se return _findMarkup(info, locs, SLANG_COUNT_OF(locs), out); } case SearchStyle::Before: + { + return _findMarkup(info, Location::Before, out); + } case SearchStyle::Function: { return _findMarkup(info, Location::Before, out); @@ -653,6 +657,70 @@ SlangResult DocMarkupExtractor::_findMarkup(const FindInfo& info, SearchStyle se } } + +static void _calcLineVisibility(SourceView* sourceView, const TokenList& toks, List<MarkupVisibility>& outLineVisibility) +{ + SourceFile* sourceFile = sourceView->getSourceFile(); + const auto& lineOffsets = sourceFile->getLineBreakOffsets(); + + outLineVisibility.setCount(lineOffsets.getCount() + 1); + + MarkupVisibility lastVisibility = MarkupVisibility::Public; + Index lastLine = 0; + + for (const auto& tok : toks) + { + if (tok.type == TokenType::LineComment) + { + UnownedStringSlice contents = tok.getContent(); + + MarkupVisibility newVisibility = lastVisibility; + + // Distinct from other markup + if (contents.startsWith(toSlice("//@"))) + { + UnownedStringSlice access = contents.tail(3).trim(); + if (access == "hidden:" || access == "private:") + { + newVisibility = MarkupVisibility::Hidden; + } + else if (access == "internal:") + { + newVisibility = MarkupVisibility::Internal; + } + else if (access == "public:") + { + newVisibility = MarkupVisibility::Public; + } + } + + if (newVisibility != lastVisibility) + { + // Work up the line it's on + const int offset = sourceView->getRange().getOffset(tok.loc); + Index line = sourceFile->calcLineIndexFromOffset(offset); + + // Fill in the span + for (Index i = lastLine; i < line; ++i) + { + outLineVisibility[i] = lastVisibility; + } + + // Record the new access and where we are up to + lastLine = line; + lastVisibility = newVisibility; + } + } + } + + // Fill in the remaining + for (Index i = lastLine; i < outLineVisibility.getCount(); ++ i) + { + outLineVisibility[i] = lastVisibility; + } +} + + SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inputCount, SourceManager* sourceManager, DiagnosticSink* sink, List<SourceView*>& outViews, List<SearchItemOutput>& out) { struct Entry @@ -726,8 +794,10 @@ SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inp { TokenList tokens; + List<MarkupVisibility> lineVisibility; + + MemoryArena memoryArena(4096); - MemoryArena memoryArena; RootNamePool rootNamePool; NamePool namePool; namePool.setRootNamePool(&rootNamePool); @@ -746,6 +816,7 @@ SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inp dst.viewIndex = -1; dst.inputIndex = entry.inputIndex; + dst.visibilty = MarkupVisibility::Public; // If there isn't a mechanism to search with, just move on if (entry.searchStyle == SearchStyle::None) @@ -767,6 +838,10 @@ SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inp // Lex everything tokens = lexer.lexAllTokens(); + + // Let's work out the access + + _calcLineVisibility(sourceView, tokens, lineVisibility); } dst.viewIndex = viewIndex; @@ -781,6 +856,8 @@ SlangResult DocMarkupExtractor::extract(const SearchItemInput* inputs, Index inp SourceFile* sourceFile = sourceView->getSourceFile(); const Index lineIndex = sourceFile->calcLineIndexFromOffset(int(offset)); + dst.visibilty = lineVisibility[lineIndex]; + // Okay, lets find the token index with a binary chop Index tokenIndex = _findTokenIndex(loc, tokens.m_tokens.getBuffer(), tokens.m_tokens.getCount()); if (tokenIndex >= 0 && lineIndex >= 0) @@ -902,6 +979,7 @@ SlangResult DocMarkupExtractor::extract(ModuleDecl* moduleDecl, SourceManager* s // Add to the documentation DocMarkup::Entry& docEntry = outDoc->addEntry(decl); docEntry.m_markup = outputItem.text; + docEntry.m_visibility = outputItem.visibilty; } } diff --git a/source/slang/slang-doc-extractor.h b/source/slang/slang-doc-extractor.h index ca03fc60b..7a33b390a 100644 --- a/source/slang/slang-doc-extractor.h +++ b/source/slang/slang-doc-extractor.h @@ -7,14 +7,22 @@ namespace Slang { +enum class MarkupVisibility : uint8_t +{ + Public, ///< Always available + Internal, ///< Can be available in more verbose 'internal' documentation + Hidden, ///< Not generally available +}; + /* 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 + NodeBase* m_node; ///< The node this documentation is associated with + String m_markup; ///< The raw contents of of markup associated with the decoration + MarkupVisibility m_visibility = MarkupVisibility::Public; ///< How visible this decl is }; /// Adds an entry, returns the reference to pre-existing node if there is one @@ -149,6 +157,7 @@ public: 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 + MarkupVisibility visibilty; ///< Visibility of the item }; struct FindInfo diff --git a/source/slang/slang-doc-mark-down.cpp b/source/slang/slang-doc-mark-down.cpp index 82de7052e..384759474 100644 --- a/source/slang/slang-doc-mark-down.cpp +++ b/source/slang/slang-doc-mark-down.cpp @@ -4,9 +4,49 @@ #include "../core/slang-string-util.h" #include "slang-ast-builder.h" +#include "slang-lookup.h" namespace Slang { + +struct DocMarkDownWriter::StringListSet +{ + Index add(const HashSet<String>& set) + { + // -1 means empty, which we don't explicitly store + Index index = -1; + if (set.Count()) + { + List<String> values; + for (auto& value : set) + { + values.add(value); + } + // Sort so that can be compared for uniqueness + values.sort(); + + index = m_uniqueValues.indexOf(values); + if (index < 0) + { + index = m_uniqueValues.getCount(); + m_uniqueValues.add(values); + } + } + m_map.add(index); + return index; + } + + Index getValueIndex(Index index) const { return m_map[index]; } + List<String>& getValuesAt(Index valueIndex) { return m_uniqueValues[valueIndex]; } + const List<List<String>>& getUniqueValues() const { return m_uniqueValues; } + + StringListSet() {} + +protected: + List<Index> m_map; + List<List<String>> m_uniqueValues; +}; + /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DocMarkDownWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ template <typename T> @@ -51,39 +91,141 @@ static void _appendAsSingleLine(const UnownedStringSlice& in, StringBuilder& out StringUtil::join(lines.getBuffer(), lines.getCount(), ' ', out); } -void DocMarkDownWriter::_appendAsBullets(const List<Decl*>& in) +void DocMarkDownWriter::_appendAsBullets(const List<NameAndText>& values, char wrapChar) { auto& out = m_builder; - for (auto decl : in) + for (const auto& value : values) { - DocMarkup::Entry* paramEntry = m_markup->getEntry(decl); - out << "* "; - Name* name = decl->getName(); - if (name) + const String& name = value.name; + if (name.getLength()) { - out << toSlice("_") << name->text << toSlice("_ "); + if (wrapChar) + { + out.appendChar(wrapChar); + out << name; + out.appendChar(wrapChar); + } + else + { + out << name; + } } - if (paramEntry) + if (value.text.getLength()) { + out.appendChar(' '); + // Hmm, we'll want to make something multiline into a single line - _appendAsSingleLine(paramEntry->m_markup.getUnownedSlice(), out); + _appendAsSingleLine(value.text.getUnownedSlice(), out); } out << "\n"; } +} - out << toSlice("\n"); +void DocMarkDownWriter::_appendAsBullets(const List<String>& values, char wrapChar) +{ + auto& out = m_builder; + for (const auto& value : values) + { + out << "* "; + + if (value.getLength()) + { + if (wrapChar) + { + out.appendChar(wrapChar); + out << value; + out.appendChar(wrapChar); + + } + else + { + out << value; + } + } + out << "\n"; + } } -template <typename T> -void DocMarkDownWriter::_appendAsBullets(FilteredMemberList<T>& list) +String DocMarkDownWriter::_getName(Decl* decl) { - List<Decl*> decls; - _toList(list, decls); - _appendAsBullets(decls); + StringBuilder buf; + ASTPrinter::appendDeclName(decl, buf); + return buf.ProduceString(); +} + +String DocMarkDownWriter::_getName(InheritanceDecl* decl) +{ + StringBuilder buf; + buf.Clear(); + buf << decl->base; + return buf.ProduceString(); +} + +DocMarkDownWriter::NameAndText DocMarkDownWriter::_getNameAndText(DocMarkup::Entry* entry, Decl* decl) +{ + NameAndText nameAndText; + + nameAndText.name = _getName(decl); + + // We could extract different text here, but for now just do all markup + if (entry) + { + // For now we'll just use all markup, but really we need something more sophisticated here + nameAndText.text = entry->m_markup; + } + + return nameAndText; +} + +DocMarkDownWriter::NameAndText DocMarkDownWriter::_getNameAndText(Decl* decl) +{ + DocMarkup::Entry* entry = m_markup->getEntry(decl); + return _getNameAndText(entry, decl); +} + +List<DocMarkDownWriter::NameAndText> DocMarkDownWriter::_getAsNameAndTextList(const List<Decl*>& in) +{ + List<NameAndText> out; + for (auto decl : in) + { + out.add(_getNameAndText(decl)); + } + return out; +} + +List<String> DocMarkDownWriter::_getAsStringList(const List<Decl*>& in) +{ + List<String> strings; + for (auto decl : in) + { + strings.add(_getName(decl)); + } + return strings; +} + +void DocMarkDownWriter::_appendCommaList(const List<String>& strings, char wrapChar) +{ + for (Index i = 0; i < strings.getCount(); ++i) + { + if (i > 0) + { + m_builder << toSlice(", "); + } + if (wrapChar) + { + m_builder.appendChar(wrapChar); + m_builder << strings[i]; + m_builder.appendChar(wrapChar); + } + else + { + m_builder << strings[i]; + } + } } /* static */void DocMarkDownWriter::getSignature(const List<Part>& parts, Signature& outSig) @@ -149,73 +291,81 @@ void DocMarkDownWriter::writeVar(const DocMarkup::Entry& entry, VarDecl* varDecl out << toSlice("```\n"); out << varDecl->type << toSlice(" ") << varDecl << toSlice("\n"); - out << toSlice("```\n"); + out << toSlice("```\n\n"); writeDescription(entry); } -void DocMarkDownWriter::writeCallable(const DocMarkup::Entry& entry, CallableDecl* callableDecl) +void DocMarkDownWriter::writeSignature(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)); - } + 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(" "); + { + // Some types (like constructors say) don't have any return type, so check before outputting + const UnownedStringSlice returnType = printer.getPartSlice(signature.returnType); + if (returnType.getLength() > 0) + { + out << returnType << toSlice(" "); + } + } - out << printer.getPartSlice(signature.name); + out << printer.getPartSlice(signature.name); - if (signature.genericParams.getCount()) +#if 0 + if (signature.genericParams.getCount()) + { + out << toSlice("<"); + const Index count = signature.genericParams.getCount(); + for (Index i = 0; i < count; ++i) { - out << toSlice("<"); - const Index count = signature.genericParams.getCount(); - for (Index i = 0; i < count; ++i) + const auto& genericParam = signature.genericParams[i]; + if (i > 0) { - const auto& genericParam = signature.genericParams[i]; - if (i > 0) - { - out << toSlice(", "); - } - out << printer.getPartSlice(genericParam.name); + out << toSlice(", "); + } + out << printer.getPartSlice(genericParam.name); - if (genericParam.type.type != Part::Type::None) - { - out << toSlice(" : "); - out << printer.getPartSlice(genericParam.type); - } + if (genericParam.type.type != Part::Type::None) + { + out << toSlice(" : "); + out << printer.getPartSlice(genericParam.type); } - out << toSlice(">"); } + out << toSlice(">"); + } +#endif - if (paramCount > 0) + switch (paramCount) + { + case 0: { + // Has no parameters + out << toSlice("();\n"); + break; + } + case 1: + { + // Place all on single line + out.appendChar('('); + const auto& param = signature.params[0]; + out << printer.getPartSlice(param.first) << toSlice(" ") << printer.getPartSlice(param.second); + out << ");\n"; + break; + } + default: + { + // Put each parameter on a line on it's own out << toSlice("(\n"); - StringBuilder line; for (Index i = 0; i < paramCount; ++i) { @@ -244,45 +394,371 @@ void DocMarkDownWriter::writeCallable(const DocMarkup::Entry& entry, CallableDec } out << ");\n"; + break; + } + } +} + +List<DocMarkDownWriter::NameAndText> DocMarkDownWriter::_getUniqueParams(const List<Decl*>& decls) +{ + List<NameAndText> out; + + Dictionary<Name*, Index> nameDict; + + for (auto decl : decls) + { + Name* name = decl->getName(); + if (!name) + { + continue; + } + + Index index = nameDict.GetOrAddValue(name, out.getCount()); + + if (index >= out.getCount()) + { + out.add(NameAndText{ getText(name), String() }); + } + + NameAndText& nameAndMarkup = out[index]; + if (nameAndMarkup.text.getLength() > 0) + { + continue; + } + + auto entry = m_markup->getEntry(decl); + if (entry && entry->m_markup.getLength()) + { + nameAndMarkup.text = entry->m_markup; + } + } + + return out; +} + +static void _addRequirements(Decl* decl, HashSet<String>& outRequirements) +{ + StringBuilder buf; + + if (auto spirvRequiredModifier = decl->findModifier<RequiredSPIRVVersionModifier>()) + { + buf.Clear(); + buf << "SPIR-V "; + spirvRequiredModifier->version.append(buf); + outRequirements.Add(buf); + } + + if (auto glslRequiredModifier = decl->findModifier<RequiredGLSLVersionModifier>()) + { + buf.Clear(); + buf << "GLSL" << glslRequiredModifier->versionNumberToken.getContent(); + outRequirements.Add(buf); + } + + if (auto cudaSMVersionModifier = decl->findModifier<RequiredCUDASMVersionModifier>()) + { + buf.Clear(); + buf << "CUDA SM "; + cudaSMVersionModifier->version.append(buf); + outRequirements.Add(buf); + } + + if (auto extensionModifier = decl->findModifier<RequiredGLSLExtensionModifier>()) + { + buf.Clear(); + buf << "GLSL " << extensionModifier->extensionNameToken.getContent(); + outRequirements.Add(buf); + } + + if (auto requiresNVAPIAttribute = decl->findModifier<RequiresNVAPIAttribute>()) + { + outRequirements.Add("NVAPI"); + } +} + +void DocMarkDownWriter::_maybeAppendSet(const UnownedStringSlice& title, const StringListSet& set) +{ + auto& out = m_builder; + + const auto& uniqueValues = set.getUniqueValues(); + + const Index uniqueCount = uniqueValues.getCount(); + if (uniqueCount > 0 && uniqueValues[0].getCount() > 0) + { + out << title; + if (uniqueCount > 1) + { + for (Index i = 0; i < uniqueCount; ++i) + { + out << (i + 1) << (". "); + _appendCommaList(uniqueValues[i], '`'); + out << toSlice("\n"); + } } else { - out << toSlice("();\n"); + _appendCommaList(uniqueValues[0], '`'); + out << toSlice("\n"); } + out << toSlice("\n"); + } +} - out << "```\n\n"; +static Decl* _getSameNameDecl(Decl* decl) +{ + auto parentDecl = decl->parentDecl; + + // Sanity check: there should always be a parent declaration. + // + SLANG_ASSERT(parentDecl); + if (!parentDecl) return nullptr; + + // If the declaration is the "inner" declaration of a generic, + // then we actually want to look one level up, because the + // peers/siblings of the declaration will belong to the same + // parent as the generic, not to the generic. + // + if (auto genericParentDecl = as<GenericDecl>(parentDecl)) + { + // Note: we need to check here to be sure `newDecl` + // is the "inner" declaration and not one of the + // generic parameters, or else we will end up + // checking them at the wrong scope. + // + if (decl == genericParentDecl->inner) + { + decl = parentDecl; + } } + return decl; +} + +static bool _isFirstOverridden(Decl* decl) +{ + decl = _getSameNameDecl(decl); + + ContainerDecl* parentDecl = decl->parentDecl; + + // Make sure we have the member dictionary. + buildMemberDictionary(parentDecl); + + Name* declName = decl->getName(); + if (declName) { - // The parameters, in order - List<Decl*> params; + Decl** firstDeclPtr = parentDecl->memberDictionary.TryGetValue(declName); + return (firstDeclPtr && *firstDeclPtr == decl) || (firstDeclPtr == nullptr); + } - if (genericDecl) + return false; +} + +void DocMarkDownWriter::writeCallableOverridable(const DocMarkup::Entry& entry, CallableDecl* callableDecl) +{ + auto& out = m_builder; + + writePreamble(entry); + + { + // Output the overridable path (ie without terminal generic parameters) + ASTPrinter printer(m_astBuilder); + printer.addOverridableDeclPath(DeclRef<Decl>(callableDecl, nullptr)); + // Extract the name + out << toSlice("# `") << printer.getStringBuilder() << toSlice("`\n\n"); + } + + writeDescription(entry); + + List<CallableDecl*> sigs; + { + Decl* sameNameDecl = _getSameNameDecl(callableDecl); + + for (Decl* curDecl = sameNameDecl; curDecl; curDecl = curDecl->nextInContainerWithSameName) + { + CallableDecl* sig = nullptr; + if (GenericDecl* genericDecl = as<GenericDecl>(curDecl)) + { + sig = as<CallableDecl>(genericDecl->inner); + } + else + { + sig = as<CallableDecl>(curDecl); + } + + if (!sig) + { + continue; + } + + // Want to add only the primary sig + if (sig->primaryDecl == nullptr || sig->primaryDecl == sig) + { + sigs.add(sig); + } + } + + // Lets put back into source order + sigs.sort([](CallableDecl* a, CallableDecl* b) -> bool { return a->loc.getRaw() < b->loc.getRaw(); }); + } + + // Set of sets of requirements, holds index map from addition to the set entry + StringListSet requirementsSetSet; + + // Similarly for targets + StringListSet targetSetSet; + + // We want to determine the unique signature, and then the requirements for the signature + { + for (Index i = 0; i < sigs.getCount(); ++i) { - for (Decl* decl : genericDecl->members) + CallableDecl* sig = sigs[i]; + // Add the requirements for all the different versions { - if (as<GenericTypeParamDecl>(decl) || - as<GenericValueParamDecl>(decl)) + HashSet<String> requirementsSet; + HashSet<String> targetSet; + for (CallableDecl* curSig = sig; curSig; curSig = curSig->nextDecl) { - params.add(decl); + _addRequirements(sig, requirementsSet); + + // Handle Target info + + for (auto targetIntrinsic : sig->getModifiersOfType<TargetIntrinsicModifier>()) + { + targetSet.Add(String(targetIntrinsic->targetToken.getContent()).toUpper()); + } + for (auto specializedForTarget : sig->getModifiersOfType<SpecializedForTargetModifier>()) + { + targetSet.Add(String(specializedForTarget->targetToken.getContent()).toUpper()); + } } + + requirementsSetSet.add(requirementsSet); + + // TODO(JS): This really isn't right, we ideally have markup that made hlsl availability explicit + // We *assume* that we have 'hlsl' for now + targetSet.Add(String("HLSL")); + targetSetSet.add(targetSet); } } + } + + // Output the signature + { + out << toSlice("## Signature \n\n"); + out << toSlice("```\n"); + + Index prevRequirementsIndex = -1; + Index prevTargetIndex = -1; - for (ParamDecl* paramDecl : callableDecl->getParameters()) + const Int sigCount = sigs.getCount(); + for (Index i = 0; i < sigCount; ++i) { - params.add(paramDecl); + auto sig = sigs[i]; + + // Output if needs unique requirements + if (requirementsSetSet.getUniqueValues().getCount() > 1) + { + const Index requirementsIndex = requirementsSetSet.getValueIndex(i); + if (requirementsIndex != prevRequirementsIndex) + { + if (requirementsIndex >= 0) + { + out << toSlice("/// See Requirement ") << (requirementsIndex + 1) << toSlice("\n"); + } + else + { + out << toSlice("/// No requirements\n"); + } + prevRequirementsIndex = requirementsIndex; + } + } + + if (targetSetSet.getUniqueValues().getCount() > 1) + { + const Index targetIndex = targetSetSet.getValueIndex(i); + if (targetIndex != prevTargetIndex) + { + if (targetIndex >= 0) + { + out << toSlice("/// See Target Availability ") << (targetIndex + 1) << toSlice("\n"); + } + else + { + out << toSlice("/// All Targets\n"); + } + prevTargetIndex = targetIndex; + } + } + + writeSignature(sig); } + out << "```\n\n"; + } - if (params.getCount()) + _maybeAppendSet(toSlice("## Requirements\n\n"), requirementsSetSet); + + // Target availability + + { + const auto& uniqueValues = targetSetSet.getUniqueValues(); + if (uniqueValues.getCount() == 1 && uniqueValues[0].getCount() == 1 && uniqueValues[0][0] == "hlsl") { - out << "## Parameters\n\n"; - // We have generic params and regular parameters, in this list - _appendAsBullets(params); + // TODO(JS): + // If something is marked up for hlsl, and nothing else, that indicates it might *only* be available on hlsl, but + // we don't correctly handle that here. + + // If the only thing we have is 'hlsl' - we injected that so we'll *assume* it's available everywhere, so + // don't bother outputting. + } + else + { + _maybeAppendSet(toSlice("## Target Availability\n\n"), targetSetSet); } } - writeDescription(entry); + { + // We will use the first documentation found for each parameter type + { + List<Decl*> paramDecls; + List<Decl*> genericDecls; + for (auto sig : sigs) + { + GenericDecl* genericDecl = as<GenericDecl>(sig->parentDecl); + + // NOTE! + // Here we assume the names of generic parameters are such that they are + + // We list generic parameters, as types of parameters, if they are directly associated with this + // callable. + if (genericDecl) + { + for (Decl* decl : genericDecl->members) + { + if (as<GenericTypeParamDecl>(decl) || + as<GenericValueParamDecl>(decl)) + { + genericDecls.add(decl); + } + } + } + + for (ParamDecl* paramDecl : sig->getParameters()) + { + paramDecls.add(paramDecl); + } + } + + if (paramDecls.getCount() > 0 || paramDecls.getCount() > 0) + { + out << "## Parameters\n\n"; + + // Get the unique generics + _appendAsBullets(_getUniqueParams(genericDecls), '`'); + // And parameters + _appendAsBullets(_getUniqueParams(paramDecls), '`'); + } + } + } } void DocMarkDownWriter::writeEnum(const DocMarkup::Entry& entry, EnumDecl* enumDecl) @@ -301,12 +777,55 @@ void DocMarkDownWriter::writeEnum(const DocMarkup::Entry& entry, EnumDecl* enumD out << toSlice("## Values \n\n"); - auto cases = enumDecl->getMembersOfType<EnumCaseDecl>(); - _appendAsBullets(cases); - + _appendAsBullets(_getAsNameAndTextList(enumDecl->getMembersOfType<EnumCaseDecl>()), '_'); + writeDescription(entry); } +void DocMarkDownWriter::_appendEscaped(const UnownedStringSlice& text) +{ + auto& out = m_builder; + + const char* start = text.begin(); + const char* cur = start; + const char*const end = text.end(); + + for (; cur < end; ++cur) + { + const char c = *cur; + + switch (c) + { + case '<': + case '>': + case '&': + case '"': + case '_': + { + // Flush if any before + if (cur > start) + { + out.append(start, cur); + } + // Prefix with the + out.appendChar('\\'); + + // Start will still include the char, for later flushing + start = cur; + break; + } + default: break; + } + } + + // Flush any remaining + if (cur > start) + { + out.append(start, cur); + } +} + + void DocMarkDownWriter::_appendDerivedFrom(const UnownedStringSlice& prefix, AggTypeDeclBase* aggTypeDecl) { auto& out = m_builder; @@ -330,43 +849,52 @@ void DocMarkDownWriter::_appendDerivedFrom(const UnownedStringSlice& prefix, Agg } } -void DocMarkDownWriter::writeAggType(const DocMarkup::Entry& entry, AggTypeDeclBase* aggTypeDecl) +void DocMarkDownWriter::_appendAggTypeName(AggTypeDeclBase* aggTypeDecl) { - writePreamble(entry); - auto& out = m_builder; // This could be lots of different things - struct/class/extension/interface/.. - out << toSlice("# "); + ASTPrinter printer(m_astBuilder); + printer.addDeclPath(DeclRef<Decl>(aggTypeDecl, nullptr)); + if (as<StructDecl>(aggTypeDecl)) { - out << toSlice("struct "); + out << toSlice("struct ") << printer.getStringBuilder(); } else if (as<ClassDecl>(aggTypeDecl)) { - out << toSlice("class "); + out << toSlice("class ") << printer.getStringBuilder(); } else if (as<InterfaceDecl>(aggTypeDecl)) { - out << toSlice("interface "); + out << toSlice("interface ") << printer.getStringBuilder(); } else if (ExtensionDecl* extensionDecl = as<ExtensionDecl>(aggTypeDecl)) { out << toSlice("extension ") << extensionDecl->targetType; - _appendDerivedFrom(toSlice(" : "), extensionDecl); + _appendDerivedFrom(toSlice(" : "), extensionDecl); } else { out << toSlice("?"); } +} - Name* name = aggTypeDecl->getName(); - if (name) - { - out << name->text; - } - out << toSlice("\n\n"); +void DocMarkDownWriter::writeAggType(const DocMarkup::Entry& entry, AggTypeDeclBase* aggTypeDecl) +{ + writePreamble(entry); + + auto& out = m_builder; + + // We can write out he name using the printer + + ASTPrinter printer(m_astBuilder); + printer.addDeclPath(DeclRef<Decl>(aggTypeDecl, nullptr)); + + out << toSlice("# `"); + _appendAggTypeName(aggTypeDecl); + out << toSlice("`\n\n"); { List<InheritanceDecl*> inheritanceDecls; @@ -374,41 +902,104 @@ void DocMarkDownWriter::writeAggType(const DocMarkup::Entry& entry, AggTypeDeclB if (inheritanceDecls.getCount()) { - out << "*Derives from:* "; + out << "*Implements:* "; + _appendCommaList(_getAsStringList(inheritanceDecls), '`'); + out << toSlice("\n\n"); + } + } - for (Index i = 0; i < inheritanceDecls.getCount(); ++i) - { - if (i > 0) + writeDescription(entry); + + { + List<AssocTypeDecl*> assocTypeDecls; + _getDecls<AssocTypeDecl>(aggTypeDecl, assocTypeDecls); + + if (assocTypeDecls.getCount()) + { + out << toSlice("# Associated types\n\n"); + + for (AssocTypeDecl* assocTypeDecl : assocTypeDecls) + { + out << "* _" << assocTypeDecl->getName()->text << "_ "; + + // Look up markup + DocMarkup::Entry* assocTypeDeclEntry = m_markup->getEntry(assocTypeDecl); + if (assocTypeDeclEntry) { - out << toSlice(", "); + _appendAsSingleLine(assocTypeDeclEntry->m_markup.getUnownedSlice(), out); + } + out << toSlice("\n"); + + List<InheritanceDecl*> inheritanceDecls; + _getDecls<InheritanceDecl>(assocTypeDecl, inheritanceDecls); + + if (inheritanceDecls.getCount()) + { + out << " "; + _appendCommaList(_getAsStringList(inheritanceDecls), '`'); + out << toSlice("\n"); } - out << inheritanceDecls[i]->base; } + out << toSlice("\n\n"); } } + if (GenericDecl* genericDecl = as<GenericDecl>(aggTypeDecl->parentDecl)) + { + // The parameters, in order + List<Decl*> params; + for (Decl* decl : genericDecl->members) + { + if (as<GenericTypeParamDecl>(decl) || + as<GenericValueParamDecl>(decl)) + { + params.add(decl); + } + } + + if (params.getCount()) + { + out << "## Generic Parameters\n\n"; + _appendAsBullets(_getAsNameAndTextList(params), '`'); + } + } + { List<Decl*> fields; _getDeclsOfType<VarDecl>(aggTypeDecl, fields); if (fields.getCount()) { out << "## Fields\n\n"; - _appendAsBullets(fields); + + _appendAsBullets(_getAsNameAndTextList(fields), '`'); } } { - List<Decl*> methods; - _getDeclsOfType<CallableDecl>(aggTypeDecl, methods); - if (methods.getCount()) + // Make sure we've got a query-able member dictionary + buildMemberDictionary(aggTypeDecl); + SLANG_ASSERT(aggTypeDecl->isMemberDictionaryValid()); + + List<Decl*> uniqueMethods; + for (const auto& pair : aggTypeDecl->memberDictionary) { + CallableDecl* callableDecl = as<CallableDecl>(pair.Value); + if (callableDecl && isVisible(callableDecl)) + { + uniqueMethods.add(callableDecl); + } + } + + if (uniqueMethods.getCount()) + { + // Put in source definition order + uniqueMethods.sort([](Decl* a, Decl* b) -> bool { return a->loc.getRaw() < b->loc.getRaw(); }); + out << "## Methods\n\n"; - _appendAsBullets(methods); + _appendAsBullets(_getAsStringList(uniqueMethods), '`'); } } - - writeDescription(entry); } void DocMarkDownWriter::writePreamble(const DocMarkup::Entry& entry) @@ -421,26 +1012,40 @@ void DocMarkDownWriter::writePreamble(const DocMarkup::Entry& entry) out << toSlice("\n"); } - void DocMarkDownWriter::writeDescription(const DocMarkup::Entry& entry) { auto& out = m_builder; - out << toSlice("\n## Description\n\n"); - out << entry.m_markup; + if (entry.m_markup.getLength() > 0) + { + out << toSlice("## Description\n\n"); + + out << entry.m_markup.getUnownedSlice(); +#if 0 + UnownedStringSlice text(entry.m_markup.getUnownedSlice()), line; + while (StringUtil::extractLine(text, line)) + { + out << line << toSlice("\n"); + } +#endif + out << toSlice("\n"); + } } 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)) + if (as<ParamDecl>(decl) || as<EnumCaseDecl>(decl) || as<AssocTypeDecl>(decl) || as<InheritanceDecl>(decl)) { return; } if (CallableDecl* callableDecl = as<CallableDecl>(decl)) { - writeCallable(entry, callableDecl); + if (_isFirstOverridden(callableDecl)) + { + writeCallableOverridable(entry, callableDecl); + } } else if (EnumDecl* enumDecl = as<EnumDecl>(decl)) { @@ -452,6 +1057,12 @@ void DocMarkDownWriter::writeDecl(const DocMarkup::Entry& entry, Decl* decl) } else if (VarDecl* varDecl = as<VarDecl>(decl)) { + // If part of aggregate type will be output there. + if (as<AggTypeDecl>(varDecl->parentDecl)) + { + return; + } + writeVar(entry, varDecl); } else if (as<GenericDecl>(decl)) @@ -460,14 +1071,41 @@ void DocMarkDownWriter::writeDecl(const DocMarkup::Entry& entry, Decl* decl) } } +bool DocMarkDownWriter::isVisible(const Name* name) +{ + return name == nullptr || !name->text.startsWith(toSlice("__")); +} + +bool DocMarkDownWriter::isVisible(const DocMarkup::Entry& entry) +{ + // For now if it's not public it's not visible + if (entry.m_visibility != MarkupVisibility::Public) + { + return false; + } + + Decl* decl = as<Decl>(entry.m_node); + return decl == nullptr || isVisible(decl->getName()); +} + +bool DocMarkDownWriter::isVisible(Decl* decl) +{ + if (!isVisible(decl->getName())) + { + return false; + } + + auto entry = m_markup->getEntry(decl); + return entry == nullptr || entry->m_visibility == MarkupVisibility::Public; +} void DocMarkDownWriter::writeAll() { for (const auto& entry : m_markup->getEntries()) { - NodeBase* node = entry.m_node; - Decl* decl = as<Decl>(node); - if (decl) + Decl* decl = as<Decl>(entry.m_node); + + if (decl && isVisible(entry)) { writeDecl(entry, decl); } diff --git a/source/slang/slang-doc-mark-down.h b/source/slang/slang-doc-mark-down.h index e1728d18a..c42d406dc 100644 --- a/source/slang/slang-doc-mark-down.h +++ b/source/slang/slang-doc-mark-down.h @@ -31,7 +31,9 @@ struct DocMarkDownWriter /// Write out all documentation to the output buffer void writeAll(); - void writeCallable(const DocMarkup::Entry& entry, CallableDecl* callable); + /// This will write information about *all* of the overridden versions of a function/method + void writeCallableOverridable(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); @@ -39,7 +41,13 @@ struct DocMarkDownWriter void writePreamble(const DocMarkup::Entry& entry); void writeDescription(const DocMarkup::Entry& entry); - + + void writeSignature(CallableDecl* callableDecl); + + bool isVisible(const DocMarkup::Entry& entry); + bool isVisible(Decl* decl); + bool isVisible(const Name* name); + /// Get the output string const StringBuilder& getOutput() const { return m_builder; } @@ -50,15 +58,61 @@ struct DocMarkDownWriter { } + struct StringListSet; + /// Given a list of ASTPrinter::Parts, works out the different parts of the sig static void getSignature(const List<Part>& parts, Signature& outSig); + struct NameAndText + { + String name; + String text; + }; + + List<NameAndText> _getUniqueParams(const List<Decl*>& decls); + + String _getName(Decl* decl); + String _getName(InheritanceDecl* decl); + + NameAndText _getNameAndText(DocMarkup::Entry* entry, Decl* decl); + NameAndText _getNameAndText(Decl* decl); + template <typename T> - void _appendAsBullets(FilteredMemberList<T>& in); - void _appendAsBullets(const List<Decl*>& in); + List<NameAndText> _getAsNameAndTextList(const FilteredMemberList<T>& in) + { + List<NameAndText> out; + for (auto decl : const_cast<FilteredMemberList<T>&>(in)) + { + out.add(_getNameAndText(decl)); + } + return out; + } + template <typename T> + List<String> _getAsStringList(const List<T*>& in) + { + List<String> strings; + for (auto decl : in) + { + strings.add(_getName(decl)); + } + return strings; + } + + List<NameAndText> _getAsNameAndTextList(const List<Decl*>& in); + List<String> _getAsStringList(const List<Decl*>& in); + + void _appendAsBullets(const List<NameAndText>& values, char wrapChar); + void _appendAsBullets(const List<String>& values, char wrapChar); + + void _appendCommaList(const List<String>& strings, char wrapChar); + + void _maybeAppendSet(const UnownedStringSlice& title, const StringListSet& set); /// Appends prefix and the list of types derived from void _appendDerivedFrom(const UnownedStringSlice& prefix, AggTypeDeclBase* aggTypeDecl); + void _appendEscaped(const UnownedStringSlice& text); + + void _appendAggTypeName(AggTypeDeclBase* aggTypeDecl); DocMarkup* m_markup; ASTBuilder* m_astBuilder; diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp index fb7ba79c6..ff7923771 100644 --- a/source/slang/slang-options.cpp +++ b/source/slang/slang-options.cpp @@ -437,6 +437,8 @@ struct OptionsParser // The default archive type is zip SlangArchiveType archiveType = SLANG_ARCHIVE_TYPE_ZIP; + bool compileStdLib = false; + slang::CompileStdLibFlags compileStdLibFlags = 0; bool hasLoadedRepro = false; char const* const* argCursor = &argv[0]; @@ -464,7 +466,7 @@ struct OptionsParser } else if (argStr == "-compile-stdlib") { - SLANG_RETURN_ON_FAIL(session->compileStdLib()); + compileStdLib = true; } else if (argStr == "-archive-type") { @@ -533,6 +535,10 @@ struct OptionsParser } else if (argStr == "-doc") { + // If compiling stdlib is enabled, will write out documentation + compileStdLibFlags |= slang::CompileStdLibFlag::WriteDocumentation; + + // Enable writing out documentation on the req requestImpl->getFrontEndReq()->shouldDocument = true; } else if (argStr == "-dump-repro") @@ -1109,6 +1115,11 @@ struct OptionsParser } } + if (compileStdLib) + { + SLANG_RETURN_ON_FAIL(session->compileStdLib(compileStdLibFlags)); + } + // TODO(JS): This is a restriction because of how setting of state works for load repro // If a repro has been loaded, then many of the following options will overwrite // what was set up. So for now they are ignored, and only parameters set as part diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 1820b24e5..de47c48ee 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -245,7 +245,7 @@ SlangResult Session::checkPassThroughSupport(SlangPassThrough inPassThrough) return checkExternalCompilerSupport(this, PassThroughMode(inPassThrough)); } -SlangResult Session::compileStdLib() +SlangResult Session::compileStdLib(slang::CompileStdLibFlags compileFlags) { if (m_builtinLinkage->mapNameToLoadedModules.Count()) { @@ -256,6 +256,43 @@ SlangResult Session::compileStdLib() // TODO(JS): Could make this return a SlangResult as opposed to exception addBuiltinSource(coreLanguageScope, "core", getCoreLibraryCode()); addBuiltinSource(hlslLanguageScope, "hlsl", getHLSLLibraryCode()); + + if (compileFlags & slang::CompileStdLibFlag::WriteDocumentation) + { + // Not 100% clear where best to get the ASTBuilder from, but from the linkage shouldn't + // cause any problems with scoping + + ASTBuilder* astBuilder = getBuiltinLinkage()->getASTBuilder(); + SourceManager* sourceManager = getBuiltinSourceManager(); + + DiagnosticSink sink(sourceManager, Lexer::sourceLocationLexer); + + List<String> docStrings; + + // For all the modules add their doc output to docStrings + for (Module* stdlibModule : stdlibModules) + { + RefPtr<DocMarkup> markup(new DocMarkup); + DocMarkupExtractor::extract(stdlibModule->getModuleDecl(), sourceManager, &sink, markup); + + DocMarkDownWriter writer(markup, astBuilder); + writer.writeAll(); + docStrings.add(writer.getOutput()); + } + + // Combine all together in stdlib-doc.md output fiel + { + String fileName("stdlib-doc.md"); + + StreamWriter writer(new FileStream(fileName, FileMode::Create)); + + for (auto& docString : docStrings) + { + writer.Write(docString); + } + } + } + return SLANG_OK; } |
