avid 0.6.1

A plug-and-play scripting language
Documentation
use std::{fmt::Display, sync};

use crate::ObjectType;

// These are so the docs link correctly.
#[allow(unused_imports)]
use crate::{Builder, Stack, PromiseBuilder, Promises};

/// A shorthand for a Result type with Avid programs.
///
/// This is more for convenience than anything else, so it can be pretty safely ignored.
pub type Result<T, E = Error> = std::result::Result<T, E>;

/// An error in an Avid program, along with the location it occurred.
///
/// # Examples
/// ```
/// # let source_code = "";
/// use avid::*;
///
/// if let Err(e) = Builder::new(source_code).build() {
///     eprintln!("There was an error at {}!", e.loc);
///     if let ErrorKind::UnclosedIf = e.kind {
///         eprintln!("You forgot to close your if statement!");
///     } else {
///         eprintln!("All if statements are good.")
///     }
/// }
/// ```
#[derive(Debug, PartialEq)]
pub struct Error {
    /// The type of error that was thrown.
    pub kind: ErrorKind,
    /// The location where the error was thrown.
    pub loc: Location,
}

impl Error {
    pub(crate) fn new(kind: ErrorKind, loc: Location) -> Self {
        Self {
            kind,
            loc
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        writeln!(f, "{}: Error: {}", self.loc, self.kind)
    }
}

/// A location in a file.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Location {
    /// The line where the error occurred.
    pub line: usize,
    /// The column where the error occurred.
    pub col: usize,
    /// The file in which the error occurred.
    ///
    /// This file is sometimes not provided if the method [Builder::src_name] is not used. In
    /// this case, `file_name` is `None`.
    // TODO(#14): Make Location.file_name use &str instead of Arc<String>.
    // This should be possible if the ownership of the file names is moved into the
    // Avid instance/Ast.
    pub file_name: Option<sync::Arc<String>>,
}

impl Display for Location {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}:{}", self.file_name.as_ref().map_or("<provided>", |a| a.as_str()), self.line, self.col)
    }
}

impl Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ErrorKind::NotEnoughArgs { expected, got } => write!(f, "Not Enough Arguments: expected {expected}, got {got}!"),
            ErrorKind::IncorrectType { expected, got } => write!(f, "Incorrect Argument Type: expected {expected:?}, got {got:?}!"),
            ErrorKind::UnknownVar { name } => write!(f, "Unknown Variable: \"{name}\"!"),
            ErrorKind::UnfulfilledPromise { expected } => write!(f, "Unfulfilled Promise: \"{expected}\"!\nNote: This is ususally the fault of the program interpreting the code. You should file a bug report if possible."),
            ErrorKind::UnpairedEnd => write!(f, "`End` Without Start of Block!"),
            ErrorKind::UnclosedIf => write!(f, "`if` Statement Without Matching `end`!"),
            ErrorKind::UnclosedWhile => write!(f, "`while` Statement Without Matching `end`!"),
            ErrorKind::WhileWithoutDo => write!(f, "`while` Statement Without `do`!"),
            ErrorKind::DoWithoutWhile => write!(f, "Unexpected `do`!"),
            ErrorKind::UnclosedString => write!(f, "Unclosed String!"),
            ErrorKind::IncorrectNumber => write!(f, "Unparseable Number!"),
            ErrorKind::UnknownEscape => write!(f, "Unknown Escaped Character!"),
        }
    }
}

/// Errors that can happen when running an Avid program.
///
/// These errors are caused by the authors of the Avid code and should be caught.
/// When writing APIs with [register_fn](Builder::register_fn()), it is expected that you return the
/// correct kind of error.
#[derive(Debug, PartialEq)]
// TODO(#15): Separate out errors into compile-time errors and runtime errors.
// Maybe also collect the compile time errors into a Vec<CompileError> or something.
pub enum ErrorKind {
    /// When not enough arguments are present on the stack for an operation.
    ///
    /// This error is automatically returned by [pop](Stack::pop) and [pop_typed](Stack::pop_typed) when
    /// there are insufficient arguments.
    NotEnoughArgs {
        /// The number of items expected on the stack.
        expected: usize,
        /// The number of items actually on the stack.
        got: usize
    },
    /// When the arguments on the stack are the incorrect kind for the operation.
    IncorrectType {
        /// The types expected on the stack.
        ///
        /// Currently, they must be requested statically, but they will be able to be requested
        /// dynamically in a later update.
        // TODO(#16): Make ErrorKind::IncorrectType.expected use Vec<ObjectType> instead of &'static [ObjectType].
        //
        // This will make it much easier for functions that might have dynamic signatures.
        expected: &'static [ObjectType],
        /// The types actually found on the stack.
        got: Vec<ObjectType>,
    },
    /// When the user tries to use a variable or function that does not exist.
    UnknownVar {
        /// The name of the unknown variable or function.
        name: String
    },
    /// When promises do not get fulfilled.
    ///
    /// Promises can be made with [Builder::promise] and fulfilled by passing them into
    /// a [Promises] struct with [PromiseBuilder::add_promise].
    UnfulfilledPromise {
        /// The promise that was made in [Builder::build] but not found at runtime.
        expected: String
    },
    /// An unpaired `end`, which is expected to finish a statement but there is no statement found.
    UnpairedEnd,
    /// An unclosed `if` statement, such as `true if print`.
    UnclosedIf,
    /// An unclosed `while` statement, such as `true while do print`.
    UnclosedWhile,
    /// A while loop without the `do`. For example: `while true end`.
    WhileWithoutDo,
    /// A `do ... end` statement without a starting `while`.
    DoWithoutWhile,
    /// An unclosed string. The location in the source code will point to the end of
    /// the data of the string, right before where closing quotes should be.
    UnclosedString,
    /// An unparseable number. The location in the source code will point to the start
    /// of where the number should be.
    IncorrectNumber,
    /// An invalid escape code. The current valid escapes in Avid are: `\n`, `\\n`
    /// (cancels out a new line in a string), `\t`, `\\`, `\"`, `\'`, and `\r`.
    UnknownEscape,
}