use crate::swar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Step {
pub(crate) byte: u8,
pub(crate) next: u32,
pub(crate) clean: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct Cursor<'a> {
src: &'a [u8],
pos: u32,
trigraphs: bool,
loose_splices: Vec<u32>,
}
impl<'a> Cursor<'a> {
pub(crate) fn new(src: &'a [u8], trigraphs: bool) -> Cursor<'a> {
let pos = if src.starts_with(&[0xEF, 0xBB, 0xBF]) { 3 } else { 0 };
Cursor { src, pos, trigraphs, loose_splices: Vec::new() }
}
#[inline]
pub(crate) fn pos(&self) -> u32 {
self.pos
}
#[inline]
pub(crate) fn bytes(&self) -> &'a [u8] {
self.src
}
#[inline]
pub(crate) fn at_end(&self) -> bool {
self.pos as usize >= self.src.len()
}
pub(crate) fn step_at(&self, at: u32) -> Option<Step> {
let mut p = at as usize;
let mut clean = true;
loop {
let b = *self.src.get(p)?;
match b {
_ if b != b'\\' && b != b'?' && b != b'\r' => {
return Some(Step { byte: b, next: p as u32 + 1, clean });
}
b'\r' => {
let next = if self.src.get(p + 1) == Some(&b'\n') { p + 2 } else { p + 1 };
return Some(Step { byte: b'\n', next: next as u32, clean: false });
}
b'?' if self.trigraphs => {
let Some(mapped) = self.trigraph_at(p) else {
return Some(Step { byte: b'?', next: p as u32 + 1, clean });
};
if mapped == b'\\' {
if let Some(after) = self.splice_from(p + 3) {
p = after;
clean = false;
continue;
}
}
return Some(Step { byte: mapped, next: p as u32 + 3, clean: false });
}
b'?' => return Some(Step { byte: b'?', next: p as u32 + 1, clean }),
_ => {
match self.splice_at(p) {
Some(after) => {
p = after;
clean = false;
}
None => return Some(Step { byte: b'\\', next: p as u32 + 1, clean }),
}
}
}
}
}
fn splice_at(&self, p: usize) -> Option<usize> {
self.splice_from(p + 1)
}
fn splice_from(&self, from: usize) -> Option<usize> {
let mut q = from;
while matches!(self.src.get(q), Some(b' ' | b'\t' | 0x0B | 0x0C)) {
q += 1;
}
match self.src.get(q) {
Some(b'\n') => Some(q + 1),
Some(b'\r') => {
if self.src.get(q + 1) == Some(&b'\n') {
Some(q + 2)
} else {
Some(q + 1)
}
}
_ => None,
}
}
fn splice_has_trailing_space(&self, p: usize) -> bool {
matches!(self.src.get(p + 1), Some(b' ' | b'\t' | 0x0B | 0x0C))
&& self.splice_at(p).is_some()
}
pub(crate) fn take_loose_splices(&mut self) -> Vec<u32> {
std::mem::take(&mut self.loose_splices)
}
fn trigraph_at(&self, p: usize) -> Option<u8> {
if self.src.get(p + 1) != Some(&b'?') {
return None;
}
match self.src.get(p + 2)? {
b'=' => Some(b'#'),
b'(' => Some(b'['),
b'/' => Some(b'\\'),
b')' => Some(b']'),
b'\'' => Some(b'^'),
b'<' => Some(b'{'),
b'!' => Some(b'|'),
b'>' => Some(b'}'),
b'-' => Some(b'~'),
_ => None,
}
}
#[inline]
pub(crate) fn first(&self) -> u8 {
self.step_at(self.pos).map_or(0, |s| s.byte)
}
pub(crate) fn nth(&self, n: usize) -> u8 {
let mut at = self.pos;
for _ in 0..n {
match self.step_at(at) {
Some(s) => at = s.next,
None => return 0,
}
}
self.step_at(at).map_or(0, |s| s.byte)
}
#[inline]
pub(crate) fn bump(&mut self) -> Option<(u8, bool)> {
let from = self.pos;
let s = self.step_at(self.pos)?;
self.pos = s.next;
if !s.clean {
self.note_loose_splices(from, s.next);
}
Some((s.byte, s.clean))
}
#[inline]
pub(crate) fn skip_blanks(&mut self) -> bool {
let from = self.pos as usize;
let to = swar::run_of_blanks(self.src, from);
self.pos = to as u32;
to > from
}
pub(crate) fn skip_plain(&mut self, stops: &[u8]) {
let mut set = [b'\n', b'\r', b'\\', b'?', 0, 0, 0, 0];
let mut n = if self.trigraphs { 4 } else { 3 };
debug_assert!(n + stops.len() <= set.len());
for &s in stops {
set[n] = s;
n += 1;
}
self.pos = swar::first_of(self.src, self.pos as usize, &set[..n]) as u32;
}
#[cold]
fn note_loose_splices(&mut self, from: u32, to: u32) {
for p in from as usize..(to as usize).min(self.src.len()) {
if self.src[p] == b'\\' && self.splice_has_trailing_space(p) {
self.loose_splices.push(p as u32);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn logical(src: &str, trigraphs: bool) -> String {
let mut c = Cursor::new(src.as_bytes(), trigraphs);
let mut out = String::new();
while let Some((b, _)) = c.bump() {
out.push(b as char);
}
out
}
#[test]
fn a_byte_order_mark_is_not_part_of_the_program() {
assert_eq!(logical("\u{feff}int x;", false), "int x;");
}
#[test]
fn crlf_reads_the_same_as_lf() {
assert_eq!(logical("a\r\nb\rc\nd", false), "a\nb\nc\nd");
}
#[test]
fn a_backslash_at_the_end_of_a_line_joins_it_to_the_next() {
assert_eq!(logical("in\\\nt x;", false), "int x;");
assert_eq!(logical("in\\\r\nt x;", false), "int x;");
}
#[test]
fn a_backslash_with_trailing_space_still_splices() {
let src = "in\\ \nt";
assert_eq!(logical(src, false), "int");
let mut c = Cursor::new(src.as_bytes(), false);
while c.bump().is_some() {}
assert_eq!(c.take_loose_splices(), vec![2]);
}
#[test]
fn an_ordinary_splice_is_not_reported() {
let mut c = Cursor::new(b"in\\\nt", false);
while c.bump().is_some() {}
assert!(c.take_loose_splices().is_empty());
}
#[test]
fn a_lone_backslash_is_just_a_backslash() {
assert_eq!(logical("\\u00e9", false), "\\u00e9");
}
#[test]
fn trigraphs_are_off_unless_asked_for() {
assert_eq!(logical("??=define", false), "??=define");
assert_eq!(logical("??=define", true), "#define");
}
#[test]
fn a_trigraph_backslash_can_still_splice() {
assert_eq!(logical("in??/\nt", true), "int");
}
#[test]
fn spans_still_point_at_real_bytes_across_a_splice() {
let mut c = Cursor::new(b"a\\\nb", false);
assert_eq!(c.bump(), Some((b'a', true)));
assert_eq!(c.pos(), 1);
assert_eq!(c.bump(), Some((b'b', false)));
assert_eq!(c.pos(), 4);
}
#[test]
fn lookahead_crosses_splices_too() {
let c = Cursor::new(b"+\\\n+", false);
assert_eq!(c.first(), b'+');
assert_eq!(c.nth(1), b'+');
assert_eq!(c.nth(2), 0);
}
}