//! 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: &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 { 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 { 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 { match self { Self::RangePair { col_first: (col_begin, _), .. } | Self::Range { col_begin, .. } => Some(*col_begin), _ => None, } } pub const fn col_end(&self) -> Option { 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, } impl ErrorCore { pub fn new>( errno: usize, kind: ErrorKind, loc: Location, desc: S, hint: Option, ) -> Self { Self { errno, kind, loc, desc: desc.into(), hint, } } pub fn dummy(errno: usize) -> Self { Self::new( errno, DummyError, Location::Line(errno as usize), "", 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: &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 , line 1, in /// 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(&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: Stream { 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, "") } }