mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 22:37:46 +01:00
377 lines
16 KiB
Rust
377 lines
16 KiB
Rust
use std::{
|
|
fmt::{Debug, Display},
|
|
rc::Rc,
|
|
sync::{atomic::AtomicUsize, Arc},
|
|
};
|
|
|
|
use colored::{ColoredString, Colorize};
|
|
use line_span::LineSpans;
|
|
|
|
#[cfg(feature = "parse")]
|
|
use crate::parsing::Source;
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct SourcePos(pub(crate) usize);
|
|
impl SourcePos {
|
|
pub fn pos(&self) -> usize {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct SourceRange {
|
|
in_file: Arc<Source>,
|
|
start: SourcePos,
|
|
end: SourcePos,
|
|
}
|
|
impl From<(SourcePos, SourcePos, &Arc<Source>)> for SourceRange {
|
|
fn from(value: (SourcePos, SourcePos, &Arc<Source>)) -> Self {
|
|
SourceRange {
|
|
in_file: Arc::clone(value.2),
|
|
start: value.0,
|
|
end: value.1,
|
|
}
|
|
}
|
|
}
|
|
impl SourceRange {
|
|
pub fn start(&self) -> SourcePos {
|
|
self.start
|
|
}
|
|
pub fn end(&self) -> SourcePos {
|
|
self.end
|
|
}
|
|
pub fn in_file(&self) -> &Arc<Source> {
|
|
&self.in_file
|
|
}
|
|
}
|
|
#[derive(Clone)]
|
|
pub struct CheckError(pub Vec<CheckErrorComponent>);
|
|
#[allow(non_upper_case_globals)]
|
|
pub mod error_colors {
|
|
use colored::Color;
|
|
|
|
pub const UnknownVariable: Color = Color::Red;
|
|
|
|
pub const WhitespaceAfterHashtag: Color = Color::Red;
|
|
pub const HashUnknown: Color = Color::Red;
|
|
pub const HashIncludeCantLoadFile: Color = Color::Red;
|
|
pub const HashIncludeNotAString: Color = Color::Red;
|
|
pub const HashIncludeErrorInIncludedFile: Color = Color::Red;
|
|
|
|
pub const BackslashEscapeUnknown: Color = Color::Red;
|
|
pub const BackslashEscapeEOF: Color = Color::Red;
|
|
pub const StringEOF: Color = Color::Red;
|
|
|
|
pub const IfConditionNotBool: Color = Color::Red;
|
|
pub const ChainWithNonFunction: Color = Color::Yellow;
|
|
|
|
pub const Function: Color = Color::BrightMagenta;
|
|
pub const FunctionArgument: Color = Color::BrightBlue;
|
|
|
|
pub const InitFrom: Color = Color::BrightCyan;
|
|
pub const InitTo: Color = Color::Green;
|
|
pub const AssignFrom: Color = InitFrom;
|
|
pub const AssignTo: Color = InitTo;
|
|
pub const AssignTargetNonReference: Color = Color::BrightYellow;
|
|
|
|
pub const AsTypeStatementWithTooBroadType: Color = InitFrom;
|
|
pub const AsTypeTypeAnnotation: Color = InitTo;
|
|
|
|
pub const BadCharInTupleType: Color = Color::Red;
|
|
pub const BadCharInFunctionType: Color = Color::Red;
|
|
pub const BadTypeFromParsed: Color = Color::Blue;
|
|
pub const TypeAnnotationNoClosingBracket: Color = Color::Blue;
|
|
}
|
|
#[derive(Clone)]
|
|
pub enum CheckErrorComponent {
|
|
Message(String),
|
|
Error(CheckError),
|
|
ErrorWithDifferentSource(CheckError),
|
|
Source(Vec<(SourceRange, Option<colored::Color>)>),
|
|
}
|
|
#[derive(Clone)]
|
|
pub struct CheckErrorHRConfig {
|
|
color_index: Rc<AtomicUsize>,
|
|
indent_start: String,
|
|
indent_default: String,
|
|
indent_end: String,
|
|
/// if true, shows "original" source code, if false, shows source with comments removed (this is what the parser uses internally)
|
|
show_comments: bool,
|
|
}
|
|
#[cfg(feature = "parse")]
|
|
pub struct CheckErrorDisplay<'a> {
|
|
e: &'a CheckError,
|
|
pub show_comments: bool,
|
|
}
|
|
#[cfg(feature = "parse")]
|
|
impl<'a> CheckErrorDisplay<'a> {
|
|
pub fn show_comments(mut self, show_comments: bool) -> Self {
|
|
self.show_comments = show_comments;
|
|
self
|
|
}
|
|
}
|
|
#[cfg(feature = "parse")]
|
|
impl Display for CheckErrorDisplay<'_> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.e.human_readable(
|
|
f,
|
|
&CheckErrorHRConfig {
|
|
color_index: Rc::new(AtomicUsize::new(0)),
|
|
indent_start: String::new(),
|
|
indent_default: String::new(),
|
|
indent_end: String::new(),
|
|
show_comments: self.show_comments,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
impl CheckError {
|
|
pub fn new() -> Self {
|
|
CheckError(vec![])
|
|
}
|
|
fn add(mut self, v: CheckErrorComponent) -> Self {
|
|
self.0.push(v);
|
|
self
|
|
}
|
|
pub(crate) fn msg(self, s: String) -> Self {
|
|
self.add(CheckErrorComponent::Message(s))
|
|
}
|
|
pub(crate) fn err(self, e: Self) -> Self {
|
|
self.add(CheckErrorComponent::Error(e))
|
|
}
|
|
pub(crate) fn err_with_diff_src(self, e: CheckError) -> Self {
|
|
self.add(CheckErrorComponent::ErrorWithDifferentSource(e))
|
|
}
|
|
pub(crate) fn src(self, s: Vec<(SourceRange, Option<colored::Color>)>) -> Self {
|
|
self.add(CheckErrorComponent::Source(s))
|
|
}
|
|
#[cfg(feature = "parse")]
|
|
pub fn display<'a>(&'a self) -> CheckErrorDisplay<'a> {
|
|
CheckErrorDisplay {
|
|
e: self,
|
|
show_comments: true,
|
|
}
|
|
}
|
|
/// will, unless empty, end in a newline
|
|
#[cfg(feature = "parse")]
|
|
fn human_readable(
|
|
&self,
|
|
f: &mut std::fmt::Formatter<'_>,
|
|
cfg: &CheckErrorHRConfig,
|
|
) -> std::fmt::Result {
|
|
use crate::parsing::SourceFrom;
|
|
|
|
let len = self.0.len();
|
|
for (i, component) in self.0.iter().enumerate() {
|
|
macro_rules! indent {
|
|
($s:expr, $e:expr) => {
|
|
if $e && i + 1 == len {
|
|
&cfg.indent_end
|
|
} else if $s && i == 0 {
|
|
&cfg.indent_start
|
|
} else {
|
|
&cfg.indent_default
|
|
}
|
|
};
|
|
}
|
|
match component {
|
|
CheckErrorComponent::Message(msg) => {
|
|
let lines = msg.lines().collect::<Vec<_>>();
|
|
let lc = lines.len();
|
|
for (i, line) in lines.into_iter().enumerate() {
|
|
writeln!(f, "{}{line}", indent!(i == 0, i + 1 == lc))?
|
|
}
|
|
}
|
|
CheckErrorComponent::Error(err) => {
|
|
let clr = Self::get_color(&cfg.color_index);
|
|
let mut cfg = cfg.clone();
|
|
cfg.indent_start.push_str(&clr("│").to_string());
|
|
cfg.indent_default.push_str(&clr("│").to_string());
|
|
cfg.indent_end.push_str(&clr("└").to_string());
|
|
err.human_readable(f, &cfg)?;
|
|
}
|
|
CheckErrorComponent::ErrorWithDifferentSource(err) => {
|
|
let clr = Self::get_color(&cfg.color_index);
|
|
let mut cfg = cfg.clone();
|
|
cfg.indent_start.push_str(&clr("┃").bold().to_string());
|
|
cfg.indent_default.push_str(&clr("┃").bold().to_string());
|
|
cfg.indent_end.push_str(&clr("┗").bold().to_string());
|
|
err.human_readable(f, &cfg)?;
|
|
}
|
|
CheckErrorComponent::Source(highlights) => {
|
|
if let Some(src) = highlights.first().map(|v| v.0.in_file.as_ref()) {
|
|
let start = highlights.iter().map(|v| v.0.start.pos()).min();
|
|
let end = highlights.iter().map(|v| v.0.end.pos()).max();
|
|
if let (Some(start_in_line), Some(end_in_line)) = (start, end) {
|
|
let start = src.get_line_start(start_in_line);
|
|
let end = src.get_line_end(end_in_line);
|
|
let (start_with_comments, end_with_comments) = (
|
|
src.pos_in_og(start_in_line, true),
|
|
src.pos_in_og(end_in_line, false),
|
|
);
|
|
let (mut start, mut end) = if cfg.show_comments {
|
|
(src.pos_in_og(start, true), src.pos_in_og(end, false))
|
|
} else {
|
|
(start, end)
|
|
};
|
|
let mut first_line_start = 0;
|
|
let first_line_nr = src
|
|
.src_og()
|
|
.line_spans()
|
|
.take_while(|l| {
|
|
if l.start() <= start_with_comments {
|
|
first_line_start = l.start();
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
})
|
|
.count();
|
|
if cfg.show_comments && first_line_start < start {
|
|
start = first_line_start;
|
|
}
|
|
let mut last_line_start = 0;
|
|
let last_line_nr = src
|
|
.src_og()
|
|
.line_spans()
|
|
.take_while(|l| {
|
|
if l.start() <= end_with_comments {
|
|
last_line_start = l.start();
|
|
if cfg.show_comments && l.end() > end {
|
|
end = l.end();
|
|
}
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
})
|
|
.count();
|
|
let src_from = match src.src_from() {
|
|
SourceFrom::File(path) => format!(" [{}]", path.to_string_lossy()),
|
|
SourceFrom::Unspecified => String::with_capacity(0),
|
|
};
|
|
if first_line_nr == last_line_nr {
|
|
writeln!(
|
|
f,
|
|
"{}Line {first_line_nr} ({}..{}){}",
|
|
indent!(true, false),
|
|
start_with_comments + 1 - first_line_start,
|
|
end_with_comments - last_line_start,
|
|
src_from,
|
|
)?;
|
|
} else {
|
|
writeln!(
|
|
f,
|
|
"{}Lines {first_line_nr}-{last_line_nr} ({}..{}){}",
|
|
indent!(true, false),
|
|
start_with_comments + 1 - first_line_start,
|
|
end_with_comments - last_line_start,
|
|
src_from,
|
|
)?;
|
|
}
|
|
let lines = if cfg.show_comments {
|
|
src.src_og()[start..end].line_spans().collect::<Vec<_>>()
|
|
} else {
|
|
src.src()[start..end].line_spans().collect::<Vec<_>>()
|
|
};
|
|
let lines_count = lines.len();
|
|
for (line_nr_rel, line) in lines.into_iter().enumerate() {
|
|
let last_line = line_nr_rel + 1 == lines_count;
|
|
let line_start = line.start();
|
|
let line_end = line.end();
|
|
let line = line.as_str();
|
|
let mut line_printed = false;
|
|
let mut right = 0;
|
|
for (pos, color) in highlights {
|
|
if let Some(color) = color {
|
|
let (highlight_start, highlight_end) = if cfg.show_comments
|
|
{
|
|
(
|
|
src.pos_in_og(pos.start.pos(), true),
|
|
src.pos_in_og(pos.end.pos(), false),
|
|
)
|
|
} else {
|
|
(pos.start.pos(), pos.end.pos())
|
|
};
|
|
let highlight_start = highlight_start - start;
|
|
let highlight_end = highlight_end - start;
|
|
if highlight_start < line_end && highlight_end > line_start
|
|
{
|
|
if !line_printed {
|
|
// this isn't the last line (important for indent)
|
|
writeln!(f, "{} {line}", indent!(false, false))?;
|
|
line_printed = true;
|
|
}
|
|
// where the highlight starts in this line
|
|
let hl_start =
|
|
highlight_start.saturating_sub(line_start);
|
|
// highlight would be further left than cursor, so we need a new line
|
|
if hl_start < right {
|
|
right = 0;
|
|
writeln!(f)?;
|
|
}
|
|
// length of the highlight
|
|
let hl_len = highlight_end
|
|
.saturating_sub(line_start)
|
|
.saturating_sub(hl_start);
|
|
let hl_space = hl_start - right;
|
|
let print_indent = right == 0;
|
|
let hl_len = hl_len.min(line.len() - right);
|
|
right += hl_space + hl_len;
|
|
if print_indent && right != 0 {
|
|
write!(f, "{} ", indent!(false, false))?;
|
|
}
|
|
write!(
|
|
f,
|
|
"{}{}",
|
|
" ".repeat(hl_space),
|
|
"~".repeat(hl_len).color(*color)
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
if !line_printed {
|
|
// may be last line (important for indent)
|
|
writeln!(f, "{} {line}", indent!(false, last_line))?;
|
|
}
|
|
if right != 0 {
|
|
writeln!(f)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
fn get_color(i: &AtomicUsize) -> impl Fn(&str) -> ColoredString {
|
|
let i = i.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
move |s| match i % 8 {
|
|
0 => s.bright_white(),
|
|
1 => s.bright_green(),
|
|
2 => s.bright_purple(),
|
|
3 => s.bright_cyan(),
|
|
4 => s.bright_red(),
|
|
5 => s.bright_yellow(),
|
|
6 => s.bright_magenta(),
|
|
_ => s.bright_blue(),
|
|
}
|
|
}
|
|
}
|
|
impl From<String> for CheckError {
|
|
fn from(value: String) -> Self {
|
|
Self::new().msg(value)
|
|
}
|
|
}
|
|
impl Debug for CheckError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{self}")
|
|
}
|
|
}
|
|
impl Display for CheckError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.display())
|
|
}
|
|
}
|