use crate::eval::Index;
use crate::expression::{OpError, UnaryOperator};
use crate::{Cco, ValueKind};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::files::SimpleFile;
use std::ops::{Range, RangeInclusive};
#[derive(thiserror::Error, Debug)]
#[error("{kind}")]
pub struct Error {
pub kind: ErrorKind,
pub stack: Vec<Index>,
pub evaluation: Box<Cco>,
pub files: Box<FileList>,
}
impl Error {
pub fn diagnostics(mut self) -> (Diagnostic<usize>, FileList) {
use codespan_reporting::diagnostic::Diagnostic;
let mut diag = Diagnostic::error();
match self.kind.clone() {
ErrorKind::CircularDependency { index } => {
diag = diag.with_message("Element references itself");
diag = self.primary(index, diag, "This is the element that references itself");
}
ErrorKind::BinaryOperationNotAllowed {
left,
index,
right,
error:
OpError::BinaryOpTypeNotAllowed {
op: operation,
left: left_kind,
right: right_kind,
},
} => {
diag = diag.with_message(format!("Can not {} these expressions", operation.verb()));
diag = self.primary(index, diag, "here");
diag = self.secondary(left, diag, &format!("of type {left_kind}"));
diag = self.secondary(right, diag, &format!("of type {right_kind}"));
}
ErrorKind::UnknownVariable {
index,
name,
contexts,
} => {
diag = diag.with_message(format!("Unknown variable `{name}`"));
diag = self.primary(index, diag, "referenced here");
for context in contexts {
diag = self.secondary(context, diag, "searched here");
}
}
ErrorKind::ExpressionNotAnIndexer { index } => {
diag = diag.with_message("Expression can not be used to index");
diag = self.primary(index, diag, "this expression");
}
ErrorKind::TypeNotAllowed {
item,
expr,
expected,
actual,
} => {
diag = diag.with_message(format!(
"Expected a value of type `{expected}`, but got `{actual}`"
));
diag = self.primary(item, diag, "here");
diag = self.secondary(expr, diag, &format!("this expression has type `{actual}`"));
}
ErrorKind::UnknownFunction {
item,
name,
namespace,
arg_kinds: _,
} => {
if namespace.is_empty() {
diag = diag.with_message(format!("Unknown function `{name}`"));
} else {
diag = diag.with_message(format!(
"Unknown function `{}::{name}`",
namespace.join("::")
));
};
diag = self.primary(item, diag, "function call");
}
ErrorKind::ArgumentCountMismatch {
item,
expected,
actual,
} => {
diag = diag.with_message(format!(
"Function expects {} arguments, but got {}",
if expected.start() == expected.end() {
expected.start().to_string()
} else {
format!("{}-{}", expected.start(), expected.end())
},
actual
));
diag = self.primary(item, diag, "function call");
}
ErrorKind::FieldNotFound { item, name } => {
diag = diag.with_message(format!("No such field '{name}'"));
diag = self.primary(item, diag, "on this expression");
diag = self.referenced(item, diag);
}
ErrorKind::IndexNotAllowed { item } => {
diag = diag.with_message("Can not be indexed with a number");
diag = self.primary(item, diag, "this value");
diag = self.referenced(item, diag);
}
ErrorKind::IndexOutOfRange {
item,
actual,
expected,
} => {
diag = diag.with_message(format!(
"Index is out of range. Expected {}-{}, but got {}",
expected.start(),
expected.end(),
actual
));
diag = self.primary(item, diag, "index access");
}
ErrorKind::DuplicateMapKey {
index,
duplicate_key,
} => {
diag = diag.with_message(format!(
"Map has two keys of the same value '{duplicate_key}'"
));
diag = self.primary(index, diag, "duplicate key");
}
ErrorKind::ForLoopTypeMismatch {
index,
expected,
actual,
} => {
diag = diag.with_message(format!("Expected a {expected} value, but got {actual}"));
diag = self.primary(index, diag, "in for loop");
}
ErrorKind::UnaryOpTypeNotAllowed { op, kind } => {
diag = diag.with_message(format!(
"Unary operation {} is not allowed for value `{kind}`",
op.symbol()
));
}
ErrorKind::DependenciesNotSatisfied { indices } => {
diag = diag.with_message("Expression needs more information");
for index in indices {
diag = self.secondary(index, diag, "depends on this");
}
}
ErrorKind::Other(e) => {
diag = diag.with_message(format!("Internal error: {e:?}"));
}
}
(diag, *self.files)
}
fn primary(
&mut self,
item: Index,
diag: Diagnostic<usize>,
message: &str,
) -> Diagnostic<usize> {
if let Some((document, range)) = self.evaluation.locate(item) {
let fid = self.files.add_or_get(document);
diag.with_label(Label::primary(fid, range).with_message(message))
} else {
diag
}
}
fn secondary(
&mut self,
item: Index,
diag: Diagnostic<usize>,
message: &str,
) -> Diagnostic<usize> {
if let Some((document, range)) = self.evaluation.locate(item) {
let fid = self.files.add_or_get(document);
diag.with_label(Label::secondary(fid, range).with_message(message))
} else {
diag
}
}
fn referenced(&mut self, item: Index, diag: Diagnostic<usize>) -> Diagnostic<usize> {
if let Ok(resolved) = self.evaluation.dereference(item) {
if item != resolved {
if let Some((document, range)) = self.evaluation.locate(resolved) {
let fid = self.files.add_or_get(document);
return diag.with_label(
Label::secondary(fid, range).with_message("resolved to this expression"),
);
}
}
}
diag
}
}
#[derive(Debug, Clone, thiserror::Error)]
pub enum ErrorKind {
#[error("Expression depends on itself")]
CircularDependency { index: Index },
#[error("Unknown variable `{name}`")]
UnknownVariable {
index: Index,
name: String,
contexts: Vec<Index>,
},
#[error("Expression can not be used to index")]
ExpressionNotAnIndexer { index: Index },
#[error("Binary operation ")]
BinaryOperationNotAllowed {
index: Index,
left: Index,
right: Index,
error: OpError,
},
#[error("Expected a value of type `{expected}`, but got `{actual}`")]
TypeNotAllowed {
item: Index,
expr: Index,
expected: ValueKind,
actual: ValueKind,
},
#[error("No function")]
UnknownFunction {
item: Index,
name: String,
namespace: Vec<String>,
arg_kinds: Vec<ValueKind>,
},
#[error("Function does not support this number of arguments")]
ArgumentCountMismatch {
item: Index,
expected: RangeInclusive<usize>,
actual: usize,
},
#[error("No such field '{name}'")]
FieldNotFound { item: Index, name: String },
#[error("Can not be indexed with a number")]
IndexNotAllowed { item: Index },
#[error("Index is out of range. Expected {expected:?}, but got {actual}")]
IndexOutOfRange {
item: Index,
actual: usize,
expected: RangeInclusive<usize>,
},
#[error("Map has two keys of the same value '{duplicate_key}'")]
DuplicateMapKey { index: Index, duplicate_key: String },
#[error(transparent)]
Other(std::sync::Arc<anyhow::Error>),
#[error("Expected a {expected} value, but got {actual}")]
ForLoopTypeMismatch {
index: Index,
expected: ValueKind,
actual: ValueKind,
},
#[error("Unary operation is not allowed for value `{kind}`")]
UnaryOpTypeNotAllowed { op: UnaryOperator, kind: ValueKind },
#[error("Expression needs more information")]
DependenciesNotSatisfied { indices: Vec<Index> },
}
#[derive(Default, Debug)]
pub struct FileList {
files: indexmap::IndexMap<crate::FileName, SimpleFile<String, String>>,
}
impl FileList {
pub fn add_or_get(&mut self, document: std::sync::Arc<crate::Document>) -> usize {
if let Some(existing) = self.files.get_index_of(&document.file_name) {
return existing;
};
self.files.insert(
document.file_name.clone(),
SimpleFile::new(document.name().to_string(), document.source.clone()),
);
self.files.len() - 1
}
}
impl<'a> codespan_reporting::files::Files<'a> for FileList {
type FileId = usize;
type Name = String;
type Source = &'a str;
fn name(&'a self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
Ok(self
.files
.get_index(id)
.ok_or(codespan_reporting::files::Error::FileMissing)?
.1
.name()
.clone())
}
fn source(
&'a self,
id: Self::FileId,
) -> Result<Self::Source, codespan_reporting::files::Error> {
Ok(self
.files
.get_index(id)
.ok_or(codespan_reporting::files::Error::FileMissing)?
.1
.source())
}
fn line_index(
&'a self,
id: Self::FileId,
byte_index: usize,
) -> Result<usize, codespan_reporting::files::Error> {
self.files
.get_index(id)
.ok_or(codespan_reporting::files::Error::FileMissing)?
.1
.line_index((), byte_index)
}
fn line_range(
&'a self,
id: Self::FileId,
line_index: usize,
) -> Result<Range<usize>, codespan_reporting::files::Error> {
self.files
.get_index(id)
.ok_or(codespan_reporting::files::Error::FileMissing)?
.1
.line_range((), line_index)
}
}