use std::fmt;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Parse error at line {line}, column {column}: {message}")]
ParseError {
line: usize,
column: usize,
message: String,
snippet: Option<String>,
},
#[error("Undefined string variable '{0}'")]
UndefinedVariable(String),
#[error("Circular reference detected in string variables: {0}")]
CircularReference(String),
#[error("Invalid entry type '{0}'")]
InvalidEntryType(String),
#[error("Missing required field '{field}' in {entry_type} entry")]
MissingRequiredField {
entry_type: String,
field: String,
},
#[error("Duplicate entry key '{0}'")]
DuplicateKey(String),
#[error("Invalid field name '{0}'")]
InvalidFieldName(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Parse error: {0}")]
WinnowError(String),
}
#[derive(Debug, Clone)]
pub struct ParseContext {
pub input: String,
pub line: usize,
pub column: usize,
}
impl ParseContext {
#[must_use]
pub fn new(input: &str) -> Self {
Self {
input: input.to_string(),
line: 1,
column: 1,
}
}
#[must_use]
pub fn snippet(&self, pos: usize, context_size: usize) -> String {
let start = pos.saturating_sub(context_size);
let end = (pos + context_size).min(self.input.len());
let snippet = &self.input[start..end];
let relative_pos = pos - start;
format!("{}\n{}^", snippet, " ".repeat(relative_pos))
}
pub fn advance(&mut self, consumed: &str) {
for ch in consumed.chars() {
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
}
}
}
impl From<winnow::error::ContextError> for Error {
fn from(err: winnow::error::ContextError) -> Self {
Self::WinnowError(err.to_string())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Location {
pub line: usize,
pub column: usize,
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.column)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SourceId(usize);
impl SourceId {
#[must_use]
pub const fn new(index: usize) -> Self {
Self(index)
}
#[must_use]
pub const fn index(self) -> usize {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SourceSpan {
pub source: Option<SourceId>,
pub byte_start: usize,
pub byte_end: usize,
pub line: usize,
pub column: usize,
pub end_line: usize,
pub end_column: usize,
}
impl SourceSpan {
#[must_use]
pub const fn new(byte_start: usize, byte_end: usize, line: usize, column: usize) -> Self {
Self {
source: None,
byte_start,
byte_end,
line,
column,
end_line: line,
end_column: column,
}
}
#[must_use]
pub const fn with_end(
byte_start: usize,
byte_end: usize,
line: usize,
column: usize,
end_line: usize,
end_column: usize,
) -> Self {
Self {
source: None,
byte_start,
byte_end,
line,
column,
end_line,
end_column,
}
}
#[must_use]
pub const fn with_source(mut self, source: SourceId) -> Self {
self.source = Some(source);
self
}
#[must_use]
pub const fn len(self) -> usize {
self.byte_end - self.byte_start
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.byte_start == self.byte_end
}
}