use crate::construct::partial_space_or_tab::{space_or_tab, space_or_tab_min_max};
use crate::event::Name;
use crate::state::{Name as StateName, State};
use crate::tokenizer::Tokenizer;
use crate::util::constant::FRONTMATTER_SEQUENCE_SIZE;
pub fn start(tokenizer: &mut Tokenizer) -> State {
if tokenizer.parse_state.options.constructs.frontmatter
&& matches!(tokenizer.current, Some(b'+' | b'-' | b'`' | b'~'))
{
tokenizer.tokenize_state.marker = tokenizer.current.unwrap();
tokenizer.enter(Name::Frontmatter);
tokenizer.enter(Name::FrontmatterFence);
tokenizer.enter(Name::FrontmatterSequence);
State::Retry(StateName::FrontmatterOpenSequence)
} else {
State::Nok
}
}
pub fn open_sequence(tokenizer: &mut Tokenizer) -> State {
if tokenizer.current == Some(tokenizer.tokenize_state.marker) {
tokenizer.tokenize_state.size += 1;
tokenizer.consume();
State::Next(StateName::FrontmatterOpenSequence)
} else if tokenizer.tokenize_state.size == FRONTMATTER_SEQUENCE_SIZE
|| (matches!(tokenizer.tokenize_state.marker, b'`' | b'~')
&& tokenizer.tokenize_state.size > FRONTMATTER_SEQUENCE_SIZE)
{
tokenizer.tokenize_state.size_b = tokenizer.tokenize_state.size;
tokenizer.tokenize_state.size = 0;
tokenizer.exit(Name::FrontmatterSequence);
let next = StateName::FrontmatterInfoBefore;
if matches!(tokenizer.current, Some(b'\t' | b' ')) {
tokenizer.attempt(State::Next(next), State::Nok);
State::Retry(space_or_tab(tokenizer))
} else {
State::Retry(next)
}
} else {
tokenizer.tokenize_state.marker = 0;
tokenizer.tokenize_state.size = 0;
State::Nok
}
}
pub fn info_before(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None | Some(b'\n') => {
if matches!(tokenizer.tokenize_state.marker, b'`' | b'~') {
tokenizer.tokenize_state.marker = 0;
tokenizer.tokenize_state.size_b = 0;
State::Nok
} else {
State::Retry(StateName::FrontmatterOpenAfter)
}
}
_ => {
tokenizer.enter(Name::FrontmatterFenceInfo);
State::Retry(StateName::FrontmatterInfo)
}
}
}
pub fn info(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None | Some(b'\n') => {
tokenizer.exit(Name::FrontmatterFenceInfo);
State::Retry(StateName::FrontmatterOpenAfter)
}
Some(b'`') if tokenizer.tokenize_state.marker == b'`' => {
tokenizer.tokenize_state.marker = 0;
tokenizer.tokenize_state.size_b = 0;
State::Nok
}
Some(_) => {
tokenizer.consume();
State::Next(StateName::FrontmatterInfo)
}
}
}
pub fn open_after(tokenizer: &mut Tokenizer) -> State {
if let Some(b'\n') = tokenizer.current {
tokenizer.exit(Name::FrontmatterFence);
tokenizer.enter(Name::LineEnding);
tokenizer.consume();
tokenizer.exit(Name::LineEnding);
tokenizer.attempt(
State::Next(StateName::FrontmatterAfter),
State::Next(StateName::FrontmatterContentStart),
);
State::Next(StateName::FrontmatterCloseStart)
} else {
tokenizer.tokenize_state.marker = 0;
tokenizer.tokenize_state.size_b = 0;
State::Nok
}
}
pub fn close_start(tokenizer: &mut Tokenizer) -> State {
if matches!(tokenizer.tokenize_state.marker, b'`' | b'~')
&& matches!(tokenizer.current, Some(b'\t' | b' '))
{
tokenizer.attempt(
State::Next(StateName::FrontmatterCloseBeforeSequence),
State::Nok,
);
State::Retry(space_or_tab_min_max(tokenizer, 0, 3))
} else {
State::Retry(StateName::FrontmatterCloseBeforeSequence)
}
}
pub fn close_before_sequence(tokenizer: &mut Tokenizer) -> State {
if tokenizer.current == Some(tokenizer.tokenize_state.marker) {
tokenizer.enter(Name::FrontmatterFence);
tokenizer.enter(Name::FrontmatterSequence);
State::Retry(StateName::FrontmatterCloseSequence)
} else {
State::Nok
}
}
pub fn close_sequence(tokenizer: &mut Tokenizer) -> State {
if tokenizer.current == Some(tokenizer.tokenize_state.marker) {
tokenizer.tokenize_state.size += 1;
tokenizer.consume();
State::Next(StateName::FrontmatterCloseSequence)
} else if tokenizer.tokenize_state.size == tokenizer.tokenize_state.size_b
|| (matches!(tokenizer.tokenize_state.marker, b'`' | b'~')
&& tokenizer.tokenize_state.size > tokenizer.tokenize_state.size_b)
{
tokenizer.tokenize_state.size = 0;
tokenizer.exit(Name::FrontmatterSequence);
if matches!(tokenizer.current, Some(b'\t' | b' ')) {
tokenizer.attempt(State::Next(StateName::FrontmatterCloseAfter), State::Nok);
State::Retry(space_or_tab(tokenizer))
} else {
State::Retry(StateName::FrontmatterCloseAfter)
}
} else {
tokenizer.tokenize_state.size = 0;
State::Nok
}
}
pub fn close_after(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None | Some(b'\n') => {
tokenizer.exit(Name::FrontmatterFence);
State::Ok
}
_ => State::Nok,
}
}
pub fn content_start(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None | Some(b'\n') => State::Retry(StateName::FrontmatterContentEnd),
Some(_) => {
tokenizer.enter(Name::FrontmatterChunk);
State::Retry(StateName::FrontmatterContentInside)
}
}
}
pub fn content_inside(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None | Some(b'\n') => {
tokenizer.exit(Name::FrontmatterChunk);
State::Retry(StateName::FrontmatterContentEnd)
}
Some(_) => {
tokenizer.consume();
State::Next(StateName::FrontmatterContentInside)
}
}
}
pub fn content_end(tokenizer: &mut Tokenizer) -> State {
match tokenizer.current {
None => {
tokenizer.tokenize_state.marker = 0;
tokenizer.tokenize_state.size_b = 0;
State::Nok
}
Some(b'\n') => {
tokenizer.enter(Name::LineEnding);
tokenizer.consume();
tokenizer.exit(Name::LineEnding);
tokenizer.attempt(
State::Next(StateName::FrontmatterAfter),
State::Next(StateName::FrontmatterContentStart),
);
State::Next(StateName::FrontmatterCloseStart)
}
Some(_) => unreachable!("expected eof/eol"),
}
}
pub fn after(tokenizer: &mut Tokenizer) -> State {
debug_assert!(
matches!(tokenizer.current, None | Some(b'\n')),
"expected eol/eof after closing fence"
);
tokenizer.exit(Name::Frontmatter);
tokenizer.tokenize_state.marker = 0;
tokenizer.tokenize_state.size_b = 0;
State::Ok
}