use crate::mdx_parser::event::{Event, Kind, Name, Point};
use crate::mdx_parser::resolve::Name as ResolveName;
use crate::mdx_parser::state::{Name as StateName, State};
use crate::mdx_parser::subtokenize::Subresult;
use crate::mdx_parser::tokenizer::Tokenizer;
use crate::mdx_parser::util::char::{
Kind as CharacterKind, after_index as char_after_index, before_index as char_before_index, classify_opt,
};
use alloc::{vec, vec::Vec};
#[derive(Debug)]
struct Sequence {
marker: u8,
scope: Option<usize>,
depth: usize,
index: usize,
start_point: Point,
end_point: Point,
size: usize,
run_size: usize,
open: bool,
close: bool,
}
pub fn start(tokenizer: &mut Tokenizer) -> State {
if (tokenizer.parse_state.options.constructs.attention
&& matches!(tokenizer.current, Some(b'*' | b'_')))
|| (tokenizer.parse_state.options.constructs.gfm_strikethrough && tokenizer.current == Some(b'~'))
{
tokenizer.tokenize_state.marker = tokenizer.current.unwrap();
tokenizer.enter(Name::AttentionSequence);
State::Retry(StateName::AttentionInside)
} else {
State::Nok
}
}
pub fn inside(tokenizer: &mut Tokenizer) -> State {
if tokenizer.current == Some(tokenizer.tokenize_state.marker) {
tokenizer.consume();
State::Next(StateName::AttentionInside)
} else {
tokenizer.exit(Name::AttentionSequence);
tokenizer.register_resolver(ResolveName::Attention);
tokenizer.tokenize_state.marker = 0;
State::Ok
}
}
pub fn resolve(tokenizer: &mut Tokenizer) -> Option<Subresult> {
let mut sequences = get_sequences(tokenizer);
let mut openers: Vec<usize> = vec![];
let mut frames: Vec<Frame> = vec![];
let mut close = 0;
while close < sequences.len() {
enter_scope(&mut frames, &mut openers, &sequences[close]);
let frame = frames.last_mut().unwrap();
if sequences[close].close {
while let Some(kind) = category(tokenizer, &sequences[close]) {
let found = (frame.bottom[kind]..openers.len())
.rev()
.find(|position| can_match(&sequences[openers[*position]], &sequences[close]));
if let Some(position) = found {
let open = openers[position];
openers.truncate(position + 1);
match_sequences(tokenizer, &mut sequences, open, close);
if sequences[open].size == 0 {
openers.pop();
}
for bottom in &mut frame.bottom {
*bottom = (*bottom).min(openers.len());
}
if sequences[close].size == 0 {
break;
}
} else {
frame.bottom[kind] = openers.len();
break;
}
}
}
if sequences[close].open && sequences[close].size > 0 {
openers.push(close);
}
close += 1;
}
for sequence in &sequences {
if sequence.size > 0 {
tokenizer.events[sequence.index].name = Name::Data;
tokenizer.events[sequence.index + 1].name = Name::Data;
}
}
tokenizer.map.consume(&mut tokenizer.events);
None
}
fn get_sequences(tokenizer: &mut Tokenizer) -> Vec<Sequence> {
let mut index = 0;
let mut stack = vec![];
let mut sequences = vec![];
while index < tokenizer.events.len() {
let enter = &tokenizer.events[index];
if enter.name == Name::AttentionSequence {
if enter.kind == Kind::Enter {
let end = index + 1;
let exit = &tokenizer.events[end];
let marker = tokenizer.parse_state.bytes[enter.point.index];
let before_char = char_before_index(tokenizer.parse_state.bytes, enter.point.index);
let before = classify_opt(before_char);
let after_char = char_after_index(tokenizer.parse_state.bytes, exit.point.index);
let after = classify_opt(after_char);
let open = after == CharacterKind::Other
|| (after == CharacterKind::Punctuation && before != CharacterKind::Other)
|| (marker != b'~' && tokenizer.parse_state.options.constructs.gfm_strikethrough && matches!(after_char, Some('~')));
let close = before == CharacterKind::Other
|| (before == CharacterKind::Punctuation && after != CharacterKind::Other)
|| (marker != b'~'
&& tokenizer.parse_state.options.constructs.gfm_strikethrough
&& matches!(before_char, Some('~')));
sequences.push(Sequence {
index,
scope: stack.last().copied(),
depth: stack.len(),
start_point: enter.point.clone(),
end_point: exit.point.clone(),
size: exit.point.index - enter.point.index,
run_size: exit.point.index - enter.point.index,
open: if marker == b'_' {
open && (before != CharacterKind::Other || !close)
} else {
open
},
close: if marker == b'_' {
close && (after != CharacterKind::Other || !open)
} else {
close
},
marker,
});
}
} else if enter.kind == Kind::Enter {
stack.push(index);
} else {
stack.pop();
}
index += 1;
}
sequences
}
const CATEGORIES: usize = 2 * 2 * 3 + 2;
struct Frame {
scope: Option<usize>,
depth: usize,
start: usize,
bottom: [usize; CATEGORIES],
}
fn enter_scope(frames: &mut Vec<Frame>, openers: &mut Vec<usize>, sequence: &Sequence) {
while let Some(frame) = frames.last() {
if frame.depth > sequence.depth || (frame.depth == sequence.depth && frame.scope != sequence.scope) {
openers.truncate(frame.start);
frames.pop();
} else {
break;
}
}
if !matches!(frames.last(), Some(frame) if frame.depth == sequence.depth) {
frames.push(Frame {
scope: sequence.scope,
depth: sequence.depth,
start: openers.len(),
bottom: [openers.len(); CATEGORIES],
});
}
}
fn category(tokenizer: &Tokenizer, close: &Sequence) -> Option<usize> {
if close.marker == b'~' {
match close.size {
2 => Some(CATEGORIES - 1),
1 if tokenizer.parse_state.options.gfm_strikethrough_single_tilde => Some(CATEGORIES - 2),
_ => None,
}
} else {
Some(usize::from(close.marker == b'_') * 6 + usize::from(close.open) * 3 + close.run_size % 3)
}
}
fn can_match(open: &Sequence, close: &Sequence) -> bool {
if open.marker != close.marker {
return false;
}
if (open.close || close.open) && close.run_size % 3 != 0 && (open.run_size + close.run_size) % 3 == 0 {
return false;
}
close.marker != b'~' || close.size == open.size
}
#[allow(clippy::too_many_lines)]
fn match_sequences(tokenizer: &mut Tokenizer, sequences: &mut [Sequence], open: usize, close: usize) {
let take = if sequences[open].size > 1 && sequences[close].size > 1 {
2
} else {
1
};
let (group_name, seq_name, text_name) = if sequences[open].marker == b'~' {
(
Name::GfmStrikethrough,
Name::GfmStrikethroughSequence,
Name::GfmStrikethroughText,
)
} else if take == 1 {
(Name::Emphasis, Name::EmphasisSequence, Name::EmphasisText)
} else {
(Name::Strong, Name::StrongSequence, Name::StrongText)
};
let open_index = sequences[open].index;
let close_index = sequences[close].index;
let open_exit = sequences[open].end_point.clone();
let close_enter = sequences[close].start_point.clone();
sequences[open].size -= take;
sequences[close].size -= take;
sequences[open].end_point.column -= take;
sequences[open].end_point.index -= take;
sequences[close].start_point.column += take;
sequences[close].start_point.index += take;
tokenizer.map.add_before(
open_index + 2,
0,
vec![
Event {
kind: Kind::Enter,
name: group_name.clone(),
point: sequences[open].end_point.clone(),
link: None,
},
Event {
kind: Kind::Enter,
name: seq_name.clone(),
point: sequences[open].end_point.clone(),
link: None,
},
Event {
kind: Kind::Exit,
name: seq_name.clone(),
point: open_exit.clone(),
link: None,
},
Event {
kind: Kind::Enter,
name: text_name.clone(),
point: open_exit,
link: None,
},
],
);
tokenizer.map.add(
close_index,
0,
vec![
Event {
kind: Kind::Exit,
name: text_name,
point: close_enter.clone(),
link: None,
},
Event {
kind: Kind::Enter,
name: seq_name.clone(),
point: close_enter,
link: None,
},
Event {
kind: Kind::Exit,
name: seq_name,
point: sequences[close].start_point.clone(),
link: None,
},
Event {
kind: Kind::Exit,
name: group_name,
point: sequences[close].start_point.clone(),
link: None,
},
],
);
if sequences[close].size == 0 {
tokenizer.map.add(close_index, 2, vec![]);
} else {
tokenizer.events[close_index].point = sequences[close].start_point.clone();
}
if sequences[open].size == 0 {
tokenizer.map.add(open_index, 2, vec![]);
} else {
tokenizer.events[open_index + 1].point = sequences[open].end_point.clone();
}
}