Skip to main content

brainfrick/
error.rs

1use std::{fmt, sync::Arc};
2
3/// An error originating from this crate.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct Error {
6    pub(crate) kind: ErrorKind,
7    pub(crate) index: Index,
8    pub(crate) brainfuck: Arc<String>,
9    pub(crate) output: Option<String>,
10}
11
12impl Error {
13    /// The [`kind`](ErrorKind) of error.
14    pub fn kind(&self) -> ErrorKind {
15        self.kind
16    }
17
18    /// The line of brainfuck this error occurred on (starting from 0).
19    pub fn line(&self) -> usize {
20        self.index.line
21    }
22
23    /// The column of brainfuck this error occurred on (starting from 0).
24    pub fn col(&self) -> usize {
25        self.index.col
26    }
27
28    /**
29        The original brainfuck 'source' this error came from.
30
31        If you don't like swearing, you may use [`Error::brainfrick`].
32
33        # Example
34        ```
35        # use brainfrick::Brainfuck;
36        // this will run infinitely and therefore error
37        let code = "+[++++]";
38        let result = Brainfuck::execute(code).unwrap_err();
39        assert_eq!(code, result.brainfuck());
40        ```
41    */
42    pub fn brainfuck(&self) -> &str {
43        &self.brainfuck
44    }
45
46    /// An alias for [`Error::brainfuck`].
47    pub fn brainfrick(&self) -> &str {
48        self.brainfuck()
49    }
50
51    /// The output produced before the error occurred, if applicable.
52    pub fn output(&self) -> Option<&str> {
53        self.output.as_deref()
54    }
55}
56
57impl std::error::Error for Error {}
58impl fmt::Display for Error {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        let Index { line, col } = self.index;
61
62        // description and location
63        writeln!(f, "{} @ line {} col {}", self.kind, line + 1, col + 1)?;
64
65        // get snippet range (try to get 10 chars on either side)
66        let line = self.brainfuck.lines().nth(line).expect("Invalid line");
67        let (start, dots_begin, mut offset) = match col.checked_sub(10) {
68            Some(num) => (num, num != 0, 11),
69            None => (0, false, col + 1),
70        };
71        let mut end = col + 10;
72        let dots_end = end < line.len();
73        if !dots_end {
74            end = line.len();
75        }
76
77        // write snippet
78        if dots_begin {
79            f.write_str("...")?;
80            offset += 3;
81        }
82        f.write_str(&line[start..end])?;
83        if dots_end {
84            f.write_str("...")?;
85        }
86
87        // add ^ indicator
88        write!(f, "\n{0:>1$}", "^", offset)
89    }
90}
91
92/// The types of [`Errors`](Error) that can be encountered.
93#[non_exhaustive]
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ErrorKind {
96    /// A bracket `[]` was found without a matching bracket.
97    UnmatchedBracket,
98    /// The [maximum number of steps](crate::Brainfuck::max_steps) was reached.
99    MaxSteps,
100}
101
102impl fmt::Display for ErrorKind {
103    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
104        f.write_str(match self {
105            Self::UnmatchedBracket => "Unmatched bracket",
106            Self::MaxSteps => "Max steps reached",
107        })
108    }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub(crate) struct Index {
113    pub line: usize,
114    pub col: usize,
115}
116
117impl Index {
118    pub fn new(line: usize, col: usize) -> Self {
119        Self { line, col }
120    }
121}