use crate::models::parse_error::{ErrorCode, ParseError};
#[derive(Debug)]
pub(crate) struct Preprocessor<'s> {
pub(crate) input: &'s str,
pub(crate) pos: usize,
next: usize,
skip_next_line_feed: bool,
last_error: Option<usize>,
pub(crate) last_chunk: bool,
pub(crate) chunk_end: bool,
}
impl<'s> Preprocessor<'s> {
pub(crate) fn new(input: &'s str) -> Self {
Self {
input,
pos: 0,
next: 0,
skip_next_line_feed: false,
last_error: None,
last_chunk: true,
chunk_end: false,
}
}
pub(crate) fn advance(&mut self, errors: &mut Vec<ParseError>) -> Option<char> {
loop {
self.pos = self.next;
let Some(char) = self
.input
.get(self.pos..)
.and_then(|rest| rest.chars().next())
else {
self.next = self.pos + 1;
self.chunk_end = !self.last_chunk;
return None;
};
self.next = self.pos + char.len_utf8();
match char {
'\r' => {
self.skip_next_line_feed = true;
return Some('\n');
}
'\n' if self.skip_next_line_feed => {
self.skip_next_line_feed = false;
}
_ => {
self.skip_next_line_feed = false;
self.check_character(char, errors);
return Some(char);
}
}
}
}
pub(crate) fn set_pos(&mut self, pos: usize) {
self.pos = pos;
self.next = pos + 1;
}
pub(crate) fn match_sequence(
&mut self,
pattern: &str,
case_sensitive: bool,
) -> Option<usize> {
let mut expected = pattern.encode_utf16();
let mut end = self.pos;
let mut units = [0; 2];
let input = self.input;
let rest = input.get(self.pos..)?;
for char in rest.chars() {
for &unit in char.encode_utf16(&mut units).iter() {
let Some(want) = expected.next() else {
return Some(end);
};
let unit = if case_sensitive { unit } else { unit | 0x20 };
if unit != want {
return None;
}
}
end += char.len_utf8();
}
if expected.next().is_some() {
self.chunk_end = !self.last_chunk;
return None;
}
Some(end)
}
pub(crate) fn peek_at(&mut self, pos: usize) -> Option<char> {
let Some(char) = self.input.get(pos..).and_then(|rest| rest.chars().next())
else {
self.chunk_end = !self.last_chunk;
return None;
};
Some(if char == '\r' { '\n' } else { char })
}
fn check_character(&mut self, char: char, errors: &mut Vec<ParseError>) {
let code = u32::from(char);
let common = (0x20..0x7f).contains(&code)
|| char == '\n'
|| (0xa0..0xfdd0).contains(&code);
if common {
return;
}
let error = if is_control(code) {
ErrorCode::ControlCharacterInInputStream
} else if is_noncharacter(code) {
ErrorCode::NoncharacterInInputStream
} else {
return;
};
if self.last_error != Some(self.pos) {
self.last_error = Some(self.pos);
errors.push(ParseError {
code: error,
start: self.pos,
end: self.pos,
});
}
}
}
pub(crate) fn is_control(code: u32) -> bool {
((0x01..=0x1f).contains(&code) && !matches!(code, 0x09 | 0x0a | 0x0c | 0x0d))
|| (0x7f..=0x9f).contains(&code)
}
pub(crate) fn is_noncharacter(code: u32) -> bool {
(0xfdd0..=0xfdef).contains(&code) || (code & 0xfffe == 0xfffe && code <= 0x10ffff)
}