Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{#if items.[0].is_header}}[%header]
{{/if}}|===
{{#each items}}{{#each cells}}| {{> doc/inline-container }} {{/each}}
{{#each items}}{{#each cells}}| {{#replace "|" "\|"}}{{> doc/inline-container }}{{/replace}} {{/each}}
{{/each}}|===
29 changes: 29 additions & 0 deletions include/mrdocs/Metadata/DocComment.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,24 @@ traverseImpl(doc::ListItem& item, F&& func)
}
}

template <bool bottomUp, class F>
void
traverseImpl(doc::TableCell& cell, F&& func)
{
if constexpr (!bottomUp && std::invocable<F, doc::TableCell&>)
{
func(cell);
}
for (Polymorphic<doc::Inline>& child : cell.children)
{
traverseImpl<bottomUp>(child, func);
}
if constexpr (bottomUp && std::invocable<F, doc::TableCell&>)
{
func(cell);
}
}

// Traverse a derived node
template <bool bottomUp, DocCommentNode NodeTy, class F>
requires(!std::same_as<NodeTy, doc::Block> && !std::same_as<NodeTy, doc::Inline>)
Expand Down Expand Up @@ -281,6 +299,17 @@ traverseImpl(NodeTy& N, F&& func)
}
}

if constexpr (std::same_as<NodeTy, doc::TableBlock>)
{
for (doc::TableRow& row: N.items)
{
for (doc::TableCell& cell: row.Cells)
{
traverseImpl<bottomUp>(cell, func);
}
}
}

if constexpr (bottomUp && std::invocable<F, NodeTy&>)
{
func(N);
Expand Down
161 changes: 104 additions & 57 deletions src/mrdocs/AST/ExtractDocComment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,12 +303,6 @@ class DocCommentVisitor
// user's to fix, so their findings go to the debug log instead.
bool warnings_{ true };

// A `\f$...\f$` inline formula is a verbatim block command to Clang, so it
// splits the surrounding paragraph in two. When that happens we record the
// source line just past the formula; a paragraph that starts on the next
// line (no blank line between) is merged back so the formula stays inline.
unsigned mergeParagraphAfterLine_{ 0 };

// --- inline assembly

template <std::derived_from<doc::Inline> InlineTy, class... Args>
Expand Down Expand Up @@ -516,10 +510,8 @@ class DocCommentVisitor
// --- Visiting using Cursor (no member iterators)

void
visitChildrenWithCursor(clang::comments::Comment const* C)
visitCursor(Cursor cur)
{
MRDOCS_COMMENT_TRACE(C, ctx_);
Cursor cur(C);
while (!cur.done())
{
auto* n = cur.cur();
Expand All @@ -529,6 +521,95 @@ class DocCommentVisitor
}
}

void
visitChildrenWithCursor(clang::comments::Comment const* C)
{
MRDOCS_COMMENT_TRACE(C, ctx_);
visitCursor(Cursor(C));
}

// --- Paragraphs split by inline formulas

bool
isInlineFormula(clang::comments::Comment const* c) const
{
using namespace clang::comments;
return c->getCommentKind() == CommentKind::VerbatimBlockComment
&& static_cast<VerbatimBlockComment const*>(c)->getCommandName(
ctx_.getCommentCommandTraits()) == "f$";
}

unsigned
lineOf(clang::SourceLocation loc) const
{
return sm_.getPresumedLoc(loc).getLine();
}

// Whether `next`, the sibling after `prev`, continues the paragraph
// `prev` belongs to: it is a formula, or the text after one, and no blank
// line separates the two.
bool
continuesParagraph(
clang::comments::Comment const* prev,
clang::comments::Comment const* next) const
{
using namespace clang::comments;
bool const joinable
= next != nullptr
&& (isInlineFormula(next)
|| (isInlineFormula(prev)
&& next->getCommentKind()
== CommentKind::ParagraphComment));
return joinable
&& lineOf(next->getBeginLoc()) <= lineOf(prev->getEndLoc()) + 1;
}

static void
appendToParagraph(Cursor& paragraph, clang::comments::Comment const* c)
{
if (c->getCommentKind()
== clang::comments::CommentKind::ParagraphComment)
{
paragraph.children.append(c->child_begin(), c->child_end());
}
else
{
paragraph.children.push_back(c);
}
}

// Clang ends a paragraph at a `\f$...\f$` inline formula, which it models
// as a verbatim block, and puts the text after the formula in a new
// paragraph. Collect the nodes of the paragraph (or formula) at `cur.i`
// and of the siblings that continue it, leaving `cur.i` at the last of
// them, so that they are visited as one paragraph: the formulas stay
// inline, and an HTML table around them finds its end tag.
Cursor
joinFormulaSplitParagraph(Cursor& cur) const
{
Cursor paragraph;
appendToParagraph(paragraph, cur.cur());
while (continuesParagraph(cur.cur(), cur.peek()))
{
cur.advance();
appendToParagraph(paragraph, cur.cur());
}
return paragraph;
}

void
visitTopLevelParagraph(Cursor& cur)
{
Cursor joined = joinFormulaSplitParagraph(cur);
doc::ParagraphBlock paragraph;
BlockScope scope = enterScope(paragraph);
visitCursor(std::move(joined));
if (!paragraph.empty())
{
jd_.Document.emplace_back(std::move(paragraph));
}
}

// What sits between an inline HTML start tag and its matching end tag.
// When every node in between is text, `text` holds it all and the span
// becomes one inline node. Otherwise (inline commands such as `@c`,
Expand Down Expand Up @@ -1087,7 +1168,7 @@ class DocCommentVisitor
return;

case CommentKind::ParagraphComment:
visitParagraph(static_cast<ParagraphComment const*>(C));
visitParagraph(static_cast<ParagraphComment const*>(C), cur);
return;

case CommentKind::BlockCommandComment:
Expand All @@ -1103,7 +1184,14 @@ class DocCommentVisitor
return;

case CommentKind::VerbatimBlockComment:
visitVerbatimBlock(static_cast<VerbatimBlockComment const*>(C));
if (!curInlines_ && isInlineFormula(C))
{
visitTopLevelParagraph(cur);
}
else
{
visitVerbatimBlock(static_cast<VerbatimBlockComment const*>(C));
}
return;

case CommentKind::VerbatimBlockLineComment:
Expand Down Expand Up @@ -1571,34 +1659,16 @@ class DocCommentVisitor
}

void
visitParagraph(clang::comments::ParagraphComment const* C)
visitParagraph(clang::comments::ParagraphComment const* C, Cursor& cur)
{
MRDOCS_COMMENT_TRACE(C, ctx_);
if (curInlines_)
{
visitChildrenWithCursor(C);
return;
}
// Merge back a paragraph split off by a preceding `\f$...\f$` inline
// formula, but only when it is contiguous (no blank line between).
unsigned const mergeLine = std::exchange(mergeParagraphAfterLine_, 0);
if (mergeLine != 0
&& !jd_.Document.empty()
&& jd_.Document.back()->Kind == doc::BlockKind::Paragraph
&& sm_.getPresumedLoc(C->getBeginLoc()).getLine() <= mergeLine + 1)
{
auto& inlines = static_cast<doc::InlineContainer&>(
jd_.Document.back()->asParagraph());
auto scope = enterScope(inlines);
visitChildrenWithCursor(C);
return;
}
doc::ParagraphBlock paragraph;
auto scope = enterScope(paragraph);
visitChildrenWithCursor(C);
if (!paragraph.empty())
else
{
jd_.Document.emplace_back(std::move(paragraph));
visitTopLevelParagraph(cur);
}
}

Expand Down Expand Up @@ -1931,31 +2001,8 @@ class DocCommentVisitor
if (name == "f$")
{
// Inline formula. Clang positions the verbatim block outside the
// surrounding paragraph, so emit into the current inline flow when
// one is open, append to the paragraph that was just closed, or
// otherwise wrap it in its own paragraph.
if (curInlines_)
{
emplaceInline<doc::MathInline>(true, std::move(payload));
return;
}
doc::InlineContainer* inlines = nullptr;
if (!jd_.Document.empty()
&& jd_.Document.back()->Kind == doc::BlockKind::Paragraph)
{
inlines = &static_cast<doc::InlineContainer&>(
jd_.Document.back()->asParagraph());
}
else
{
jd_.Document.emplace_back(doc::ParagraphBlock{});
inlines = &static_cast<doc::InlineContainer&>(
jd_.Document.back()->asParagraph());
}
inlines->children.emplace_back(
std::in_place_type<doc::MathInline>, std::move(payload));
mergeParagraphAfterLine_
= sm_.getPresumedLoc(C->getEndLoc()).getLine();
// surrounding paragraph; visitTopLevelParagraph puts it back.
emplaceInline<doc::MathInline>(true, std::move(payload));
return;
}
if (name == "f[")
Expand Down
50 changes: 50 additions & 0 deletions tests/golden/fixtures/javadoc/html-table/html-table-math.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
= Reference
:mrdocs:

[#index]
== Global index

=== Functions

[cols="1,4"]
|===
| Name| Description
| link:#norm[`norm`]
| Compute the norm of a vector&period;
|===


[#norm]
== norm

Compute the norm of a vector&period;

=== Synopsis

Declared in `&lt;html&hyphen;table&hyphen;math&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
double
norm(
double x,
double y,
int p);
----

=== Description

[%header]
|===
| Norm | Formula
| Euclidean | stem:[\sqrt{x^2 + y^2}]
| Maximum | stem:[\max(\|x\|, \|y\|)]
|===
stem:[p] selects the norm&period;

The result is stem:[\|v\|_p]&period;

stem:[p = 2] is the default&period;


[.small]#Created with https://www.mrdocs.com[MrDocs]#
15 changes: 15 additions & 0 deletions tests/golden/fixtures/javadoc/html-table/html-table-math.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** Compute the norm of a vector.

<table>
<tr><th>Norm</th><th>Formula</th></tr>
<tr><td>Euclidean</td><td>@f$ \sqrt{x^2 + y^2} @f$</td></tr>
<tr><td>Maximum</td><td>$\max(|x|, |y|)$</td></tr>
</table>

\f$p\f$ selects the norm.

The result is \f$\|v\|_p\f$.

\f$p = 2\f$ is the default.
*/
double norm(double x, double y, int p);
Loading
Loading