1use thiserror::Error;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct Span {
8 pub start: usize,
9 pub end: usize,
10 pub line: u32,
11 pub column: u32,
12}
13
14impl Span {
15 pub fn new(start: usize, end: usize, line: u32, column: u32) -> Self {
16 Self { start, end, line, column }
17 }
18
19 pub fn merge(self, other: Span) -> Span {
20 Span {
21 start: self.start.min(other.start),
22 end: self.end.max(other.end),
23 line: self.line,
24 column: self.column,
25 }
26 }
27}
28
29#[derive(Clone)]
31pub struct Source {
32 pub code: String,
33 pub path: Option<String>,
34}
35
36impl Source {
37 pub fn new(code: String, path: Option<String>) -> Self {
38 Self { code, path }
39 }
40
41 pub fn slice(&self, span: Span) -> &str {
42 &self.code[span.start..span.end.min(self.code.len())]
43 }
44
45 pub fn line_at(&self, line: u32) -> &str {
46 self.code
47 .lines()
48 .nth((line as usize).saturating_sub(1))
49 .unwrap_or("")
50 }
51}
52
53#[derive(Error, Debug)]
55pub enum NewtError {
56 #[error("lexer: {message}")]
57 Lexer {
58 span: Span,
59 message: String,
60 suggestion: Option<String>,
61 },
62
63 #[error("parse: {message}")]
64 Parse {
65 span: Span,
66 message: String,
67 suggestion: Option<String>,
68 },
69
70 #[error("semantic: {message}")]
71 Semantic {
72 span: Span,
73 message: String,
74 suggestion: Option<String>,
75 },
76
77 #[error("io error: {0}")]
78 Io(#[from] std::io::Error),
79
80 #[error("{0}")]
81 Other(String),
82}
83
84impl NewtError {
85 pub fn lexer(span: Span, message: impl Into<String>) -> Self {
86 Self::Lexer {
87 span,
88 message: message.into(),
89 suggestion: None,
90 }
91 }
92
93 pub fn lexer_with_suggestion(span: Span, message: impl Into<String>, suggestion: impl Into<String>) -> Self {
94 Self::Lexer {
95 span,
96 message: message.into(),
97 suggestion: Some(suggestion.into()),
98 }
99 }
100
101 pub fn parse(span: Span, message: impl Into<String>) -> Self {
102 Self::Parse {
103 span,
104 message: message.into(),
105 suggestion: None,
106 }
107 }
108
109 pub fn parse_with_suggestion(span: Span, message: impl Into<String>, suggestion: impl Into<String>) -> Self {
110 Self::Parse {
111 span,
112 message: message.into(),
113 suggestion: Some(suggestion.into()),
114 }
115 }
116
117 pub fn semantic(span: Span, message: impl Into<String>) -> Self {
118 Self::Semantic {
119 span,
120 message: message.into(),
121 suggestion: None,
122 }
123 }
124
125 pub fn span(&self) -> Option<Span> {
126 match self {
127 Self::Lexer { span, .. } | Self::Parse { span, .. } | Self::Semantic { span, .. } => Some(*span),
128 _ => None,
129 }
130 }
131
132 pub fn suggestion(&self) -> Option<&str> {
133 match self {
134 Self::Lexer { suggestion: s, .. } | Self::Parse { suggestion: s, .. } | Self::Semantic { suggestion: s, .. } => s.as_deref(),
135 _ => None,
136 }
137 }
138}
139
140pub fn format_error(source: &Source, err: &NewtError) -> String {
142 let mut out = String::new();
143 if let Some(span) = err.span() {
144 let path = source.path.as_deref().unwrap_or("<input>");
145 let line_content = source.line_at(span.line);
146 out.push_str(&format!(" --> {}:{}:{}\n", path, span.line, span.column));
147 out.push_str(" |\n");
148 out.push_str(&format!("{:>4} | {}\n", span.line, line_content));
149 let pad = span.column.saturating_sub(1) as usize;
150 out.push_str(&format!(" | {}^\n", " ".repeat(pad)));
151 }
152 out.push_str(&format!("error: {}\n", err));
153 if let Some(s) = err.suggestion() {
154 out.push_str(&format!(" hint: {}\n", s));
155 }
156 out
157}