// Where a block came from: the lines of the source it was read from. The
// caller that splits a document by heading, or that points a reader at the
// text behind a node, needs this and nothing finer.
///|
/// A run of source lines, counted from one, that a block was read from.
pub(all) struct LineSpan {
/// The first line of the run.
first : Int32
/// The last line of the run.
last : Int32
}
///|
/// The lines each top level block was read from, one entry per block of the
/// document as it was parsed, in the order the blocks were written.
fn top_level_lines(tree : Tree) -> Array[LineSpan] {
let out : Array[LineSpan] = []
let mut node = tree.nodes[0].first_child
while node >= 0 {
out.push({
first: tree.nodes[node].start_line + 1,
last: tree.nodes[node].last_line + 1,
})
node = tree.nodes[node].next
}
out
}
///|
/// The lines a definition was written on, by the label it is looked up with.
/// A definition inside a quote or a list is written inside that block, so the
/// line it is given is the one the block around it starts on.
fn collect_definition_lines(
block : Block,
span : LineSpan,
out : Map[String, LineSpan],
) -> Unit {
match block {
Footnote(footnote) => {
let key = normalize_label(footnote.label[:])
if key.length() > 0 && !out.contains(key) {
out[key] = span
}
for child in footnote.blocks {
collect_definition_lines(child, span, out)
}
}
Quote(children) =>
for child in children {
collect_definition_lines(child, span, out)
}
List(list) =>
for item in list.items {
for child in item.blocks {
collect_definition_lines(child, span, out)
}
}
_ => ()
}
}
///|
/// Put the lines back in step with the blocks a document settled into. Reading
/// footnotes takes their definitions out of the body and writes them at the
/// end, and drops the ones nothing refers to, so the entries are taken from
/// where each term now stands rather than by their first order.
fn realign_lines(
before : Array[Block],
after : Array[Block],
lines : Array[LineSpan],
) -> Array[LineSpan] {
if before.length() != lines.length() {
return []
}
let definitions : Map[String, LineSpan] = Map([])
for i, block in before {
collect_definition_lines(block, lines[i], definitions)
}
let out : Array[LineSpan] = []
let mut cursor = 0
for block in after {
match block {
Footnote(footnote) => {
let key = normalize_label(footnote.label[:])
out.push(definitions.get(key).unwrap_or({ first: 0, last: 0, }))
}
_ => {
while cursor < before.length() && is_footnote(before[cursor]) {
cursor = cursor + 1
}
if cursor < lines.length() {
out.push(lines[cursor])
cursor = cursor + 1
}
}
}
}
out
}
///|
fn is_footnote(block : Block) -> Bool {
match block {
Footnote(_) => true
_ => false
}
}