use crate::ported::lint::markedjson::nodes::Mark;
use regex::Regex;
use std::sync::OnceLock;
#[allow(non_snake_case)]
fn NON_PRINTABLE_RE() -> &'static Regex {
static R: OnceLock<Regex> = OnceLock::new();
R.get_or_init(|| {
Regex::new(r"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]").unwrap()
})
}
#[derive(Debug, Clone)]
pub struct ReaderError {
pub context: String,
pub problem: String,
pub mark: Option<Mark>,
}
impl std::fmt::Display for ReaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ReaderError: {} ({})", self.problem, self.context)
}
}
impl std::error::Error for ReaderError {}
pub struct Reader {
pub name: String,
pub buffer: Vec<char>,
pub pointer: usize,
pub index: usize,
pub line: usize,
pub column: usize,
}
impl Reader {
pub fn new(stream: impl Into<String>, name: impl Into<String>) -> Self {
let s: String = stream.into();
let mut buffer: Vec<char> = s.chars().collect();
buffer.push('\0');
Self {
name: name.into(),
buffer,
pointer: 0,
index: 0,
line: 0,
column: 0,
}
}
pub fn peek(&self, index: usize) -> char {
self.buffer
.get(self.pointer + index)
.copied()
.unwrap_or('\0')
}
pub fn prefix(&self, length: usize) -> String {
let end = std::cmp::min(self.pointer + length, self.buffer.len());
self.buffer[self.pointer..end].iter().collect()
}
pub fn update_pointer(&mut self, length: usize) {
let mut left = length;
while left > 0 && self.pointer < self.buffer.len() {
let ch = self.buffer[self.pointer];
self.pointer += 1;
self.index += 1;
if ch == '\n' {
self.line += 1;
self.column = 0;
} else {
self.column += 1;
}
left -= 1;
}
}
pub fn forward(&mut self, length: usize) {
self.update_pointer(length);
}
pub fn get_mark(&self) -> Mark {
Mark {
line: self.line,
column: self.column,
}
}
pub fn check_printable(&mut self, data: &str) -> Result<(), ReaderError> {
if let Some(m) = NON_PRINTABLE_RE().find(data) {
self.update_pointer(m.start());
return Err(ReaderError {
context: "while reading from stream".to_string(),
problem: "found special characters which are not allowed".to_string(),
mark: Some(self.get_mark()),
});
}
Ok(())
}
pub fn update(&mut self, _length: usize) {
}
pub fn update_raw(&mut self) {
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_reader_buffer_has_null_terminator() {
let r = Reader::new("abc", "test");
assert_eq!(r.buffer.len(), 4);
assert_eq!(r.buffer[3], '\0');
}
#[test]
fn peek_returns_char_at_offset() {
let r = Reader::new("abc", "t");
assert_eq!(r.peek(0), 'a');
assert_eq!(r.peek(1), 'b');
assert_eq!(r.peek(2), 'c');
assert_eq!(r.peek(3), '\0');
}
#[test]
fn peek_past_eof_returns_null() {
let r = Reader::new("a", "t");
assert_eq!(r.peek(99), '\0');
}
#[test]
fn prefix_returns_substring() {
let r = Reader::new("hello world", "t");
assert_eq!(r.prefix(5), "hello");
assert_eq!(r.prefix(0), "");
}
#[test]
fn forward_advances_pointer_and_column() {
let mut r = Reader::new("abc", "t");
r.forward(2);
assert_eq!(r.pointer, 2);
assert_eq!(r.column, 2);
assert_eq!(r.line, 0);
assert_eq!(r.index, 2);
}
#[test]
fn forward_across_newline_resets_column_increments_line() {
let mut r = Reader::new("a\nb", "t");
r.forward(2);
assert_eq!(r.line, 1);
assert_eq!(r.column, 0);
}
#[test]
fn get_mark_returns_current_position() {
let mut r = Reader::new("a\nbc", "t");
r.forward(3);
let m = r.get_mark();
assert_eq!(m.line, 1);
assert_eq!(m.column, 1);
}
#[test]
fn check_printable_accepts_normal_text() {
let mut r = Reader::new("hello world", "t");
assert!(r.check_printable("hello world").is_ok());
}
#[test]
fn check_printable_rejects_control_chars() {
let mut r = Reader::new("ok", "t");
assert!(r.check_printable("ok\x07bad").is_err());
}
#[test]
fn check_printable_accepts_tab_newline_cr() {
let mut r = Reader::new("ok", "t");
assert!(r.check_printable("ok\tnext\nline\rmore").is_ok());
}
#[test]
fn reader_error_implements_error_traits() {
let e = ReaderError {
context: "ctx".to_string(),
problem: "prob".to_string(),
mark: None,
};
assert!(e.to_string().contains("ReaderError"));
let _: &dyn std::error::Error = &e;
}
}