mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-28 12:14:43 +00:00
607 lines
19 KiB
Rust
607 lines
19 KiB
Rust
//! provides common components for error handling.
|
||
//!
|
||
//! エラー処理に関する汎用的なコンポーネントを提供する
|
||
use std::cmp;
|
||
use std::fmt;
|
||
use std::fmt::Write as _;
|
||
use std::io::{stderr, BufWriter, Write as _};
|
||
|
||
use crate::astr::AtomicStr;
|
||
use crate::color::*;
|
||
use crate::config::Input;
|
||
use crate::traits::{Locational, Stream};
|
||
use crate::{fmt_option, impl_display_from_debug, switch_lang};
|
||
|
||
/// ErrorKindと言っているが、Errorだけでなく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,
|
||
BytecodeError,
|
||
CompilerSystemError,
|
||
EnvironmentError,
|
||
FeatureError,
|
||
ImportError,
|
||
IndentationError,
|
||
NameError,
|
||
NotImplementedError,
|
||
PatternError,
|
||
SyntaxError,
|
||
TabError,
|
||
TypeError,
|
||
UnboundLocalError,
|
||
PurityError,
|
||
HasEffect,
|
||
MoveError,
|
||
NotConstExpr,
|
||
InheritanceError,
|
||
VisibilityError,
|
||
MethodError,
|
||
DummyError,
|
||
/* compile warnings */
|
||
AttributeWarning = 60,
|
||
CastWarning,
|
||
DeprecationWarning,
|
||
FutureWarning,
|
||
ImportWarning,
|
||
PendingDeprecationWarning,
|
||
SyntaxWarning,
|
||
TypeWarning,
|
||
NameWarning,
|
||
UnusedWarning,
|
||
Warning,
|
||
/* runtime errors */
|
||
ArithmeticError = 100,
|
||
AssertionError,
|
||
BlockingIOError,
|
||
BrokenPipeError,
|
||
BufferError,
|
||
ChildProcessError,
|
||
ConnectionAbortedError,
|
||
ConnectionError,
|
||
ConnectionRefusedError,
|
||
ConnectionResetError,
|
||
EOFError,
|
||
FileExistsError,
|
||
FileNotFoundError,
|
||
IndexError,
|
||
InterruptedError,
|
||
IoError,
|
||
IsADirectoryError,
|
||
KeyError,
|
||
LookupError,
|
||
MemoryError,
|
||
ModuleNotFoundError,
|
||
NotADirectoryError,
|
||
OSError,
|
||
OverflowError,
|
||
PermissionError,
|
||
ProcessLookupError,
|
||
RecursionError,
|
||
ReferenceError,
|
||
RuntimeAttributeError,
|
||
RuntimeError,
|
||
RuntimeTypeError,
|
||
RuntimeUnicodeError,
|
||
TimeoutError,
|
||
UnicodeError,
|
||
UserError,
|
||
ValueError,
|
||
VMSystemError,
|
||
WindowsError,
|
||
ZeroDivisionError,
|
||
/* runtime warnings */
|
||
BytesWarning = 180,
|
||
ResourceWarning,
|
||
RuntimeWarning,
|
||
UnicodeWarning,
|
||
UserWarning,
|
||
/* exceptions */
|
||
BaseException = 200,
|
||
Exception,
|
||
GeneratorExit,
|
||
KeyboardInterrupt,
|
||
StopAsyncIteration,
|
||
StopIteration,
|
||
SystemExit,
|
||
UserException,
|
||
}
|
||
|
||
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
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||
pub enum Location {
|
||
RangePair {
|
||
ln_first: (usize, usize),
|
||
col_first: (usize, usize),
|
||
ln_second: (usize, usize),
|
||
col_second: (usize, usize),
|
||
},
|
||
Range {
|
||
ln_begin: usize,
|
||
col_begin: usize,
|
||
ln_end: usize,
|
||
col_end: usize,
|
||
},
|
||
LineRange(usize, usize),
|
||
Line(usize),
|
||
#[default]
|
||
Unknown,
|
||
}
|
||
|
||
impl Location {
|
||
pub fn concat<L: Locational, R: Locational>(l: &L, r: &R) -> Self {
|
||
match (l.ln_begin(), l.col_begin(), r.ln_end(), r.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 const fn range(ln_begin: usize, col_begin: usize, ln_end: usize, col_end: usize) -> Self {
|
||
Self::Range {
|
||
ln_begin,
|
||
col_begin,
|
||
ln_end,
|
||
col_end,
|
||
}
|
||
}
|
||
|
||
pub fn pair(lhs: Self, rhs: Self) -> Self {
|
||
Self::RangePair {
|
||
ln_first: (lhs.ln_begin().unwrap(), lhs.ln_end().unwrap()),
|
||
col_first: (lhs.col_begin().unwrap(), lhs.col_end().unwrap()),
|
||
ln_second: (rhs.ln_begin().unwrap(), rhs.ln_end().unwrap()),
|
||
col_second: (rhs.col_begin().unwrap(), rhs.col_end().unwrap()),
|
||
}
|
||
}
|
||
|
||
pub const fn ln_begin(&self) -> Option<usize> {
|
||
match self {
|
||
Self::RangePair {
|
||
ln_first: (ln_begin, _),
|
||
..
|
||
}
|
||
| 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<usize> {
|
||
match self {
|
||
Self::RangePair {
|
||
ln_second: (_, ln_end),
|
||
..
|
||
}
|
||
| Self::Range { ln_end, .. }
|
||
| Self::LineRange(ln_end, _)
|
||
| Self::Line(ln_end) => Some(*ln_end),
|
||
Self::Unknown => None,
|
||
}
|
||
}
|
||
|
||
pub const fn col_begin(&self) -> Option<usize> {
|
||
match self {
|
||
Self::RangePair {
|
||
col_first: (col_begin, _),
|
||
..
|
||
}
|
||
| Self::Range { col_begin, .. } => Some(*col_begin),
|
||
_ => None,
|
||
}
|
||
}
|
||
|
||
pub const fn col_end(&self) -> Option<usize> {
|
||
match self {
|
||
Self::RangePair {
|
||
col_second: (_, col_end),
|
||
..
|
||
}
|
||
| Self::Range { col_end, .. } => Some(*col_end),
|
||
_ => None,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Erg内で使われるエラーの共通部分
|
||
/// 使用する場合は必ずwrapすること
|
||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||
pub struct ErrorCore {
|
||
pub errno: usize,
|
||
pub kind: ErrorKind,
|
||
pub loc: Location,
|
||
pub desc: AtomicStr,
|
||
pub hint: Option<AtomicStr>,
|
||
}
|
||
|
||
impl ErrorCore {
|
||
pub fn new<S: Into<AtomicStr>>(
|
||
errno: usize,
|
||
kind: ErrorKind,
|
||
loc: Location,
|
||
desc: S,
|
||
hint: Option<AtomicStr>,
|
||
) -> Self {
|
||
Self {
|
||
errno,
|
||
kind,
|
||
loc,
|
||
desc: desc.into(),
|
||
hint,
|
||
}
|
||
}
|
||
|
||
pub fn dummy(errno: usize) -> Self {
|
||
Self::new(
|
||
errno,
|
||
DummyError,
|
||
Location::Line(errno as usize),
|
||
"<dummy>",
|
||
None,
|
||
)
|
||
}
|
||
|
||
pub fn unreachable(fn_name: &str, line: u32) -> Self {
|
||
Self::bug(line as usize, Location::Line(line as usize), fn_name, line)
|
||
}
|
||
|
||
pub fn bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self {
|
||
Self::new(
|
||
errno,
|
||
CompilerSystemError,
|
||
loc,
|
||
switch_lang!(
|
||
"japanese" => format!("これはErgのバグです、開発者に報告して下さい (https://github.com/erg-lang/erg)\n{fn_name}:{line}より発生"),
|
||
"simplified_chinese" => format!("这是Erg的bug,请报告给https://github.com/erg-lang/erg\n原因来自: {fn_name}:{line}"),
|
||
"traditional_chinese" => format!("这是Erg的bug,请报告给https://github.com/erg-lang/erg\n原因来自: {fn_name}:{line}"),
|
||
"english" => format!("this is a bug of Erg, please report it to https://github.com/erg-lang/erg\ncaused from: {fn_name}:{line}"),
|
||
),
|
||
None,
|
||
)
|
||
}
|
||
}
|
||
|
||
pub const VBAR_UNICODE: &str = "│";
|
||
pub const VBAR_BREAK_UNICODE: &str = "·";
|
||
|
||
fn format_code_and_pointer<E: ErrorDisplay + ?Sized>(
|
||
e: &E,
|
||
ln_begin: usize,
|
||
ln_end: usize,
|
||
col_begin: usize,
|
||
col_end: usize,
|
||
) -> String {
|
||
let codes = if e.input().is_repl() {
|
||
vec![e.input().reread()]
|
||
} else {
|
||
e.input().reread_lines(ln_begin, ln_end)
|
||
};
|
||
let mut res = CYAN.to_string();
|
||
let final_step = ln_end - ln_begin;
|
||
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
|
||
let mut pointer = " ".repeat(lineno.to_string().len() + 2); // +2 means `| `
|
||
if i == 0 && i == final_step {
|
||
pointer += &" ".repeat(col_begin);
|
||
pointer += &"^".repeat(cmp::max(1, col_end.saturating_sub(col_begin)));
|
||
} else if i == 0 {
|
||
pointer += &" ".repeat(col_begin);
|
||
pointer += &"^".repeat(cmp::max(1, codes[i].len().saturating_sub(col_begin)));
|
||
} else if i == final_step {
|
||
pointer += &"^".repeat(col_end);
|
||
} else {
|
||
pointer += &"^".repeat(cmp::max(1, codes[i].len()));
|
||
}
|
||
writeln!(
|
||
res,
|
||
"{lineno}{VBAR_UNICODE} {code}\n{pointer}",
|
||
code = codes.get(i).unwrap_or(&String::new()),
|
||
)
|
||
.unwrap();
|
||
}
|
||
res + RESET
|
||
}
|
||
|
||
/// format:
|
||
/// ```console
|
||
/// Error[#{.errno}]: File {file}, line {.loc (as line)}, in {.caused_by}
|
||
/// {.loc (as line)}| {src}
|
||
/// {pointer}
|
||
/// {.kind}: {.desc}
|
||
/// ```
|
||
///
|
||
/// example:
|
||
/// ```console
|
||
/// Error[#12]: File <stdin>, line 1, in <module>
|
||
/// 1| 100 = i
|
||
/// ^^^
|
||
/// 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.
|
||
/// As for the internal error, do not put the fn name here.
|
||
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 {
|
||
format!(
|
||
"{}{}{}: {}{}\n",
|
||
self.format_header(),
|
||
self.format_code_and_pointer(),
|
||
self.core().kind,
|
||
self.core().desc,
|
||
fmt_option!(pre format!("\n{GREEN}hint{RESET}: "), self.core().hint)
|
||
)
|
||
}
|
||
|
||
/// for fmt::Display
|
||
fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
writeln!(
|
||
f,
|
||
"{}{}{}: {}{}",
|
||
self.format_header(),
|
||
self.format_code_and_pointer(),
|
||
self.core().kind,
|
||
self.core().desc,
|
||
fmt_option!(pre format!("\n{GREEN}hint{RESET}: "), self.core().hint),
|
||
)?;
|
||
if let Some(inner) = self.ref_inner() {
|
||
inner.format(f)
|
||
} else {
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
fn format_header(&self) -> String {
|
||
let (color, err_or_warn) = if self.core().kind.is_warning() {
|
||
(YELLOW, "Warning")
|
||
} else if self.core().kind.is_exception() {
|
||
("", "Exception")
|
||
} else {
|
||
(RED, "Error")
|
||
};
|
||
let loc = match self.core().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::RangePair {
|
||
ln_first: (l1, l2),
|
||
ln_second: (l3, l4),
|
||
..
|
||
} => format!(", line {l1}..{l2}, {l3}..{l4}"),
|
||
Location::Line(lineno) => format!(", line {lineno}"),
|
||
Location::Unknown => "".to_string(),
|
||
};
|
||
let caused_by = if self.caused_by() != "" {
|
||
format!(", in {}", self.caused_by())
|
||
} else {
|
||
"".to_string()
|
||
};
|
||
format!(
|
||
"{color}{err_or_warn}[#{errno:>04}]{RESET}: File {input}{loc}{caused_by}\n",
|
||
errno = self.core().errno,
|
||
input = self.input().enclosed_name(),
|
||
)
|
||
}
|
||
|
||
fn format_code_and_pointer(&self) -> String {
|
||
match self.core().loc {
|
||
Location::RangePair {
|
||
ln_first,
|
||
col_first,
|
||
ln_second,
|
||
col_second,
|
||
} => {
|
||
format_code_and_pointer(self, ln_first.0, ln_first.1, col_first.0, col_first.1)
|
||
+ &format_code_and_pointer(
|
||
self,
|
||
ln_second.0,
|
||
ln_second.1,
|
||
col_second.0,
|
||
col_second.1,
|
||
)
|
||
}
|
||
Location::Range {
|
||
ln_begin,
|
||
col_begin,
|
||
ln_end,
|
||
col_end,
|
||
} => format_code_and_pointer(self, ln_begin, ln_end, col_begin, col_end),
|
||
Location::LineRange(ln_begin, ln_end) => {
|
||
let codes = if self.input().is_repl() {
|
||
vec![self.input().reread()]
|
||
} else {
|
||
self.input().reread_lines(ln_begin, ln_end)
|
||
};
|
||
let mut res = CYAN.to_string();
|
||
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
|
||
let mut pointer = " ".repeat(lineno.to_string().len() + 2); // +2 means `| `
|
||
pointer += &"^".repeat(cmp::max(1, codes[i].len()));
|
||
writeln!(
|
||
res,
|
||
"{lineno}{VBAR_UNICODE} {code}\n{pointer}",
|
||
code = codes[i]
|
||
)
|
||
.unwrap();
|
||
}
|
||
res + RESET
|
||
}
|
||
Location::Line(lineno) => {
|
||
let code = if self.input().is_repl() {
|
||
self.input().reread()
|
||
} else {
|
||
self.input().reread_lines(lineno, lineno).remove(0)
|
||
};
|
||
format!("{CYAN}{lineno}{VBAR_UNICODE} {code}\n{RESET}")
|
||
}
|
||
Location::Unknown => match self.input() {
|
||
Input::File(_) => "\n".to_string(),
|
||
other => format!(
|
||
"{CYAN}?{VBAR_UNICODE} {code}\n{RESET}",
|
||
code = other.reread()
|
||
),
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
#[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 fmt_all_stderr(&self) {
|
||
for err in self.iter() {
|
||
err.write_to_stderr();
|
||
}
|
||
}
|
||
|
||
fn fmt_all(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
for err in self.iter() {
|
||
err.format(f)?;
|
||
}
|
||
write!(f, "")
|
||
}
|
||
}
|