1use peg::{error::ParseError, str::LineCol};
4use rusqlite::Error as SqlError;
5use thiserror::Error;
6
7use crate::ast::{Identifier, Term};
8use crate::ast::L;
9
10#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
14pub struct Offset(pub usize);
15
16#[derive(Error, Debug, PartialEq)]
17pub enum SolverError {
18
19 #[error("{0}")]
20 ParseError(#[from] ParseError<LineCol>),
21
22 #[error("{0}")]
23 ExprError(String),
24
25 #[error("{0}: {1}")]
26 TermError(&'static str, L<Term>),
27
28 #[error("{0}: {1}")]
29 IdentifierError(&'static str, L<Identifier>),
30
31 #[error("Database error: {0}")]
32 DatabaseError(#[from] SqlError),
33
34 #[error("{0}")]
35 InternalError(usize), }
37
38use crate::error::SolverError::*;
39
40
41pub fn format_error(input: &str, e: SolverError) -> String {
43 match e {
44
45 ParseError(e) =>
46 pretty_print(input, e.location, format!("Expected: {}\n", e.expected)),
47
48 DatabaseError(e) => format!("****** Database Error: {}\n", e),
49
50 ExprError(msg) => format!("****** Error: {}\n", msg),
51
52 TermError(msg, term) => {
53 match offset_to_line_col_utf8(&input, term.start()) {
54 None => format!("****** Error: {}\n", msg),
55 Some(location) =>
56 pretty_print(input, location, msg.to_string())
57 }
58 },
59
60 IdentifierError(msg, id) => {
61 let msg = if id.start() != Offset(0) { msg.to_string() }
62 else { format!("{msg} for {id}") };
63 match offset_to_line_col_utf8(&input, id.start()) {
64 None => format!("****** Error: {}\n", msg),
65 Some(location) =>
66 pretty_print(input, location, msg.to_string())
67 }
68 },
69
70 InternalError(n) => format!("****** Internal Error: {}\n", n)
71 }
72}
73
74
75fn pretty_print(input: &str, location: LineCol, msg: String) -> String {
77 if let Some(source) = input.lines().nth(location.line-1) {
78 chic::Error::new(format!("at position ({}, {}): {}", location.line, location.column, msg))
79 .error(
80 location.line,
81 location.column - 1,
82 location.column,
83 &source,
84 msg,
85 )
86 .to_string()
87 } else {
88 format!("****** Error: {msg} at {location}\n")
89 }
90
91}
92
93fn offset_to_line_col_utf8(s: &str, offset: Offset) -> Option<LineCol> {
94 if offset.0 > s.len() {
95 return None;
96 }
97
98 let mut current_offset = 0;
99
100 for (line_number, line) in s.split('\n').enumerate() {
101 let char_indices: Vec<_> = line.char_indices().collect();
102 let line_length = line.len() + 1; if current_offset + line_length > offset.0 {
105 for (column, (byte_index, _)) in char_indices.iter().enumerate() {
106 if current_offset + byte_index >= offset.0 {
107 return Some(LineCol{column: column + 1, line: line_number + 1, offset:offset.0});
108 }
109 }
110 }
111
112 current_offset += line_length;
113 }
114
115 None
116}