1use std::fmt;
4use thiserror::Error;
5
6pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Error, Debug)]
11pub enum Error {
12 #[error("Parse error at line {line}, column {column}: {message}")]
14 ParseError {
15 line: usize,
17 column: usize,
19 message: String,
21 snippet: Option<String>,
23 },
24
25 #[error("Undefined string variable '{0}'")]
27 UndefinedVariable(String),
28
29 #[error("Circular reference detected in string variables: {0}")]
31 CircularReference(String),
32
33 #[error("Invalid entry type '{0}'")]
35 InvalidEntryType(String),
36
37 #[error("Missing required field '{field}' in {entry_type} entry")]
39 MissingRequiredField {
40 entry_type: String,
42 field: String,
44 },
45
46 #[error("Duplicate entry key '{0}'")]
48 DuplicateKey(String),
49
50 #[error("Invalid field name '{0}'")]
52 InvalidFieldName(String),
53
54 #[error("IO error: {0}")]
56 IoError(#[from] std::io::Error),
57
58 #[error("Parse error: {0}")]
60 WinnowError(String),
61}
62
63#[derive(Debug, Clone)]
65pub struct ParseContext {
66 pub input: String,
68 pub line: usize,
70 pub column: usize,
72}
73
74impl ParseContext {
75 #[must_use]
77 pub fn new(input: &str) -> Self {
78 Self {
79 input: input.to_string(),
80 line: 1,
81 column: 1,
82 }
83 }
84
85 #[must_use]
87 pub fn snippet(&self, pos: usize, context_size: usize) -> String {
88 let start = pos.saturating_sub(context_size);
89 let end = (pos + context_size).min(self.input.len());
90 let snippet = &self.input[start..end];
91 let relative_pos = pos - start;
92 format!("{}\n{}^", snippet, " ".repeat(relative_pos))
93 }
94
95 pub fn advance(&mut self, consumed: &str) {
97 for ch in consumed.chars() {
98 if ch == '\n' {
99 self.line += 1;
100 self.column = 1;
101 } else {
102 self.column += 1;
103 }
104 }
105 }
106}
107
108impl From<winnow::error::ContextError> for Error {
110 fn from(err: winnow::error::ContextError) -> Self {
111 Self::WinnowError(err.to_string())
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub struct Location {
118 pub line: usize,
120 pub column: usize,
122}
123
124impl fmt::Display for Location {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{}:{}", self.line, self.column)
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
132pub struct SourceId(usize);
133
134impl SourceId {
135 #[must_use]
137 pub const fn new(index: usize) -> Self {
138 Self(index)
139 }
140
141 #[must_use]
143 pub const fn index(self) -> usize {
144 self.0
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155pub struct SourceSpan {
156 pub source: Option<SourceId>,
158 pub byte_start: usize,
160 pub byte_end: usize,
162 pub line: usize,
164 pub column: usize,
166 pub end_line: usize,
168 pub end_column: usize,
170}
171
172impl SourceSpan {
173 #[must_use]
175 pub const fn new(byte_start: usize, byte_end: usize, line: usize, column: usize) -> Self {
176 Self {
177 source: None,
178 byte_start,
179 byte_end,
180 line,
181 column,
182 end_line: line,
183 end_column: column,
184 }
185 }
186
187 #[must_use]
189 pub const fn with_end(
190 byte_start: usize,
191 byte_end: usize,
192 line: usize,
193 column: usize,
194 end_line: usize,
195 end_column: usize,
196 ) -> Self {
197 Self {
198 source: None,
199 byte_start,
200 byte_end,
201 line,
202 column,
203 end_line,
204 end_column,
205 }
206 }
207
208 #[must_use]
210 pub const fn with_source(mut self, source: SourceId) -> Self {
211 self.source = Some(source);
212 self
213 }
214
215 #[must_use]
217 pub const fn len(self) -> usize {
218 self.byte_end - self.byte_start
219 }
220
221 #[must_use]
223 pub const fn is_empty(self) -> bool {
224 self.byte_start == self.byte_end
225 }
226}