// The entry point: normalise the input, cut it into lines, run the block phase
// and build the document.
///|
/// Parse a Markdown document.
///
/// ```mbt check
/// test {
/// let document = @moonmd.parse("# Title")
/// assert_eq(document.html(safe=false), "Title
\n")
/// }
/// ```
pub fn parse(text : StringView, flavor~ : Flavor = flavor) -> Document {
let normalized = normalize_input(text)
let parser = BlockParser::new(flavor, (Map([]) : Map[String, Reference]))
for line in split_lines(normalized) {
parser.process_line(line)
}
parser.close_all()
let blocks = build_blocks(parser.tree, parser.refs, flavor, parser.root, -1)
Document::of_blocks(blocks, flavor~)
}
///|
/// Undo the line endings the platform may have left in, drop a byte order mark,
/// and replace the NUL characters that would otherwise cut a string short.
fn normalize_input(text : StringView) -> String {
let buf = StringBuilder()
let mut started = false
let mut skip_newline = false
for c in text {
if skip_newline {
skip_newline = false
if c == '\n' {
continue
}
}
if !started {
started = true
if c == '\u{FEFF}' {
continue
}
}
if c == '\r' {
buf.write_char('\n')
skip_newline = true
} else if c == '\u{0}' {
buf.write_char('\u{FFFD}')
} else {
buf.write_char(c)
}
}
buf.to_string()
}
///|
/// Cut the text into lines, each ending with a newline: the block phase relies
/// on every line it sees being terminated, which is what the last line of a
/// document without a trailing newline needs too.
fn split_lines(text : String) -> Array[String] {
let out : Array[String] = []
let n = text.length()
let mut start = 0
let mut i = 0
while i < n {
if cu(text[:], i) == 0x0A {
out.push(text.clamped_view(start~, end=i + 1).to_owned())
start = i + 1
}
i = i + 1
}
if start < n {
out.push(text.clamped_view(start~, end=n).to_owned() + "\n")
}
out
}