Files @ 06f259bf8031
Branch filter:

Location: CSY/reowolf/src/protocol/inputsource.rs

06f259bf8031 11.2 KiB application/rls-services+xml Show Annotation Show as Raw Download as Raw
Christopher Esterhuyse
first
use std::fmt;
use std::fs::File;
use std::io;
use std::path::Path;

use backtrace::Backtrace;

#[derive(Clone)]
pub struct InputSource {
    filename: String,
    input: Vec<u8>,
    line: usize,
    column: usize,
    offset: usize,
}

impl InputSource {
    // Constructors
    pub fn new<A: io::Read, S: ToString>(filename: S, reader: &mut A) -> io::Result<InputSource> {
        let mut vec = Vec::new();
        reader.read_to_end(&mut vec)?;
        Ok(InputSource {
            filename: filename.to_string(),
            input: vec,
            line: 1,
            column: 1,
            offset: 0,
        })
    }
    // Constructor helpers
    pub fn from_file(path: &Path) -> io::Result<InputSource> {
        let filename = path.file_name();
        match filename {
            Some(filename) => {
                let mut f = File::open(path)?;
                InputSource::new(filename.to_string_lossy(), &mut f)
            }
            None => Err(io::Error::new(io::ErrorKind::NotFound, "Invalid path")),
        }
    }
    pub fn from_string(string: &str) -> io::Result<InputSource> {
        let buffer = Box::new(string);
        let mut bytes = buffer.as_bytes();
        InputSource::new(String::new(), &mut bytes)
    }
    pub fn from_buffer(buffer: &[u8]) -> io::Result<InputSource> {
        InputSource::new(String::new(), &mut Box::new(buffer))
    }
    // Internal methods
    pub fn pos(&self) -> InputPosition {
        InputPosition { line: self.line, column: self.column, offset: self.offset }
    }
    pub fn error<S: ToString>(&self, message: S) -> ParseError {
        self.pos().parse_error(message)
    }
    pub fn is_eof(&self) -> bool {
        self.next() == None
    }
    pub fn next(&self) -> Option<u8> {
        if self.offset < self.input.len() {
            Some((*self.input)[self.offset])
        } else {
            None
        }
    }
    pub fn lookahead(&self, pos: usize) -> Option<u8> {
        if let Some(x) = usize::checked_add(self.offset, pos) {
            if x < self.input.len() {
                return Some((*self.input)[x]);
            }
        }
        None
    }
    pub fn consume(&mut self) {
        match self.next() {
            Some(x) if x == b'\r' && self.lookahead(1) != Some(b'\n') || x == b'\n' => {
                self.line += 1;
                self.offset += 1;
                self.column = 1;
            }
            Some(_) => {
                self.offset += 1;
                self.column += 1;
            }
            None => {}
        }
    }
}

impl fmt::Display for InputSource {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.pos().fmt(f)
    }
}

#[derive(Debug, Clone, Copy, Default)]
pub struct InputPosition {
    line: usize,
    column: usize,
    offset: usize,
}

impl InputPosition {
    fn context<'a>(&self, source: &'a InputSource) -> &'a [u8] {
        let start = self.offset - (self.column - 1);
        let mut end = self.offset;
        while end < source.input.len() {
            let cur = (*source.input)[end];
            if cur == b'\n' || cur == b'\r' {
                break;
            }
            end += 1;
        }
        &source.input[start..end]
    }
    fn parse_error<S: ToString>(&self, message: S) -> ParseError {
        ParseError { position: *self, message: message.to_string(), backtrace: Backtrace::new() }
    }
    fn eval_error<S: ToString>(&self, message: S) -> EvalError {
        EvalError { position: *self, message: message.to_string(), backtrace: Backtrace::new() }
    }
}

impl fmt::Display for InputPosition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}:{}", self.line, self.column)
    }
}

pub trait SyntaxElement {
    fn position(&self) -> InputPosition;
    fn error<S: ToString>(&self, message: S) -> EvalError {
        self.position().eval_error(message)
    }
}

#[derive(Debug, Clone)]
pub struct ParseError {
    position: InputPosition,
    message: String,
    backtrace: Backtrace,
}

impl ParseError {
    pub fn new<S: ToString>(position: InputPosition, message: S) -> ParseError {
        ParseError { position, message: message.to_string(), backtrace: Backtrace::new() }
    }
    // Diagnostic methods
    pub fn write<A: io::Write>(&self, source: &InputSource, writer: &mut A) -> io::Result<()> {
        if !source.filename.is_empty() {
            writeln!(
                writer,
                "Parse error at {}:{}: {}",
                source.filename, self.position, self.message
            )?;
        } else {
            writeln!(writer, "Parse error at {}: {}", self.position, self.message)?;
        }
        let line = self.position.context(source);
        writeln!(writer, "{}", String::from_utf8_lossy(line))?;
        let mut arrow: Vec<u8> = Vec::new();
        for pos in 1..self.position.column {
            let c = line[pos - 1];
            if c == b'\t' {
                arrow.push(b'\t')
            } else {
                arrow.push(b' ')
            }
        }
        arrow.push(b'^');
        writeln!(writer, "{}", String::from_utf8_lossy(&arrow))
    }
    pub fn print(&self, source: &InputSource) {
        self.write(source, &mut std::io::stdout()).unwrap()
    }
    pub fn display<'a>(&'a self, source: &'a InputSource) -> DisplayParseError<'a> {
        DisplayParseError::new(self, source)
    }
}

impl From<ParseError> for io::Error {
    fn from(_: ParseError) -> io::Error {
        io::Error::new(io::ErrorKind::InvalidInput, "parse error")
    }
}

#[derive(Clone, Copy)]
pub struct DisplayParseError<'a> {
    error: &'a ParseError,
    source: &'a InputSource,
}

impl DisplayParseError<'_> {
    fn new<'a>(error: &'a ParseError, source: &'a InputSource) -> DisplayParseError<'a> {
        DisplayParseError { error, source }
    }
}

impl fmt::Display for DisplayParseError<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut vec: Vec<u8> = Vec::new();
        match self.error.write(self.source, &mut vec) {
            Err(_) => {
                return fmt::Result::Err(fmt::Error);
            }
            Ok(_) => {}
        }
        write!(f, "{}", String::from_utf8_lossy(&vec))
    }
}

#[derive(Debug, Clone)]
pub struct EvalError {
    position: InputPosition,
    message: String,
    backtrace: Backtrace,
}

impl EvalError {
    pub fn new<S: ToString>(position: InputPosition, message: S) -> EvalError {
        EvalError { position, message: message.to_string(), backtrace: Backtrace::new() }
    }
    // Diagnostic methods
    pub fn write<A: io::Write>(&self, source: &InputSource, writer: &mut A) -> io::Result<()> {
        if !source.filename.is_empty() {
            writeln!(
                writer,
                "Evaluation error at {}:{}: {}",
                source.filename, self.position, self.message
            )?;
        } else {
            writeln!(writer, "Evaluation error at {}: {}", self.position, self.message)?;
        }
        let line = self.position.context(source);
        writeln!(writer, "{}", String::from_utf8_lossy(line))?;
        let mut arrow: Vec<u8> = Vec::new();
        for pos in 1..self.position.column {
            let c = line[pos - 1];
            if c == b'\t' {
                arrow.push(b'\t')
            } else {
                arrow.push(b' ')
            }
        }
        arrow.push(b'^');
        writeln!(writer, "{}", String::from_utf8_lossy(&arrow))
    }
    pub fn print(&self, source: &InputSource) {
        self.write(source, &mut std::io::stdout()).unwrap()
    }
    pub fn display<'a>(&'a self, source: &'a InputSource) -> DisplayEvalError<'a> {
        DisplayEvalError::new(self, source)
    }
}

impl From<EvalError> for io::Error {
    fn from(_: EvalError) -> io::Error {
        io::Error::new(io::ErrorKind::InvalidInput, "eval error")
    }
}

#[derive(Clone, Copy)]
pub struct DisplayEvalError<'a> {
    error: &'a EvalError,
    source: &'a InputSource,
}

impl DisplayEvalError<'_> {
    fn new<'a>(error: &'a EvalError, source: &'a InputSource) -> DisplayEvalError<'a> {
        DisplayEvalError { error, source }
    }
}

impl fmt::Display for DisplayEvalError<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut vec: Vec<u8> = Vec::new();
        match self.error.write(self.source, &mut vec) {
            Err(_) => {
                return fmt::Result::Err(fmt::Error);
            }
            Ok(_) => {}
        }
        write!(f, "{}", String::from_utf8_lossy(&vec))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_from_string() {
        let mut is = InputSource::from_string("#version 100\n").unwrap();
        assert!(is.input.len() == 13);
        assert!(is.line == 1);
        assert!(is.column == 1);
        assert!(is.offset == 0);
        let ps = is.pos();
        assert!(ps.line == 1);
        assert!(ps.column == 1);
        assert!(ps.offset == 0);
        assert!(is.next() == Some(b'#'));
        is.consume();
        assert!(is.next() == Some(b'v'));
        assert!(is.lookahead(1) == Some(b'e'));
        is.consume();
        assert!(is.next() == Some(b'e'));
        is.consume();
        assert!(is.next() == Some(b'r'));
        is.consume();
        assert!(is.next() == Some(b's'));
        is.consume();
        assert!(is.next() == Some(b'i'));
        is.consume();
        {
            let ps = is.pos();
            assert_eq!(b"#version 100", ps.context(&is));
            let er = is.error("hello world!");
            let mut vec: Vec<u8> = Vec::new();
            er.write(&is, &mut vec).unwrap();
            assert_eq!(
                "Parse error at 1:7: hello world!\n#version 100\n      ^\n",
                String::from_utf8_lossy(&vec)
            );
        }
        assert!(is.next() == Some(b'o'));
        is.consume();
        assert!(is.next() == Some(b'n'));
        is.consume();
        assert!(is.input.len() == 13);
        assert!(is.line == 1);
        assert!(is.column == 9);
        assert!(is.offset == 8);
        assert!(is.next() == Some(b' '));
        is.consume();
        assert!(is.next() == Some(b'1'));
        is.consume();
        assert!(is.next() == Some(b'0'));
        is.consume();
        assert!(is.next() == Some(b'0'));
        is.consume();
        assert!(is.input.len() == 13);
        assert!(is.line == 1);
        assert!(is.column == 13);
        assert!(is.offset == 12);
        assert!(is.next() == Some(b'\n'));
        is.consume();
        assert!(is.input.len() == 13);
        assert!(is.line == 2);
        assert!(is.column == 1);
        assert!(is.offset == 13);
        {
            let ps = is.pos();
            assert_eq!(b"", ps.context(&is));
        }
        assert!(is.next() == None);
        is.consume();
        assert!(is.next() == None);
    }

    #[test]
    fn test_split() {
        let mut is = InputSource::from_string("#version 100\n").unwrap();
        let backup = is.clone();
        assert!(is.next() == Some(b'#'));
        is.consume();
        assert!(is.next() == Some(b'v'));
        is.consume();
        assert!(is.next() == Some(b'e'));
        is.consume();
        is = backup;
        assert!(is.next() == Some(b'#'));
        is.consume();
        assert!(is.next() == Some(b'v'));
        is.consume();
        assert!(is.next() == Some(b'e'));
        is.consume();
    }
}