cco 0.2.0

cascading configuration
Documentation
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)
    }
}