oxttl/toolkit/
error.rs

1use std::ops::Range;
2use std::{fmt, io};
3
4/// A position in a text i.e. a `line` number starting from 0, a `column` number starting from 0 (in number of code points) and a global file `offset` starting from 0 (in number of bytes).
5#[derive(Eq, PartialEq, Debug, Copy, Clone)]
6pub struct TextPosition {
7    pub line: u64,
8    pub column: u64,
9    pub offset: u64,
10}
11
12/// An error in the syntax of the parsed file.
13///
14/// It is composed of a message and a byte range in the input.
15#[derive(Debug, thiserror::Error)]
16pub struct TurtleSyntaxError {
17    location: Range<TextPosition>,
18    message: String,
19}
20
21impl TurtleSyntaxError {
22    pub(crate) fn new(location: Range<TextPosition>, message: impl Into<String>) -> Self {
23        Self {
24            location,
25            message: message.into(),
26        }
27    }
28
29    /// The location of the error inside of the file.
30    #[inline]
31    pub fn location(&self) -> Range<TextPosition> {
32        self.location.clone()
33    }
34
35    /// The error message.
36    #[inline]
37    pub fn message(&self) -> &str {
38        &self.message
39    }
40}
41
42impl fmt::Display for TurtleSyntaxError {
43    #[inline]
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        if self.location.start.offset + 1 >= self.location.end.offset {
46            write!(
47                f,
48                "Parser error at line {} column {}: {}",
49                self.location.start.line + 1,
50                self.location.start.column + 1,
51                self.message
52            )
53        } else if self.location.start.line == self.location.end.line {
54            write!(
55                f,
56                "Parser error at line {} between columns {} and {}: {}",
57                self.location.start.line + 1,
58                self.location.start.column + 1,
59                self.location.end.column + 1,
60                self.message
61            )
62        } else {
63            write!(
64                f,
65                "Parser error between line {} column {} and line {} column {}: {}",
66                self.location.start.line + 1,
67                self.location.start.column + 1,
68                self.location.end.line + 1,
69                self.location.end.column + 1,
70                self.message
71            )
72        }
73    }
74}
75
76impl From<TurtleSyntaxError> for io::Error {
77    #[inline]
78    fn from(error: TurtleSyntaxError) -> Self {
79        Self::new(io::ErrorKind::InvalidData, error)
80    }
81}
82
83/// A parsing error.
84///
85/// It is the union of [`TurtleSyntaxError`] and [`io::Error`].
86#[derive(Debug, thiserror::Error)]
87pub enum TurtleParseError {
88    /// I/O error during parsing (file not found...).
89    #[error(transparent)]
90    Io(#[from] io::Error),
91    /// An error in the file syntax.
92    #[error(transparent)]
93    Syntax(#[from] TurtleSyntaxError),
94}
95
96impl From<TurtleParseError> for io::Error {
97    #[inline]
98    fn from(error: TurtleParseError) -> Self {
99        match error {
100            TurtleParseError::Syntax(e) => e.into(),
101            TurtleParseError::Io(e) => e,
102        }
103    }
104}