teacat_lib 0.1.1

Tools for working with TeaCat files
Documentation
use std::{
	error::Error,
	fmt::{Display, Formatter, Result},
};

use anstyle::{AnsiColor, Color, Style};

use crate::lexer::Token;
use lines::Lines;

pub mod lines;

const VERT: char = '';
const VERT_DASH: char = '';

const HELP: Style = colorstyle(AnsiColor::Magenta);
const DARK: Style = colorstyle(AnsiColor::BrightBlack);
const DEFAULT: Style = colorstyle(AnsiColor::White);
const BOLD: Style = Style::new().bold();

#[derive(Debug, Clone)]
pub enum TeaCatError {
	UndefinedVar(usize, String),
	UndefinedMacr(usize, String),
	UnexpectedAttr(usize, Token),
	UnexpectedToken(usize, Token),
	ExpectedIdent(usize, Token),
	ExpectedSemicolon(usize, Token),
	EarlyEof(usize, Token),
}

impl Display for TeaCatError {
	fn fmt(&self, f: &mut Formatter<'_>) -> Result {
		f.write_str(&match self {
			Self::UndefinedVar(_, var) => format!("variable '&{var}' undefined"),
			Self::UndefinedMacr(_, macr) => format!("macro '&{macr}' undefined"),
			Self::UnexpectedAttr(_, token) => {
				format!("unexpected input in attributes: '{token}'")
			}
			Self::UnexpectedToken(_, token) => {
				format!("unexpected token: '{token}'")
			}
			Self::ExpectedIdent(_, token) => format!("expected identifier, found '{token}'"),
			Self::ExpectedSemicolon(_, token) => format!("expected ';', found '{token}'"),
			Self::EarlyEof(_, token) => format!("early end of file while seeking token '{token}'"),
		})
	}
}

impl Error for TeaCatError {}

impl TeaCatError {
	fn line_num(&self) -> usize {
		macro_rules! get_line {
			( $( $name:ident, )* ) => {
				match self {
					$( | TeaCatError::$name(line, ..) )* => *line,
				}
			};
		}

		get_line!(
			EarlyEof,
			UndefinedVar,
			UndefinedMacr,
			UnexpectedToken,
			UnexpectedAttr,
			ExpectedSemicolon,
			ExpectedIdent,
		)
	}

	pub fn help_msg(&self) -> String {
		match self {
			Self::UndefinedVar(_, var) => {
				format!("to insert an '&' directly, use a backslash: '\\&{var}'")
			}
			Self::UndefinedMacr(_, macr) => {
				format!("to insert an '@' directly, use a backslash: '\\@{macr}'")
			}
			Self::UnexpectedAttr(_, token) => {
				format!("try surrounding the token with quotation marks: '\"{token}\"'")
			}
			Self::UnexpectedToken(_, token) => format!("remove the '{token}'"),
			Self::ExpectedIdent(_, token) => {
				format!(
					"if you intended '{token}' to be an identifier, be sure to remove or escape whitespace"
				)
			}
			Self::ExpectedSemicolon(_, token) => format!("Add a semicolon: '..; {token}'"),
			Self::EarlyEof(_, token) => {
				format!("add the expected token to the end of the file: '..{token}'")
			}
		}
	}

	pub fn err_fancy(&self, teacat_str: impl Into<String>) -> String {
		let help = format!(
			"{DARK}    ╰─▶ {HELP}{BOLD}help: {}{BOLD:#}{DEFAULT}",
			self.help_msg()
		);
		let lines = Lines::new(self.line_num(), teacat_str);

		format!("{BOLD}{self}{BOLD:#}\n\n{lines}{DARK}    {VERT_DASH}\n{help}\n")
	}
}

const fn colorstyle(color: AnsiColor) -> Style {
	Style::new().fg_color(Some(Color::Ansi(color)))
}