//! Second part of the first pass: paragraph/table/line parsing and leaf parsers.
///|
/// Scanning modes for `parse_line`.
priv enum TableParseMode {
/// Inside a paragraph, scanning for table headers.
Scan
/// Inside a table.
Active
/// Inside a paragraph, not scanning for table headers.
Disabled
}
///|
/// footnote definitions and GFM quote markers can be "interrupted"
/// like paragraphs, but otherwise can't have other blocks after them.
fn FirstPass::scan_next_line_or_lazy_continuation(
self : FirstPass,
bytes : BytesView,
) -> LineStart? {
let line_start = LineStart::new(bytes)
let tree_position = line_start.scan_containers(self.tree, self.options)
let current_container = tree_position == self.tree.spine_len()
let interrupted = !line_start.scan_space(4) &&
self.scan_paragraph_interrupt(
bytes.view(start=line_start.bytes_scanned()),
current_container,
tree_position,
)
let blank = scan_blank_line(bytes.view(start=line_start.bytes_scanned()))
is Some(_)
guard !(interrupted || blank) else { None }
line_start.scan_all_space()
Some(line_start)
}
///|
/// Returns the offset of the first line after the table.
fn FirstPass::parse_table(
self : FirstPass,
table_cols : Int,
head_start : Int,
body_start : Int,
) -> Int? {
// filled empty cells are limited to protect against quadratic growth
let missing_empty_cells = 0
// parse header
guard self.parse_table_row_inner(head_start, table_cols, missing_empty_cells)
is Some((_sep_start, thead_ix)) else {
None
}
self.tree.nodes[thead_ix].item.body = TableHead
// parse body
let mut ix = body_start
while true {
match self.parse_table_row(ix, table_cols, missing_empty_cells) {
Some((next_ix, _row_ix)) => ix = next_ix
None => break
}
}
self.pop(ix)
Some(ix)
}
///|
/// Call this when containers are taken care of.
/// Returns bytes scanned, row_ix
fn FirstPass::parse_table_row_inner(
self : FirstPass,
ix : Int,
row_cells : Int,
missing_empty_cells : Int,
) -> (Int, Int)? {
let mut ix = ix
// Limit to prevent a malicious input from causing a denial of service.
let max_autocompleted_cells = 1 << 18
let bytes = self.text
let mut cells = 0
let mut final_cell_ix : Int? = None
let mut missing_empty_cells = missing_empty_cells
let old_cur = self.tree.cur()
let row_ix = self.tree.append(Item::{ start: ix, end: 0, body: TableRow })
ignore(self.tree.push())
while true {
ix += scan_ch(bytes.view(start=ix), b'|')
let start_ix = ix
ix += scan_whitespace_no_nl(bytes.view(start=ix))
match scan_eol(bytes.view(start=ix)) {
Some(eol_bytes) => {
ix += eol_bytes
break
}
None => ()
}
let cell_ix = self.tree.append(Item::{
start: start_ix,
end: ix,
body: TableCell,
})
ignore(self.tree.push())
let (next_ix, _brk) = self.parse_line(ix, None, Active)
self.tree.nodes[cell_ix].item.end = next_ix
ignore(self.tree.pop())
ix = next_ix
cells += 1
if cells == row_cells {
final_cell_ix = Some(cell_ix)
}
}
match old_cur {
Some(cur) =>
if cells == 0 {
ignore(self.pop(ix))
self.tree.nodes[cur].next = None
return None
}
None => ()
}
// fill empty cells if needed
for _ in cells.. self.tree.nodes[cell_ix].next = None
None => ()
}
ignore(self.pop(ix))
Some((ix, row_ix))
}
///|
/// Returns first offset after the row and the tree index of the row.
fn FirstPass::parse_table_row(
self : FirstPass,
ix : Int,
row_cells : Int,
missing_empty_cells : Int,
) -> (Int, Int)? {
let mut ix = ix
let bytes = self.text
let line_start = LineStart::new(bytes.view(start=ix))
let tree_position = line_start.scan_containers(self.tree, self.options)
let current_container = tree_position == self.tree.spine_len()
guard current_container else { return None }
line_start.scan_all_space()
ix += line_start.bytes_scanned()
guard !scan_paragraph_interrupt_no_table(
bytes.view(start=ix),
current_container,
self.options.contains(enable_footnotes()),
self.options.contains(enable_definition_list()),
self.tree,
tree_position,
) else {
return None
}
self.parse_table_row_inner(ix, row_cells, missing_empty_cells)
}
///|
/// Returns offset of line start after paragraph.
fn FirstPass::parse_paragraph(
self : FirstPass,
start_ix : Int,
tasklist_marker : Item?,
) -> Int {
let body = match self.tree.peek_up() {
Some(idx) => {
let is_deflist = self.tree.nodes[idx].item.body is DefinitionList(_)
if is_deflist {
let cur_is_def = self.tree
.cur()
.map_or(true, fn(idx2) {
self.tree.nodes[idx2].item.body is DefinitionListDefinition(_)
})
if cur_is_def {
// blank lines between the previous definition and this one don't count
self.last_line_blank = false
MaybeDefinitionListTitle
} else {
self.finish_list(start_ix)
Paragraph
}
} else {
self.finish_list(start_ix)
Paragraph
}
}
None => {
self.finish_list(start_ix)
Paragraph
}
}
let node_ix = self.tree.append(Item::{ start: start_ix, end: 0, body })
ignore(self.tree.push())
match tasklist_marker {
Some(item) => ignore(self.tree.append(item))
None => ()
}
let bytes = self.text
let mut ix = start_ix
while true {
let scan_mode = if self.options.contains(enable_tables()) && ix == start_ix {
Scan
} else {
Disabled
}
let (next_ix, brk) = self.parse_line(ix, None, scan_mode)
// break out when we find a table
match brk {
Some(brk_item) =>
match brk_item.body {
Table(alignment_ix) => {
let table_cols = self.allocs.alignments[alignment_ix].length()
self.tree.nodes[node_ix].item.body = Table(alignment_ix)
// this clears out any stuff we may have appended
self.tree.nodes[node_ix].child = None
ignore(self.tree.pop())
let is_maybe_title = body is MaybeDefinitionListTitle
if is_maybe_title {
self.finish_list(ix)
}
ignore(self.tree.push())
match self.parse_table(table_cols, ix, next_ix) {
Some(result) => return result
None => ()
}
}
_ => ()
}
None => ()
}
ix = next_ix
let line_start = LineStart::new(bytes.view(start=ix))
let tree_position = line_start.scan_containers(self.tree, self.options)
let current_container = tree_position == self.tree.spine_len()
let trailing_backslash_pos = match brk {
Some(brk_item) =>
match brk_item.body {
HardBreak(true) if bytes.length() > brk_item.start &&
bytes.unsafe_get(brk_item.start) == b'\\' => Some(brk_item.start)
_ => None
}
_ => None
}
if !line_start.scan_space(4) {
let ix_new = ix + line_start.bytes_scanned()
if current_container {
match
self.parse_setext_heading(
ix_new,
node_ix,
trailing_backslash_pos is Some(_),
) {
Some(ix_setext) => {
match trailing_backslash_pos {
Some(pos) => self.tree.append_text(pos, pos + 1, false)
None => ()
}
self.pop(ix_setext)
let is_maybe_title = body is MaybeDefinitionListTitle
if is_maybe_title {
self.finish_list(ix)
}
return ix_setext
}
None => ()
}
}
// first check for non-empty lists, then for other interrupts
let suffix = bytes.view(start=ix_new)
if self.scan_paragraph_interrupt(suffix, current_container, tree_position) {
match trailing_backslash_pos {
Some(pos) => self.tree.append_text(pos, pos + 1, false)
None => ()
}
break
}
let close_container = self.options.contains(enable_container_extensions()) &&
!current_container &&
line_start.scan_closing_container_extensions_fence(3)
guard !close_container else { break }
}
line_start.scan_all_space()
if line_start.is_at_eol() {
match trailing_backslash_pos {
Some(pos) => self.tree.append_text(pos, pos + 1, false)
None => ()
}
break
}
if self.options.contains(enable_container_extensions()) {
let mut closes = false
let spine = self.tree.walk_spine()
let mut idx = 1
while idx < spine.length() {
let node_ix = spine[spine.length() - 1 - idx]
match self.tree.nodes[node_ix].item.body {
Container(length, _, _) =>
if line_start.scan_closing_container_extensions_fence(length) {
closes = true
break
}
_ => break
}
idx += 1
}
guard !closes else { break }
}
ix = next_ix + line_start.bytes_scanned()
match brk {
Some(item) => ignore(self.tree.append(item))
None => ()
}
}
self.pop(ix)
ix
}
///|
/// Mutable state for `parse_line`.
priv struct LineState {
mut pipes : Int
mut last_pipe_ix : Int
mut begin_text : Int
mut backslash_escaped : Bool
}
///|
/// Returns end ix of setext_heading on success.
fn FirstPass::parse_setext_heading(
self : FirstPass,
ix : Int,
node_ix : Int,
has_trailing_content : Bool,
) -> Int? {
let bytes = self.text
match scan_setext_heading(bytes.view(start=ix)) {
Some((n, level)) => {
let mut attrs : HeadingAttributes? = None
match self.tree.cur() {
Some(cur_ix) => {
let parent_ix = self.tree.peek_up().unwrap()
let header_start = self.tree.nodes[parent_ix].item.start
// Note that `item.end` might be zero at this point.
let header_end = self.tree.nodes[cur_ix].item.end
// extract the trailing attribute block
let (content_end, attrs_) = self.extract_and_parse_heading_attribute_block(
header_start, header_end,
)
attrs = attrs_
// strip trailing whitespace
let new_end = if has_trailing_content {
content_end
} else {
let mut last_line_start = header_start
if attrs is Some(_) {
while true {
let next_line_start = last_line_start +
scan_nextline(bytes.view(start=last_line_start))
if next_line_start >= content_end {
break
}
let line_start = LineStart::new(
bytes.view(start=next_line_start, end=content_end),
)
if line_start.scan_containers(self.tree, self.options) !=
self.tree.spine_len() {
break
}
last_line_start = next_line_start + line_start.bytes_scanned()
}
}
let trailing_ws = scan_rev_while(
bytes.view(start=last_line_start, end=content_end),
fn(c) { c.is_ascii_whitespace_no_nl() },
)
content_end - trailing_ws
}
if attrs is Some(_) {
// remove trailing block attributes
self.tree.truncate_siblings(new_end)
}
match self.tree.cur() {
Some(cur_ix) => self.tree.nodes[cur_ix].item.end = new_end
None => ()
}
}
None => ()
}
self.tree.nodes[node_ix].item.body = attrs.map_or(Heading(level, None), fn(
attrs,
) {
Heading(level, Some(self.allocs.allocate_heading(attrs)))
})
Some(ix + n)
}
None => None
}
}
///|
/// Parse a line of input, appending text and items to tree.
/// Returns: index after line and an item representing the break.
fn FirstPass::parse_line(
self : FirstPass,
start : Int,
end : Int?,
mode : TableParseMode,
) -> (Int, Item?) {
let bytes = self.text
let bytes_len = bytes.length()
let limit = end.unwrap_or(bytes_len)
let mut state = LineState::{
pipes: 0,
last_pipe_ix: start,
begin_text: start,
backslash_escaped: false,
}
let mut final_ix = limit
let mut brk : Item? = None
let lut = special_bytes(self.options)
let mut ix = start
let mut done = false
while ix < limit && !done {
let byte = bytes.unsafe_get(ix)
if lut[byte.to_int()] {
let (action, new_state) = self.handle_special_byte(
ix, byte, mode, limit, bytes_len, start, state,
)
state = new_state
match action {
ParseLineAction::Continue(skip) => ix += skip
ParseLineAction::BreakWith(end_ix, item) => {
final_ix = end_ix
brk = item
done = true
}
}
}
ix += 1
}
match brk {
Some(_) => ()
None => {
let trailing_whitespace = scan_rev_while(
bytes.view(start=state.begin_text, end=final_ix),
fn(c) { c.is_ascii_whitespace_no_nl() },
)
// need to close text at eof
self.tree.append_text(
state.begin_text,
final_ix - trailing_whitespace,
state.backslash_escaped,
)
}
}
(final_ix, brk)
}
///|
priv enum ParseLineAction {
Continue(Int)
BreakWith(Int, Item?)
}