use memchr::{memchr, memchr2};
use super::Parser;
impl<'a> Parser<'a> {
pub(super) fn is_at_end(&self) -> bool {
self.position >= self.source.len()
}
pub(super) fn remaining(&self) -> &'a str {
&self.source[self.position..]
}
#[inline]
pub(super) fn peek(&self) -> Option<char> {
let bytes = self.source.as_bytes();
let pos = self.position;
let &b = bytes.get(pos)?;
if b < 0x80 {
Some(b as char)
} else {
self.source[pos..].chars().next()
}
}
#[inline]
pub(super) fn advance(&mut self) -> Option<char> {
let ch = self.peek()?;
self.position += ch.len_utf8();
Some(ch)
}
pub(super) fn skip_whitespace(&mut self) {
let bytes = self.source.as_bytes();
let mut pos = self.position;
while pos < bytes.len() && matches!(bytes[pos], b' ' | b'\t') {
pos += 1;
}
self.position = pos;
}
pub(super) fn skip_blank_lines(&mut self) {
let bytes = self.source.as_bytes();
let mut pos = self.position;
loop {
let line_start = pos;
while pos < bytes.len() && matches!(bytes[pos], b' ' | b'\t') {
pos += 1;
}
if pos < bytes.len() && bytes[pos] == b'\n' {
pos += 1;
} else {
self.position = line_start;
return;
}
}
}
pub(super) fn current_line(&self) -> &'a str {
let bytes = self.source.as_bytes();
let end = memchr(b'\n', &bytes[self.position..])
.map_or(self.source.len(), |off| self.position + off);
&self.source[self.position..end]
}
pub(super) fn line_starts_block(&self) -> bool {
let line_start = self.position;
let bytes = self.source.as_bytes();
let Some(trimmed_start) = self.first_non_whitespace_in_line(line_start) else {
return false;
};
let starts_block = match bytes[trimmed_start] {
b'#' => self.try_parse_heading_start(line_start, trimmed_start),
b'-' | b'*' => {
let line = self.line_at(line_start);
let trimmed = &line[trimmed_start - line_start..];
Self::try_parse_thematic_break_line(line) || Self::try_parse_list_line(trimmed)
}
b'_' => Self::try_parse_thematic_break_line(self.line_at(line_start)),
b'>' => true,
b'`' | b'~' => {
let line = self.line_at(line_start);
let trimmed = &line[trimmed_start - line_start..];
Self::try_parse_fenced_code_at(line, trimmed)
}
b'<' => {
let line = self.line_at(line_start);
Self::parse_html_block_start(&line[trimmed_start - line_start..]).is_some()
}
b'+' | b'0'..=b'9' => {
let line = self.line_at(line_start);
Self::try_parse_list_line(&line[trimmed_start - line_start..])
}
_ => false,
};
starts_block
|| (self.options.tables
&& self.line_contains_byte(line_start, b'|')
&& self.try_parse_table())
}
pub(super) fn line_at(&self, line_start: usize) -> &'a str {
let bytes = self.source.as_bytes();
let end =
memchr(b'\n', &bytes[line_start..]).map_or(self.source.len(), |off| line_start + off);
&self.source[line_start..end]
}
pub(super) fn next_line_start(&self, line_start: usize) -> usize {
let bytes = self.source.as_bytes();
match memchr(b'\n', &bytes[line_start..]) {
Some(off) => line_start + off + 1,
None => self.source.len(),
}
}
pub(super) fn consume_line(&mut self) -> &'a str {
let start = self.position;
let bytes = self.source.as_bytes();
if let Some(off) = memchr(b'\n', &bytes[start..]) {
let line_end = start + off;
self.position = line_end + 1;
&self.source[start..line_end]
} else {
self.position = self.source.len();
&self.source[start..]
}
}
pub(super) fn first_non_whitespace_in_line(&self, line_start: usize) -> Option<usize> {
let bytes = self.source.as_bytes();
let mut cursor = line_start;
while cursor < bytes.len() {
match bytes[cursor] {
b' ' | b'\t' => cursor += 1,
b'\n' => return None,
_ => return Some(cursor),
}
}
None
}
pub(super) fn line_contains_byte(&self, line_start: usize, needle: u8) -> bool {
let bytes = self.source.as_bytes();
match memchr2(needle, b'\n', &bytes[line_start..]) {
Some(off) => bytes[line_start + off] == needle,
None => false,
}
}
}