erg/compiler/erg_common/error.rs
2022-10-21 21:28:44 +09:00

607 lines
19 KiB
Rust
Raw 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;
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, "")
}
}