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)]
132pub struct SourceSpan {
133 pub byte_start: usize,
135 pub byte_end: usize,
137 pub line: usize,
139 pub column: usize,
141}
142
143impl SourceSpan {
144 #[must_use]
146 pub const fn new(byte_start: usize, byte_end: usize, line: usize, column: usize) -> Self {
147 Self {
148 byte_start,
149 byte_end,
150 line,
151 column,
152 }
153 }
154
155 #[must_use]
157 pub const fn len(self) -> usize {
158 self.byte_end - self.byte_start
159 }
160
161 #[must_use]
163 pub const fn is_empty(self) -> bool {
164 self.byte_start == self.byte_end
165 }
166}