1use std::{fmt, sync::Arc};
2
3#[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 pub fn kind(&self) -> ErrorKind {
15 self.kind
16 }
17
18 pub fn line(&self) -> usize {
20 self.index.line
21 }
22
23 pub fn col(&self) -> usize {
25 self.index.col
26 }
27
28 pub fn brainfuck(&self) -> &str {
43 &self.brainfuck
44 }
45
46 pub fn brainfrick(&self) -> &str {
48 self.brainfuck()
49 }
50
51 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 writeln!(f, "{} @ line {} col {}", self.kind, line + 1, col + 1)?;
64
65 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 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 write!(f, "\n{0:>1$}", "^", offset)
89 }
90}
91
92#[non_exhaustive]
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum ErrorKind {
96 UnmatchedBracket,
98 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}