use std::ops::Range;
use crate::lexer::{Lexer, Token, TokenKind};
use crate::op::{Content, Node, Op};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ListKind {
Unordered,
Ordered,
}
impl ListKind {
pub fn node(&self) -> Node {
match self {
ListKind::Unordered => Node::UnorderedList,
ListKind::Ordered => Node::OrderedList,
}
}
}
impl TryFrom<&Token> for ListKind {
type Error = ();
fn try_from(value: &Token) -> Result<Self, Self::Error> {
if value.kind == TokenKind::Minus && value.range.len() == 1 {
Ok(ListKind::Unordered)
} else if value.kind == TokenKind::Plus && value.range.len() == 1 {
Ok(ListKind::Ordered)
} else {
Err(())
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum StopCondition {
Terminator,
CollapsibleEnd,
HighlightEnd,
ListBoundary { level: usize, kind: ListKind },
}
fn is_list_marker(t: &Token, kind: Option<ListKind>) -> bool {
match kind {
Some(ListKind::Unordered) => t.kind == TokenKind::Minus && t.range.len() == 1,
Some(ListKind::Ordered) => t.kind == TokenKind::Plus && t.range.len() == 1,
None => (t.kind == TokenKind::Minus || t.kind == TokenKind::Plus) && t.range.len() == 1,
}
}
fn is_space_1(t: &Token) -> bool {
t.kind == TokenKind::Space && t.range.len() == 1
}
fn at_list_boundary(p: &Parser, current_level: usize, max_level: usize, kind: ListKind) -> bool {
for level in 0..=max_level {
let k = if level == current_level {
Some(kind)
} else {
None
};
let mut offset = p.pos;
let matched = if level == 0 {
p.tokens.get(offset).is_some_and(|t| t.position.column == 0) && {
p.tokens.get(offset).is_some_and(|t| is_list_marker(t, k)) && {
offset += 1;
p.tokens.get(offset).is_some_and(is_space_1)
}
}
} else {
p.tokens.get(offset).is_some_and(|t| t.position.column == 0) && {
p.tokens
.get(offset)
.is_some_and(|t| t.kind == TokenKind::Space && t.range.len() == level)
&& {
offset += 1;
p.tokens.get(offset).is_some_and(|t| is_list_marker(t, k)) && {
offset += 1;
p.tokens.get(offset).is_some_and(is_space_1)
}
}
}
};
if matched {
return true;
}
}
false
}
impl StopCondition {
fn matches(&self, token: &Token, parser: &Parser) -> bool {
match self {
Self::Terminator => token.kind == TokenKind::Terminator,
Self::CollapsibleEnd => {
token.kind == TokenKind::CollapsibleEnd && token.position.column == 0
}
Self::HighlightEnd => {
token.kind == TokenKind::Bang
&& token.position.column == 0
&& token.range.len() == 2
}
Self::ListBoundary { level, kind } => {
token.position.column == 0 && at_list_boundary(parser, *level, *level + 1, *kind)
}
}
}
}
pub struct Parser<'a> {
pub source: &'a str,
tokens: Vec<Token>,
pub pos: usize,
eof_stack: Vec<StopCondition>,
pub ops: Vec<Op>,
}
impl<'a> From<&'a str> for Parser<'a> {
fn from(input: &'a str) -> Self {
Self {
source: input,
tokens: Lexer::new(input).collect(),
pos: 0,
eof_stack: Vec::new(),
ops: Vec::new(),
}
}
}
impl Parser<'_> {
#[inline]
pub fn is_eof(&self) -> bool {
self.pos >= self.tokens.len()
}
#[inline]
pub fn len(&self) -> usize {
self.tokens.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.tokens.is_empty()
}
#[inline]
pub fn get(&self, index: usize) -> Option<&Token> {
self.tokens.get(index)
}
#[inline]
pub fn peek(&self) -> Option<(usize, &Token)> {
Some((self.pos, self.tokens.get(self.pos)?))
}
#[inline]
pub fn advance(&mut self) -> Option<usize> {
if self.pos >= self.tokens.len() {
return None;
}
let pos = self.pos;
self.pos += 1;
Some(pos)
}
#[inline]
pub fn slice(&self, range: Range<usize>) -> &[Token] {
&self.tokens[range]
}
#[inline]
pub fn next(&mut self) {
if self.pos < self.tokens.len() {
self.pos += 1;
}
}
#[inline]
pub fn eat(&mut self, pred: impl Fn(&Token) -> bool) -> Option<Range<usize>> {
let token = self.tokens.get(self.pos)?;
if pred(token) {
let start = self.pos;
self.pos += 1;
Some(start..self.pos)
} else {
None
}
}
#[inline]
pub fn at(&self, pred: impl Fn(&Token) -> bool) -> bool {
self.peek().is_some_and(|(_, t)| pred(t))
}
pub fn with_eof<R>(&mut self, cond: StopCondition, f: impl FnOnce(&mut Self) -> R) -> R {
self.eof_stack.push(cond);
let result = f(self);
self.eof_stack.pop();
result
}
pub fn with_eofs<R>(&mut self, conds: &[StopCondition], f: impl FnOnce(&mut Self) -> R) -> R {
for &c in conds {
self.eof_stack.push(c);
}
let result = f(self);
for _ in conds {
self.eof_stack.pop();
}
result
}
#[inline]
pub fn at_eof(&self) -> bool {
let Some((_, token)) = self.peek() else {
return true;
};
self.eof_stack.iter().any(|cond| cond.matches(token, self))
}
pub fn flip_to_literal(&mut self, pos: usize) {
if let Some(token) = self.tokens.get_mut(pos) {
token.kind = TokenKind::Literal;
}
}
#[inline]
pub fn at_block_boundary(&self) -> bool {
self.at_eof() || self.at(|t| t.kind == TokenKind::Terminator)
}
pub fn advance_until(
&mut self,
matcher: impl Fn(&Token) -> bool,
) -> Option<(Range<usize>, Range<usize>)> {
let start = self.pos;
while let Some(token) = self.tokens.get(self.pos) {
if self.at_eof() {
break;
}
if matcher(token) {
let before = start..self.pos;
let match_start = self.pos;
self.pos += 1;
return Some((before, match_start..self.pos));
}
self.pos += 1;
}
self.pos = start;
None
}
#[inline]
pub fn span(&self, range: Range<usize>) -> Content {
if range.is_empty() {
return Content::Span(0..0);
}
let tokens = &self.tokens[range];
if tokens.iter().any(|t| t.escaped) {
Content::from_tokens(tokens, self.source)
} else {
let byte_start = tokens.first().unwrap().range.start;
let byte_end = tokens.last().unwrap().range.end;
Content::Span(byte_start..byte_end)
}
}
pub fn into_ops(self) -> Vec<Op> {
self.ops
}
}
pub fn eol(t: &Token) -> bool {
t.kind == TokenKind::Eol
}
#[macro_export]
macro_rules! eat_seq {
($p:expr, $($pred:expr),+ $(,)?) => {{
let start = $p.pos;
if $( $p.eat($pred).is_some() )&&+ {
Some(start..$p.pos)
} else {
$p.pos = start;
None
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexer::TokenKind;
#[test]
fn eat_matches_and_consumes() {
let mut p = Parser::from("hello world");
let result = p.eat(|t| t.kind == TokenKind::Literal);
assert!(result.is_some());
assert_eq!(result.unwrap(), 0..1);
assert_eq!(p.pos, 1);
}
#[test]
fn eat_no_match_does_not_advance() {
let mut p = Parser::from("hello world");
let result = p.eat(|t| t.kind == TokenKind::Star);
assert!(result.is_none());
assert_eq!(p.pos, 0);
}
#[test]
fn eat_at_eof_returns_none() {
let mut p = Parser::from("");
let result = p.eat(|t| t.kind == TokenKind::Literal);
assert!(result.is_none());
}
#[test]
fn at_matches_without_consuming() {
let p = Parser::from("hello");
assert!(p.at(|t| t.kind == TokenKind::Literal));
assert_eq!(p.pos, 0);
}
#[test]
fn at_no_match() {
let p = Parser::from("hello");
assert!(!p.at(|t| t.kind == TokenKind::Star));
assert_eq!(p.pos, 0);
}
#[test]
fn at_eof_returns_false() {
let p = Parser::from("");
assert!(!p.at(|t| t.kind == TokenKind::Literal));
}
#[test]
fn eat_seq_matches_sequence() {
let mut p = Parser::from("# hello");
let result = eat_seq!(p, |t: &Token| t.kind == TokenKind::Hash, |t: &Token| t.kind
== TokenKind::Space);
assert!(result.is_some());
assert_eq!(result.unwrap(), 0..2);
assert_eq!(p.pos, 2);
}
#[test]
fn eat_seq_backtracks_on_partial_match() {
let mut p = Parser::from("# hello");
let result = eat_seq!(p, |t: &Token| t.kind == TokenKind::Hash, |t: &Token| t.kind
== TokenKind::Star);
assert!(result.is_none());
assert_eq!(p.pos, 0);
}
#[test]
fn eat_seq_single_predicate() {
let mut p = Parser::from("hello");
let result = eat_seq!(p, |t: &Token| t.kind == TokenKind::Literal);
assert!(result.is_some());
assert_eq!(p.pos, 1);
}
#[test]
fn advance_until_finds_match() {
let mut p = Parser::from("hello\nworld");
let result = p.advance_until(|t: &Token| t.kind == TokenKind::Eol);
assert!(result.is_some());
let (before, matched) = result.unwrap();
assert_eq!(before, 0..1);
assert_eq!(matched, 1..2);
}
#[test]
fn advance_until_eof_hit() {
let mut p = Parser::from("hello\n\nworld");
p.with_eof(StopCondition::Terminator, |p| {
let result = p.advance_until(|t: &Token| t.kind == TokenKind::Star);
assert!(result.is_none());
assert_eq!(p.pos, 0);
});
}
#[test]
fn advance_until_no_match_at_eof() {
let mut p = Parser::from("hello");
let result = p.advance_until(|t: &Token| t.kind == TokenKind::Star);
assert!(result.is_none());
assert_eq!(p.pos, 0);
}
#[test]
fn at_eof_empty_stack_not_at_end() {
let p = Parser::from("hello");
assert!(!p.at_eof());
}
#[test]
fn at_eof_empty_stack_at_end() {
let p = Parser::from("");
assert!(p.at_eof());
}
#[test]
fn at_eof_terminator_on_stack() {
let mut p = Parser::from("hello\n\nworld");
p.with_eof(StopCondition::Terminator, |p| {
assert!(!p.at_eof());
p.next();
assert!(p.at_eof());
});
}
#[test]
fn eof_guard_pops_on_drop() {
let mut p = Parser::from("hello\n\nworld");
p.with_eof(StopCondition::Terminator, |p| {
p.next();
assert!(p.at_eof());
});
assert!(!p.at_eof());
}
#[test]
fn parser_len_is_empty_is_eof() {
let p = Parser::from("");
assert!(p.is_empty());
assert_eq!(p.len(), 0);
assert!(p.is_eof());
let p = Parser::from("hello");
assert!(!p.is_empty());
assert!(p.len() > 0);
assert!(!p.is_eof());
}
#[test]
fn advance_returns_position_and_none_at_eof() {
let mut p = Parser::from("a");
assert_eq!(p.advance(), Some(0));
assert_eq!(p.advance(), None);
assert!(p.is_eof());
}
#[test]
fn slice_returns_tokens() {
let p = Parser::from("hello world");
let slice = p.slice(0..1);
assert_eq!(slice.len(), 1);
assert_eq!(slice[0].kind, TokenKind::Literal);
}
#[test]
fn list_kind_node() {
assert_eq!(ListKind::Unordered.node(), Node::UnorderedList);
assert_eq!(ListKind::Ordered.node(), Node::OrderedList);
}
#[test]
fn nested_guards_pop_in_order() {
let mut p = Parser::from("hello\n\nworld");
p.with_eof(StopCondition::Terminator, |p| {
p.next();
assert!(p.at_eof());
p.with_eof(StopCondition::HighlightEnd, |p| {
assert!(p.at_eof());
});
assert!(p.at_eof());
});
}
}