use crate::internal::*;
use crate::variants::token::*;
#[derive(Debug, Clone)]
pub struct ContiguousLines<'arena> {
lines: Deq<'arena, Line<'arena>>,
}
impl<'arena> ContiguousLines<'arena> {
pub const fn new(lines: Deq<'arena, Line<'arena>>) -> Self {
ContiguousLines { lines }
}
pub fn empty(bump: &'arena Bump) -> Self {
ContiguousLines::new(Deq::new(bump))
}
pub fn with_capacity(capacity: usize, bump: &'arena Bump) -> Self {
ContiguousLines::new(Deq::with_capacity(capacity, bump))
}
pub fn push(&mut self, line: Line<'arena>) {
self.lines.push(line);
}
pub fn len(&self) -> usize {
self.lines.len()
}
pub fn num_tokens(&self) -> usize {
self.lines.iter().map(Line::num_tokens).sum()
}
pub fn current(&self) -> Option<&Line<'arena>> {
self.lines.get(0)
}
pub fn current_mut(&mut self) -> Option<&mut Line<'arena>> {
self.lines.get_mut(0)
}
pub fn current_satisfies(&self, f: impl Fn(&Line<'arena>) -> bool) -> bool {
self.current().map(f).unwrap_or(false)
}
fn first(&self) -> Option<&Line<'arena>> {
self.current()
}
pub fn iter(&'arena self) -> impl ExactSizeIterator<Item = &'arena Line<'arena>> + 'arena {
self.lines.iter()
}
pub fn pop(&mut self) -> Option<Line<'arena>> {
self.lines.pop()
}
pub fn last(&self) -> Option<&Line<'arena>> {
self.lines.last()
}
pub fn last_mut(&mut self) -> Option<&mut Line<'arena>> {
self.lines.last_mut()
}
pub fn nth(&self, n: usize) -> Option<&Line<'arena>> {
self.lines.get(n)
}
pub fn current_token(&self) -> Option<&Token<'arena>> {
self.current().and_then(|line| line.current_token())
}
pub fn consume_current_token(&mut self) -> Option<Token<'arena>> {
let current_line = self.current_mut()?;
let maybe_token = current_line.consume_current();
if current_line.is_empty() {
self.consume_current();
}
maybe_token
}
pub fn nth_token(&self, n: usize) -> Option<&Token<'arena>> {
self.current().and_then(|line| line.nth_token(n))
}
pub fn is_empty(&self) -> bool {
self.lines.is_empty()
}
pub fn consume_current(&mut self) -> Option<Line<'arena>> {
self.lines.pop_front()
}
pub fn consume_current_line_with_token(&mut self) -> Option<Token<'arena>> {
self
.consume_current()
.and_then(|mut line| line.consume_current())
}
pub fn extend(&mut self, other: BumpVec<'arena, Line<'arena>>) {
self.lines.reserve(other.len());
self.lines.extend(other);
}
pub fn restore_if_nonempty(&mut self, line: Line<'arena>) {
if !line.is_empty() {
self.lines.restore_front(line);
}
}
pub fn any(&self, f: impl FnMut(&Line<'arena>) -> bool) -> bool {
self.lines.iter().any(f)
}
pub fn contains_seq(&self, specs: &[TokenSpec]) -> bool {
self.lines.iter().any(|line| line.contains_seq(specs))
}
pub fn contains_len(&self, kind: TokenKind, len: usize) -> bool {
self.lines.iter().any(|line| line.contains_len(kind, len))
}
pub fn terminates_constrained(&self, stop_tokens: &[TokenSpec], ctx: &InlineCtx) -> bool {
self
.lines
.iter()
.any(|line| line.terminates_constrained(stop_tokens, ctx))
}
pub fn terminates_index_term(&self) -> Option<usize> {
self
.lines
.iter()
.find_map(|line| line.terminates_index_term())
}
pub fn is_block_macro(&self) -> bool {
self.current().is_some_and(Line::is_block_macro)
}
pub fn loc(&self) -> Option<SourceLocation> {
self
.first_loc()
.zip(self.last_loc())
.map(|(start, end)| SourceLocation::spanning(start, end))
}
pub fn last_loc(&self) -> Option<SourceLocation> {
self.lines.last().and_then(|line| line.last_loc())
}
pub fn first_loc(&self) -> Option<SourceLocation> {
self.lines.first().and_then(|line| line.first_loc())
}
pub fn is_quoted_paragraph(&self, in_markdown_blockquote: bool) -> bool {
if self.lines.len() < 2 {
return false;
}
let last_line = self.last().unwrap();
if !last_line.starts_with_seq(&[Kind(Dashes), Kind(Whitespace)])
|| last_line.num_tokens() < 3
|| !last_line.current_satisfies(Len(2, Dashes))
{
return false;
}
let first_line = self.current().unwrap();
if !in_markdown_blockquote {
if !first_line.starts(DoubleQuote) || first_line.num_tokens() < 2 {
return false;
}
let penult = self.nth(self.lines.len() - 2).unwrap();
penult.ends(DoubleQuote)
} else {
!first_line.is_empty()
}
}
pub fn starts_list(&self) -> bool {
for line in self.lines.iter() {
if line.starts_list_item() {
return true;
} else if line.is_comment() {
continue;
} else {
return false;
}
}
false
}
pub fn starts_extra_description_list_term(&self, ctx: ListMarker) -> bool {
for line in self.lines.iter() {
if line.list_marker() == Some(ctx) {
return true;
} else if line.is_comment() {
continue;
} else {
return false;
}
}
false
}
pub fn starts_with_seq(&self, kinds: &[TokenSpec]) -> bool {
self
.first()
.map(|line| line.starts_with_seq(kinds))
.unwrap_or(false)
}
pub fn starts_nested_list(&self, stack: &ListStack, allow_attrs: bool) -> bool {
let Some(line) = self.first() else {
return false;
};
if !allow_attrs || !line.is_block_attr_list() {
return line.starts_nested_list(stack);
}
self
.nth(1)
.map(|line| line.starts_nested_list(stack))
.unwrap_or(false)
}
pub fn starts_list_continuation(&self) -> bool {
if self.len() < 2 {
return false;
}
let Some(line) = self.first() else {
return false;
};
line.is_list_continuation()
}
pub fn starts(&self, kind: TokenKind) -> bool {
self.first().map(|line| line.starts(kind)).unwrap_or(false)
}
pub fn starts_with_comment_line(&self) -> bool {
self.first().map(Line::is_comment).unwrap_or(false)
}
pub fn discard_leading_comment_lines(&mut self) -> Option<SourceLocation> {
let mut loc = SourceLocation::default();
while self.starts_with_comment_line() {
let line = self.consume_current().unwrap();
loc.extend(line.last_loc().unwrap());
}
if loc.is_empty() { None } else { Some(loc) }
}
pub fn discard_leading_empty_lines(&mut self) {
while self.current().is_some_and(|l| l.is_empty()) {
self.consume_current();
}
}
pub fn discard_until(&mut self, predicate: impl Fn(&Line<'arena>) -> bool) -> bool {
while let Some(line) = self.first() {
if predicate(line) {
return true;
}
self.consume_current();
}
false
}
pub fn trim_uniform_leading_whitespace(&mut self) -> bool {
if self.is_empty() || !self.first().unwrap().starts(Whitespace) {
return false;
}
let len = self.first().unwrap().current_token().unwrap().lexeme.len();
if !self
.lines
.iter()
.all(|l| l.current_satisfies(Len(len as u8, Whitespace)) && l.num_tokens() > 1)
{
return false;
}
for line in self.lines.iter_mut() {
line.discard_assert(Whitespace);
}
true
}
pub fn get_indentation(&self) -> usize {
let mut indent = usize::MAX;
for line in self.iter() {
let line_indent = line.get_indentation();
if line_indent < indent {
indent = line_indent;
}
}
if indent == usize::MAX { 0 } else { indent }
}
pub fn set_indentation(&mut self, indent: usize) {
let current = self.get_indentation();
if current == indent {
return;
}
self.lines.iter_mut().for_each(|line| {
let line_indent = line.get_indentation();
if line_indent >= current {
line.set_indentation(line_indent - current + indent);
}
});
}
#[cfg(debug_assertions)]
pub fn debug_print(&self) {
eprintln!("```");
for line in self.iter() {
eprintln!("{}", line.reassemble_src());
}
eprintln!("```");
}
}
impl<'arena> DefaultIn<'arena> for Line<'arena> {
fn default_in(bump: &'arena Bump) -> Self {
Line::new(Deq::new(bump))
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_utils::*;
#[test]
fn test_is_quoted_paragraph() {
let cases = vec![
("\"foo bar\nso baz\"\n-- me", true),
("foo bar\nso baz\"\n-- me", false),
("\"foo bar\nso baz\n-- me", false),
("\"foo bar\nso baz\"\n-- ", false),
("\"foo bar\nso baz\"\nme -- too", false),
];
for (input, expected) in cases {
let mut parser = test_parser!(input);
let lines = parser.read_lines().unwrap().unwrap();
expect_eq!(lines.is_quoted_paragraph(false), expected, from: input);
}
}
}