use chumsky::span::SimpleSpan;
use colorsys::{ColorAlpha, Hsl, Rgb};
use indexmap::IndexMap;
use crate::{
color::format::{format_alpha_hex, format_alpha_hex_stripped},
{
BinaryOperatorError, Error, FilterError, FilterReturnType, IfError, KeywordError,
LoopError, ParseErrorKind, SpannedValue, Value,
engine::{BinaryOperator, Expression, SpannedBinaryOperator, SpannedExpr, Template},
},
};
use crate::color::format::{
format_hex, format_hex_alpha, format_hex_alpha_stripped, format_hex_stripped, format_hsl,
format_hsla, format_rgb, format_rgba,
};
use super::Engine;
pub const FORMATS: &[&str] = &[
"hex",
"hex_stripped",
"hex_alpha",
"hex_alpha_stripped",
"alpha_hex",
"alpha_hex_stripped",
"rgb",
"rgba",
"hsl",
"hsla",
"red",
"green",
"blue",
"alpha",
"hue",
"saturation",
"lightness",
];
pub fn get_str<'a>(source: &'a str, span: &SimpleSpan) -> &'a str {
&source[span.start..span.end]
}
pub fn get_str_vec<'a>(source: &'a str, spans: &Vec<SimpleSpan>) -> Vec<&'a str> {
spans
.iter()
.map(|s| get_str(source, s))
.collect::<Vec<&str>>()
}
pub fn format_color(base_color: Rgb, format: &str) -> Option<Value> {
let hsl_color = Hsl::from(&base_color);
match format {
f if FORMATS.contains(&f) => match f {
"hex" => Some(format_hex(&base_color).into()),
"hex_stripped" => Some(format_hex_stripped(&base_color).into()),
"hex_alpha" => Some(format_hex_alpha(&base_color).into()),
"hex_alpha_stripped" => Some(format_hex_alpha_stripped(&base_color).into()),
"alpha_hex" => Some(format_alpha_hex(&base_color).into()),
"alpha_hex_stripped" => Some(format_alpha_hex_stripped(&base_color).into()),
"rgb" => Some(format_rgb(&base_color).into()),
"rgba" => Some(format_rgba(&base_color).into()),
"hsl" => Some(format_hsl(&hsl_color).into()),
"hsla" => Some(format_hsla(&hsl_color).into()),
"red" => Some(Value::Int(base_color.red() as i64)),
"green" => Some(Value::Int(base_color.green() as i64)),
"blue" => Some(Value::Int(base_color.blue() as i64)),
"alpha" => Some(Value::Float(base_color.alpha())),
"hue" => Some(Value::Int(hsl_color.hue() as i64)),
"saturation" => Some(Value::Int(hsl_color.saturation() as i64)),
"lightness" => Some(Value::Int(hsl_color.lightness() as i64)),
_ => unreachable!(),
},
_ => None,
}
}
pub fn format_color_all(base_color: Rgb) -> IndexMap<String, Value> {
let hsl_color = Hsl::from(&base_color);
let mut map = IndexMap::new();
map.insert("hex".to_string(), Value::Ident(format_hex(&base_color)));
map.insert(
"hex_stripped".to_string(),
Value::Ident(format_hex_stripped(&base_color)),
);
map.insert(
"hex_alpha".to_string(),
Value::Ident(format_hex_alpha(&base_color)),
);
map.insert(
"hex_alpha_stripped".to_string(),
Value::Ident(format_hex_alpha_stripped(&base_color)),
);
map.insert("rgb".to_string(), Value::Ident(format_rgb(&base_color)));
map.insert("rgba".to_string(), Value::Ident(format_rgba(&base_color)));
map.insert("hsl".to_string(), Value::Ident(format_hsl(&hsl_color)));
map.insert("hsla".to_string(), Value::Ident(format_hsla(&hsl_color)));
map.insert("red".to_string(), Value::Int(base_color.red() as i64));
map.insert("green".to_string(), Value::Int(base_color.green() as i64));
map.insert("blue".to_string(), Value::Int(base_color.blue() as i64));
map.insert(
"alpha".to_string(),
Value::Ident(format!("{:?}", base_color.alpha() as u8)),
);
map.insert(
"hue".to_string(),
Value::Ident(format!("{:?}", &hsl_color.hue())),
);
map.insert(
"saturation".to_string(),
Value::Ident(format!("{:?}", &hsl_color.saturation())),
);
map.insert(
"lightness".to_string(),
Value::Ident(format!("{:?}", &hsl_color.lightness())),
);
map
}
impl Engine {
pub fn generate_template(&self, template: &Template, name: String) -> String {
self.build_string(&template.ast, &self.sources[template.source_id], &name)
}
fn build_string(&self, exprs: &[Box<SpannedExpr>], source: &String, name: &str) -> String {
let src = &mut String::from("");
for expr in exprs.iter() {
let _range = expr.span.into_range();
self.eval(src, expr, source, name);
}
src.to_string()
}
fn eval(&self, src: &mut String, expr: &SpannedExpr, source: &String, name: &str) {
match &expr.expr {
Expression::Keyword { keywords } => {
src.push_str(
&self
.get_value(keywords, source, true, false, name)
.to_string(),
);
}
Expression::KeywordWithFilters { keyword, filters } => {
let value = self.get_value(keyword, source, false, false, name);
let keywords = keyword.expr.as_keywords(source);
src.push_str(
&self
.get_replacement_filter(
value.into(),
keywords.as_deref(),
filters,
source,
keyword.span,
name,
true,
)
.to_string(),
);
}
Expression::Raw { value } => {
let str = get_str(source, value);
src.push_str(str);
}
Expression::ForLoop { var, iter, body } => {
let format_color = true;
match &iter.expr {
Expression::Range { start, end } => {
for (index, i) in (*start..*end).enumerate() {
self.runtime.borrow_mut().push_scope();
let total = (*end - *start) as usize;
self.add_loop_variables(index, total);
self.runtime.borrow_mut().insert("i", Value::Int(i));
src.push_str(&self.eval_loop_body(body.clone(), source, name));
self.runtime.borrow_mut().pop_scope();
}
}
Expression::Access { keywords: _ } => {
let values = match iter.expr.as_keywords(source) {
Some(v) => self.resolve_path(v, format_color, expr.span, name),
None => unreachable!(),
};
let Ok(values) = values else {
let spans = iter.expr.as_spans().unwrap();
let error = Error::ResolveError {
span: SimpleSpan::from(
spans.first().unwrap().start..spans.last().unwrap().end,
),
name: name.to_string(),
};
self.errors.add(error);
return;
};
match values {
Value::Map(map) => {
let res = self.eval_map(map, body, var, source, iter.span, name);
src.push_str(&res);
}
Value::LazyColor { color, scheme: _ } | Value::Color(color) => {
let formats = format_color_all(color);
let res =
self.eval_map(formats, body, var, source, iter.span, name);
src.push_str(&res);
}
Value::Array(arr) => {
let total = arr.len();
for (index, item) in arr.iter().enumerate() {
self.runtime.borrow_mut().push_scope();
if var.len() == 1 {
self.runtime
.borrow_mut()
.insert(var[0].value.to_string(), item.clone());
} else {
self.errors.add(Error::ParseError {
kind: ParseErrorKind::Loop(
LoopError::TooManyLoopVariablesArray,
),
span: iter.span,
name: name.to_string(),
});
}
self.add_loop_variables(index, total);
src.push_str(&self.eval_loop_body(body.clone(), source, name));
self.runtime.borrow_mut().pop_scope();
}
}
_ => {
self.errors.add(Error::ParseError {
kind: ParseErrorKind::Loop(
crate::LoopError::LoopOverNonIterableValue,
),
span: iter.span,
name: name.to_string(),
});
}
}
}
_ => {}
}
}
Expression::Include { name: include_name } => match &include_name.value {
Value::Ident(s) => {
let template = self.templates.get(s);
match template {
Some(v) => {
let res = self.build_string(&v.ast, &self.sources[v.source_id], s);
src.push_str(&res);
}
None => {
let error = Error::IncludeError {
span: include_name.span,
name: name.to_string(),
};
self.errors.add(error);
return;
}
};
}
_ => {}
},
Expression::If { .. } => {
let value = &self.get_value(expr, source, true, false, name);
match value {
Value::Null => {}
_ => src.push_str(&value.to_string()),
}
}
Expression::Filter { name: _, args: _ } => unreachable!(),
Expression::Range { start: _, end: _ } => unreachable!(),
Expression::LiteralValue { value: _ } => unreachable!(),
Expression::BinaryOp {
lhs: _,
op: _,
rhs: _,
} => unreachable!(),
Expression::Access { keywords: _ } => unreachable!(),
}
}
fn add_loop_variables(&self, index: usize, total: usize) {
let is_first = index == 0;
let is_last = index == total - 1;
let mut map = IndexMap::new();
map.insert("index".to_string(), Value::Int(index as i64));
map.insert("first".to_string(), Value::Bool(is_first));
map.insert("last".to_string(), Value::Bool(is_last));
self.runtime.borrow_mut().insert("loop", Value::Map(map));
}
fn eval_map(
&self,
map: IndexMap<String, Value>,
body: &Vec<Box<SpannedExpr>>,
var: &Vec<SpannedValue>,
source: &String,
span: SimpleSpan,
name: &str,
) -> String {
let mut output = String::from("");
let total = map.len();
for (index, (key, value)) in map.iter().enumerate() {
self.runtime.borrow_mut().push_scope();
if var.len() == 1 {
self.runtime
.borrow_mut()
.insert(var[0].value.to_string(), Value::Ident(key.clone()));
} else if var.len() == 2 {
self.runtime
.borrow_mut()
.insert(var[0].value.to_string(), Value::Ident(key.clone()));
self.runtime
.borrow_mut()
.insert(var[1].value.to_string(), value.clone());
} else {
self.errors.add(Error::ParseError {
kind: ParseErrorKind::Loop(LoopError::TooManyLoopVariables),
span: span,
name: name.to_string(),
});
}
self.add_loop_variables(index, total);
output.push_str(&self.eval_loop_body(body.clone(), source, name));
self.runtime.borrow_mut().pop_scope();
}
output
}
fn eval_loop_body(&self, exprs: Vec<Box<SpannedExpr>>, source: &String, name: &str) -> String {
let mut output = String::from("");
for expr in exprs.into_iter() {
let _range = expr.span.into_range();
self.eval(&mut output, &expr, source, name);
}
output
}
fn get_replacement(
&self,
keywords: &[&str],
span: SimpleSpan,
format_value: bool,
get_color_value: bool,
name: &str,
) -> Value {
match self.resolve_path(keywords.iter().copied(), format_value, span, name) {
Ok(v) => {
if get_color_value {
match v {
Value::Color(c) => format_color(c, self.get_format(keywords)).unwrap(),
Value::HslColor(c) => {
format_color(c.into(), self.get_format(keywords)).unwrap()
}
Value::LazyColor { color: c, .. } => {
format_color(c, self.get_format(keywords)).unwrap()
}
_ => v,
}
} else {
v
}
}
Err(e) => {
self.errors.add(e);
if keywords[0] == "colors" {
Value::Color(Rgb::from_hex_str("#ffffff").unwrap())
} else {
Value::Ident(String::from(""))
}
}
}
}
fn replace_binary_op(
&self,
lhs: &Box<SpannedExpr>,
op: SpannedBinaryOperator,
rhs: &Box<SpannedExpr>,
source: &String,
span: SimpleSpan,
name: &str,
) -> Value {
let left = self.get_value(lhs, source, false, true, name);
let right = self.get_value(rhs, source, false, true, name);
let left_val = left.get_float();
let right_val = right.get_float();
match (left_val, right_val) {
(Some(l), Some(r)) => self.apply_binary_op(l, r, op.op),
(l, r) => {
if l.is_none() | r.is_none() {
self.errors.add(Error::ParseError {
kind: ParseErrorKind::BinOp(
BinaryOperatorError::InvalidBinaryOperatorType {
lhs: left.to_string(),
op: op.op.to_string(),
rhs: right.to_string(),
},
),
span,
name: name.to_string(),
});
}
Value::Int(0)
}
}
}
fn normalize_number(&self, v: f64) -> Value {
if v.fract() == 0.0 {
Value::Int(v as i64)
} else {
Value::Float(v)
}
}
fn apply_binary_op(&self, left: f64, right: f64, op: BinaryOperator) -> Value {
let v = match op {
BinaryOperator::Add => left + right,
BinaryOperator::Sub => left - right,
BinaryOperator::Mul => left * right,
BinaryOperator::Div => left / right,
};
self.normalize_number(v)
}
fn get_value(
&self,
expr: &SpannedExpr,
source: &String,
format_value: bool,
get_color_value: bool,
name: &str,
) -> Value {
match &expr.expr {
Expression::Keyword { keywords } => {
self.get_value(&keywords, source, format_value, get_color_value, name)
}
Expression::KeywordWithFilters { keyword, filters } => {
let value = self.get_value(&keyword, source, false, get_color_value, name);
let keywords = keyword.expr.as_keywords(source);
Value::from(self.get_replacement_filter(
value.into(),
keywords.as_deref(),
filters,
source,
keyword.span,
name,
format_value,
))
}
Expression::LiteralValue { value } => value.value.clone(),
Expression::Access { keywords } => self.get_replacement(
&get_str_vec(source, keywords),
expr.span,
format_value,
get_color_value,
name,
),
Expression::BinaryOp { lhs, op, rhs } => {
self.replace_binary_op(lhs, *op, rhs, source, expr.span, name)
}
Expression::Raw { value } => Value::Ident(get_str(source, value).to_string()),
Expression::If {
condition,
then_branch,
else_branch,
negated,
} => {
let bool = match self.get_value(condition, source, false, get_color_value, name) {
Value::Bool(b) => {
if *negated {
!b
} else {
b
}
}
_ => {
self.errors.add(Error::ParseError {
kind: ParseErrorKind::If(IfError::InvalidIfCondition),
span: expr.span,
name: name.to_string(),
});
true
}
};
let mut values = vec![];
if bool {
if format_value {
let str = self.build_string(&then_branch, source, name);
return Value::Ident(str);
} else {
for expr in then_branch {
values.push(self.get_value(
expr,
source,
format_value,
get_color_value,
name,
))
}
}
return Value::Array(values);
} else {
if let Some(exprs) = else_branch {
if format_value {
let str = self.build_string(&exprs, source, name);
return Value::Ident(str);
} else {
for expr in exprs {
values.push(self.get_value(
expr,
source,
format_value,
get_color_value,
name,
))
}
}
return Value::Array(values);
} else {
return Value::Null;
};
}
}
_ => {
dbg!(&expr);
panic!("");
}
}
}
fn get_replacement_filter(
&self,
mut current_value: FilterReturnType,
keywords: Option<&[&str]>,
filters: &[SpannedExpr],
source: &String,
span: SimpleSpan,
name: &str,
format_value: bool,
) -> FilterReturnType {
let is_color = match ¤t_value {
FilterReturnType::Rgb(_) => true,
FilterReturnType::Hsl(_) => true,
FilterReturnType::String(_) => false,
FilterReturnType::Bool(_) => false,
};
let (format, is_format_empty) = match keywords {
Some(v) => (self.get_format(v), false),
None => ("hex", true),
};
for filter in filters {
if let Expression::Filter {
name: filter_name,
args,
} = &filter.expr
{
let mut args_resolved = vec![];
for arg in args {
match &arg.expr {
Expression::Keyword { keywords } => args_resolved.push(SpannedValue {
value: self.get_value(keywords, source, false, false, name),
span: arg.span,
}),
Expression::KeywordWithFilters { keyword, filters } => {
let value = self.get_value(&keyword, source, false, false, name);
let keywords = keyword.expr.as_keywords(source);
args_resolved.push(SpannedValue {
value: self
.get_replacement_filter(
value.into(),
keywords.as_deref(),
filters,
source,
span,
name,
false,
)
.into(),
span: arg.span,
});
}
Expression::LiteralValue { value } => args_resolved.push(value.clone()),
Expression::BinaryOp { lhs, op, rhs } => {
args_resolved.push(SpannedValue {
value: self
.replace_binary_op(lhs, *op, rhs, source, arg.span, name),
span: arg.span,
});
}
Expression::If { .. } => {
let val = self.get_value(arg, source, false, false, name);
match val {
Value::Array(array) => {
for value in array {
args_resolved.push(SpannedValue {
value,
span: arg.span,
})
}
}
v => {
args_resolved.push(SpannedValue {
value: v,
span: arg.span,
});
}
}
}
_ => {
panic!("Unsupported filter arg")
}
}
}
let filter_name = get_str(source, filter_name);
let (is_alpha_filter_invalid, format_replacement): (bool, Option<&'static str>) =
match format {
"hex" => (
true,
Some("hex_alpha, hex_alpha_stripped, alpha_hex or alpha_hex_stripped"),
),
"rgb" => (true, Some("rgba")),
"hsl" => (true, Some("hsla")),
_ => (false, None),
};
if filter_name == "set_alpha" && is_alpha_filter_invalid && !is_format_empty {
let error = Error::ParseError {
kind: ParseErrorKind::Filter(FilterError::SetAlphaOnNonAlphaFormat {
replacement: format_replacement.unwrap(),
}),
span: filter.span,
name: name.to_string(),
};
self.errors.add(error);
}
current_value = match self.apply_filter(
filter_name,
&args_resolved,
keywords.unwrap_or(&vec![]),
current_value,
filter.span,
name,
) {
Ok(val) => val,
Err(e) => {
let error = Error::ParseError {
kind: ParseErrorKind::Filter(e),
span: filter.span,
name: name.to_string(),
};
self.errors.add(error);
match &is_color {
false => FilterReturnType::from(String::from("")),
true => FilterReturnType::from(Rgb::from_hex_str("#ffffff").unwrap()),
}
}
};
}
}
if !format_value {
return current_value;
}
match current_value {
FilterReturnType::String(_) => current_value,
FilterReturnType::Rgb(argb) => match format_color(argb, format) {
Some(v) => FilterReturnType::String(v.to_string()),
None => {
let error = Error::ParseError {
kind: ParseErrorKind::Keyword(KeywordError::InvalidFormat {
formats: FORMATS,
}),
span,
name: name.to_string(),
};
self.errors.add(error);
FilterReturnType::String(String::from(""))
}
},
FilterReturnType::Hsl(hsl) => match format_color(hsl.into(), format) {
Some(v) => FilterReturnType::String(v.to_string()),
None => {
let error = Error::ParseError {
kind: ParseErrorKind::Keyword(KeywordError::InvalidFormat {
formats: FORMATS,
}),
span,
name: name.to_string(),
};
self.errors.add(error);
FilterReturnType::String(String::from(""))
}
},
FilterReturnType::Bool(_) => current_value,
}
}
fn apply_filter(
&self,
filtername: &str,
args: &[SpannedValue],
keywords: &[&str],
input: FilterReturnType,
span: SimpleSpan,
name: &str,
) -> Result<FilterReturnType, FilterError> {
match self.filters.get(filtername) {
Some(f) => f(keywords, args, input, self),
None => {
let error = Error::ParseError {
kind: ParseErrorKind::Filter(FilterError::FilterNotFound {
filter: filtername.to_owned(),
}),
span,
name: name.to_string(),
};
self.errors.add(error);
Ok(FilterReturnType::from(
Rgb::from_hex_str("#ffffff").unwrap(),
))
}
}
}
}