erg/crates/erg_common/error.rs
2024-12-31 13:45:02 +09:00

1113 lines
35 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! provides common components for error handling.
//!
//! エラー処理に関する汎用的なコンポーネントを提供する
use std::cmp::{self, Ordering};
use std::fmt;
use std::io::{stderr, BufWriter, Write as _};
use crate::io::{Input, InputKind};
use crate::style::Attribute;
use crate::style::Characters;
use crate::style::Color;
use crate::style::StyledStr;
use crate::style::StyledStrings;
use crate::style::Theme;
use crate::style::THEME;
use crate::traits::{Locational, Stream};
use crate::{impl_display_from_debug, switch_lang};
#[cfg(feature = "pylib")]
use pyo3::prelude::*;
/// This includes not only Error but also Warning, Exception
/// Numbering of this is not specifically related to ErrFmt.errno().
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum ErrorKind {
/* compile errors */
AssignError = 0,
AttributeError = 1,
BytecodeError = 2,
CompilerSystemError = 3,
EnvironmentError = 4,
FeatureError = 5,
ImportError = 6,
IndentationError = 7,
NameError = 8,
NotImplementedError = 9,
PatternError = 10,
SyntaxError = 11,
TabError = 12,
TypeError = 13,
UnboundLocalError = 14,
PurityError = 15,
HasEffect = 16,
MoveError = 17,
NotConstExpr = 18,
InheritanceError = 19,
VisibilityError = 20,
MethodError = 21,
DummyError = 22,
ExpectNextLine = 23,
/* compile warnings */
AttributeWarning = 60,
CastWarning = 61,
DeprecationWarning = 62,
FutureWarning = 63,
ImportWarning = 64,
PendingDeprecationWarning = 65,
SyntaxWarning = 66,
TypeWarning = 67,
NameWarning = 68,
UnusedWarning = 69,
Warning = 70,
/* runtime errors */
ArithmeticError = 100,
AssertionError = 101,
BlockingIOError = 102,
BrokenPipeError = 103,
BufferError = 104,
ChildProcessError = 105,
ConnectionAbortedError = 106,
ConnectionError = 107,
ConnectionRefusedError = 108,
ConnectionResetError = 109,
EOFError = 110,
FileExistsError = 111,
FileNotFoundError = 112,
IndexError = 113,
InterruptedError = 114,
IoError = 115,
IsADirectoryError = 116,
KeyError = 117,
LookupError = 118,
MemoryError = 119,
ModuleNotFoundError = 120,
NotADirectoryError = 121,
OSError = 122,
OverflowError = 123,
PermissionError = 124,
ProcessLookupError = 125,
RecursionError = 126,
ReferenceError = 127,
RuntimeAttributeError = 128,
RuntimeError = 129,
RuntimeTypeError = 130,
RuntimeUnicodeError = 131,
TimeoutError = 132,
UnicodeError = 133,
UserError = 134,
ValueError = 135,
VMSystemError = 136,
WindowsError = 137,
ZeroDivisionError = 138,
/* runtime warnings */
BytesWarning = 180,
ResourceWarning = 181,
RuntimeWarning = 182,
UnicodeWarning = 183,
UserWarning = 184,
/* exceptions */
BaseException = 200,
Exception = 201,
GeneratorExit = 202,
KeyboardInterrupt = 203,
StopAsyncIteration = 204,
StopIteration = 205,
SystemExit = 206,
UserException = 207,
}
use ErrorKind::*;
impl_display_from_debug!(ErrorKind);
impl ErrorKind {
pub fn is_warning(&self) -> bool {
(60..=100).contains(&(*self as u8)) || (180..=200).contains(&(*self as u8))
}
pub fn is_error(&self) -> bool {
(0..=59).contains(&(*self as u8)) || (100..=179).contains(&(*self as u8))
}
pub fn is_exception(&self) -> bool {
(200..=255).contains(&(*self as u8))
}
}
impl From<&str> for ErrorKind {
fn from(s: &str) -> ErrorKind {
match s {
"AssignError" => Self::AssignError,
"AttributeError" => Self::AttributeError,
"BytecodeError" => Self::BytecodeError,
"CompilerSystemError" => Self::CompilerSystemError,
"EnvironmentError" => Self::EnvironmentError,
"FeatureError" => Self::FeatureError,
"ImportError" => Self::ImportError,
"IndentationError" => Self::IndentationError,
"NameError" => Self::NameError,
"NotImplementedError" => Self::NotImplementedError,
"PatternError" => Self::PatternError,
"SyntaxError" => Self::SyntaxError,
"TabError" => Self::TabError,
"TypeError" => Self::TypeError,
"UnboundLocalError" => Self::UnboundLocalError,
"HasEffect" => Self::HasEffect,
"PurityError" => Self::PurityError,
"MoveError" => Self::MoveError,
"AttributeWarning" => Self::AttributeWarning,
"CastWarning" => Self::CastWarning,
"DeprecationWarning" => Self::DeprecationWarning,
"FutureWarning" => Self::FutureWarning,
"ImportWarning" => Self::ImportWarning,
"PendingDeprecationWarning" => Self::PendingDeprecationWarning,
"SyntaxWarning" => Self::SyntaxWarning,
"TypeWarning" => Self::TypeWarning,
"NameWarning" => Self::NameWarning,
"UnusedWarning" => Self::UnusedWarning,
"Warning" => Self::Warning,
"ArithmeticError" => Self::ArithmeticError,
"AssertionError" => Self::AssertionError,
"BlockingIOError" => Self::BlockingIOError,
"BrokenPipeError" => Self::BrokenPipeError,
"BufferError" => Self::BufferError,
"ChildProcessError" => Self::ChildProcessError,
"ConnectionAbortedError" => Self::ConnectionAbortedError,
"ConnectionError" => Self::ConnectionError,
"ConnectionRefusedError" => Self::ConnectionRefusedError,
"ConnectionResetError" => Self::ConnectionResetError,
"EOFError" => Self::EOFError,
"FileExistsError" => Self::FileExistsError,
"FileNotFoundError" => Self::FileNotFoundError,
"IndexError" => Self::IndexError,
"InterruptedError" => Self::InterruptedError,
"IoError" => Self::IoError,
"IsADirectoryError" => Self::IsADirectoryError,
"KeyError" => Self::KeyError,
"LookupError" => Self::LookupError,
"MemoryError" => Self::MemoryError,
"ModuleNotFoundError" => Self::ModuleNotFoundError,
"NotADirectoryError" => Self::NotADirectoryError,
"OSError" => Self::OSError,
"OverflowError" => Self::OverflowError,
"PermissionError" => Self::PermissionError,
"ProcessLookupError" => Self::ProcessLookupError,
"RecursionError" => Self::RecursionError,
"ReferenceError" => Self::ReferenceError,
"RuntimeAttributeError" => Self::RuntimeAttributeError,
"RuntimeError" => Self::RuntimeError,
"RuntimeTypeError" => Self::RuntimeTypeError,
"RuntimeUnicodeError" => Self::RuntimeUnicodeError,
"TimeoutError" => Self::TimeoutError,
"UnicodeError" => Self::UnicodeError,
"UserError" => Self::UserError,
"ValueError" => Self::ValueError,
"VMSystemError" => Self::VMSystemError,
"WindowsError" => Self::WindowsError,
"ZeroDivisionError" => Self::ZeroDivisionError,
"BytesWarning" => Self::BytesWarning,
"ResourceWarning" => Self::ResourceWarning,
"RuntimeWarning" => Self::RuntimeWarning,
"UnicodeWarning" => Self::UnicodeWarning,
"UserWarning" => Self::UserWarning,
"BaseException" => Self::BaseException,
"Exception" => Self::Exception,
"GeneratorExit" => Self::GeneratorExit,
"KeyboardInterrupt" => Self::KeyboardInterrupt,
"StopAsyncIteration" => Self::StopAsyncIteration,
"StopIteration" => Self::StopIteration,
"SystemExit" => Self::SystemExit,
"UserException" => Self::UserException,
_ => Self::UserError,
}
}
}
///
/// Points the location (of an error) in a code.
/// The beginning and end of each row and column where the error occurred.
/// Basically, the beginning and end of each row and column where the error occurred is kept.
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Location {
///
/// Location used for basic errors
/// ```erg
/// # erg
/// a = 1
/// a = 2 # Error, `a` is assigned twice
/// // Value assigned to the structure
/// Location::Range {
/// ln_begin: 2,
/// col_begin: 0,
/// ln_end: 2,
/// col_end: 1,
/// }
/// ```
///
Range {
/// 1-origin
ln_begin: u32,
/// 0-origin
col_begin: u32,
ln_end: u32,
col_end: u32,
},
/// Used for loss of location information when desugared.
/// If there are guaranteed to be multiple rows
LineRange(u32, u32),
/// Used when Location information is lost when desugared
/// If it is guaranteed to be a single line
Line(u32),
/// Used by default in case of loss of Location information
#[default]
Unknown,
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => write!(f, "{ln_begin}:{col_begin}-{ln_end}:{col_end}"),
Self::LineRange(ln_begin, ln_end) => write!(f, "{ln_begin}:?-{ln_end}:?"),
Self::Line(ln) => write!(f, "{ln}:??-{ln}:??"),
Self::Unknown => write!(f, "?"),
}
}
}
#[cfg(feature = "pylib")]
impl FromPyObject<'_> for Location {
fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult<Self> {
if let Ok(s) = ob.extract::<String>() {
Ok(s.parse::<Location>().unwrap())
} else if let Ok(s) = ob.extract::<u32>() {
Ok(Location::Line(s))
} else if let Ok((l, r)) = ob.extract::<(u32, u32)>() {
Ok(Location::LineRange(l, r))
} else if let Ok((lb, cb, le, ce)) = ob.extract::<(u32, u32, u32, u32)>() {
Ok(Location::Range {
ln_begin: lb,
col_begin: cb,
ln_end: le,
col_end: ce,
})
} else {
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(format!(
"expected Into<Location>, but got {:?}",
ob.get_type().name()?
)))
}
}
}
#[cfg(feature = "pylib")]
impl IntoPy<PyObject> for Location {
fn into_py(self, py: Python<'_>) -> PyObject {
match self {
Self::Line(l) => (l, py.None(), l, py.None()).into_py(py),
Self::LineRange(lb, le) => (lb, py.None(), le, py.None()).into_py(py),
Self::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => (ln_begin, col_begin, ln_end, col_end).into_py(py),
Self::Unknown => (py.None(), py.None(), py.None(), py.None()).into_py(py),
}
}
}
impl std::str::FromStr for Location {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "?" {
return Ok(Self::Unknown);
}
// ln_begin:col_begin-ln_end:col_end
let mut comps = s.split('-');
let mut comp1 = comps.next().ok_or(())?.split(':');
let mut comp2 = comps.next().ok_or(())?.split(':');
let ln_begin = comp1.next().unwrap().parse::<u32>().map_err(|_| ())?;
let col_begin = comp1.next().unwrap().parse::<u32>();
let ln_end = comp2.next().unwrap().parse::<u32>().map_err(|_| ())?;
let col_end = comp2.next().unwrap().parse::<u32>();
match (col_begin, col_end) {
(Ok(col_begin), Ok(col_end)) => Ok(Self::Range {
ln_begin,
col_begin,
ln_end,
col_end,
}),
_ if ln_begin == ln_end => Ok(Self::Line(ln_begin)),
_ => Ok(Self::LineRange(ln_begin, ln_end)),
}
}
}
impl Ord for Location {
fn cmp(&self, other: &Location) -> Ordering {
if self.ln_end() < other.ln_begin() {
Ordering::Less
} else if other.ln_end() < self.ln_begin() {
Ordering::Greater
} else if self.ln_begin() == self.ln_end() && other.ln_begin() == other.ln_end() {
// assert_eq!(self.line_begin, other.line_begin);
// assert_eq!(self.line_end, other.line_end);
if self.col_end() <= other.col_begin() {
Ordering::Less
} else if other.col_end() <= self.col_begin() {
Ordering::Greater
} else {
Ordering::Equal
}
} else {
Ordering::Equal
}
}
}
impl PartialOrd for Location {
#[allow(clippy::non_canonical_partial_ord_impl)]
fn partial_cmp(&self, other: &Location) -> Option<Ordering> {
if self.is_unknown() || other.is_unknown() {
None
} else {
Some(self.cmp(other))
}
}
}
impl Locational for Location {
fn loc(&self) -> Self {
*self
}
}
impl Location {
pub fn concat<L: Locational, R: Locational>(l: &L, r: &R) -> Self {
let l_loc = l.loc();
let r_loc = r.loc();
match (
l_loc.ln_begin(),
l_loc.col_begin(),
r_loc.ln_end(),
r_loc.col_end(),
) {
(Some(lb), Some(cb), Some(le), Some(ce)) => Self::range(lb, cb, le, ce),
(Some(lb), _, Some(le), _) => Self::LineRange(lb, le),
(Some(l), _, _, _) | (_, _, Some(l), _) => Self::Line(l),
_ => Self::Unknown,
}
}
pub fn left_main_concat<L: Locational, R: Locational>(l: &L, r: &R) -> Self {
let l_loc = l.loc();
let r_loc = r.loc();
match (
l_loc.ln_begin(),
l_loc.col_begin(),
r_loc.ln_end(),
r_loc.col_end(),
) {
(Some(lb), Some(cb), Some(le), Some(ce)) => Self::range(lb, cb, le, ce),
(Some(_), _, None, None) => l_loc,
(Some(lb), _, Some(le), _) => Self::LineRange(lb, le),
(Some(l), _, _, _) | (_, _, Some(l), _) => Self::Line(l),
_ => Self::Unknown,
}
}
pub fn slow_stream<L: Locational>(ls: &[L]) -> Self {
if ls.is_empty() {
return Self::Unknown;
}
let Some(first_known) = ls.iter().find(|l| !l.loc().is_unknown()) else {
return Self::Unknown;
};
let Some(last_known) = ls.iter().rfind(|l| !l.loc().is_unknown()) else {
return Self::Unknown;
};
Self::concat(first_known, last_known)
}
pub fn stream<L: Locational>(ls: &[L]) -> Self {
if ls.is_empty() {
return Self::Unknown;
}
let first_known = ls.first().unwrap();
let last_known = ls.last().unwrap();
Self::concat(first_known, last_known)
}
pub const fn range(ln_begin: u32, col_begin: u32, ln_end: u32, col_end: u32) -> Self {
Self::Range {
ln_begin,
col_begin,
ln_end,
col_end,
}
}
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown)
}
pub const fn is_real(&self) -> bool {
match self {
Self::Line(l) => *l != 0,
Self::LineRange(lb, le) => *lb != 0 && *le != 0,
Self::Range {
ln_begin, ln_end, ..
} => *ln_begin != 0 && *ln_end != 0,
Self::Unknown => false,
}
}
pub const fn unknown_or(&self, other: Self) -> Self {
if self.is_unknown() {
other
} else {
*self
}
}
/// 1-origin
pub const fn ln_begin(&self) -> Option<u32> {
match self {
Self::Range { ln_begin, .. } | Self::LineRange(ln_begin, _) | Self::Line(ln_begin) => {
Some(*ln_begin)
}
Self::Unknown => None,
}
}
pub const fn ln_end(&self) -> Option<u32> {
match self {
Self::Range { ln_end, .. } | Self::LineRange(_, ln_end) | Self::Line(ln_end) => {
Some(*ln_end)
}
Self::Unknown => None,
}
}
/// 0-origin
pub const fn col_begin(&self) -> Option<u32> {
match self {
Self::Range { col_begin, .. } => Some(*col_begin),
_ => None,
}
}
pub const fn col_end(&self) -> Option<u32> {
match self {
Self::Range { col_end, .. } => Some(*col_end),
_ => None,
}
}
pub const fn length(&self) -> Option<u32> {
match self {
Self::Range {
col_begin, col_end, ..
} => Some(*col_end - *col_begin),
_ => None,
}
}
/// ```
/// # use erg_common::error::Location;
/// let loc = Location::range(1, 3, 1, 7);
/// assert_eq!(loc.ln_begin(), Some(1));
/// assert!(loc.contains(Location::range(1, 4, 1, 5)));
/// let loc = Location::range(1, 3, 3, 2);
/// assert!(loc.contains(Location::range(1, 4, 1, 5)));
/// assert!(!loc.contains(Location::range(1, 4, 3, 5)));
/// assert!(loc.contains(Location::range(1, 4, 2, 5)));
/// assert!(!loc.contains(Location::range(1, 2, 2, 5)));
/// ```
pub fn contains(&self, other: Self) -> bool {
match (*self, other) {
(
Self::Range {
ln_begin: lb1,
col_begin: cb1,
ln_end: le1,
col_end: ce1,
},
Self::Range {
ln_begin: lb2,
col_begin: cb2,
ln_end: le2,
col_end: ce2,
},
) => {
let same_start_line = lb1 == lb2;
let same_end_line = le1 == le2;
if same_start_line && same_end_line {
cb1 <= cb2 && ce1 >= ce2
} else if same_start_line {
cb1 <= cb2 && le1 >= le2
} else if same_end_line {
lb1 <= lb2 && ce1 >= ce2
} else {
lb1 <= lb2 && le1 >= le2
}
}
_ => false,
}
}
}
#[allow(clippy::too_many_arguments)]
fn format_context<E: ErrorDisplay + ?Sized>(
e: &E,
ln_begin: usize,
ln_end: usize,
col_begin: usize,
col_end: usize,
err_color: Color,
gutter_color: Color,
// for formatting points
chars: &Characters,
// kinds of error for specify the color
mark: char,
sub_msg: &[String],
hint: Option<&String>,
) -> String {
let mark = mark.to_string();
let codes = e.input().reread_lines(ln_begin, ln_end);
let mut context = StyledStrings::default();
let final_step = ln_end - ln_begin;
let max_digit = ln_end.to_string().len();
let (vbreak, vbar) = chars.gutters();
let offset = format!("{} {} ", &" ".repeat(max_digit), vbreak);
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
context.push_str_with_color(format!("{lineno:<max_digit$} {vbar} "), gutter_color);
let not_found = "???".to_string();
let code = codes.get(i).unwrap_or(&not_found);
context.push_str(code);
context.push_str("\n");
context.push_str_with_color(&offset, gutter_color);
if i == 0 && i == final_step {
context.push_str(&" ".repeat(col_begin));
context.push_str_with_color(
mark.repeat(cmp::max(1, col_end.saturating_sub(col_begin))),
err_color,
);
} else if i == 0 {
context.push_str(&" ".repeat(col_begin));
context.push_str_with_color(
mark.repeat(cmp::max(1, code.len().saturating_sub(col_begin))),
err_color,
);
} else if i == final_step {
context.push_str_with_color(mark.repeat(col_end), err_color);
} else {
context.push_str_with_color(mark.repeat(cmp::max(1, code.len())), err_color);
}
context.push_str("\n");
}
let msg_num = sub_msg.len().saturating_sub(1);
for (i, msg) in sub_msg.iter().enumerate() {
context.push_str_with_color(&offset, gutter_color);
context.push_str(&" ".repeat(col_end.saturating_sub(1)));
if i == msg_num && hint.is_none() {
context.push_str_with_color(chars.left_bottom_line(), err_color);
} else {
context.push_str_with_color(chars.left_cross(), err_color);
}
context.push_str(msg);
context.push_str("\n")
}
if let Some(hint) = hint {
context.push_str_with_color(&offset, gutter_color);
context.push_str(&" ".repeat(col_end.saturating_sub(1)));
context.push_str_with_color(chars.left_bottom_line(), err_color);
context.push_str(hint);
context.push_str("\n")
}
context.to_string() + "\n"
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SubMessage {
pub loc: Location,
pub msg: Vec<String>,
pub hint: Option<String>,
}
impl SubMessage {
///
/// Used when the msg or hint is empty.
/// `msg` is type of `Vec<String>` instead of `Option<String>` because it can be used when there are multiple `msg`s as well as multiple lines.
/// # Example
/// ```
/// # use erg_common::error::{Location, SubMessage};
/// # use erg_common::style::{Color, StyledString};
/// let loc = Location::Line(1);
/// let msg = SubMessage::ambiguous_new(loc, vec![], None); // this code same as only_loc()
///
/// let hint = Some("hint message here".to_string());
/// let msg = SubMessage::ambiguous_new(loc, vec![], hint);
/// /* example
/// -------
/// `- hint message here
/// */
///
/// let hint = Some("hint here".to_string());
/// let first = StyledString::new("1th message", Some(Color::Red), None);
/// let second = StyledString::new("2th message", Some(Color::White), None);
/// let nth = StyledString::new("nth message", Some(Color::Green), None);
/// let msg = SubMessage::ambiguous_new(
/// loc,
/// vec![
/// first.to_string(),
/// second.to_string(),
/// // ...,
/// nth.to_string(),
/// ],
/// hint);
/// /* example
/// -------
/// :- 1th message
/// :- 2th message
/// :
/// :- nth message
/// `- hint here
/// */
///
/// ```
///
pub fn ambiguous_new(loc: Location, msg: Vec<String>, hint: Option<String>) -> Self {
Self { loc, msg, hint }
}
///
/// Used when only Location is fixed.
/// In this case, error position is just modified
/// # Example
/// ```
/// # use erg_common::error::{Location, SubMessage};
/// let loc = Location::Line(1);
/// let sub_msg = SubMessage::only_loc(loc);
/// ```
pub fn only_loc(loc: Location) -> Self {
Self {
loc,
msg: Vec::new(),
hint: None,
}
}
pub fn set_hint<S: Into<String>>(&mut self, hint: S) {
self.hint = Some(hint.into());
}
pub fn get_hint(&self) -> Option<&str> {
self.hint.as_deref()
}
pub fn get_msg(&self) -> &[String] {
self.msg.as_ref()
}
// Line breaks are not included except for line breaks that signify the end of a sentence.
// In other words, do not include blank lines for formatting purposes.
fn format_code_and_pointer<E: ErrorDisplay + ?Sized>(
&self,
e: &E,
err_color: Color,
gutter_color: Color,
mark: char,
chars: &Characters,
) -> String {
match self.loc.unknown_or(e.core().loc) {
Location::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => format_context(
e,
ln_begin as usize,
ln_end as usize,
col_begin as usize,
col_end as usize,
err_color,
gutter_color,
chars,
mark,
&self.msg,
self.hint.as_ref(),
),
Location::LineRange(ln_begin, ln_end) => {
let (vbreak, vbar) = chars.gutters();
let mut cxt = StyledStrings::default();
let codes = e.input().reread_lines(ln_begin as usize, ln_end as usize);
let mark = mark.to_string();
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
cxt.push_str_with_color(format!("{lineno} {vbar} "), gutter_color);
cxt.push_str(codes.get(i).unwrap_or(&String::new()));
cxt.push_str("\n");
cxt.push_str_with_color(
format!("{} {}", &" ".repeat(lineno.to_string().len()), vbreak),
gutter_color,
);
cxt.push_str(&" ".repeat(lineno.to_string().len()));
cxt.push_str_with_color(
mark.repeat(cmp::max(1, codes.get(i).map_or(1, |code| code.len()))),
err_color,
);
cxt.push_str("\n");
}
cxt.push_str("\n");
for msg in self.msg.iter() {
cxt.push_str(msg);
cxt.push_str("\n");
}
if let Some(hint) = self.hint.as_ref() {
cxt.push_str(hint);
cxt.push_str("\n");
}
cxt.to_string()
}
Location::Line(lineno) => {
let input = e.input();
let (_, vbar) = chars.gutters();
let codes = input.reread_lines(lineno as usize, lineno as usize);
let default = "???".to_string();
let code = codes.first().unwrap_or(&default);
let mut cxt = StyledStrings::default();
cxt.push_str_with_color(format!(" {lineno} {vbar} "), gutter_color);
cxt.push_str(code);
cxt.push_str("\n");
for msg in self.msg.iter() {
cxt.push_str(msg);
cxt.push_str("\n");
}
if let Some(hint) = self.hint.as_ref() {
cxt.push_str(hint);
cxt.push_str("\n");
}
cxt.push_str("\n");
cxt.to_string()
}
Location::Unknown => match &e.input().kind {
InputKind::File { .. } => "\n".to_string(),
_other => {
let (_, vbar) = chars.gutters();
let mut cxt = StyledStrings::default();
cxt.push_str_with_color(format!(" ? {vbar} "), gutter_color);
cxt.push_str(&e.input().reread());
cxt.push_str("\n");
for msg in self.msg.iter() {
cxt.push_str(msg);
cxt.push_str("\n");
}
if let Some(hint) = self.hint.as_ref() {
cxt.push_str(hint);
cxt.push_str("\n");
}
cxt.push_str("\n");
cxt.to_string()
}
},
}
}
}
/// In Erg, common parts used by error.
/// Must be wrap when to use.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ErrorCore {
pub sub_messages: Vec<SubMessage>,
pub main_message: String,
pub errno: usize,
pub kind: ErrorKind,
pub loc: Location,
theme: Theme,
}
impl fmt::Display for ErrorCore {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for ErrorCore {}
impl ErrorCore {
pub fn new<S: Into<String>>(
sub_messages: Vec<SubMessage>,
main_message: S,
errno: usize,
kind: ErrorKind,
loc: Location,
) -> Self {
Self {
sub_messages,
main_message: main_message.into(),
errno,
kind,
loc,
theme: THEME,
}
}
pub fn dummy(errno: usize) -> Self {
Self::new(
vec![SubMessage::only_loc(Location::Unknown)],
"<dummy>",
errno,
DummyError,
Location::Unknown,
)
}
pub fn unreachable(fn_name: &str, line: u32) -> Self {
Self::bug(line as usize, Location::Line(line), fn_name, line)
}
pub fn bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self {
const URL: StyledStr = StyledStr::new(
"https://github.com/erg-lang/erg",
Some(Color::White),
Some(Attribute::Underline),
);
let main_msg = switch_lang!(
"japanese" => format!("\
これはErgのバグです、開発者に報告して下さい({URL})
発生箇所: {fn_name}:{line}"),
"simplified_chinese" => format!("\
这是Erg的bug请报告给{URL}
原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("\
這是Erg的bug請報告給{URL}
原因來自: {fn_name}:{line}"),
"english" => format!("\
This is a bug of Erg, please report it to {URL}
Caused from: {fn_name}:{line}"),
);
let main_msg =
StyledStr::new(&main_msg, Some(Color::Red), Some(Attribute::Bold)).to_string();
Self::new(
vec![SubMessage::only_loc(loc)],
main_msg,
errno,
CompilerSystemError,
loc,
)
}
pub fn get_loc_with_fallback(&self) -> Location {
if self.loc == Location::Unknown {
for sub in &self.sub_messages {
if sub.loc != Location::Unknown {
return sub.loc;
}
}
Location::Unknown
} else {
self.loc
}
}
pub fn get_hint(&self) -> Option<&str> {
for sub in self.sub_messages.iter() {
if let Some(hint) = &sub.hint {
return Some(hint);
}
}
None
}
pub fn hints(&self) -> Vec<&str> {
self.sub_messages
.iter()
.filter_map(|sub| sub.hint.as_deref())
.collect()
}
pub fn mut_hints(&mut self) -> Vec<&mut String> {
self.sub_messages
.iter_mut()
.filter_map(|sub| sub.hint.as_mut())
.collect()
}
pub fn fmt_header(&self, color: Color, caused_by: &str, input: &str) -> String {
let loc = match self.loc {
Location::Range {
ln_begin, ln_end, ..
} if ln_begin == ln_end => format!(", line {ln_begin}"),
Location::Range {
ln_begin, ln_end, ..
}
| Location::LineRange(ln_begin, ln_end) => format!(", line {ln_begin}..{ln_end}"),
Location::Line(lineno) => format!(", line {lineno}"),
Location::Unknown => "".to_string(),
};
let kind = if self.kind.is_error() {
"Error"
} else if self.kind.is_warning() {
"Warning"
} else {
"Exception"
};
let kind = self.theme.characters.error_kind_format(kind, self.errno);
format!(
"{kind}: File {input}{loc}, {caused_by}",
kind = StyledStr::new(&kind, Some(color), Some(Attribute::Bold))
)
}
fn specified_theme(&self) -> (Color, char) {
let (color, mark) = if self.kind.is_error() {
self.theme.error()
} else if self.kind.is_warning() {
self.theme.warning()
} else {
self.theme.exception()
};
(color, mark)
}
}
/// format:
/// ```txt
/// Error[#{.errno}]: File {file}, line {.loc (as line)}, in {.caused_by}
///
/// {.loc (as line)}| {src}
/// {offset} : {pointer}
/// {offset} : {sub_msgs}
/// {offset} : {.hint}
///
/// {.kind}: {.desc}
///
/// ```
///
/// example:
/// ```txt
/// Error[#2223]: File <stdin>, line 1, in <module>
///
/// 1 │ 100 = i
/// · ---
/// · │─ sub_msg1: first sub message here
/// · │─ sub_msg2: second sub message here
/// · ╰─ hint: hint message here
///
/// SyntaxError: cannot assign to 100
///
/// ```
pub trait ErrorDisplay {
fn core(&self) -> &ErrorCore;
fn input(&self) -> &Input;
/// The block name the error caused.
/// This will be None if the error occurred before semantic analysis.
fn caused_by(&self) -> &str;
/// the previous error that caused this error.
fn ref_inner(&self) -> Option<&Self>;
fn write_to_stderr(&self) {
let mut stderr = stderr();
self.write_to(&mut stderr)
}
fn write_to<W: std::io::Write>(&self, w: &mut W) {
let mut writer = BufWriter::new(w);
writer.write_all(self.show().as_bytes()).unwrap();
writer.flush().unwrap();
if let Some(inner) = self.ref_inner() {
inner.write_to_stderr()
}
}
fn show(&self) -> String {
let core = self.core();
let (color, mark) = core.specified_theme();
let (gutter_color, chars) = core.theme.characters();
let mut msg = String::new();
msg += &core.fmt_header(color, self.caused_by(), self.input().kind.as_str());
msg += "\n\n";
for sub_msg in &core.sub_messages {
msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars);
}
if core.sub_messages.is_empty() {
let sub_msg = SubMessage::ambiguous_new(self.core().loc, vec![], None);
msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars);
}
msg += &core.kind.to_string();
msg += ": ";
msg += &core.main_message;
msg += "\n\n";
msg
}
/// for fmt::Display
fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let core = self.core();
let (color, mark) = core.specified_theme();
let (gutter_color, chars) = core.theme.characters();
write!(
f,
"{}\n\n",
core.fmt_header(color, self.caused_by(), self.input().kind.as_str())
)?;
for sub_msg in &core.sub_messages {
write!(
f,
"{}",
&sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars)
)?;
}
write!(f, "{}\n\n", core.main_message)?;
if let Some(inner) = self.ref_inner() {
inner.format(f)
} else {
Ok(())
}
}
}
#[macro_export]
macro_rules! impl_display_and_error {
($Strc: ident) => {
impl std::fmt::Display for $Strc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
$crate::error::ErrorDisplay::format(self, f)
}
}
impl std::error::Error for $Strc {}
};
}
pub trait MultiErrorDisplay<Item: ErrorDisplay>: Stream<Item> {
fn write_all_stderr(&self) {
for err in self.iter() {
err.write_to_stderr();
}
}
fn write_all_to(&self, w: &mut impl std::io::Write) {
for err in self.iter() {
err.write_to(w);
}
}
fn fmt_all(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for err in self.iter() {
err.format(f)?;
}
write!(f, "")
}
}