From 9ffe2f3ef245034a2dae42017a9059dfe4d02647 Mon Sep 17 00:00:00 2001 From: jsmall-nvidia Date: Thu, 11 Mar 2021 17:56:03 -0500 Subject: MarkDown -> Markdown (#1748) * #include an absolute path didn't work - because paths were taken to always be relative. * MarkDown -> Markdown slang-doc-mark-down -> slang-doc-markdown-writer --- source/slang/slang-doc-mark-down.cpp | 1115 ---------------------------- source/slang/slang-doc-mark-down.h | 124 ---- source/slang/slang-doc-markdown-writer.cpp | 1114 +++++++++++++++++++++++++++ source/slang/slang-doc-markdown-writer.h | 124 ++++ source/slang/slang.cpp | 6 +- 5 files changed, 1241 insertions(+), 1242 deletions(-) delete mode 100644 source/slang/slang-doc-mark-down.cpp delete mode 100644 source/slang/slang-doc-mark-down.h create mode 100644 source/slang/slang-doc-markdown-writer.cpp create mode 100644 source/slang/slang-doc-markdown-writer.h (limited to 'source') diff --git a/source/slang/slang-doc-mark-down.cpp b/source/slang/slang-doc-mark-down.cpp deleted file mode 100644 index 384759474..000000000 --- a/source/slang/slang-doc-mark-down.cpp +++ /dev/null @@ -1,1115 +0,0 @@ -// slang-doc-mark-down.cpp -#include "slang-doc-mark-down.h" - -#include "../core/slang-string-util.h" - -#include "slang-ast-builder.h" -#include "slang-lookup.h" - -namespace Slang { - - -struct DocMarkDownWriter::StringListSet -{ - Index add(const HashSet& set) - { - // -1 means empty, which we don't explicitly store - Index index = -1; - if (set.Count()) - { - List 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& getValuesAt(Index valueIndex) { return m_uniqueValues[valueIndex]; } - const List>& getUniqueValues() const { return m_uniqueValues; } - - StringListSet() {} - -protected: - List m_map; - List> m_uniqueValues; -}; - -/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DocMarkDownWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ - -template -static void _getDecls(ContainerDecl* containerDecl, List& out) -{ - for (Decl* decl : containerDecl->members) - { - if (T* declAsType = as(decl)) - { - out.add(declAsType); - } - } -} - -template -static void _getDeclsOfType(ContainerDecl* containerDecl, List& out) -{ - for (Decl* decl : containerDecl->members) - { - if (as(decl)) - { - out.add(decl); - } - } -} - -template -static void _toList(FilteredMemberList& list, List& out) -{ - for (Decl* decl : list) - { - out.add(decl); - } -} - -static void _appendAsSingleLine(const UnownedStringSlice& in, StringBuilder& out) -{ - List 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& values, char wrapChar) -{ - auto& out = m_builder; - for (const auto& value : values) - { - out << "* "; - - const String& name = value.name; - if (name.getLength()) - { - if (wrapChar) - { - out.appendChar(wrapChar); - out << name; - out.appendChar(wrapChar); - } - else - { - out << name; - } - } - - if (value.text.getLength()) - { - out.appendChar(' '); - - // Hmm, we'll want to make something multiline into a single line - _appendAsSingleLine(value.text.getUnownedSlice(), out); - } - - out << "\n"; - } -} - -void DocMarkDownWriter::_appendAsBullets(const List& 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"; - } -} - -String DocMarkDownWriter::_getName(Decl* decl) -{ - 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::_getAsNameAndTextList(const List& in) -{ - List out; - for (auto decl : in) - { - out.add(_getNameAndText(decl)); - } - return out; -} - -List DocMarkDownWriter::_getAsStringList(const List& in) -{ - List strings; - for (auto decl : in) - { - strings.add(_getName(decl)); - } - return strings; -} - -void DocMarkDownWriter::_appendCommaList(const List& 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& 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\n"); - - writeDescription(entry); -} - -void DocMarkDownWriter::writeSignature(CallableDecl* callableDecl) -{ - auto& out = m_builder; - - List parts; - - ASTPrinter printer(m_astBuilder, ASTPrinter::OptionFlag::ParamNames, &parts); - printer.addDeclSignature(DeclRef(callableDecl, nullptr)); - - Signature signature; - getSignature(parts, signature); - - const Index paramCount = signature.params.getCount(); - - { - // 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); - -#if 0 - 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(">"); - } -#endif - - 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) - { - 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"; - break; - } - } -} - -List DocMarkDownWriter::_getUniqueParams(const List& decls) -{ - List out; - - Dictionary 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& outRequirements) -{ - StringBuilder buf; - - if (auto spirvRequiredModifier = decl->findModifier()) - { - buf.Clear(); - buf << "SPIR-V "; - spirvRequiredModifier->version.append(buf); - outRequirements.Add(buf); - } - - if (auto glslRequiredModifier = decl->findModifier()) - { - buf.Clear(); - buf << "GLSL" << glslRequiredModifier->versionNumberToken.getContent(); - outRequirements.Add(buf); - } - - if (auto cudaSMVersionModifier = decl->findModifier()) - { - buf.Clear(); - buf << "CUDA SM "; - cudaSMVersionModifier->version.append(buf); - outRequirements.Add(buf); - } - - if (auto extensionModifier = decl->findModifier()) - { - buf.Clear(); - buf << "GLSL " << extensionModifier->extensionNameToken.getContent(); - outRequirements.Add(buf); - } - - if (auto requiresNVAPIAttribute = decl->findModifier()) - { - 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 - { - _appendCommaList(uniqueValues[0], '`'); - out << toSlice("\n"); - } - out << toSlice("\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(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) - { - Decl** firstDeclPtr = parentDecl->memberDictionary.TryGetValue(declName); - return (firstDeclPtr && *firstDeclPtr == decl) || (firstDeclPtr == nullptr); - } - - 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(callableDecl, nullptr)); - // Extract the name - out << toSlice("# `") << printer.getStringBuilder() << toSlice("`\n\n"); - } - - writeDescription(entry); - - List sigs; - { - Decl* sameNameDecl = _getSameNameDecl(callableDecl); - - for (Decl* curDecl = sameNameDecl; curDecl; curDecl = curDecl->nextInContainerWithSameName) - { - CallableDecl* sig = nullptr; - if (GenericDecl* genericDecl = as(curDecl)) - { - sig = as(genericDecl->inner); - } - else - { - sig = as(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) - { - CallableDecl* sig = sigs[i]; - // Add the requirements for all the different versions - { - HashSet requirementsSet; - HashSet targetSet; - for (CallableDecl* curSig = sig; curSig; curSig = curSig->nextDecl) - { - _addRequirements(sig, requirementsSet); - - // Handle Target info - - for (auto targetIntrinsic : sig->getModifiersOfType()) - { - targetSet.Add(String(targetIntrinsic->targetToken.getContent()).toUpper()); - } - for (auto specializedForTarget : sig->getModifiersOfType()) - { - 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; - - const Int sigCount = sigs.getCount(); - for (Index i = 0; i < sigCount; ++i) - { - 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"; - } - - _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") - { - // 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); - } - } - - { - // We will use the first documentation found for each parameter type - { - List paramDecls; - List genericDecls; - for (auto sig : sigs) - { - GenericDecl* genericDecl = as(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(decl) || - as(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) -{ - 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"); - - _appendAsBullets(_getAsNameAndTextList(enumDecl->getMembersOfType()), '_'); - - 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; - - List 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::_appendAggTypeName(AggTypeDeclBase* aggTypeDecl) -{ - auto& out = m_builder; - - // This could be lots of different things - struct/class/extension/interface/.. - - ASTPrinter printer(m_astBuilder); - printer.addDeclPath(DeclRef(aggTypeDecl, nullptr)); - - if (as(aggTypeDecl)) - { - out << toSlice("struct ") << printer.getStringBuilder(); - } - else if (as(aggTypeDecl)) - { - out << toSlice("class ") << printer.getStringBuilder(); - } - else if (as(aggTypeDecl)) - { - out << toSlice("interface ") << printer.getStringBuilder(); - } - else if (ExtensionDecl* extensionDecl = as(aggTypeDecl)) - { - out << toSlice("extension ") << extensionDecl->targetType; - _appendDerivedFrom(toSlice(" : "), extensionDecl); - } - else - { - out << toSlice("?"); - } -} - -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(aggTypeDecl, nullptr)); - - out << toSlice("# `"); - _appendAggTypeName(aggTypeDecl); - out << toSlice("`\n\n"); - - { - List inheritanceDecls; - _getDecls(aggTypeDecl, inheritanceDecls); - - if (inheritanceDecls.getCount()) - { - out << "*Implements:* "; - _appendCommaList(_getAsStringList(inheritanceDecls), '`'); - out << toSlice("\n\n"); - } - } - - writeDescription(entry); - - { - List assocTypeDecls; - _getDecls(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) - { - _appendAsSingleLine(assocTypeDeclEntry->m_markup.getUnownedSlice(), out); - } - out << toSlice("\n"); - - List inheritanceDecls; - _getDecls(assocTypeDecl, inheritanceDecls); - - if (inheritanceDecls.getCount()) - { - out << " "; - _appendCommaList(_getAsStringList(inheritanceDecls), '`'); - out << toSlice("\n"); - } - } - - out << toSlice("\n\n"); - } - } - - if (GenericDecl* genericDecl = as(aggTypeDecl->parentDecl)) - { - // The parameters, in order - List params; - for (Decl* decl : genericDecl->members) - { - if (as(decl) || - as(decl)) - { - params.add(decl); - } - } - - if (params.getCount()) - { - out << "## Generic Parameters\n\n"; - _appendAsBullets(_getAsNameAndTextList(params), '`'); - } - } - - { - List fields; - _getDeclsOfType(aggTypeDecl, fields); - if (fields.getCount()) - { - out << "## Fields\n\n"; - - _appendAsBullets(_getAsNameAndTextList(fields), '`'); - } - } - - { - // Make sure we've got a query-able member dictionary - buildMemberDictionary(aggTypeDecl); - SLANG_ASSERT(aggTypeDecl->isMemberDictionaryValid()); - - List uniqueMethods; - for (const auto& pair : aggTypeDecl->memberDictionary) - { - CallableDecl* callableDecl = as(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(_getAsStringList(uniqueMethods), '`'); - } - } -} - -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; - - 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(decl) || as(decl) || as(decl) || as(decl)) - { - return; - } - - if (CallableDecl* callableDecl = as(decl)) - { - if (_isFirstOverridden(callableDecl)) - { - writeCallableOverridable(entry, callableDecl); - } - } - else if (EnumDecl* enumDecl = as(decl)) - { - writeEnum(entry, enumDecl); - } - else if (AggTypeDeclBase* aggType = as(decl)) - { - writeAggType(entry, aggType); - } - else if (VarDecl* varDecl = as(decl)) - { - // If part of aggregate type will be output there. - if (as(varDecl->parentDecl)) - { - return; - } - - writeVar(entry, varDecl); - } - else if (as(decl)) - { - // We can ignore as inner decls will be picked up, and written - } -} - -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(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()) - { - Decl* decl = as(entry.m_node); - - if (decl && isVisible(entry)) - { - writeDecl(entry, decl); - } - } -} - -} // namespace Slang diff --git a/source/slang/slang-doc-mark-down.h b/source/slang/slang-doc-mark-down.h deleted file mode 100644 index c42d406dc..000000000 --- a/source/slang/slang-doc-mark-down.h +++ /dev/null @@ -1,124 +0,0 @@ -// 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 params; - List genericParams; - Part name; - }; - - /// Write out all documentation to the output buffer - void writeAll(); - - /// 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); - void writeVar(const DocMarkup::Entry& entry, VarDecl* varDecl); - - 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; } - - /// Ctor. - DocMarkDownWriter(DocMarkup* markup, ASTBuilder* astBuilder) : - m_markup(markup), - m_astBuilder(astBuilder) - { - } - - struct StringListSet; - - /// Given a list of ASTPrinter::Parts, works out the different parts of the sig - static void getSignature(const List& parts, Signature& outSig); - - struct NameAndText - { - String name; - String text; - }; - - List _getUniqueParams(const List& decls); - - String _getName(Decl* decl); - String _getName(InheritanceDecl* decl); - - NameAndText _getNameAndText(DocMarkup::Entry* entry, Decl* decl); - NameAndText _getNameAndText(Decl* decl); - - template - List _getAsNameAndTextList(const FilteredMemberList& in) - { - List out; - for (auto decl : const_cast&>(in)) - { - out.add(_getNameAndText(decl)); - } - return out; - } - template - List _getAsStringList(const List& in) - { - List strings; - for (auto decl : in) - { - strings.add(_getName(decl)); - } - return strings; - } - - List _getAsNameAndTextList(const List& in); - List _getAsStringList(const List& in); - - void _appendAsBullets(const List& values, char wrapChar); - void _appendAsBullets(const List& values, char wrapChar); - - void _appendCommaList(const List& 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; - StringBuilder m_builder; -}; - -} // namespace Slang - -#endif diff --git a/source/slang/slang-doc-markdown-writer.cpp b/source/slang/slang-doc-markdown-writer.cpp new file mode 100644 index 000000000..259762e88 --- /dev/null +++ b/source/slang/slang-doc-markdown-writer.cpp @@ -0,0 +1,1114 @@ +// slang-doc-markdown-writer.cpp +#include "slang-doc-markdown-writer.h" + +#include "../core/slang-string-util.h" + +#include "slang-ast-builder.h" +#include "slang-lookup.h" + +namespace Slang { + +struct DocMarkdownWriter::StringListSet +{ + Index add(const HashSet& set) + { + // -1 means empty, which we don't explicitly store + Index index = -1; + if (set.Count()) + { + List 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& getValuesAt(Index valueIndex) { return m_uniqueValues[valueIndex]; } + const List>& getUniqueValues() const { return m_uniqueValues; } + + StringListSet() {} + +protected: + List m_map; + List> m_uniqueValues; +}; + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! DocMarkDownWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +template +static void _getDecls(ContainerDecl* containerDecl, List& out) +{ + for (Decl* decl : containerDecl->members) + { + if (T* declAsType = as(decl)) + { + out.add(declAsType); + } + } +} + +template +static void _getDeclsOfType(ContainerDecl* containerDecl, List& out) +{ + for (Decl* decl : containerDecl->members) + { + if (as(decl)) + { + out.add(decl); + } + } +} + +template +static void _toList(FilteredMemberList& list, List& out) +{ + for (Decl* decl : list) + { + out.add(decl); + } +} + +static void _appendAsSingleLine(const UnownedStringSlice& in, StringBuilder& out) +{ + List 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& values, char wrapChar) +{ + auto& out = m_builder; + for (const auto& value : values) + { + out << "* "; + + const String& name = value.name; + if (name.getLength()) + { + if (wrapChar) + { + out.appendChar(wrapChar); + out << name; + out.appendChar(wrapChar); + } + else + { + out << name; + } + } + + if (value.text.getLength()) + { + out.appendChar(' '); + + // Hmm, we'll want to make something multiline into a single line + _appendAsSingleLine(value.text.getUnownedSlice(), out); + } + + out << "\n"; + } +} + +void DocMarkdownWriter::_appendAsBullets(const List& 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"; + } +} + +String DocMarkdownWriter::_getName(Decl* decl) +{ + 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::_getAsNameAndTextList(const List& in) +{ + List out; + for (auto decl : in) + { + out.add(_getNameAndText(decl)); + } + return out; +} + +List DocMarkdownWriter::_getAsStringList(const List& in) +{ + List strings; + for (auto decl : in) + { + strings.add(_getName(decl)); + } + return strings; +} + +void DocMarkdownWriter::_appendCommaList(const List& 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& 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\n"); + + writeDescription(entry); +} + +void DocMarkdownWriter::writeSignature(CallableDecl* callableDecl) +{ + auto& out = m_builder; + + List parts; + + ASTPrinter printer(m_astBuilder, ASTPrinter::OptionFlag::ParamNames, &parts); + printer.addDeclSignature(DeclRef(callableDecl, nullptr)); + + Signature signature; + getSignature(parts, signature); + + const Index paramCount = signature.params.getCount(); + + { + // 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); + +#if 0 + 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(">"); + } +#endif + + 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) + { + 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"; + break; + } + } +} + +List DocMarkdownWriter::_getUniqueParams(const List& decls) +{ + List out; + + Dictionary 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& outRequirements) +{ + StringBuilder buf; + + if (auto spirvRequiredModifier = decl->findModifier()) + { + buf.Clear(); + buf << "SPIR-V "; + spirvRequiredModifier->version.append(buf); + outRequirements.Add(buf); + } + + if (auto glslRequiredModifier = decl->findModifier()) + { + buf.Clear(); + buf << "GLSL" << glslRequiredModifier->versionNumberToken.getContent(); + outRequirements.Add(buf); + } + + if (auto cudaSMVersionModifier = decl->findModifier()) + { + buf.Clear(); + buf << "CUDA SM "; + cudaSMVersionModifier->version.append(buf); + outRequirements.Add(buf); + } + + if (auto extensionModifier = decl->findModifier()) + { + buf.Clear(); + buf << "GLSL " << extensionModifier->extensionNameToken.getContent(); + outRequirements.Add(buf); + } + + if (auto requiresNVAPIAttribute = decl->findModifier()) + { + 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 + { + _appendCommaList(uniqueValues[0], '`'); + out << toSlice("\n"); + } + out << toSlice("\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(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) + { + Decl** firstDeclPtr = parentDecl->memberDictionary.TryGetValue(declName); + return (firstDeclPtr && *firstDeclPtr == decl) || (firstDeclPtr == nullptr); + } + + 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(callableDecl, nullptr)); + // Extract the name + out << toSlice("# `") << printer.getStringBuilder() << toSlice("`\n\n"); + } + + writeDescription(entry); + + List sigs; + { + Decl* sameNameDecl = _getSameNameDecl(callableDecl); + + for (Decl* curDecl = sameNameDecl; curDecl; curDecl = curDecl->nextInContainerWithSameName) + { + CallableDecl* sig = nullptr; + if (GenericDecl* genericDecl = as(curDecl)) + { + sig = as(genericDecl->inner); + } + else + { + sig = as(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) + { + CallableDecl* sig = sigs[i]; + // Add the requirements for all the different versions + { + HashSet requirementsSet; + HashSet targetSet; + for (CallableDecl* curSig = sig; curSig; curSig = curSig->nextDecl) + { + _addRequirements(sig, requirementsSet); + + // Handle Target info + + for (auto targetIntrinsic : sig->getModifiersOfType()) + { + targetSet.Add(String(targetIntrinsic->targetToken.getContent()).toUpper()); + } + for (auto specializedForTarget : sig->getModifiersOfType()) + { + 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; + + const Int sigCount = sigs.getCount(); + for (Index i = 0; i < sigCount; ++i) + { + 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"; + } + + _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") + { + // 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); + } + } + + { + // We will use the first documentation found for each parameter type + { + List paramDecls; + List genericDecls; + for (auto sig : sigs) + { + GenericDecl* genericDecl = as(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(decl) || + as(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) +{ + 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"); + + _appendAsBullets(_getAsNameAndTextList(enumDecl->getMembersOfType()), '_'); + + 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; + + List 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::_appendAggTypeName(AggTypeDeclBase* aggTypeDecl) +{ + auto& out = m_builder; + + // This could be lots of different things - struct/class/extension/interface/.. + + ASTPrinter printer(m_astBuilder); + printer.addDeclPath(DeclRef(aggTypeDecl, nullptr)); + + if (as(aggTypeDecl)) + { + out << toSlice("struct ") << printer.getStringBuilder(); + } + else if (as(aggTypeDecl)) + { + out << toSlice("class ") << printer.getStringBuilder(); + } + else if (as(aggTypeDecl)) + { + out << toSlice("interface ") << printer.getStringBuilder(); + } + else if (ExtensionDecl* extensionDecl = as(aggTypeDecl)) + { + out << toSlice("extension ") << extensionDecl->targetType; + _appendDerivedFrom(toSlice(" : "), extensionDecl); + } + else + { + out << toSlice("?"); + } +} + +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(aggTypeDecl, nullptr)); + + out << toSlice("# `"); + _appendAggTypeName(aggTypeDecl); + out << toSlice("`\n\n"); + + { + List inheritanceDecls; + _getDecls(aggTypeDecl, inheritanceDecls); + + if (inheritanceDecls.getCount()) + { + out << "*Implements:* "; + _appendCommaList(_getAsStringList(inheritanceDecls), '`'); + out << toSlice("\n\n"); + } + } + + writeDescription(entry); + + { + List assocTypeDecls; + _getDecls(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) + { + _appendAsSingleLine(assocTypeDeclEntry->m_markup.getUnownedSlice(), out); + } + out << toSlice("\n"); + + List inheritanceDecls; + _getDecls(assocTypeDecl, inheritanceDecls); + + if (inheritanceDecls.getCount()) + { + out << " "; + _appendCommaList(_getAsStringList(inheritanceDecls), '`'); + out << toSlice("\n"); + } + } + + out << toSlice("\n\n"); + } + } + + if (GenericDecl* genericDecl = as(aggTypeDecl->parentDecl)) + { + // The parameters, in order + List params; + for (Decl* decl : genericDecl->members) + { + if (as(decl) || + as(decl)) + { + params.add(decl); + } + } + + if (params.getCount()) + { + out << "## Generic Parameters\n\n"; + _appendAsBullets(_getAsNameAndTextList(params), '`'); + } + } + + { + List fields; + _getDeclsOfType(aggTypeDecl, fields); + if (fields.getCount()) + { + out << "## Fields\n\n"; + + _appendAsBullets(_getAsNameAndTextList(fields), '`'); + } + } + + { + // Make sure we've got a query-able member dictionary + buildMemberDictionary(aggTypeDecl); + SLANG_ASSERT(aggTypeDecl->isMemberDictionaryValid()); + + List uniqueMethods; + for (const auto& pair : aggTypeDecl->memberDictionary) + { + CallableDecl* callableDecl = as(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(_getAsStringList(uniqueMethods), '`'); + } + } +} + +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; + + 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(decl) || as(decl) || as(decl) || as(decl)) + { + return; + } + + if (CallableDecl* callableDecl = as(decl)) + { + if (_isFirstOverridden(callableDecl)) + { + writeCallableOverridable(entry, callableDecl); + } + } + else if (EnumDecl* enumDecl = as(decl)) + { + writeEnum(entry, enumDecl); + } + else if (AggTypeDeclBase* aggType = as(decl)) + { + writeAggType(entry, aggType); + } + else if (VarDecl* varDecl = as(decl)) + { + // If part of aggregate type will be output there. + if (as(varDecl->parentDecl)) + { + return; + } + + writeVar(entry, varDecl); + } + else if (as(decl)) + { + // We can ignore as inner decls will be picked up, and written + } +} + +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(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()) + { + Decl* decl = as(entry.m_node); + + if (decl && isVisible(entry)) + { + writeDecl(entry, decl); + } + } +} + +} // namespace Slang diff --git a/source/slang/slang-doc-markdown-writer.h b/source/slang/slang-doc-markdown-writer.h new file mode 100644 index 000000000..93607d5f4 --- /dev/null +++ b/source/slang/slang-doc-markdown-writer.h @@ -0,0 +1,124 @@ +// slang-doc-markdown-writer.h +#ifndef SLANG_DOC_MARKDOWN_WRITER_H +#define SLANG_DOC_MARKDOWN_WRITER_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 params; + List genericParams; + Part name; + }; + + /// Write out all documentation to the output buffer + void writeAll(); + + /// 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); + void writeVar(const DocMarkup::Entry& entry, VarDecl* varDecl); + + 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; } + + /// Ctor. + DocMarkdownWriter(DocMarkup* markup, ASTBuilder* astBuilder) : + m_markup(markup), + m_astBuilder(astBuilder) + { + } + + struct StringListSet; + + /// Given a list of ASTPrinter::Parts, works out the different parts of the sig + static void getSignature(const List& parts, Signature& outSig); + + struct NameAndText + { + String name; + String text; + }; + + List _getUniqueParams(const List& decls); + + String _getName(Decl* decl); + String _getName(InheritanceDecl* decl); + + NameAndText _getNameAndText(DocMarkup::Entry* entry, Decl* decl); + NameAndText _getNameAndText(Decl* decl); + + template + List _getAsNameAndTextList(const FilteredMemberList& in) + { + List out; + for (auto decl : const_cast&>(in)) + { + out.add(_getNameAndText(decl)); + } + return out; + } + template + List _getAsStringList(const List& in) + { + List strings; + for (auto decl : in) + { + strings.add(_getName(decl)); + } + return strings; + } + + List _getAsNameAndTextList(const List& in); + List _getAsStringList(const List& in); + + void _appendAsBullets(const List& values, char wrapChar); + void _appendAsBullets(const List& values, char wrapChar); + + void _appendCommaList(const List& 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; + StringBuilder m_builder; +}; + +} // namespace Slang + +#endif diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index de47c48ee..2ad5d25f8 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -31,7 +31,7 @@ #include "slang-serialize-container.h" #include "slang-doc-extractor.h" -#include "slang-doc-mark-down.h" +#include "slang-doc-markdown-writer.h" #include "slang-check-impl.h" @@ -275,7 +275,7 @@ SlangResult Session::compileStdLib(slang::CompileStdLibFlags compileFlags) RefPtr markup(new DocMarkup); DocMarkupExtractor::extract(stdlibModule->getModuleDecl(), sourceManager, &sink, markup); - DocMarkDownWriter writer(markup, astBuilder); + DocMarkdownWriter writer(markup, astBuilder); writer.writeAll(); docStrings.add(writer.getOutput()); } @@ -1859,7 +1859,7 @@ SlangResult FrontEndCompileRequest::executeActionsInner() String fileName = Path::getFileNameWithoutExt(path); fileName.append(".md"); - DocMarkDownWriter writer(markup, astBuilder); + DocMarkdownWriter writer(markup, astBuilder); writer.writeAll(); File::writeAllText(fileName, writer.getOutput()); -- cgit v1.2.3