fml 0.1.0

Friendly Markup Language
use std::io::{self, ErrorKind::Unsupported};
#[cfg(feature = "parser")]
use std::str::FromStr;

#[cfg(feature = "parser")]
use chumsky::Parser;

use crate::ast::token::TokenVec;
#[cfg(feature = "parser")]
use crate::ast::{Token, token};

use super::data::{FmlValue::*, *};

impl FmlFunction {
	/// Interprets a generic function to one of the more specified ones.\
	/// If needed params are missing, fills them in with insane defaults chosen by me.\
	/// Generic functions stay as generic functions.
	pub fn interpret(self) -> FmlValue {
		match &self.name.to_lowercase()[..] {
			"fg" | "color" => ColorFg(FmlColor {
				color: self.params.unwrap_or("#db4e11".to_string()),
				body: self.body,
			}),
			"bg" => ColorBg(FmlColor {
				color: self.params.unwrap_or("#501c06".to_string()),
				body: self.body,
			}),
			"cw" => ContentWarning {
				reason: self.params.unwrap_or("Spoilers".to_string()),
				body: self.body,
			},
			"li" => ListItem(self.body),
			"quo" => Quote(self.body),
			_ => Function(self),
		}
	}
}

impl FmlValue {
	/// Append another fml-value to oneself.
	pub fn join(&mut self, other: Self) -> io::Result<()> {
		match self {
			Heading1(v) | Heading2(v) | Heading3(v) | Superline(v) | Subline(v) | Bold(v)
			| Italic(v) | Strikethrough(v) | Quote(v) | ListItem(v) | Superscript(v)
			| Subscript(v) | Underline(v) => {
				v.push(other);
			}

			ColorFg(FmlColor { color: _, body })
			| ColorBg(FmlColor { color: _, body })
			| ContentWarning { reason: _, body }
			| Function(FmlFunction {
				name: _,
				params: _,
				body,
			}) => {
				body.push(other);
			}

			Text(s1) => {
				if let Text(s2) = other {
					s1.push_str(&s2);
				} else {
					return Err(io::Error::from(Unsupported));
				}
			}

			_ => {
				return Err(io::Error::from(Unsupported));
			}
		}

		Ok(())
	}
}

#[cfg(feature = "parser")]
impl TryFrom<Token> for FmlValue {
	type Error = ();

	fn try_from(value: Token) -> Result<Self, Self::Error> {
		Ok(match value {
			Token::Newline => FmlValue::Newline,
			Token::Heading1(tokens) => FmlValue::Heading1(TokenVec(tokens).try_into()?),
			Token::Heading2(tokens) => FmlValue::Heading2(TokenVec(tokens).try_into()?),
			Token::Heading3(tokens) => FmlValue::Heading3(TokenVec(tokens).try_into()?),
			Token::SupLine(tokens) => FmlValue::Superline(TokenVec(tokens).try_into()?),
			Token::SubLine(tokens) => FmlValue::Subline(TokenVec(tokens).try_into()?),
			Token::ListItem(tokens) => FmlValue::ListItem(TokenVec(tokens).try_into()?),
			Token::Quote(s) => {
				let p = crate::parser::functions::fml_values();
				let tokens = p.parse(&s).into_result().map_err(|_| ())?;
				let values = TokenVec(tokens).try_into()?;
				Quote(values)
			}
			Token::CwSpoiler(tokens) => FmlValue::ContentWarning {
				reason: "Spoilers".into(),
				body: TokenVec(tokens).try_into()?,
			},
			Token::Bold(tokens) => FmlValue::Bold(TokenVec(tokens).try_into()?),
			Token::Italic(tokens) => FmlValue::Italic(TokenVec(tokens).try_into()?),
			Token::Strikethrough(tokens) => FmlValue::Strikethrough(TokenVec(tokens).try_into()?),
			Token::Underline(tokens) => FmlValue::Underline(TokenVec(tokens).try_into()?),
			Token::Superscript(tokens) => FmlValue::Superscript(TokenVec(tokens).try_into()?),
			Token::Subscript(tokens) => FmlValue::Subscript(TokenVec(tokens).try_into()?),
			Token::Function(fml_function) => fml_function.interpret(),
			Token::Typst(s) => FmlValue::Typst(s),
			Token::Text(s) => FmlValue::Text(s),
		})
	}
}

impl TryFrom<TokenVec> for Vec<FmlValue> {
	type Error = ();

	fn try_from(tokens: TokenVec) -> Result<Self, Self::Error> {
		let mut vec = vec![];
		for t in tokens.0 {
			vec.push(t.try_into()?);
		}
		Ok(vec)
	}
}