Update: use Span and const Theme in style.rs

This commit is contained in:
GreasySlug 2022-11-12 18:03:10 +09:00
parent 2e8ac2848d
commit a16d46423f
6 changed files with 449 additions and 233 deletions

View file

@ -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()
}
},
}
}