mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-30 04:44:44 +00:00
Update: use Span
and const Theme in style.rs
This commit is contained in:
parent
2e8ac2848d
commit
a16d46423f
6 changed files with 449 additions and 233 deletions
|
@ -3,16 +3,17 @@
|
|||
//! エラー処理に関する汎用的なコンポーネントを提供する
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
use std::fmt::Write as _;
|
||||
use std::io::{stderr, BufWriter, Write as _};
|
||||
|
||||
use crate::astr::AtomicStr;
|
||||
use crate::config::Input;
|
||||
use crate::style::Attribute;
|
||||
use crate::style::Characters;
|
||||
use crate::style::Color;
|
||||
use crate::style::Color::{Cyan, Green, Magenta, Red, Yellow};
|
||||
use crate::style::Spans;
|
||||
use crate::style::RESET;
|
||||
use crate::style::{ATT_RESET, UNDERLINE};
|
||||
use crate::style::StrSpan;
|
||||
use crate::style::StringSpan;
|
||||
use crate::style::StringSpans;
|
||||
use crate::style::Theme;
|
||||
use crate::traits::{Locational, Stream};
|
||||
use crate::{fmt_option, impl_display_from_debug, switch_lang};
|
||||
|
||||
|
@ -362,59 +363,83 @@ impl ErrorCore {
|
|||
}
|
||||
|
||||
pub fn bug(errno: usize, loc: Location, fn_name: &str, line: u32) -> Self {
|
||||
const URL: StrSpan = StrSpan::new(
|
||||
"https://github.com/erg-lang/erg",
|
||||
Some(Color::White),
|
||||
Some(Attribute::Underline),
|
||||
);
|
||||
|
||||
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}"),
|
||||
"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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub const VBAR_UNICODE: &str = "│";
|
||||
pub const VBAR_BREAK_UNICODE: &str = "·";
|
||||
|
||||
fn format_code_and_pointer<E: ErrorDisplay + ?Sized>(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn format_context<E: ErrorDisplay + ?Sized>(
|
||||
e: &E,
|
||||
ln_begin: usize,
|
||||
ln_end: usize,
|
||||
col_begin: usize,
|
||||
col_end: usize,
|
||||
// kinds of error color and gutter(line number and vertical bar)
|
||||
colors: (Color, Color),
|
||||
// for formatting points
|
||||
chars: &Characters,
|
||||
// kinds of error for specify the color
|
||||
mark: char,
|
||||
) -> String {
|
||||
let (err_color, gutter_color) = colors;
|
||||
let mark = mark.to_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 mut context = StringSpans::default();
|
||||
let final_step = ln_end - ln_begin;
|
||||
let max_digit = ln_end.to_string().len();
|
||||
let offset = format!("{} {} ", &" ".repeat(max_digit), chars.vbreak);
|
||||
for (i, lineno) in (ln_begin..=ln_end).enumerate() {
|
||||
let mut pointer = " ".repeat(lineno.to_string().len() + 2); // +2 means `| `
|
||||
context.push_str_with_color(
|
||||
&format!("{:<max_digit$} {vbar} ", lineno, vbar = chars.vbar),
|
||||
gutter_color,
|
||||
);
|
||||
context.push_str(&codes[i]);
|
||||
context.push_str("\n");
|
||||
context.push_str_with_color(&offset, gutter_color);
|
||||
if i == 0 && i == final_step {
|
||||
pointer += &" ".repeat(col_begin);
|
||||
pointer += &"^".repeat(cmp::max(1, col_end.saturating_sub(col_begin)));
|
||||
context.push_str(&" ".repeat(col_begin));
|
||||
context.push_str_with_color(
|
||||
&mark.repeat(cmp::max(1, col_end.saturating_sub(col_begin))),
|
||||
err_color,
|
||||
);
|
||||
} else if i == 0 {
|
||||
pointer += &" ".repeat(col_begin);
|
||||
pointer += &"^".repeat(cmp::max(1, codes[i].len().saturating_sub(col_begin)));
|
||||
context.push_str(&" ".repeat(col_begin));
|
||||
context.push_str_with_color(
|
||||
&mark.repeat(cmp::max(1, codes[i].len().saturating_sub(col_begin))),
|
||||
err_color,
|
||||
);
|
||||
} else if i == final_step {
|
||||
pointer += &"^".repeat(col_end);
|
||||
context.push_str_with_color(&mark.repeat(col_end), err_color);
|
||||
} else {
|
||||
pointer += &"^".repeat(cmp::max(1, codes[i].len()));
|
||||
context.push_str_with_color(&mark.repeat(cmp::max(1, codes[i].len())), err_color);
|
||||
}
|
||||
writeln!(
|
||||
res,
|
||||
"{lineno}{VBAR_UNICODE} {code}\n{pointer}",
|
||||
code = codes.get(i).unwrap_or(&String::new()),
|
||||
)
|
||||
.unwrap();
|
||||
context.push_str("\n");
|
||||
}
|
||||
res + RESET
|
||||
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()
|
||||
}
|
||||
|
||||
/// format:
|
||||
|
@ -435,6 +460,8 @@ fn format_code_and_pointer<E: ErrorDisplay + ?Sized>(
|
|||
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.
|
||||
|
@ -457,26 +484,85 @@ pub trait ErrorDisplay {
|
|||
}
|
||||
|
||||
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)
|
||||
)
|
||||
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 = StringSpan::new(
|
||||
&theme.error_kind_format(kind, self.core().errno),
|
||||
Some(color),
|
||||
Some(Attribute::Bold),
|
||||
);
|
||||
|
||||
if let Some(hint) = self.core().hint.as_ref() {
|
||||
let (hint_color, _) = theme.hint();
|
||||
let mut hints = StringSpans::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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 (hint, _) = theme.hint();
|
||||
let (gutter_color, chars) = theme.characters();
|
||||
let context = self.format_code_and_pointer((color, gutter_color), mark, chars);
|
||||
let kind = StringSpan::new(
|
||||
&theme.error_kind_format(kind, self.core().errno),
|
||||
Some(color),
|
||||
Some(Attribute::Bold),
|
||||
);
|
||||
writeln!(
|
||||
f,
|
||||
"{}{}{}: {}{}",
|
||||
self.format_header(),
|
||||
self.format_code_and_pointer(),
|
||||
"\
|
||||
{}
|
||||
{}{}: {}
|
||||
{}",
|
||||
self.format_header(kind),
|
||||
context,
|
||||
self.core().kind,
|
||||
self.core().desc,
|
||||
fmt_option!(pre format!("\n{GREEN}hint{RESET}: "), self.core().hint),
|
||||
fmt_option!(pre StrSpan::new("hint: ", Some(hint), Some(Attribute::Bold)), self.core().hint)
|
||||
)?;
|
||||
if let Some(inner) = self.ref_inner() {
|
||||
inner.format(f)
|
||||
|
@ -485,14 +571,7 @@ pub trait ErrorDisplay {
|
|||
}
|
||||
}
|
||||
|
||||
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")
|
||||
};
|
||||
fn format_header(&self, kind: StringSpan) -> String {
|
||||
let loc = match self.core().loc {
|
||||
Location::Range {
|
||||
ln_begin, ln_end, ..
|
||||
|
@ -515,13 +594,17 @@ pub trait ErrorDisplay {
|
|||
"".to_string()
|
||||
};
|
||||
format!(
|
||||
"{color}{err_or_warn}[#{errno:>04}]{RESET}: File {input}{loc}{caused_by}\n",
|
||||
errno = self.core().errno,
|
||||
"{kind}: File {input}{loc}{caused_by}\n",
|
||||
input = self.input().enclosed_name(),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_code_and_pointer(&self) -> String {
|
||||
fn format_code_and_pointer(
|
||||
&self,
|
||||
colors: (Color, Color),
|
||||
mark: char,
|
||||
chars: &Characters,
|
||||
) -> String {
|
||||
match self.core().loc {
|
||||
Location::RangePair {
|
||||
ln_first,
|
||||
|
@ -529,13 +612,25 @@ pub trait ErrorDisplay {
|
|||
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(
|
||||
format_context(
|
||||
self,
|
||||
ln_first.0,
|
||||
ln_first.1,
|
||||
col_first.0,
|
||||
col_first.1,
|
||||
colors,
|
||||
chars,
|
||||
mark,
|
||||
) +
|
||||
+ &format_context(
|
||||
self,
|
||||
ln_second.0,
|
||||
ln_second.1,
|
||||
col_second.0,
|
||||
col_second.1,
|
||||
colors,
|
||||
chars,
|
||||
mark,
|
||||
)
|
||||
}
|
||||
Location::Range {
|
||||
|
@ -543,40 +638,49 @@ pub trait ErrorDisplay {
|
|||
col_begin,
|
||||
ln_end,
|
||||
col_end,
|
||||
} => format_code_and_pointer(self, ln_begin, ln_end, col_begin, col_end),
|
||||
} => format_context(
|
||||
self, ln_begin, ln_end, col_begin, col_end, colors, chars, mark,
|
||||
),
|
||||
Location::LineRange(ln_begin, ln_end) => {
|
||||
let (err_color, gutter_color) = colors;
|
||||
let mut cxt = StringSpans::default();
|
||||
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();
|
||||
let mark = mark.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();
|
||||
cxt.push_str_with_color(&format!("{lineno} {}", chars.vbar), err_color);
|
||||
cxt.push_str(&codes[i]);
|
||||
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)
|
||||
}
|
||||
res + RESET
|
||||
cxt.to_string()
|
||||
}
|
||||
Location::Line(lineno) => {
|
||||
let (_, gutter_color) = colors;
|
||||
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}")
|
||||
let mut cxt = StringSpans::default();
|
||||
cxt.push_str_with_color(&format!(" {lineno} {} ", chars.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 => format!(
|
||||
"{CYAN}?{VBAR_UNICODE} {code}\n{RESET}",
|
||||
code = other.reread()
|
||||
),
|
||||
|
||||
other => {
|
||||
let (_, gutter_color) = colors;
|
||||
let mut cxt = StringSpans::default();
|
||||
cxt.push_str_with_color(&format!(" ? {}", chars.vbar), gutter_color);
|
||||
cxt.push_str(&other.reread());
|
||||
cxt.to_string()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue