//! Special-byte handling for `parse_line`.
///|
fn FirstPass::handle_special_byte(
self : FirstPass,
ix : Int,
byte : Byte,
mode : TableParseMode,
limit : Int,
bytes_len : Int,
start : Int,
state : LineState,
) -> (ParseLineAction, LineState) {
self.handle_special_byte_inner(ix, byte, mode, limit, bytes_len, start, state)
}
///|
fn FirstPass::handle_special_byte_inner(
self : FirstPass,
ix : Int,
byte : Byte,
mode : TableParseMode,
limit : Int,
bytes_len : Int,
start : Int,
state : LineState,
) -> (ParseLineAction, LineState) {
let bytes = self.text
match byte {
b'\n' | b'\r' => {
guard !is_active_mode(mode) else {
return (ParseLineAction::BreakWith(ix, None), state)
}
let mut i = ix
let eol_bytes = scan_eol(bytes.view(start=ix)).unwrap()
let end_ix = ix + eol_bytes
let trailing_backslashes = scan_rev_while(bytes.view(start=0, end=ix), fn(
b,
) {
b == b'\\'
})
if trailing_backslashes % 2 == 1 && end_ix < bytes_len {
i -= 1
self.tree.append_text(state.begin_text, i, state.backslash_escaped)
state.backslash_escaped = false
return (
ParseLineAction::BreakWith(
end_ix,
Some(Item::{ start: i, end: end_ix, body: HardBreak(true) }),
),
state,
)
}
let is_scan = is_scan_mode(mode)
if is_scan && state.pipes > 0 {
// check if we may be parsing a table
let next_line_ix = ix + eol_bytes
let line_start = LineStart::new(bytes.view(start=next_line_ix))
if line_start.scan_containers(self.tree, self.options) ==
self.tree.spine_len() {
let table_head_ix = next_line_ix + line_start.bytes_scanned()
let (table_head_bytes, alignment) = scan_table_head(
bytes.view(start=table_head_ix),
)
if table_head_bytes > 0 {
// computing header count from number of pipes
let header_count = count_header_cols(
bytes,
state.pipes,
start,
state.last_pipe_ix,
)
// make sure they match the number of columns
if alignment.length() == header_count {
let alignment_ix = self.allocs.allocate_alignment(alignment)
let end_ix = table_head_ix + table_head_bytes
return (
ParseLineAction::BreakWith(
end_ix,
Some(Item::{
start: i,
end: end_ix,
body: Table(alignment_ix),
}),
),
state,
)
}
}
}
}
let trailing_whitespace = scan_rev_while(bytes.view(start=0, end=ix), fn(
c,
) {
c.is_ascii_whitespace_no_nl()
})
if trailing_whitespace >= 2 {
i -= trailing_whitespace
self.tree.append_text(state.begin_text, i, state.backslash_escaped)
state.backslash_escaped = false
return (
ParseLineAction::BreakWith(
end_ix,
Some(Item::{ start: i, end: end_ix, body: HardBreak(false) }),
),
state,
)
}
self.tree.append_text(
state.begin_text,
ix - trailing_whitespace,
state.backslash_escaped,
)
state.backslash_escaped = false
(
ParseLineAction::BreakWith(
end_ix,
Some(Item::{ start: i, end: end_ix, body: SoftBreak }),
),
state,
)
}
b'\\' => {
let is_escape = ix + 1 < limit &&
is_ascii_punctuation(bytes.unsafe_get(ix + 1).to_int())
guard is_escape else { return (ParseLineAction::Continue(0), state) }
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
if bytes.unsafe_get(ix + 1) == b'`' {
let count = 1 + scan_ch_repeat(bytes.view(start=ix + 2), b'`')
ignore(
self.tree.append(Item::{
start: ix + 1,
end: ix + count + 1,
body: MaybeCode(count, true),
}),
)
state.begin_text = ix + 1 + count
state.backslash_escaped = false
(ParseLineAction::Continue(count), state)
} else {
let in_table = is_active_mode(mode)
if bytes.unsafe_get(ix + 1) == b'|' && in_table {
// backslash escaped pipes in tables aren't "real" backslash escapes
state.begin_text = ix + 1
state.backslash_escaped = false
(ParseLineAction::Continue(1), state)
} else {
let is_double_escape = ix + 2 < limit &&
bytes.unsafe_get(ix + 1) == b'\\' &&
bytes.unsafe_get(ix + 2) == b'|' &&
in_table
if is_double_escape {
// To parse `\\|`, discard the backslashes and parse the `|` that follows it.
state.begin_text = ix + 2
state.backslash_escaped = true
(ParseLineAction::Continue(2), state)
} else {
state.begin_text = ix + 1
state.backslash_escaped = true
(ParseLineAction::Continue(1), state)
}
}
}
}
b'*' | b'_' | b'~' | b'^' | b'=' => {
let count = 1 + scan_ch_repeat(bytes.view(start=ix + 1), byte)
let can_open = delim_run_can_open(
self.text,
start,
ix,
count,
mode,
self.options,
)
let can_close = delim_run_can_close(
self.text,
start,
ix,
count,
mode,
self.options,
)
let is_valid_seq = match byte {
b'~' => count <= 2
b'=' => count == 2
_ => true
}
if (can_open || can_close) && is_valid_seq {
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
for k in 0.. {
let byte_suffix = bytes.view(start=ix)
let can_open = self.options.contains(enable_math_everywhere()) ||
byte_suffix.length() < 2 ||
!byte_suffix.unsafe_get(1).to_char().is_ascii_whitespace()
let can_close = self.options.contains(enable_math_everywhere()) ||
(
ix > start &&
!bytes.unsafe_get(ix - 1).to_char().is_ascii_whitespace() &&
!(ix + 1 < limit &&
bytes.unsafe_get(ix + 1).to_char().is_ascii_digit())
)
// 0xFF represents the root brace context
let brace_context = if self.brace_context_stack.length() >
MATH_BRACE_CONTEXT_MAX_NESTING {
self.brace_context_next & 0xff
} else {
match self.brace_context_stack.last() {
Some(v) => v
None => {
self.brace_context_stack.push(0xff)
0xff
}
}
}
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
ignore(
self.tree.append(Item::{
start: ix,
end: ix + 1,
body: MaybeMath(can_open, can_close, brace_context),
}),
)
state.begin_text = ix + 1
(ParseLineAction::Continue(0), state)
}
b'{' => {
if self.brace_context_stack.length() == MATH_BRACE_CONTEXT_MAX_NESTING {
self.brace_context_stack.push(self.brace_context_next & 0xff)
self.brace_context_next = MATH_BRACE_CONTEXT_MAX_NESTING
} else if self.brace_context_stack.length() >
MATH_BRACE_CONTEXT_MAX_NESTING {
// When we reach the limit of nesting, switch from actually matching
// braces to just counting them.
self.brace_context_next += 1
} else if !self.brace_context_stack.is_empty() {
// Store nothing if no math environment has been reached yet.
self.brace_context_stack.push(self.brace_context_next & 0xff)
self.brace_context_next += 1
}
(ParseLineAction::Continue(0), state)
}
b'}' => {
if self.brace_context_stack.length() == 1 {
// Unbalanced Braces
self.brace_context_stack[0] = (self.brace_context_stack[0] - 1) & 0xff
} else if self.brace_context_stack.length() >
MATH_BRACE_CONTEXT_MAX_NESTING {
if self.brace_context_next <= MATH_BRACE_CONTEXT_MAX_NESTING {
ignore(self.brace_context_stack.pop())
} else {
self.brace_context_next -= 1
}
} else {
ignore(self.brace_context_stack.pop())
}
(ParseLineAction::Continue(0), state)
}
b'`' => {
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
let count = 1 + scan_ch_repeat(bytes.view(start=ix + 1), b'`')
ignore(
self.tree.append(Item::{
start: ix,
end: ix + count,
body: MaybeCode(count, false),
}),
)
state.begin_text = ix + count
(ParseLineAction::Continue(count - 1), state)
}
b'<' => {
let not_backslash = limit <= ix + 1 || bytes.unsafe_get(ix + 1) != b'\\'
guard not_backslash else { return (ParseLineAction::Continue(0), state) }
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{ start: ix, end: ix + 1, body: MaybeHtml }),
)
state.begin_text = ix + 1
(ParseLineAction::Continue(0), state)
}
b'!' => {
guard ix + 1 < limit && bytes.unsafe_get(ix + 1) == b'[' else {
return (ParseLineAction::Continue(0), state)
}
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{ start: ix, end: ix + 2, body: MaybeImage }),
)
state.begin_text = ix + 2
(ParseLineAction::Continue(1), state)
}
b'[' => {
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{ start: ix, end: ix + 1, body: MaybeLinkOpen }),
)
state.begin_text = ix + 1
(ParseLineAction::Continue(0), state)
}
b']' => {
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{
start: ix,
end: ix + 1,
body: MaybeLinkClose(true),
}),
)
state.begin_text = ix + 1
(ParseLineAction::Continue(0), state)
}
b'&' =>
match scan_entity(bytes.view(start=ix)) {
(n, Some(value)) => {
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{
start: ix,
end: ix + n,
body: SynthesizeText(self.allocs.allocate_cow(value)),
}),
)
state.begin_text = ix + n
(ParseLineAction::Continue(n - 1), state)
}
_ => (ParseLineAction::Continue(0), state)
}
b'|' =>
if ix != 0 && bytes.unsafe_get(ix - 1) == b'\\' {
(ParseLineAction::Continue(0), state)
} else {
let in_table = is_active_mode(mode)
if in_table {
(ParseLineAction::BreakWith(ix, None), state)
} else {
state.last_pipe_ix = ix
state.pipes += 1
(ParseLineAction::Continue(0), state)
}
}
b'.' => {
let is_ellipsis = ix + 2 < limit &&
bytes.unsafe_get(ix + 1) == b'.' &&
bytes.unsafe_get(ix + 2) == b'.'
guard is_ellipsis else { return (ParseLineAction::Continue(0), state) }
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{
start: ix,
end: ix + 3,
body: SynthesizeChar('…'),
}),
)
state.begin_text = ix + 3
(ParseLineAction::Continue(2), state)
}
b'-' => {
let count = 1 + scan_ch_repeat(bytes.view(start=ix + 1), b'-')
if count == 1 {
(ParseLineAction::Continue(0), state)
} else {
let itembody = if count == 2 {
SynthesizeChar('–')
} else if count == 3 {
SynthesizeChar('—')
} else {
let (ems, ens) = match count % 6 {
0 | 3 => (count / 3, 0)
2 | 4 => (0, count / 2)
1 => (count / 3 - 1, 2)
_ => (count / 3, 1)
}
// – and — are 3 bytes each in utf8
let buf = StringBuilder::new()
for _ in 0.. {
let can_open = delim_run_can_open(
self.text,
start,
ix,
1,
mode,
self.options,
)
let can_close = delim_run_can_close(
self.text,
start,
ix,
1,
mode,
self.options,
)
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{
start: ix,
end: ix + 1,
body: MaybeSmartQuote(byte, can_open, can_close),
}),
)
state.begin_text = ix + 1
(ParseLineAction::Continue(0), state)
}
b'\x00' => {
// U+0000 must be replaced with U+FFFD
self.tree.append_text(state.begin_text, ix, state.backslash_escaped)
state.backslash_escaped = false
ignore(
self.tree.append(Item::{
start: ix,
end: ix + 1,
body: SynthesizeChar('\u{fffd}'),
}),
)
state.begin_text = ix + 1
(ParseLineAction::Continue(0), state)
}
_ => (ParseLineAction::Continue(0), state)
}
}