Merge pull request #232 from erg-lang/split-err-msg

Split error messages
This commit is contained in:
Shunsuke Shibayama 2022-11-23 15:55:19 +09:00 committed by GitHub
commit 2caa6b6ec9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1652 additions and 1006 deletions

View file

@ -10,9 +10,9 @@ use crate::style::Attribute;
use crate::style::Characters;
use crate::style::Color;
use crate::style::StyledStr;
use crate::style::StyledString;
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};
@ -228,37 +228,6 @@ impl From<&str> for ErrorKind {
///
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Location {
///
/// Error used when the error is caused by a discrepancy with a code on another line
///
/// # Example
///
/// Ownership error
///
/// ```erg
/// a: Nat = 1
/// a.consume_ownership() // move occurs
///
/// function(a) // borrowed after moved
/// ```
///
/// `a` moves ownership in a method(or function) that are defined and consume it.
///
/// ```erg
/// Location::RangePair {
/// ln_first: (2, 2),
/// col_first: (0, 1),
/// ln_second: (4, 4),
/// col_second: (9, 10),
/// }
/// ```
///
RangePair {
ln_first: (usize, usize),
col_first: (usize, usize),
ln_second: (usize, usize),
col_second: (usize, usize),
},
///
/// Location used for basic errors
/// ```erg
@ -310,128 +279,39 @@ impl Location {
}
}
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::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::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),
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),
Self::Range { col_end, .. } => Some(*col_end),
_ => None,
}
}
}
/// In Erg, common parts used by error.
/// Must be wrap when to use.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ErrorCore {
pub errno: usize,
pub kind: ErrorKind,
pub loc: Location,
pub desc: String,
pub hint: Option<String>,
}
impl ErrorCore {
pub fn new<S: Into<String>>(
errno: usize,
kind: ErrorKind,
loc: Location,
desc: S,
hint: Option<String>,
) -> 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 {
const URL: StyledStr = StyledStr::new(
"https://github.com/erg-lang/erg",
Some(Color::White),
Some(Attribute::Underline),
);
Self::new(
errno,
CompilerSystemError,
loc,
switch_lang!(
"japanese" => format!("これはErgのバグです、開発者に報告して下さい({URL})\n{fn_name}:{line}より発生"),
"simplified_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"english" => format!("this is a bug of Erg, please report it to {URL}\ncaused from: {fn_name}:{line}"),
),
None,
)
}
}
#[allow(clippy::too_many_arguments)]
fn format_context<E: ErrorDisplay + ?Sized>(
e: &E,
@ -445,6 +325,8 @@ fn format_context<E: ErrorDisplay + ?Sized>(
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 = if e.input().is_repl() {
@ -484,20 +366,326 @@ fn format_context<E: ErrorDisplay + ?Sized>(
}
context.push_str("\n");
}
context.push_str_with_color(&offset, gutter_color);
context.push_str(&" ".repeat(col_end - 1));
context.push_str_with_color(&chars.left_bottom_line(), err_color);
context.to_string()
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 - 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 - 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,
msg: Vec<String>,
hint: Option<String>,
}
impl SubMessage {
///
/// Used when the msg or hint si empty.
/// `msg` is Vec\<String\> instead of Option\<String\> because it can be used when there are multiple `msg`s as well as multiple lines.
/// # Example
/// ```
/// 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", Color::Red, None);
/// let second = StyledString::new("2th message", Color::White, None);
/// :
/// let nth = StyledString::new("nth message", 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
/// ```
/// 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<String> {
self.hint
}
// 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 {
Location::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => format_context(
e,
ln_begin,
ln_end,
col_begin,
col_end,
err_color,
gutter_color,
chars,
mark,
&self.msg,
self.hint.as_ref(),
),
Location::LineRange(ln_begin, ln_end) => {
let input = e.input();
let (vbreak, vbar) = chars.gutters();
let mut cxt = StyledStrings::default();
let codes = if input.is_repl() {
vec![input.reread()]
} else {
input.reread_lines(ln_begin, ln_end)
};
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[i]);
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[i].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 code = if input.is_repl() {
input.reread()
} else {
input.reread_lines(lineno, lineno).remove(0)
};
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() {
Input::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(&other.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 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::Line(errno as usize))],
"<dummy>",
errno,
DummyError,
Location::Line(errno as usize),
)
}
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 {
const URL: StyledStr = StyledStr::new(
"https://github.com/erg-lang/erg",
Some(Color::White),
Some(Attribute::Underline),
);
let m_msg = switch_lang!(
"japanese" => format!("これはErgのバグです、開発者に報告して下さい({URL})\n{fn_name}:{line}より発生"),
"simplified_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("这是Erg的bug请报告给{URL}\n原因来自: {fn_name}:{line}"),
"english" => format!("this is a bug of Erg, please report it to {URL}\ncaused from: {fn_name}:{line}"),
);
Self::new(
vec![SubMessage::only_loc(loc)],
&m_msg,
errno,
CompilerSystemError,
loc,
)
}
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}
/// {pointer}
/// {.kind}: {.desc}
///
/// {.hint}
/// {.loc (as line)}| {src}
/// {offset} : {pointer}
/// {offset} : {sub_msgs}
/// {offset} : {.hint}
///
/// {.kind}: {.desc}
///
/// ```
///
@ -505,21 +693,20 @@ fn format_context<E: ErrorDisplay + ?Sized>(
/// ```txt
/// Error[#2223]: File <stdin>, line 1, in <module>
///
/// 1 | 100 = i
/// ---
/// ╰─ SyntaxError: cannot assign to 100
/// 1 │ 100 = i
/// · ---
/// · │─ sub_msg1: first sub message here
/// · │─ sub_msg2: second sub message here
/// · ╰─ hint: hint message here
///
/// hint: hint message here
/// SyntaxError: cannot assign to 100
///
/// ```
pub trait ErrorDisplay {
fn core(&self) -> &ErrorCore;
fn input(&self) -> &Input;
/// Colors and indication char for each type(error, warning, exception)
fn theme(&self) -> &Theme;
/// 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>;
@ -539,248 +726,44 @@ pub trait ErrorDisplay {
}
fn show(&self) -> String {
let theme = self.theme();
let ((color, mark), kind) = if self.core().kind.is_error() {
(theme.error(), "Error")
} else if self.core().kind.is_warning() {
(theme.warning(), "Warning")
} else {
(theme.exception(), "Exception")
};
let (gutter_color, chars) = theme.characters();
let kind = StyledString::new(
&chars.error_kind_format(kind, self.core().errno),
Some(color),
Some(Attribute::Bold),
);
// When hint is None, hint desc is "" and empty line is displayed, but hint is Some(...), hint desc is "..." and filled by text
if let Some(hint) = self.core().hint.as_ref() {
let (hint_color, _) = theme.hint();
let mut hints = StyledStrings::default();
hints.push_str_with_color_and_attribute("hint: ", hint_color, Attribute::Bold);
hints.push_str(hint);
format!(
"\
{}
{}{}: {}
{}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
hints,
)
} else {
format!(
"\
{}
{}{}: {}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
)
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().enclosed_name());
msg += "\n\n";
for sub_msg in &core.sub_messages {
msg += &sub_msg.format_code_and_pointer(self, color, gutter_color, mark, chars);
}
msg += &core.main_message;
msg += "\n\n";
msg
}
/// for fmt::Display
fn format(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let theme = self.theme();
let ((color, mark), kind) = if self.core().kind.is_error() {
(theme.error(), "Error")
} else if self.core().kind.is_warning() {
(theme.warning(), "Warning")
} else {
(theme.exception(), "Exception")
};
let (gutter_color, chars) = theme.characters();
let kind = StyledString::new(
&chars.error_kind_format(kind, self.core().errno),
Some(color),
Some(Attribute::Bold),
);
// When hint is None, hint desc is "" and empty line is displayed, but hint is Some(...), hint desc is "..." and filled by text
if let Some(hint) = self.core().hint.as_ref() {
let (hint_color, _) = theme.hint();
let mut hints = StyledStrings::default();
hints.push_str_with_color_and_attribute("hint: ", hint_color, Attribute::Bold);
hints.push_str(hint);
writeln!(
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().enclosed_name())
)?;
for sub_msg in &core.sub_messages {
write!(
f,
"\
{}
{}{}: {}
{}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
hints,
)?;
} else {
writeln!(
f,
"\
{}
{}{}: {}
",
self.format_header(kind),
self.format_code_and_pointer(color, gutter_color, mark, chars),
self.core().kind,
self.core().desc,
"{}",
&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(())
}
}
fn format_header(&self, kind: StyledString) -> String {
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!(
"{kind}: File {input}{loc}{caused_by}\n",
input = self.input().enclosed_name(),
)
}
fn format_code_and_pointer(
&self,
err_color: Color,
gutter_color: Color,
mark: char,
chars: &Characters,
) -> String {
match self.core().loc {
// TODO: Current implementation does not allow for multiple descriptions of errors to be given at each location
// In the future, this will be implemented in a different structure that can handle multiple lines and files
Location::RangePair {
ln_first,
col_first,
ln_second,
col_second,
} => {
format_context(
self,
ln_first.0,
ln_first.1,
col_first.0,
col_first.1,
err_color,
gutter_color,
chars,
mark,
) +
"\n" // TODO: dealing with error chains
+ &format_context(
self,
ln_second.0,
ln_second.1,
col_second.0,
col_second.1,
err_color,
gutter_color,
chars,
mark,
)
}
Location::Range {
ln_begin,
col_begin,
ln_end,
col_end,
} => format_context(
self,
ln_begin,
ln_end,
col_begin,
col_end,
err_color,
gutter_color,
chars,
mark,
),
Location::LineRange(ln_begin, ln_end) => {
let (_, vbar) = chars.gutters();
let mut cxt = StyledStrings::default();
let codes = if self.input().is_repl() {
vec![self.input().reread()]
} else {
self.input().reread_lines(ln_begin, ln_end)
};
let mark = mark.to_string();
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
cxt.push_str_with_color(&format!("{lineno} {}", vbar), err_color);
cxt.push_str(&codes[i]);
cxt.push_str("\n");
cxt.push_str(&" ".repeat(lineno.to_string().len() + 3)); // +3 means ` | `
cxt.push_str_with_color(
&mark.repeat(cmp::max(1, codes[i].len())),
gutter_color,
);
cxt.push_str("\n");
}
cxt.to_string()
}
Location::Line(lineno) => {
let (_, vbar) = chars.gutters();
let code = if self.input().is_repl() {
self.input().reread()
} else {
self.input().reread_lines(lineno, lineno).remove(0)
};
let mut cxt = StyledStrings::default();
cxt.push_str_with_color(&format!(" {lineno} {} ", vbar), gutter_color);
cxt.push_str(&code);
cxt.push_str("\n");
cxt.to_string()
}
Location::Unknown => match self.input() {
Input::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(&other.reread());
cxt.to_string()
}
},
}
}
}
#[macro_export]

View file

@ -1,4 +1,4 @@
use std::borrow::Borrow;
use std::borrow::{Borrow, Cow};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{Add, Deref};
@ -61,6 +61,15 @@ impl From<Str> for String {
}
}
impl<'a> From<Str> for Cow<'a, str> {
fn from(s: Str) -> Self {
match s {
Str::Static(s) => Cow::Owned(s.to_owned()),
_ => unreachable!(),
}
}
}
// &'static str -> &strになってしまわないように
// あえて`impl<S: Into<Str>> From<S> for Str { ... }`はしない
impl From<&'static str> for Str {

View file

@ -1,3 +1,5 @@
use std::borrow::Cow;
pub const ATTR_RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const UNDERLINE: &str = "\x1b[4m";
@ -15,15 +17,15 @@ pub const RED: &str = "\x1b[91m";
pub const WHITE: &str = "\x1b[97m";
pub const YELLOW: &str = "\x1b[93m";
// custom colors when use `pretty`
pub const CUSTOM_RED: &str = "\x1b[38;2;185;64;71m";
pub const CUSTOM_BLUE: &str = "\x1b[38;2;230;234;227m";
pub const CUSTOM_GRAY: &str = "\x1b[38;2;244;0;25m";
pub const CUSTOM_CYAN: &str = "\x1b[38;2;160;216;239m";
pub const CUSTOM_MAGENTA: &str = "\x1b[38;2;103;65;150m";
pub const CUSTOM_GREEN: &str = "\x1b[38;2;170;209;71m";
pub const CUSTOM_YELLOW: &str = "\x1b[38;2;230;180;34m";
pub const CUSTOM_RED: &str = "\x1b[38;2;255;76;76m";
pub const CUSTOM_BLUE: &str = "\x1b[38;2;76;76;255m";
pub const CUSTOM_GRAY: &str = "\x1b[38;2;231;231;235m";
pub const CUSTOM_CYAN: &str = "\x1b[38;2;76;255;255m";
pub const CUSTOM_MAGENTA: &str = "\x1b[38;2;165;76;255m";
pub const CUSTOM_GREEN: &str = "\x1b[38;2;76;255;76m";
pub const CUSTOM_YELLOW: &str = "\x1b[38;2;255;255;76m";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub enum Color {
Reset,
Black,
@ -68,7 +70,7 @@ impl Color {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
pub enum Attribute {
Reset,
Underline,
@ -87,13 +89,14 @@ impl Attribute {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ThemeColors {
pub error: Color,
pub warning: Color,
pub exception: Color,
pub gutter: Color,
pub hint: Color,
pub accent: Color,
}
#[cfg(not(feature = "pretty"))]
@ -103,6 +106,7 @@ pub const COLORS: ThemeColors = ThemeColors {
exception: Color::Magenta,
gutter: Color::Cyan,
hint: Color::Green,
accent: Color::White,
};
#[cfg(feature = "pretty")]
@ -112,9 +116,10 @@ pub const COLORS: ThemeColors = ThemeColors {
exception: Color::CustomMagenta,
gutter: Color::CustomCyan,
hint: Color::CustomGreen,
accent: Color::CustomGray,
};
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Characters {
hat: char, // error
wave: char, // exception
@ -141,10 +146,10 @@ impl Characters {
(self.vbreak, self.vbar)
}
// " `- "
// "`- "
#[cfg(not(feature = "unicode"))]
pub fn left_bottom_line(&self) -> String {
format!(" {}{} ", self.lbot, self.line)
format!("{}{} ", self.lbot, self.line)
}
// `╰─ `
@ -153,6 +158,18 @@ impl Characters {
format!("{}{} ", self.lbot, self.line)
}
// "|- "
#[cfg(not(feature = "unicode"))]
pub fn left_cross(&self) -> String {
format!("{}{} ", self.vbar, self.line)
}
// "│─ "
#[cfg(feature = "unicode")]
pub fn left_cross(&self) -> String {
format!("{}{} ", self.vbar, self.line)
}
// kind[padded error number]
#[cfg(not(feature = "pretty"))]
pub fn error_kind_format(&self, kind: &str, err_num: usize) -> String {
@ -177,7 +194,7 @@ impl Characters {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Theme {
pub colors: ThemeColors,
pub characters: Characters,
@ -254,11 +271,7 @@ pub struct StyledStr<'a> {
}
impl<'a> StyledStr<'a> {
pub const fn new<'b: 'a>(
text: &'b str,
color: Option<Color>,
attribute: Option<Attribute>,
) -> Self {
pub const fn new(text: &'a str, color: Option<Color>, attribute: Option<Attribute>) -> Self {
Self {
text,
color,
@ -300,9 +313,22 @@ pub struct StyledString {
}
impl StyledString {
pub fn new(s: &str, color: Option<Color>, attribute: Option<Attribute>) -> Self {
///
/// # Example
/// ```
/// let s = String::from("Hello, world");
/// StyledString::new(s, None, None);
/// let s = "Hello, world";
/// StyledString::new(s, None, None);
/// ```
pub fn new<'a, S: Into<Cow<'a, str>>>(
s: S,
color: Option<Color>,
attribute: Option<Attribute>,
) -> Self {
let text: Cow<'a, str> = s.into();
Self {
text: String::from(s),
text: text.into_owned(),
color,
attribute,
}
@ -320,7 +346,11 @@ impl StyledString {
/// println!("{text}"); // Two lines of text underlined are displayed
/// ```
pub fn push_str(&mut self, s: &str) {
self.text.push_str(s);
self.text.push_str(s)
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
}
@ -405,9 +435,10 @@ impl StyledStrings {
/// texts.push_str_with_color("\n If you want to add break lines, you should add `\n`.", Color::Magenta);
/// println!("{}", texts);
/// ```
pub fn push_str_with_color(&mut self, s: &str, color: Color) {
pub fn push_str_with_color<'a, S: Into<Cow<'a, str>>>(&mut self, s: S, color: Color) {
if self.is_same_color(color) {
self.texts.last_mut().unwrap().text.push_str(s);
let text = s.into();
self.texts.last_mut().unwrap().text.push_str(&text);
} else {
self.texts.push(StyledString::new(s, Some(color), None));
}
@ -426,23 +457,37 @@ impl StyledStrings {
/// // texts.push_str_with_color_and_attribute("Must be specify the color and attribute", None, Attribute::Underline);
/// println!("{}", texts);
/// ```
pub fn push_str_with_color_and_attribute(&mut self, s: &str, color: Color, attr: Attribute) {
pub fn push_str_with_color_and_attribute<'a, S: Into<Cow<'a, str>>>(
&mut self,
s: S,
color: Color,
attr: Attribute,
) {
if self.is_same_color(color) && self.is_same_attribute(attr) {
self.texts.last_mut().unwrap().text.push_str(s);
let text = s.into();
self.texts.last_mut().unwrap().text.push_str(&text);
} else {
self.texts
.push(StyledString::new(s, Some(color), Some(attr)));
}
}
pub fn is_same_color(&self, color: Color) -> bool {
///
/// Determine if all strings in Vec are empty
/// Returns False if any string is present.
///
pub fn is_empty(&self) -> bool {
self.texts.iter().all(|s| s.is_empty())
}
fn is_same_color(&self, color: Color) -> bool {
if let Some(text) = self.texts.last() {
return text.color == Some(color);
}
false
}
pub fn is_same_attribute(&self, attr: Attribute) -> bool {
fn is_same_attribute(&self, attr: Attribute) -> bool {
if let Some(text) = self.texts.last() {
if let Some(text_attr) = text.attribute {
return text_attr == attr;
@ -461,6 +506,12 @@ impl std::fmt::Display for StyledStrings {
}
}
impl From<StyledStrings> for String {
fn from(s: StyledStrings) -> Self {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -513,10 +564,15 @@ mod tests {
Attribute::Bold,
);
texts.push_str_with_color_and_attribute(
"White and underlined text",
Color::White,
"Blue and underlined text\n",
Color::Blue,
Attribute::Underline,
);
texts.push_str_with_color_and_attribute(
"Red and reversed text",
Color::Red,
Attribute::Reversed,
);
println!("{}", texts);
}
}

View file

@ -477,12 +477,7 @@ pub trait Locational {
fn ln_begin(&self) -> Option<usize> {
match self.loc() {
Location::RangePair {
ln_first: (ln_begin, _),
..
}
| Location::Range { ln_begin, .. }
| Location::LineRange(ln_begin, _) => Some(ln_begin),
Location::Range { ln_begin, .. } | Location::LineRange(ln_begin, _) => Some(ln_begin),
Location::Line(lineno) => Some(lineno),
Location::Unknown => None,
}
@ -490,12 +485,7 @@ pub trait Locational {
fn ln_end(&self) -> Option<usize> {
match self.loc() {
Location::RangePair {
ln_second: (_, ln_end),
..
}
| Location::Range { ln_end, .. }
| Location::LineRange(_, ln_end) => Some(ln_end),
Location::Range { ln_end, .. } | Location::LineRange(_, ln_end) => Some(ln_end),
Location::Line(lineno) => Some(lineno),
Location::Unknown => None,
}

View file

@ -241,7 +241,8 @@ impl Context {
match subr {
ConstSubr::User(_user) => todo!(),
ConstSubr::Builtin(builtin) => builtin.call(args, self).map_err(|mut e| {
e.0.loc = loc;
// TODO: Is it possible to get 0?
e.0.sub_messages.get_mut(0).unwrap().loc = loc;
EvalErrors::from(EvalError::new(
*e.0,
self.cfg.input.clone(),

View file

@ -6,29 +6,39 @@ use crate::context::Context;
use crate::ty::constructors::{and, mono};
use crate::ty::value::{EvalValueResult, GenTypeObj, TypeObj, ValueObj};
use crate::ty::ValueArgs;
use erg_common::error::{ErrorCore, ErrorKind, Location};
use erg_common::style::{RED, RESET, YELLOW};
use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
use erg_common::style::{Color, StyledStr, StyledString, THEME};
const ERR: Color = THEME.colors.error;
const WARN: Color = THEME.colors.warning;
const SUP_ERR: StyledStr = StyledStr::new("Super", Some(ERR), None);
const SUP_WARN: StyledStr = StyledStr::new("Super", Some(WARN), None);
const CLASS_ERR: StyledStr = StyledStr::new("Class", Some(ERR), None);
const REQ_ERR: StyledStr = StyledStr::new("Requirement", Some(ERR), None);
const REQ_WARN: StyledStr = StyledStr::new("Requirement", Some(WARN), None);
/// Requirement: Type, Impl := Type -> ClassType
pub fn class_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let require = args.remove_left_or_key("Requirement").ok_or_else(|| {
ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{REQ_ERR} is not passed"),
line!() as usize,
ErrorKind::TypeError,
Location::Unknown,
format!("{RED}Requirement{RESET} is not passed"),
None,
)
})?;
let Some(require) = require.as_type() else {
let require = StyledString::new(&format!("{}", require), Some(ERR), None);
return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-type object {require} is passed to {REQ_WARN}",
),
line!() as usize,
ErrorKind::TypeError,
Location::Unknown,
format!(
"non-type object {RED}{require}{RESET} is passed to {YELLOW}Requirement{RESET}",
),
None,
).into());
};
let impls = args.remove_left_or_key("Impl");
@ -40,23 +50,25 @@ pub fn class_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueOb
/// Super: ClassType, Impl := Type, Additional := Type -> ClassType
pub fn inherit_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let sup = args.remove_left_or_key("Super").ok_or_else(|| {
let sup = StyledStr::new("Super", Some(ERR), None);
ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{sup} is not passed"),
line!() as usize,
ErrorKind::KeyError,
Location::Unknown,
format!("{RED}Super{RESET} is not passed"),
None,
)
})?;
let Some(sup) = sup.as_type() else {
let sup_ty = StyledString::new(&format!("{}", sup), Some(ERR), None);
return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-class object {sup_ty} is passed to {SUP_WARN}",
),
line!() as usize,
ErrorKind::TypeError,
Location::Unknown,
format!(
"non-class object {RED}{sup}{RESET} is passed to {YELLOW}Super{RESET}",
),
None,
).into());
};
let impls = args.remove_left_or_key("Impl");
@ -74,11 +86,11 @@ pub fn inherit_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<Value
pub fn inheritable_func(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult<ValueObj> {
let class = args.remove_left_or_key("Class").ok_or_else(|| {
ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{CLASS_ERR} is not passed"),
line!() as usize,
ErrorKind::KeyError,
Location::Unknown,
format!("{RED}Class{RESET} is not passed"),
None,
)
})?;
match class {
@ -106,22 +118,23 @@ pub fn inheritable_func(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult<
pub fn trait_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let require = args.remove_left_or_key("Requirement").ok_or_else(|| {
ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{REQ_ERR} is not passed"),
line!() as usize,
ErrorKind::KeyError,
Location::Unknown,
format!("{RED}Requirement{RESET} is not passed"),
None,
)
})?;
let Some(require) = require.as_type() else {
let require = StyledString::new(&format!("{}", require), Some(ERR), None);
return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-type object {require} is passed to {REQ_WARN}",
),
line!() as usize,
ErrorKind::TypeError,
Location::Unknown,
format!(
"non-type object {RED}{require}{RESET} is passed to {YELLOW}Requirement{RESET}",
),
None,
).into());
};
let impls = args.remove_left_or_key("Impl");
@ -134,22 +147,23 @@ pub fn trait_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueOb
pub fn subsume_func(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<ValueObj> {
let sup = args.remove_left_or_key("Super").ok_or_else(|| {
ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{SUP_ERR} is not passed"),
line!() as usize,
ErrorKind::KeyError,
Location::Unknown,
format!("{RED}Super{RESET} is not passed"),
None,
)
})?;
let Some(sup) = sup.as_type() else {
let sup = StyledString::new(&format!("{}", sup), Some(ERR), None);
return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"non-trait object {sup} is passed to {SUP_WARN}",
),
line!() as usize,
ErrorKind::TypeError,
Location::Unknown,
format!(
"non-trait object {RED}{sup}{RESET} is passed to {YELLOW}Super{RESET}",
),
None,
).into());
};
let impls = args.remove_left_or_key("Impl");
@ -171,16 +185,16 @@ pub fn __array_getitem__(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<
Ok(v.clone())
} else {
Err(ErrorCore::new(
line!() as usize,
ErrorKind::IndexError,
Location::Unknown,
vec![SubMessage::only_loc(Location::Unknown)],
format!(
"[{}] has {} elements, but accessed {}th element",
erg_common::fmt_vec(&slf),
slf.len(),
index
),
None,
line!() as usize,
ErrorKind::IndexError,
Location::Unknown,
)
.into())
}
@ -210,11 +224,11 @@ pub fn __dict_getitem__(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<V
Ok(v.clone())
} else {
Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("{slf} has no key {index}"),
line!() as usize,
ErrorKind::IndexError,
Location::Unknown,
format!("{slf} has no key {index}"),
None,
)
.into())
}
@ -235,11 +249,11 @@ pub fn __range_getitem__(mut args: ValueArgs, _ctx: &Context) -> EvalValueResult
Ok(ValueObj::Nat(start + index))
} else {
Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("Index out of range: {}", index),
line!() as usize,
ErrorKind::IndexError,
Location::Unknown,
format!("Index out of range: {index}"),
None,
)
.into())
}

View file

@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
use erg_common::config::Input;
use erg_common::env::erg_pystd_path;
use erg_common::error::{ErrorCore, ErrorKind, Location};
use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
use erg_common::levenshtein::get_similar_name;
use erg_common::set::Set;
use erg_common::traits::{Locational, Stream};
@ -756,13 +756,21 @@ impl Context {
TyCheckErrors::new(
errs.into_iter()
.map(|e| {
// HACK: dname.loc()はダミーLocationしか返さないので、エラーならop.loc()で上書きする
let mut sub_msges = Vec::new();
for sub_msg in e.core.sub_messages {
sub_msges.push(SubMessage::ambiguous_new(
// HACK: dname.loc()はダミーLocationしか返さないので、エラーならop.loc()で上書きする
bin.loc(),
vec![],
sub_msg.get_hint(),
));
}
let core = ErrorCore::new(
sub_msges,
e.core.main_message,
e.core.errno,
e.core.kind,
bin.loc(),
e.core.desc,
e.core.hint,
e.core.loc,
);
TyCheckError::new(core, self.cfg.input.clone(), e.caused_by)
})
@ -797,12 +805,20 @@ impl Context {
TyCheckErrors::new(
errs.into_iter()
.map(|e| {
let mut sub_msges = Vec::new();
for sub_msg in e.core.sub_messages {
sub_msges.push(SubMessage::ambiguous_new(
unary.loc(),
vec![],
sub_msg.get_hint(),
));
}
let core = ErrorCore::new(
sub_msges,
e.core.main_message,
e.core.errno,
e.core.kind,
unary.loc(),
e.core.desc,
e.core.hint,
e.core.loc,
);
TyCheckError::new(core, self.cfg.input.clone(), e.caused_by)
})
@ -1052,7 +1068,8 @@ impl Context {
TyCheckError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
e.core.loc,
// TODO: Is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by,
&name[..],
Some(nth),
@ -1107,7 +1124,8 @@ impl Context {
TyCheckError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
e.core.loc,
// TODO: Is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by,
&name[..],
Some(nth),
@ -1164,7 +1182,8 @@ impl Context {
TyCheckError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
e.core.loc,
// TODO: Is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by,
&name[..],
Some(nth),

View file

@ -416,7 +416,8 @@ impl Context {
TyCheckError::return_type_error(
self.cfg.input.clone(),
line!() as usize,
e.core.loc,
// TODO: is it possible to get 0?
e.core.sub_messages.get(0).unwrap().loc,
e.caused_by,
readable_name(name.inspect()),
spec_ret_t,

File diff suppressed because it is too large Load diff

View file

@ -191,7 +191,7 @@ impl ASTLowerer {
"traditional_chinese" => "如果您不想使用該值請使用discard函數",
"english" => "if you don't use the value, use discard function",
)
.into(),
.to_owned(),
),
))
} else {
@ -266,7 +266,8 @@ impl ASTLowerer {
"simplified_chinese" => "数组元素必须全部是相同类型",
"traditional_chinese" => "數組元素必須全部是相同類型",
"english" => "all elements of an array must be of the same type",
),
)
.to_owned(),
Some(
switch_lang!(
"japanese" => "Int or Strなど明示的に型を指定してください",
@ -274,7 +275,7 @@ impl ASTLowerer {
"traditional_chinese" => "請明確指定類型,例如: Int or Str",
"english" => "please specify the type explicitly, e.g. Int or Str",
)
.into(),
.to_owned(),
),
)));
}
@ -412,7 +413,8 @@ impl ASTLowerer {
"simplified_chinese" => "集合元素必须全部是相同类型",
"traditional_chinese" => "集合元素必須全部是相同類型",
"english" => "all elements of a set must be of the same type",
),
)
.to_owned(),
Some(
switch_lang!(
"japanese" => "Int or Strなど明示的に型を指定してください",
@ -420,7 +422,7 @@ impl ASTLowerer {
"traditional_chinese" => "明確指定類型,例如: Int or Str",
"english" => "please specify the type explicitly, e.g. Int or Str",
)
.into(),
.to_owned(),
),
)));
}
@ -538,7 +540,8 @@ impl ASTLowerer {
"simplified_chinese" => "Dict的值必须是同一类型",
"traditional_chinese" => "Dict的值必須是同一類型",
"english" => "Values of Dict must be the same type",
),
)
.to_owned(),
Some(
switch_lang!(
"japanese" => "Int or Strなど明示的に型を指定してください",
@ -546,7 +549,7 @@ impl ASTLowerer {
"traditional_chinese" => "明確指定類型,例如: Int or Str",
"english" => "please specify the type explicitly, e.g. Int or Str",
)
.into(),
.to_owned(),
),
)));
}
@ -692,7 +695,7 @@ impl ASTLowerer {
line!() as usize,
call.args.loc(),
self.ctx.caused_by(),
"invalid assert casting type",
"invalid assert casting type".to_owned(),
None,
)));
}
@ -751,7 +754,7 @@ impl ASTLowerer {
line!() as usize,
other.loc(),
self.ctx.caused_by(),
"",
"".to_owned(),
None,
)))
}

View file

@ -5,7 +5,7 @@ use std::string::FromUtf8Error;
use erg_common::cache::CacheSet;
use erg_common::config::{ErgConfig, Input};
use erg_common::dict::Dict;
use erg_common::error::{ErrorCore, ErrorKind, Location};
use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
use erg_common::python_util::PythonVersion;
use erg_common::serialize::DataTypePrefix;
use erg_common::{fn_name, switch_lang};
@ -39,11 +39,11 @@ impl From<FromUtf8Error> for DeserializeError {
impl From<DeserializeError> for ErrorCore {
fn from(err: DeserializeError) -> Self {
ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
err.desc,
err.errno,
ErrorKind::ImportError,
Location::Unknown,
err.desc,
Option::<String>::None,
)
}
}

View file

@ -4,8 +4,10 @@
use std::fmt;
use erg_common::config::Input;
use erg_common::error::{ErrorCore, ErrorDisplay, ErrorKind::*, Location, MultiErrorDisplay};
use erg_common::style::{Attribute, Color, StyledStr, StyledString, Theme};
use erg_common::error::{
ErrorCore, ErrorDisplay, ErrorKind::*, Location, MultiErrorDisplay, SubMessage,
};
use erg_common::style::{Attribute, Color, StyledStr, StyledString, THEME};
use erg_common::traits::Stream;
use erg_common::{impl_display_and_error, impl_stream_for_wrapper, switch_lang};
@ -29,62 +31,68 @@ pub struct LexErrors(Vec<LexError>);
impl_stream_for_wrapper!(LexErrors, LexError);
const ERR: Color = THEME.colors.error;
const HINT: Color = THEME.colors.hint;
const ACCENT: Color = THEME.colors.accent;
impl LexError {
pub fn new(core: ErrorCore) -> Self {
Self(Box::new(core))
}
pub fn set_hint<S: Into<String>>(&mut self, hint: S) {
self.0.hint = Some(hint.into());
if let Some(sub_msg) = self.0.sub_messages.get_mut(0) {
sub_msg.set_hint(hint)
}
}
pub fn compiler_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(ACCENT),
Some(Attribute::Underline),
);
Self::new(ErrorCore::new(
errno,
CompilerSystemError,
loc,
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => format!("これはErg compilerのバグです、開発者に報告して下さい ({URL})\n{fn_name}:{line}より発生"),
"simplified_chinese" => format!("这是Erg编译器的一个错误请报告给{URL}\n原因来自: {fn_name}:{line}"),
"traditional_chinese" => format!("這是Erg編譯器的一個錯誤請報告給{URL}\n原因來自: {fn_name}:{line}"),
"english" => format!("this is a bug of the Erg compiler, please report it to {URL}\ncaused from: {fn_name}:{line}"),
),
None,
errno,
CompilerSystemError,
loc,
))
}
pub fn feature_error(errno: usize, loc: Location, name: &str) -> Self {
Self::new(ErrorCore::new(
errno,
FeatureError,
loc,
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => format!("この機能({name})はまだ正式に提供されていません"),
"simplified_chinese" => format!("此功能({name})尚未实现"),
"traditional_chinese" => format!("此功能({name})尚未實現"),
"english" => format!("this feature({name}) is not implemented yet"),
),
None,
errno,
FeatureError,
loc,
))
}
pub fn simple_syntax_error(errno: usize, loc: Location) -> Self {
Self::new(ErrorCore::new(
errno,
SyntaxError,
loc,
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => "不正な構文です",
"simplified_chinese" => "无效的语法",
"traditional_chinese" => "無效的語法",
"english" => "invalid syntax",
),
None,
errno,
SyntaxError,
loc,
))
}
@ -94,7 +102,13 @@ impl LexError {
desc: S,
hint: Option<String>,
) -> Self {
Self::new(ErrorCore::new(errno, SyntaxError, loc, desc, hint))
Self::new(ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], hint)],
desc,
errno,
SyntaxError,
loc,
))
}
pub fn syntax_warning<S: Into<String>>(
@ -103,7 +117,13 @@ impl LexError {
desc: S,
hint: Option<String>,
) -> Self {
Self::new(ErrorCore::new(errno, SyntaxWarning, loc, desc, hint))
Self::new(ErrorCore::new(
vec![SubMessage::ambiguous_new(loc, vec![], hint)],
desc,
errno,
SyntaxWarning,
loc,
))
}
pub fn no_var_error(
@ -113,6 +133,7 @@ impl LexError {
similar_name: Option<String>,
) -> Self {
let hint = similar_name.map(|n| {
let n = StyledString::new(&n, Some(HINT), Some(Attribute::Bold));
switch_lang!(
"japanese" => format!("似た名前の変数があります: {n}"),
"simplified_chinese" => format!("存在相同名称变量: {n}"),
@ -120,18 +141,18 @@ impl LexError {
"english" => format!("exists a similar name variable: {n}"),
)
});
let name = StyledString::new(name, Some(Color::Red), Some(Attribute::Underline));
let name = StyledString::new(name, Some(ERR), Some(Attribute::Underline));
Self::new(ErrorCore::new(
errno,
NameError,
loc,
vec![SubMessage::ambiguous_new(loc, vec![], hint)],
switch_lang!(
"japanese" => format!("{name}という変数は定義されていません"),
"simplified_chinese" => format!("{name}未定义"),
"traditional_chinese" => format!("{name}未定義"),
"english" => format!("{name} is not defined"),
),
hint,
errno,
NameError,
loc,
))
}
}
@ -164,7 +185,6 @@ pub type DesugaringResult<T> = Result<T, DesugaringError>;
pub struct ParserRunnerError {
pub core: ErrorCore,
pub input: Input,
pub theme: Theme,
}
impl_display_and_error!(ParserRunnerError);
@ -176,9 +196,6 @@ impl ErrorDisplay for ParserRunnerError {
fn input(&self) -> &Input {
&self.input
}
fn theme(&self) -> &Theme {
&self.theme
}
fn caused_by(&self) -> &str {
""
}
@ -188,8 +205,8 @@ impl ErrorDisplay for ParserRunnerError {
}
impl ParserRunnerError {
pub const fn new(core: ErrorCore, input: Input, theme: Theme) -> Self {
Self { core, input, theme }
pub const fn new(core: ErrorCore, input: Input) -> Self {
Self { core, input }
}
}
@ -209,10 +226,10 @@ impl fmt::Display for ParserRunnerErrors {
}
impl ParserRunnerErrors {
pub fn convert(input: &Input, errs: ParseErrors, theme: Theme) -> Self {
pub fn convert(input: &Input, errs: ParseErrors) -> Self {
Self(
errs.into_iter()
.map(|err| ParserRunnerError::new(*err.0, input.clone(), theme))
.map(|err| ParserRunnerError::new(*err.0, input.clone()))
.collect(),
)
}

View file

@ -4,7 +4,6 @@ use std::cmp::Ordering;
use erg_common::cache::CacheSet;
use erg_common::config::ErgConfig;
use erg_common::config::Input;
use erg_common::style::THEME;
use erg_common::traits::{Locational, Runnable, Stream};
use erg_common::{debug_power_assert, fn_name_full, normalize_newline, switch_lang};
@ -43,7 +42,7 @@ impl Runnable for LexerRunner {
let lexer = Lexer::from_str(self.input().read());
let ts = lexer
.lex()
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs, THEME))?;
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?;
println!("{ts}");
Ok(0)
}
@ -53,13 +52,13 @@ impl Runnable for LexerRunner {
if cfg!(feature = "debug") {
let ts = lexer
.lex()
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs, THEME))?;
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?;
println!("{ts}");
Ok(ts.to_string())
} else {
Ok(lexer
.lex()
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs, THEME))?
.map_err(|errs| LexerRunnerErrors::convert(self.input(), errs))?
.to_string())
}
}

View file

@ -11,7 +11,6 @@ use erg_common::error::Location;
use erg_common::option_enum_unwrap;
use erg_common::set::Set as HashSet;
use erg_common::str::Str;
use erg_common::style::THEME;
use erg_common::traits::Runnable;
use erg_common::traits::{Locational, Stream};
use erg_common::{
@ -207,16 +206,16 @@ impl ParserRunner {
pub fn parse_token_stream(&mut self, ts: TokenStream) -> Result<Module, ParserRunnerErrors> {
Parser::new(ts)
.parse()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs, THEME))
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))
}
pub fn parse(&mut self, src: String) -> Result<Module, ParserRunnerErrors> {
let ts = Lexer::new(Input::Str(src))
.lex()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs, THEME))?;
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))?;
Parser::new(ts)
.parse()
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs, THEME))
.map_err(|errs| ParserRunnerErrors::convert(self.input(), errs))
}
}

View file

@ -1,6 +1,5 @@
use erg_common::config::{ErgConfig, Input};
use erg_common::error::MultiErrorDisplay;
use erg_common::style::THEME;
use erg_common::traits::Runnable;
use erg_parser::error::ParserRunnerErrors;
@ -64,7 +63,7 @@ fn parse_test_from_code(file_path: &'static str) -> Result<(), ParserRunnerError
match parser.parse_token_stream(
lexer
.lex()
.map_err(|errs| ParserRunnerErrors::convert(&input, errs, THEME))?,
.map_err(|errs| ParserRunnerErrors::convert(&input, errs))?,
) {
Ok(module) => {
println!("{module}");