// Block structure parsing.
///|
fn Parser::curr_col(self : Parser) -> Col {
self.curr_char_col + self.tab_consumed_cols
}
///|
fn Parser::curr_indent(self : Parser) -> Col {
self.next_non_blank_col - self.curr_col()
}
///|
fn Parser::end_of_line(self : Parser) -> Bool {
self.curr_char > self.curr_line_last_char
}
///|
fn Parser::only_blanks(self : Parser) -> Bool {
self.next_non_blank > self.curr_line_last_char
}
///|
fn Parser::has_next_non_blank(self : Parser) -> Bool {
self.next_non_blank <= self.curr_line_last_char
}
///|
fn Parser::update_next_non_blank(self : Parser) -> Unit {
let s = self.i
let last = self.curr_line_last_char
for k = self.curr_char, col = self.curr_char_col {
break if k > last {
self.next_non_blank = k
self.next_non_blank_col = col
} else {
match s[k] {
' ' => continue k + 1, col + 1
'\t' => continue k + 1, next_tab_stop(col)
_ => {
self.next_non_blank = k
self.next_non_blank_col = col
}
}
}
}
}
///|
fn Parser::accept_cols(self : Parser, count~ : Int32) -> Unit {
for count = count, k = self.curr_char, col = self.curr_char_col {
// Stop at the end of the line: a list marker or other prefix can
// be the very last thing in the input, with nothing left to accept.
break if count == 0 || k > self.curr_line_last_char {
self.curr_char = k
self.curr_char_col = col
} else if self.i[k] != '\t' {
continue count - 1, k + 1, col + 1
} else {
let col1 = next_tab_stop(col)
let tab_cols = col1 - (col + self.tab_consumed_cols)
if tab_cols > count {
self.tab_consumed_cols = count
continue 0, k, col
} else {
self.tab_consumed_cols = 0
continue count - tab_cols, k + 1, col1
}
}
}
self.update_next_non_blank()
}
///|
/// https://spec.commonmark.org/current/#block-quote-marker
fn Parser::match_and_accept_block_quote(self : Parser) -> Bool {
if self.end_of_line() || self.i[self.curr_char] != '>' {
return false
}
let next_is_blank = {
let next = self.curr_char + 1
next <= self.curr_line_last_char &&
@char.is_ascii_blank(self.i.get_char(next).unwrap())
}
self.accept_cols(
count=1 + next_is_blank.to_int(), // We eat a space
)
true
}
///|
/// Returns min indent after marker for list item
fn Parser::accept_list_marker_and_indent(
self : Parser,
marker_size~ : Int32,
last~ : Int32,
) -> Int32 {
let _ = last
self.accept_cols(count=marker_size)
let indent = self.curr_indent()
let min_indent = if self.only_blanks() || indent > 4 {
1
} else {
@cmp.minimum(indent, 4)
}
self.accept_cols(count=min_indent)
min_indent
}
///|
/// Returns padding for partially consumed tab and content first char
fn Parser::accept_code_indent(
self : Parser,
count~ : Int32,
) -> (Int32, CharCodePos) {
self.accept_cols(count~)
if self.tab_consumed_cols == 0 {
return (0, self.curr_char)
}
let col1 = next_tab_stop(self.curr_char_col)
let pad = col1 - (self.curr_char_col + self.tab_consumed_cols)
(pad, self.curr_char + 1) // curr_char is '\t
}
// These data types are only used during parsing, to find out the
// block structure. All the lists (blocks, lines) are in reverse
// order. We don't extract data from the input here. We just store
// line spans. See:
// https://spec.commonmark.org/current/#phase-1-block-structure
///|
/// Number of space characters to pad content with
type SpacePad = Int32
///|
priv struct IndentedCodeLine {
pad : SpacePad
code : LineSpan
is_blank : Bool
} derive(Debug)
///|
priv struct Fence {
indent : Indent
opening_fence : LineSpan
fence : (Char, Int32) // Fence length
info_string : LineSpan? // We drop the trailing blanks
closing_fence : LineSpan?
} derive(Debug)
///|
priv struct FenceCodeBlockStruct {
fence : Fence
code : Array[(SpacePad, LineSpan)]
} derive(Debug)
///|
priv enum CodeBlockStruct {
Indented(Array[IndentedCodeLine])
Fenced(FenceCodeBlockStruct)
} derive(Debug)
///|
priv struct Atx {
indent : Indent
level : HeadingLevel
after_open : CharCodePos
heading : LineSpan
layout_after : LineSpan
} derive(Debug)
///|
priv struct Setext {
level : HeadingLevel
heading_lines : Array[LineSpan]
/// Indent, underline char count, blanks
underline : (Indent, LineSpan, LineSpan)
} derive(Debug)
///|
priv enum Heading {
Atx(Atx)
Setext(Setext)
} derive(Debug)
///|
priv struct HtmlBlockStruct {
end_cond : HtmlBlockEndCond?
html : Array[LineSpan]
} derive(Debug)
///|
priv struct Paragraph {
maybe_ref : Bool
lines : Array[LineSpan]
} derive(Debug)
///|
priv enum BlockStruct {
BlockQuote(Indent, Array[BlockStruct])
BlankLine(SpacePad, LineSpan)
CodeBlock(CodeBlockStruct)
Heading(Heading)
HtmlBlock(HtmlBlockStruct)
List(ListBlockStruct)
LinkRefDef(Node[LinkDefinition])
Paragraph(Paragraph)
ThematicBreak(Indent, LineSpan) // Including trailing blanks
ExtTable(Indent, Array[(LineSpan, LineSpan)]) // The second `LineSpan` is for trailing blanks
ExtFootnote(Indent, (Label, Label?), Array[BlockStruct])
} derive(Debug)
///|
priv struct ListItemStruct {
before_marker : Indent
marker : LineSpan
after_marker : Indent
ext_task_marker : (Char, LineSpan)?
blocks : Array[BlockStruct]
} derive(Debug)
///|
priv struct ListBlockStruct {
mut last_blank : Bool // Last added line was blank and not first line of item
loose : Bool // Inter-item looseness, intra-item is computed later
item_min_indent : Indent // Last item minimal indent
list_type : ListType
items : Array[ListItemStruct]
} derive(Debug)
///|
fn BlockStruct::is_blank_line(self : BlockStruct) -> Bool {
guard self is BlankLine(_, _) else { return false }
true
}
// Making blocks from the current line status
///|
fn Parser::blank_line(self : Parser) -> BlockStruct {
let first = self.curr_char
let last = self.curr_line_last_char
BlankLine(0, self.curr_line_span(first~, last~))
}
///|
fn Parser::thematic_break(
self : Parser,
indent~ : Indent,
last~ : CharCodePos,
) -> BlockStruct {
let _ = last
let last = self.curr_line_last_char // Let's keep everything
ThematicBreak(indent, self.curr_line_span(first=self.curr_char, last~))
}
///|
fn Parser::atx_heading(
self : Parser,
indent~ : Indent,
level~ : HeadingLevel,
after_open~ : CharCodePos,
first_content~ : CharCodePos,
last_content~ : CharCodePos,
) -> BlockStruct {
let heading = self.curr_line_span(first=first_content, last=last_content)
let layout_after = self.curr_line_span(
first=last_content + 1,
last=self.curr_line_last_char,
)
Heading(Atx({ indent, level, after_open, heading, layout_after, }))
}
///|
fn Parser::setext_heading(
self : Parser,
level~ : HeadingLevel,
heading_lines : Array[LineSpan],
indent~ : Indent,
last_underline~ : CharCodePos,
) -> BlockStruct {
let u = self.curr_line_span(first=self.curr_char, last=last_underline)
let blanks = self.curr_line_span(
first=last_underline + 1,
last=self.curr_line_last_char,
)
let underline = (indent, u, blanks)
Heading(Setext({ level, heading_lines, underline, }))
}
///|
fn Parser::indented_code_block(self : Parser) -> BlockStruct {
let (pad, first) = self.accept_code_indent(count=4)
let code = self.curr_line_span(first~, last=self.curr_line_last_char)
CodeBlock(Indented([{ pad, code, is_blank: false, }]))
}
///|
fn Parser::fenced_code_block(
self : Parser,
indent~ : Indent,
fence_first~ : CharCodePos,
fence_last~ : CharCodePos,
info~ : (CharCodePos, CharCodePos)?,
) -> BlockStruct {
let (info_string, layout_last) = match info {
None => (None, self.curr_line_last_char)
Some((first, last)) => (Some(self.curr_line_span(first~, last~)), first)
}
let opening_fence = self.curr_line_span(first=fence_first, last=layout_last)
let fence = (
self.i[fence_first].unsafe_to_char(),
fence_last - fence_first + 1,
)
let fence = {
indent,
opening_fence,
fence,
info_string,
closing_fence: None,
}
CodeBlock(Fenced({ fence, code: [], }))
}
///|
fn Parser::html_block(
self : Parser,
end_cond~ : HtmlBlockEndCond,
indent_start~ : Indent,
) -> BlockStruct {
let first = indent_start
let last = self.curr_line_last_char
// Check if the same line matches the end condition.
let end_cond = if LineType::html_block_end(
self.i,
end_cond~,
last~,
start=self.curr_char,
) {
None // We are already closed
} else {
Some(end_cond)
}
HtmlBlock({ end_cond, html: [self.curr_line_span(first~, last~)], })
}
///|
fn Parser::paragraph(self : Parser, start~ : CharCodePos) -> BlockStruct {
let last = self.curr_line_last_char
let maybe_ref = @cmark_base.could_be_link_ref_definition(
self.i,
last~,
start~,
)
let lines = [self.curr_line_span(first=start, last~)]
Paragraph({ maybe_ref, lines, })
}
///|
fn Parser::add_paragraph_line(
self : Parser,
indent_start~ : Indent,
par~ : Paragraph,
bs~ : Array[BlockStruct],
) -> Unit {
let first = indent_start
let last = self.curr_line_last_char
let lines = par.lines
lines.push(self.curr_line_span(first~, last~))
bs.push(Paragraph({ ..par, lines, }))
}
///|
fn Parser::table_row(
self : Parser,
first~ : CharCodePos,
last~ : CharCodePos,
) -> (LineSpan, LineSpan) {
(
self.curr_line_span(first~, last~),
self.curr_line_span(first=last + 1, last=self.curr_line_last_char),
)
}
///|
fn Parser::table(
self : Parser,
indent~ : Indent,
last~ : CharCodePos,
) -> BlockStruct {
let row = self.table_row(first=self.curr_char, last~)
ExtTable(indent, [row])
}
// Link reference definition parsing
// This is invoked when we close a paragraph and works on the paragraph lines.
///|
/// Has no side effect on [p], parsing occurs on [lines] spans.
/// https://spec.commonmark.org/current/#link-reference-definitions
fn Parser::parse_link_ref_definition(
self : Parser,
lines : Array[LineSpan],
) -> Node[LinkDefinition]? {
let lines_arr = lines
fn next_line(i : Ref[Int32]) {
guard i.val < lines_arr.length() else { None }
let res = Some(lines_arr[i.val])
i.val += 1
res
}
let lines : Ref[Int32] = Ref(0)
guard next_line(lines) is Some(line) else { None }
let start = self.first_non_blank_in_span(line)
let indent = start - line.first
let meta_first = { ..line, first: start, }
guard @cmark_base.link_label(
self.buf,
next_line~,
self.i,
lines,
line~,
start~,
)
is Some((line, spans, last, key)) else {
None
}
let colon = last + 1
guard colon <= line.last && self.i[colon] == ':' else { None }
let label = Parser::label_of_spans(self, key~, spans[:])
let (lines, line, label, start) = (lines, line, label, colon + 1)
guard self.first_non_blank_over_nl(next_line~, lines, line, start~)
is Some((line, before_dest, start)) else {
None
}
guard @cmark_base.link_destination(self.i, last=line.last, start~)
is Some((angled, first, last)) else {
None
}
let dest = Parser::clean_unesc_unref_span(self, { ..line, first, last, })
let next = if angled { last + 2 } else { last + 1 }
let (angled_dest, dest, start, meta_last) = (
angled,
Some(dest),
next,
{ ..line, last, },
)
let lines1 = lines
let lines = lines1.val
let (lines, after_dest, title_open_delim, title, after_title, meta_last) = match
self.first_non_blank_over_nl(next_line~, lines1, line, start~) {
None => {
lines1.val = lines
(lines1, [], '"', None, [], meta_last)
}
Some((line1, after_dest, start1)) => {
guard start1 != start else { return None }
let no_newline = line1.pos == line.pos
guard @cmark_base.link_title(
next_line~,
self.i,
lines1,
line=line1,
start=start1,
)
is Some((line1, spans, last)) else {
guard !no_newline else { return None }
lines1.val = lines
(lines1, [], '"', None, [], meta_last)
}
// Only blanks may follow the title
let after_title = {
let start = last + 1
let nb = @cmark_base.first_non_blank(self.i, last=line1.last, start~)
if nb <= line1.last {
None
} else {
Some([self.layout_clean_raw_span({ ..line1, first: start, })])
}
}
guard after_title is Some(after_title) else {
guard !no_newline else { return None }
lines1.val = lines
(lines1, [], '"', None, [], meta_last)
}
let t : Seq[_] = self.tight_block_lines(spans=spans[:])
(
lines1,
after_dest,
self.i[start1].unsafe_to_char(),
Some(t),
after_title,
{ ..line1, last, },
)
}
}
let meta = self.meta_of_spans(first=meta_first, last=meta_last)
let layout : LinkDefinitionLayout = {
indent,
angled_dest,
before_dest,
after_dest,
title_open_delim,
after_title,
}
let defined_label = self.def_label(label)
let label = Some(label)
let ld = { v: { layout, label, defined_label, dest, title, }, meta, }
if defined_label is Some(def) {
self.set_label_def(def, LinkDef(ld))
}
let _ = lines_arr.drain(0, lines.val)
Some(ld)
}
///|
fn Parser::maybe_add_link_ref_definitions(
self : Parser,
lines : Array[LineSpan],
prevs : Array[BlockStruct],
) -> Unit {
while !lines.is_empty() {
guard self.parse_link_ref_definition(lines) is Some(ld) else {
// Link defs can't interrupt a paragraph so we are good now.
prevs.push(Paragraph({ maybe_ref: false, lines, }))
break
}
prevs.push(LinkRefDef(ld))
}
}
///|
fn Parser::close_indented_code_block(
self : Parser,
lines : Array[IndentedCodeLine],
bs : Array[BlockStruct],
) -> Unit {
let _ = self
let blanks = []
for ;; {
match lines.last() {
Some({ is_blank: true, .. }) => {
guard! lines.pop() is Some({ pad, code, .. })
blanks.push(BlockStruct::BlankLine(pad, code))
}
None => break // Likely `abort()`
Some(_) => {
bs.push(CodeBlock(Indented(lines)))
break
}
}
}
bs.push_iter(blanks.iter())
}
// Closing blocks and finishing the document.
///|
fn Parser::close_paragraph(
self : Parser,
par : Paragraph,
bs : Array[BlockStruct],
) -> Unit {
guard par.maybe_ref else {
bs.push(Paragraph(par))
return
}
self.maybe_add_link_ref_definitions(par.lines, bs)
}
///|
fn Parser::close_last_block(self : Parser, bs : Array[BlockStruct]) -> Unit {
match bs.pop() {
Some(CodeBlock(Indented(ls))) => self.close_indented_code_block(ls, bs)
Some(Paragraph(par)) => self.close_paragraph(par, bs)
Some(List(l)) => self.close_list(l, bs)
Some(ExtFootnote(i, l, blocks)) => self.close_footnote(i, l, blocks, bs)
Some(t) => bs.push(t)
None => ()
}
}
///|
fn Parser::close_list(
self : Parser,
l : ListBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
guard! l.items.last() is Some(i)
let blocks = i.blocks
self.close_last_block(blocks)
// The final blank line extraction of the list item entails less blank
// line churn for CommonMark rendering but we don't do it on empty list items.
match blocks {
[.., _, BlankLine(_, _)] => {
guard! blocks.pop() is Some(bl)
let items = l.items
items[items.length() - 1] = { ..i, blocks, }
bs..push(List({ ..l, items, })).push(bl)
}
_ => {
let items = l.items
items[items.length() - 1] = { ..i, blocks, }
bs.push(List({ ..l, items, }))
}
}
}
///|
fn Parser::close_footnote(
self : Parser,
indent : Indent,
label : (Label, Label?),
blocks : Array[BlockStruct],
bs : Array[BlockStruct],
) -> Unit {
self.close_last_block(blocks)
// Like for lists above we do blank line extraction (except if blocks is only a blank line)
let blanks = []
for ;; {
match blocks {
[.., _, BlankLine(_, _)] => {
guard! blocks.pop() is Some(bl)
blanks.push(bl)
}
_ => break
}
}
bs.push(ExtFootnote(indent, label, blocks))
bs.push_iter(blanks.iter())
}
///|
fn Parser::close_last_list_item(self : Parser, l : ListBlockStruct) -> Unit {
guard! l.items.last() is Some(item)
self.close_last_block(item.blocks)
}
///|
/// Whether `l` is the empty line that follows a final newline of the
/// input. Its raw line is empty, unlike a line made empty by stripping
/// container markers or indentation, which is content.
fn is_end_of_input_line(l : LineSpan) -> Bool {
l.last < l.pos.1
}
///|
fn Parser::end_doc_close_fenced_code_block(
self : Parser,
fenced : FenceCodeBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
let _ = self
let { code, .. } = fenced
if fenced.fence.closing_fence is None &&
code is [.., (_, l)] &&
is_end_of_input_line(l) {
let _ = code.pop()
bs..push(CodeBlock(Fenced({ ..fenced, code, }))).push(BlankLine(0, l))
return
}
bs.push(CodeBlock(Fenced(fenced)))
}
///|
fn Parser::end_doc_close_html(
self : Parser,
h : HtmlBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
let _ = self
let { html, .. } = h
if h.end_cond is Some(_) && html is [.., l] && is_end_of_input_line(l) {
let _ = html.pop()
bs..push(HtmlBlock({ end_cond: None, html, })).push(BlankLine(0, l))
return
}
bs.push(HtmlBlock({ ..h, end_cond: None, }))
}
///|
fn Parser::end_doc(self : Parser, bs : Array[BlockStruct]) -> Unit {
match bs.pop() {
Some(BlockQuote(indent, bq)) => {
self.end_doc(bq)
bs.push(BlockQuote(indent, bq))
}
Some(List(l)) => {
// The last item may end with an unclosed fenced code or HTML block.
guard! l.items.last() is Some(item)
self.end_doc(item.blocks)
self.close_list(l, bs)
}
Some(Paragraph(par)) => self.close_paragraph(par, bs)
Some(CodeBlock(Indented(ls))) => self.close_indented_code_block(ls, bs)
Some(CodeBlock(Fenced(f))) => self.end_doc_close_fenced_code_block(f, bs)
Some(HtmlBlock(html)) => self.end_doc_close_html(html, bs)
Some(ExtFootnote(indent, label, blocks)) => {
self.end_doc(blocks)
self.close_footnote(indent, label, blocks, bs)
}
Some(b) => bs.push(b)
None => ()
}
}
// Adding lines to blocks
///|
/// Effects on [p]'s column advance
fn Parser::match_line_type(
self : Parser,
no_setext~ : Bool,
indent~ : SpacePad,
) -> LineType {
if self.only_blanks() {
return BlankLine
}
if indent >= 4 {
return IndentedCodeBlockLine
}
self.accept_cols(count=indent)
if self.end_of_line() {
return BlankLine
}
let start = self.curr_char
let last = self.curr_line_last_char
match self.i[start] {
// Early dispatch shaves a few ms but may not be worth doing vs
// testing all the cases in sequences.
'>' => {
if self.match_and_accept_block_quote() {
return BlockQuoteLine
}
ParagraphLine
}
'=' if !no_setext => {
let r = LineType::setext_heading_underline(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'-' => {
let r = if no_setext {
LineType::Nomatch
} else {
LineType::setext_heading_underline(self.i, last~, start~)
}
guard r is Nomatch else { r }
let r = LineType::thematic_break(self.i, last~, start~)
guard r is Nomatch else { r }
let r = LineType::list_marker(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'#' => {
let r = LineType::atx_heading(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'+' | '*' | '0'..<='9' => {
let r = LineType::thematic_break(self.i, last~, start~)
guard r is Nomatch else { r }
let r = LineType::list_marker(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'_' => {
let r = LineType::thematic_break(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'~' | '`' => {
let r = LineType::fenced_code_block_start(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'<' => {
let r = LineType::html_block_start(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'|' if self.exts => {
let r = LineType::ext_table_row(self.i, last~, start~)
guard r is Nomatch else { r }
ParagraphLine
}
'[' if self.exts => {
let r = LineType::ext_footnote_label(
self.buf,
self.i,
line_pos=self.curr_line_pos,
last~,
start~,
)
guard r is Nomatch else { r }
ParagraphLine
}
_ => ParagraphLine
}
}
///|
fn Parser::list_marker_can_interrupt_paragraph(
self : Parser,
marker : ListType,
marker_last : CharCodePos,
) -> Bool {
guard marker is (Ordered(1, _) | Unordered(_)) else { return false }
let non_blank = @cmark_base.first_non_blank(
self.i,
last=self.curr_line_last_char,
start=marker_last + 1,
)
non_blank <= self.curr_line_last_char // Line is not blank
}
///|
fn Parser::add_open_blocks_with_line_class(
self : Parser,
indent_start~ : Indent,
indent~ : Indent,
bs : Array[BlockStruct],
lt : LineType,
) -> Unit {
match lt {
BlankLine => bs.push(self.blank_line())
IndentedCodeBlockLine => bs.push(self.indented_code_block())
BlockQuoteLine => {
let bs1 = []
self.add_open_blocks(bs1)
bs.push(BlockQuote(indent, bs1))
}
ThematicBreakLine(last) => bs.push(self.thematic_break(indent~, last~))
ListMarkerLine(marker, marker_last) =>
self.list(indent~, marker, marker_last, bs)
AtxHeadingLine(level, after_open, first_content, last_content) =>
bs.push(
self.atx_heading(
indent~,
level~,
after_open~,
first_content~,
last_content~,
),
)
FencedCodeBlockLine(fence_first, fence_last, info) =>
bs.push(self.fenced_code_block(indent~, fence_first~, fence_last~, info~))
HtmlBlockLine(end_cond) =>
bs.push(self.html_block(end_cond~, indent_start~))
ParagraphLine => bs.push(self.paragraph(start=indent_start))
ExtTableRow(last) => bs.push(self.table(indent~, last~))
ExtFootnoteLabel(spans, last, key) =>
bs.push(self.footnote(indent~, last~, spans=spans[:], key~))
_ => abort("unimplemented")
}
}
///|
fn Parser::add_open_blocks(self : Parser, bs : Array[BlockStruct]) -> Unit {
let indent_start = self.curr_char
let indent = self.curr_indent()
let line_type = self.match_line_type(no_setext=true, indent~)
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, line_type)
}
///|
fn Parser::footnote(
self : Parser,
indent~ : Indent,
last~ : CharCodePos,
spans~ : ArrayView[Span],
key~ : String,
) -> BlockStruct {
let label = Parser::label_of_spans(self, key~, spans)
let defined_label = self.def_label(label)
if defined_label is Some(def) {
self.set_label_def(def, Label::stub(label, defined_label))
}
self.accept_cols(count=last - self.curr_char + 1)
let blocks = []
self.add_open_blocks(blocks)
ExtFootnote(indent, (label, defined_label), blocks)
}
///|
fn Parser::list_item(
self : Parser,
indent~ : Indent,
_list_type : ListType,
last : CharCodePos,
) -> (Indent, ListItemStruct) {
let before_marker = indent
let marker_size = last - self.curr_char + 1
let marker = self.curr_line_span(first=self.curr_char, last~)
let after_marker = self.accept_list_marker_and_indent(marker_size~, last~)
let (ext_task_marker, ext_task_marker_size) = if self.exts {
let start = self.curr_char
let last = self.curr_line_last_char
match @cmark_base.ext_task_marker(self.i, last~, start~) {
None => (None, 0)
Some((u, last)) => {
self.accept_cols(count=last - start + 1)
let last = if last == self.curr_line_last_char {
last
} else {
last - 1
}
(Some((u, self.curr_line_span(first=start, last~))), 4)
}
}
} else {
(None, 0)
}
let min = indent + marker_size + after_marker + ext_task_marker_size
let blocks = []
self.add_open_blocks(blocks)
(min, { before_marker, marker, after_marker, ext_task_marker, blocks, })
}
///|
fn Parser::list(
self : Parser,
indent~ : Indent,
list_type : ListType,
marker_last : CharCodePos,
bs : Array[BlockStruct],
) -> Unit {
let (item_min_indent, item) = self.list_item(indent~, list_type, marker_last)
bs.push(
List({
last_blank: false,
loose: false,
item_min_indent,
list_type,
items: [item],
}),
)
}
///|
fn Parser::try_add_to_list(
self : Parser,
indent~ : Indent,
list_type : ListType,
marker_last : CharCodePos,
list : ListBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
let (item_min_indent, item) = self.list_item(indent~, list_type, marker_last)
if list.list_type.is_same_type(list_type) {
let l = list
self.close_last_list_item(l)
l.items.push(item)
bs.push(
List({
last_blank: false,
loose: l.last_blank,
item_min_indent,
list_type: l.list_type,
items: l.items,
}),
)
} else {
self.close_list(list, bs)
bs.push(
List({
last_blank: false,
loose: false,
item_min_indent,
list_type,
items: [item],
}),
)
}
}
///|
fn Parser::try_add_to_paragraph(
self : Parser,
par : Paragraph,
bs : Array[BlockStruct],
) -> Unit {
let indent_start = self.curr_char
let indent = self.curr_indent()
match self.match_line_type(no_setext=false, indent~) {
// These can't interrupt paragraphs
HtmlBlockLine(EndBlank7)
| IndentedCodeBlockLine
| ExtTableRow(_)
| ExtFootnoteLabel(_)
| ParagraphLine => self.add_paragraph_line(indent_start~, par~, bs~)
ListMarkerLine(marker, marker_last) =>
if self.list_marker_can_interrupt_paragraph(marker, marker_last) {
self.close_paragraph(par, bs)
self.list(indent~, marker, marker_last, bs)
} else {
self.add_paragraph_line(indent_start~, par~, bs~)
}
BlankLine => {
self.close_paragraph(par, bs)
bs.push(self.blank_line())
}
BlockQuoteLine => {
self.close_paragraph(par, bs)
let blocks = []
self.add_open_blocks(blocks)
bs.push(BlockQuote(indent, blocks))
}
SetextUnderlineLine(level, last_underline) => {
self.close_paragraph(par, bs)
match bs {
[.., Paragraph(par)] =>
bs[bs.length() - 1] = self.setext_heading(
indent~,
level~,
last_underline~,
par.lines,
)
bs => bs.push(self.paragraph(start=indent_start))
}
}
ThematicBreakLine(last) => {
self.close_paragraph(par, bs)
bs.push(self.thematic_break(indent~, last~))
}
AtxHeadingLine(level, after_open, first_content, last_content) => {
self.close_paragraph(par, bs)
bs.push(
self.atx_heading(
indent~,
level~,
after_open~,
first_content~,
last_content~,
),
)
}
FencedCodeBlockLine(fence_first, fence_last, info) => {
self.close_paragraph(par, bs)
bs.push(self.fenced_code_block(indent~, fence_first~, fence_last~, info~))
}
HtmlBlockLine(end_cond) => {
self.close_paragraph(par, bs)
bs.push(self.html_block(end_cond~, indent_start~))
}
Nomatch => abort("unreachable")
}
}
///|
fn Parser::try_add_to_indented_code_block(
self : Parser,
ls : Array[IndentedCodeLine],
bs : Array[BlockStruct],
) -> Unit {
if self.curr_indent() < 4 {
if self.has_next_non_blank() {
self.close_indented_code_block(ls, bs)
self.add_open_blocks(bs)
} else {
// Blank but white is not data, make an empty span
let first = self.curr_line_last_char + 1
let last = self.curr_line_last_char
let code = self.curr_line_span(first~, last~)
let l = { pad: 0, code, is_blank: true, }
ls.push(l)
bs.push(CodeBlock(Indented(ls)))
}
} else {
let (pad, first) = self.accept_code_indent(count=4)
let last = self.curr_line_last_char
let is_blank = self.only_blanks()
let l = { pad, code: self.curr_line_span(first~, last~), is_blank, }
ls.push(l)
bs.push(CodeBlock(Indented(ls)))
}
}
///|
fn Parser::try_add_to_fenced_code_block(
self : Parser,
f : FenceCodeBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
match f {
{ fence: { closing_fence: Some(_), .. }, .. } => {
// Block is closed
bs.push(CodeBlock(Fenced(f)))
self.add_open_blocks(bs)
}
{ fence: { indent, fence, .. }, code: ls, } as b => {
let start = self.curr_char
let last = self.curr_line_last_char
match
@cmark_base.FencedCodeBlockContinue::new(self.i, fence~, last~, start~) {
Code => {
let strip = @cmp.minimum(indent, self.curr_indent())
let (pad, first) = self.accept_code_indent(count=strip)
ls.push((pad, self.curr_line_span(first~, last~)))
bs.push(CodeBlock(Fenced({ ..b, code: ls, })))
}
Close(first, _fence_last) => {
let close = self.curr_line_span(first~, last~) // With layout
let fence = { ..b.fence, closing_fence: Some(close), }
bs.push(CodeBlock(Fenced({ ..b, fence, })))
}
}
}
}
}
///|
fn Parser::try_add_to_html_block(
self : Parser,
b : HtmlBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
match b.end_cond {
None => {
bs.push(HtmlBlock({ ..b, end_cond: None, }))
self.add_open_blocks(bs)
}
Some(end_cond) => {
let start = self.curr_char
let last = self.curr_line_last_char
let l = self.curr_line_span(first=start, last~)
if LineType::html_block_end(self.i, end_cond~, last~, start~) {
match end_cond {
EndBlank7 | EndBlank =>
bs
..push(HtmlBlock({ ..b, end_cond: None, }))
.push(self.blank_line())
_ => {
b.html.push(l)
bs.push(HtmlBlock({ html: b.html, end_cond: None, }))
}
}
} else {
b.html.push(l)
bs.push(HtmlBlock(b))
}
}
}
}
///|
fn Parser::try_lazy_continuation(
self : Parser,
indent_start~ : Indent,
bs : Array[BlockStruct],
) -> Bool {
match bs.last() {
Some(Paragraph(par)) => {
let _ = bs.pop()
self.add_paragraph_line(indent_start~, par~, bs~)
true
}
Some(BlockQuote(_, bq)) => self.try_lazy_continuation(indent_start~, bq)
Some(List(l)) => {
guard! l.items.last() is Some(i)
let res = self.try_lazy_continuation(indent_start~, i.blocks)
if res {
l.last_blank = false
}
res
}
_ => false
}
}
///|
fn Parser::try_add_to_table(
self : Parser,
indent : Indent,
rows : Array[(LineSpan, LineSpan)],
bs : Array[BlockStruct],
) -> Unit {
let indent_start = self.curr_char
let curr_indent = self.curr_indent()
match self.match_line_type(no_setext=true, indent=curr_indent) {
ExtTableRow(last) => {
let row = self.table_row(first=self.curr_char, last~)
rows.push(row)
bs.push(ExtTable(indent, rows))
}
lt => {
bs.push(ExtTable(indent, rows))
self.add_open_blocks_with_line_class(
indent_start~,
indent=curr_indent,
bs,
lt,
)
}
}
}
///|
fn Parser::try_add_to_block_quote(
self : Parser,
indent_layout : Indent,
bq : Array[BlockStruct],
bs : Array[BlockStruct],
) -> Unit {
let indent_start = self.curr_char
let indent = self.curr_indent()
match self.match_line_type(no_setext=true, indent~) {
BlockQuoteLine => {
self.add_line(bq)
bs.push(BlockQuote(indent_layout, bq))
}
// These can't interrupt a paragraph: lazy continuation lines
IndentedCodeBlockLine | ParagraphLine | HtmlBlockLine(EndBlank7) as ltype =>
if self.try_lazy_continuation(indent_start~, bq) {
bs.push(BlockQuote(indent_layout, bq))
} else {
self.close_last_block(bq)
bs.push(BlockQuote(indent_layout, bq))
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, ltype)
}
lt => {
self.close_last_block(bq)
bs.push(BlockQuote(indent_layout, bq))
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, lt)
}
}
}
///|
fn Parser::try_add_to_footnote(
self : Parser,
fn_indent : Indent,
label : (Label, Label?),
blocks : Array[BlockStruct],
bs : Array[BlockStruct],
) -> Unit {
let indent_start = self.curr_char
let indent = self.curr_indent()
if indent < fn_indent + 1 { // Position of ^
match self.match_line_type(no_setext=true, indent~) {
// These can't interrupt a paragraph: lazy continuation lines
IndentedCodeBlockLine | ParagraphLine | HtmlBlockLine(EndBlank7) as lt =>
if self.try_lazy_continuation(indent_start~, blocks) {
bs.push(ExtFootnote(fn_indent, label, blocks))
} else {
self.close_last_block(blocks)
self.close_footnote(fn_indent, label, blocks, bs)
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, lt)
}
BlankLine => {
self.add_line(blocks)
bs.push(ExtFootnote(fn_indent, label, blocks))
}
lt => {
self.close_footnote(fn_indent, label, blocks, bs)
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, lt)
}
}
} else {
self.accept_cols(count=fn_indent + 1)
self.add_line(blocks)
bs.push(ExtFootnote(fn_indent, label, blocks))
}
}
///|
fn Parser::try_add_to_list_item(
self : Parser,
list : ListBlockStruct,
bs : Array[BlockStruct],
) -> Unit {
let indent_start = self.curr_char
let indent = self.curr_indent()
if indent >= list.item_min_indent {
let last_blank = self.only_blanks()
guard! list.items.last() is Some(item)
return if list.last_blank &&
!last_blank &&
item.blocks.iter().all(BlockStruct::is_blank_line) {
// Item can only start with a single blank line,
// if we are here it's not a new item so the list ends
bs.push(List(list))
self.add_open_blocks(bs)
} else {
self.accept_cols(count=list.item_min_indent)
self.add_line(item.blocks)
bs.push(List({ ..list, last_blank, }))
}
}
match self.match_line_type(no_setext=true, indent~) {
BlankLine => {
let items = list.items
guard! items.last() is Some(item)
self.add_line(item.blocks)
bs.push(List({ ..list, items, last_blank: true, }))
}
// These can't interrupt a paragraph: lazy continuation lines
IndentedCodeBlockLine | ParagraphLine | HtmlBlockLine(EndBlank7) as ltype => {
let items = list.items
guard! items.last() is Some(item)
if self.try_lazy_continuation(indent_start~, item.blocks) {
bs.push(List({ ..list, items, last_blank: false, }))
} else {
self.close_list(list, bs)
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, ltype)
}
}
ListMarkerLine(marker, marker_last) =>
self.try_add_to_list(indent~, marker, marker_last, list, bs)
lt => {
self.close_list(list, bs)
self.add_open_blocks_with_line_class(indent_start~, indent~, bs, lt)
}
}
}
///|
fn Parser::add_line(self : Parser, bs : Array[BlockStruct]) -> Unit {
match bs.last() {
Some(Paragraph(par)) => {
let _ = bs.pop()
self.try_add_to_paragraph(par, bs)
}
Some(ThematicBreak(_) | Heading(_) | BlankLine(_) | LinkRefDef(_)) | None =>
self.add_open_blocks(bs)
Some(List(l)) => {
let _ = bs.pop()
self.try_add_to_list_item(l, bs)
}
Some(CodeBlock(Indented(ls))) => {
let _ = bs.pop()
self.try_add_to_indented_code_block(ls, bs)
}
Some(CodeBlock(Fenced(f))) => {
let _ = bs.pop()
self.try_add_to_fenced_code_block(f, bs)
}
Some(BlockQuote(indent, bq)) => {
let _ = bs.pop()
self.try_add_to_block_quote(indent, bq, bs)
}
Some(HtmlBlock(html)) => {
let _ = bs.pop()
self.try_add_to_html_block(html, bs)
}
Some(ExtTable(indent, rows)) => {
let _ = bs.pop()
self.try_add_to_table(indent, rows, bs)
}
Some(ExtFootnote(i, l, blocks)) => {
let _ = bs.pop()
self.try_add_to_footnote(i, l, blocks, bs)
}
}
}
///|
fn Parser::get_first_line(self : Parser) -> String {
let max = self.i.length() - 1
let mut k = 0
let last_char = while k <= max && self.i[k] != '\r' && self.i[k] != '\n' {
k += 1
} nobreak {
// If the line is empty, we have -1
k - 1
}
self.curr_line_last_char = last_char
self.update_next_non_blank()
// Return first used newline (or "\n" if there is none)
if k > max || self.i[k] == '\n' {
return "\n"
}
let next = k + 1
if next <= max && self.i[next] == '\n' {
return "\r\n"
}
"\r"
}
///|
fn Parser::get_next_line(self : Parser) -> Bool {
let max = self.i.length() - 1
if self.curr_line_last_char == max {
return false
}
let first_char = {
let nl = self.curr_line_last_char + 1
if self.i[nl] == '\n' {
nl + 1
} else {
let mut next = nl + 1
if next <= max && self.i[next] == '\n' {
next += 1
}
next
}
}
let last_char = for k = first_char
k <= max && !(self.i[k] is ('\r' | '\n'))
k = k + 1 {
} nobreak {
k - 1
}
self.curr_line_pos = LinePos(self.curr_line_pos.0 + 1, first_char)
self.curr_line_last_char = last_char
self.curr_char = first_char
self.curr_char_col = 0
self.tab_consumed_cols = 0
self.update_next_non_blank()
true
}
///|
fn Parser::parse_block(self : Parser) -> (String, Node[Array[BlockStruct]]) {
fn meta() {
self.meta({
file: self.file,
first_ccode: 0,
last_ccode: self.curr_line_last_char,
first_line: LinePos(1, 0),
last_line: self.curr_line_pos,
})
}
let nl = self.get_first_line()
let bs = []
let blocks = {
while true {
self.add_line(bs)
if !self.get_next_line() {
self.end_doc(bs)
break
}
}
{ v: bs, meta: meta(), }
}
(nl, blocks)
}
///|
fn Parser::block_struct_to_blank_line(
self : Parser,
pad : Indent,
span : LineSpan,
) -> Block {
BlankLine(self.clean_raw_span(pad~, span))
}
///|
fn Parser::block_struct_to_code_block(
self : Parser,
b : CodeBlockStruct,
) -> Block {
match b {
Indented(ls) => {
let line = fn(i : IndentedCodeLine) {
self.clean_raw_span(pad=i.pad, i.code)
}
let layout = CodeBlockLayout::Indented
let info_string = None
let last = ls.last().unwrap().code
let code = ls.map(line)
let meta = {
let last_line = last.pos
let last_ccode = last.last
let start = code[0].meta.loc
self.meta({ ..start, last_ccode, last_line, })
}
CodeBlock({ v: { layout, info_string, code, }, meta, })
}
Fenced({ fence, code: ls, }) => {
let layout = {
let opening_fence = self.layout_clean_raw_span(fence.opening_fence)
let closing_fence = fence.closing_fence.map(fn(i) {
self.layout_clean_raw_span(i)
})
{ indent: fence.indent, opening_fence, closing_fence, }
}
let info_string = fence.info_string.map(fn(i) {
self.clean_unesc_unref_span(i)
})
let code = ls.map(fn(i) { self.clean_raw_span(pad=i.0, i.1) })
let meta = {
let first = fence.opening_fence
let last = match fence.closing_fence {
Some(last) => last
None =>
match ls.last() {
Some((_, last)) => last
None => first
}
}
self.meta_of_spans(first~, last~)
}
let cb = { layout: Fenced(layout), info_string, code, }
if self.exts && CodeBlock::is_math_block(info_string.map(fn(i) { i.v })) {
return ExtMathBlock({ v: cb, meta, })
}
CodeBlock({ v: cb, meta, })
}
}
}
///|
fn Parser::block_struct_to_heading(self : Parser, b : Heading) -> Block {
match b {
Atx({ indent, level, after_open, heading, layout_after, }) => {
let after_opening = {
let first = after_open
let last = heading.first - 1
self.layout_clean_raw_span1({ ..heading, first, last, })
}
let closing = self.layout_clean_raw_span1(layout_after)
let layout = BlockHeadingLayout::Atx({ indent, after_opening, closing, })
let meta = self.meta(
self.text_loc_of_span({ ..heading, first: after_open - level, }),
)
let (_, inline) = self.parse_inline([heading])
let id = if self.heading_auto_ids {
Some(Auto(inline.id()))
} else {
None
}
Heading({ v: { layout, level, inline, id, }, meta, })
}
Setext({ level, heading_lines, underline, }) => {
let ((leading_indent, trailing_blanks), inline) = self.parse_inline(
heading_lines,
)
let (underline_indent, u, blanks) = underline
let underline_blanks = self.layout_clean_raw_span1(blanks)
let underline_count = {
v: u.last - u.first + 1,
meta: self.meta(self.text_loc_of_span(u)),
}
let layout = {
leading_indent,
trailing_blanks,
underline_indent,
underline_count,
underline_blanks,
}
let meta = {
let last_line = u.pos
let last_ccode = u.last
let start = inline.meta().loc
self.meta({ ..start, last_ccode, last_line, })
}
let id = if self.heading_auto_ids {
Some(Auto(inline.id(buf=self.buf)))
} else {
None
}
Heading({ v: { layout: Setext(layout), level, inline, id, }, meta, })
}
}
}
///|
fn Parser::block_struct_to_html_block(
self : Parser,
b : HtmlBlockStruct,
) -> Block {
guard! b.html.last() is Some({ last: last_ccode, pos: last_line, .. })
let lines = b.html.map(fn(i) { self.clean_raw_span(i) })
let start_loc = lines[0].meta.loc
let meta = self.meta({ ..start_loc, last_ccode, last_line, })
HtmlBlock({ v: Seq::from_array(lines), meta, })
}
///|
fn Parser::block_struct_to_paragraph(self : Parser, par : Paragraph) -> Block {
let (layout, inline) = self.parse_inline(par.lines)
let (leading_indent, trailing_blanks) = layout
let meta = inline.meta()
Paragraph({ v: { leading_indent, inline, trailing_blanks, }, meta, })
}
///|
fn Parser::block_struct_to_thematic_break(
self : Parser,
indent : Indent,
span : LineSpan,
) -> Block {
let { v: layout, meta, } = self.clean_raw_span(span) // No layout because of loc
ThematicBreak({ v: { indent, layout, }, meta, })
}
///|
fn Parser::block_struct_to_table(
self : Parser,
indent : Indent,
rows : Array[(LineSpan, LineSpan)],
) -> Block {
guard! rows.last() is Some((last, _))
let acc : Array[_] = []
let (first, col_count, rows) = for col_count = 0, last_was_sep = false, rs = rows[:] {
match rs {
[.. rs, (row, blanks)] => {
let meta = self.meta(self.text_loc_of_span(row))
let row1 = { ..row, first: row.first + 1, last: row.last, }
let cols = self.parse_table_row(row1)
let col_count = @cmp.maximum(col_count, cols.length())
let (r, last_was_sep) = match Table::parse_sep_row(cols[:]) {
Some(seps) => ({ v: Sep(seps), meta, }, true)
None => {
let v = if last_was_sep { Header(cols) } else { Data(cols) }
({ v, meta, }, false)
}
}
acc.push((r, self.layout_clean_raw_span1(blanks)))
guard rs.length() != 0 else { break (row, col_count, acc) }
continue col_count, last_was_sep, rs
}
[] => abort("unreachable")
}
}
rows.rev_in_place()
let meta = self.meta_of_spans(first~, last~)
ExtTable({ v: { indent, col_count, rows, }, meta, })
}
///|
fn Parser::block_struct_to_block_quote(
self : Parser,
indent : Indent,
bs : Array[BlockStruct],
) -> Block {
let acc = bs.map(fn(b) { self.block_struct_to_block(b) })
let block = match acc {
[b] => b
quote => {
let first = quote[0].meta()
let last = quote.last().unwrap().meta()
let meta = self.meta_of_metas(first~, last~)
Blocks({ v: quote, meta, })
}
}
BlockQuote({ v: { indent, block, }, meta: block.meta(), })
}
///|
fn Parser::block_struct_to_footnote_definition(
self : Parser,
indent : Indent,
label : Label,
defined_label : Label?,
bs : Array[BlockStruct],
) -> Block {
let block = bs.map(fn(b) { self.block_struct_to_block(b) })
let last = block.last().unwrap().meta()
let block = match block {
[b] => b
blocks => {
let first = blocks[0].meta()
let meta = self.meta_of_metas(first~, last~)
Blocks({ v: blocks, meta, })
}
}
let loc = {
let labelloc = label.text_loc()
let lastloc = last.loc
let loc = labelloc.span(lastloc)
let first_ccode = loc.first_ccode - 1
{ ..loc, first_ccode, first_line: loc.first_line, }
}
let footnote = {
v: { indent, label, defined_label, block, },
meta: self.meta(loc),
}
if defined_label is Some(def) {
self.set_label_def(def, FootnoteDef(footnote))
}
ExtFootnoteDefinition(footnote)
}
///|
priv enum BState {
Blank
TrailBlank
NonBlank
} derive(Eq)
///|
fn Parser::block_struct_to_list_item(
self : Parser,
i : ListItemStruct,
) -> (Node[ListItem], Bool) {
fn go(bstate : BState, tight, acc : Array[_], bs : ArrayView[BlockStruct]) {
for bstate = bstate, tight = tight, bs = bs {
match bs {
[BlankLine(_) as bl, .. bs] => {
let bstate = if bstate == TrailBlank { TrailBlank } else { Blank }
acc.push(self.block_struct_to_block(bl))
continue bstate, tight, bs
}
[
List({ items: [.., { blocks: [.., BlankLine(_)], .. }], .. }) as l,
.. bs,
] => {
acc.push(self.block_struct_to_block(l))
continue bstate, false, bs
}
[b, .. bs] => {
let tight = tight && bstate != Blank
acc.push(self.block_struct_to_block(b))
continue NonBlank, tight, bs
}
[] => break (tight, acc)
}
}
}
let (last_meta, (tight, blocks)) = match i.blocks[:] {
[BlankLine(_) as blank] => {
let bl = self.block_struct_to_block(blank)
(bl.meta(), (true, [bl]))
}
[BlankLine(_) as blank, .. bs] => {
let bl = self.block_struct_to_block(blank)
(bl.meta(), go(TrailBlank, true, [bl], bs))
}
[b, .. bs] => {
let b = self.block_struct_to_block(b)
(b.meta(), go(NonBlank, true, [b], bs))
}
[] => abort("unreachable")
}
let block = match blocks {
[i] => i
blocks => {
let first = blocks[0].meta()
Blocks({ v: blocks, meta: self.meta_of_metas(first~, last=last_meta), })
}
}
let before_marker = i.before_marker
let after_marker = i.after_marker
let marker = self.clean_raw_span(i.marker)
let ext_task_marker = i.ext_task_marker.map(v_span => {
v: v_span.0,
meta: self.meta(self.text_loc_of_span(v_span.1)),
})
let v = { before_marker, marker, after_marker, block, ext_task_marker, }
let meta = self.meta_of_metas(first=marker.meta, last=last_meta)
({ v, meta, }, tight)
}
///|
fn Parser::block_struct_to_list(self : Parser, list : ListBlockStruct) -> Block {
let items = list.items
let (first, tight) = self.block_struct_to_list_item(items[0])
let acc = [first]
let tight = for tight = !list.loose && tight, items = items[1:] {
match items {
[] => break tight
[item, .. items] => {
let (item, item_tight) = self.block_struct_to_list_item(item)
acc.push(item)
continue tight && item_tight, items
}
}
}
let meta = self.meta_of_metas(first=first.meta, last=acc.last().unwrap().meta)
List({ v: { ty: list.list_type, tight, items: acc, }, meta, })
}
///|
fn Parser::block_struct_to_block(self : Parser, b : BlockStruct) -> Block {
match b {
BlockQuote(indent, bs) => self.block_struct_to_block_quote(indent, bs)
List(l) => self.block_struct_to_list(l)
Paragraph(par) => self.block_struct_to_paragraph(par)
ThematicBreak(indent, span) =>
self.block_struct_to_thematic_break(indent, span)
CodeBlock(cb) => self.block_struct_to_code_block(cb)
Heading(h) => self.block_struct_to_heading(h)
HtmlBlock(html) => self.block_struct_to_html_block(html)
BlankLine(pad, span) => self.block_struct_to_blank_line(pad, span)
LinkRefDef(r) => LinkRefDefinition(r)
ExtTable(i, rows) => self.block_struct_to_table(i, rows)
ExtFootnote(i, (lb, dlb), bs) =>
self.block_struct_to_footnote_definition(i, lb, dlb, bs)
}
}
///|
fn Parser::block_struct_to_doc(
self : Parser,
doc : Node[Array[BlockStruct]],
) -> Block {
let { v: doc, meta, } = doc
match doc.map(fn(b) { self.block_struct_to_block(b) }) {
[b] => b
bs => Blocks({ v: bs, meta, })
}
}