use crate::{
rendertime::WRONG_TYPE_MESSAGE,
type_checking::ir::{Call, Expression, Literal},
Escaper,
};
use std::{
collections::BTreeMap,
fmt::{self, Write},
iter,
marker::PhantomData,
};
use itertools::{Itertools, Position};
use crate::{type_checking::ir::TemplateContent, Templates};
use super::{Error, Value};
struct Context<'a, 'b, D, W, Esc> {
_w: PhantomData<W>,
drain: D,
tmpls: &'a Templates<Esc>,
vars: &'b mut [Option<Value>],
}
pub(crate) trait TemplateContentDrain<W: fmt::Write> {
fn next_line(&mut self, newline: &str) -> fmt::Result;
fn current(&mut self) -> &mut W;
}
impl<W: fmt::Write> TemplateContentDrain<W> for W {
fn next_line(&mut self, newline: &str) -> fmt::Result {
self.write_str(newline)
}
fn current(&mut self) -> &mut W {
self
}
}
#[derive(Debug, Default)]
struct VecDrain(Vec<Value>);
impl TemplateContentDrain<String> for &mut VecDrain {
fn next_line(&mut self, _newline: &str) -> fmt::Result {
self.0.push(Value::Text(String::new()));
Ok(())
}
fn current(&mut self) -> &mut String {
if self.0.is_empty() {
self.0.push(Value::Text(String::new()));
}
let last = self.0.len() - 1;
let Value::Text(ref mut current) = &mut self.0[last] else {
panic!("the vec should only contain texts");
};
current
}
}
impl<D, W: fmt::Write, Esc: Escaper> Context<'_, '_, D, W, Esc>
where
D: TemplateContentDrain<W>,
W: fmt::Write,
{
fn run_template_body(&mut self, body: &[TemplateContent<Esc>]) -> Result<(), Error> {
fn write_value<W: Write>(
drain: &mut impl TemplateContentDrain<W>,
value: Value,
indentation: &str,
newline: &str,
) -> fmt::Result {
match value {
Value::Text(text) => drain.current().write_str(&text),
Value::Int(v) => write!(drain.current(), "{v}"),
Value::Bool(v) => write!(drain.current(), "{v}"),
Value::Float(v) => write!(drain.current(), "{v}"),
Value::ArrayOrTuple(values) => {
for (pos, value) in values.into_iter().with_position() {
if !matches!(pos, Position::Only | Position::First) {
drain.current().write_str(indentation)?;
}
write_value(drain, value, indentation, newline)?;
if !matches!(pos, Position::Only | Position::Last) {
drain.next_line(newline)?;
}
}
Ok(())
}
Value::Struct(_) => {
panic!("Cannot write {value:?}; {WRONG_TYPE_MESSAGE}")
}
}
}
for content in body {
match content {
TemplateContent::OriginalTemplateNewline(newline) => {
self.drain.next_line(newline).map_err(Error::Fmt)?;
}
TemplateContent::Static(static_str) => {
self.drain
.current()
.write_str(static_str)
.map_err(Error::Fmt)?;
}
TemplateContent::Assignment(var_index, expr) => {
self.vars[*var_index] = Some(self.eval_expression(expr)?);
}
TemplateContent::BodyExpression {
indentation,
expression,
newline,
} => {
let value = self.eval_expression(expression)?;
assert!(value.is_printable(), "Wrong type of ({value:?}); expected array of printable values for body array expression; {WRONG_TYPE_MESSAGE}");
write_value(&mut self.drain, value, indentation, newline)
.map_err(Error::Fmt)?;
}
}
}
Ok(())
}
fn eval_literal(&mut self, literal: &Literal<Esc>) -> Result<Value, Error> {
Ok(match literal {
Literal::Int(v) => Value::Int(*v),
Literal::Float(v) => Value::Float(*v),
Literal::Bool(v) => Value::Bool(*v),
Literal::String(str) => Value::Text(str.clone()),
Literal::Template(body) => {
let mut result = VecDrain::default();
Context {
_w: PhantomData,
drain: &mut result,
tmpls: self.tmpls,
vars: self.vars,
}
.run_template_body(body)?;
Value::ArrayOrTuple(result.0)
}
Literal::ArrayOrTuple(values) => Value::ArrayOrTuple(
values
.iter()
.map(|expr| self.eval_expression(expr))
.collect::<Result<_, _>>()?,
),
Literal::Struct(values) => Value::Struct(
values
.iter()
.map(|(name, expr)| Ok((name.clone(), self.eval_expression(expr)?)))
.collect::<Result<_, _>>()?,
),
})
}
#[allow(clippy::too_many_lines)]
fn eval_expression(&mut self, expr: &Expression<Esc>) -> Result<Value, Error> {
Ok(match expr {
Expression::Escaped(inner, escer) => escer
.escape(self.eval_expression(&**inner)?)
.map_err(Error::Escape)?,
Expression::Literal(literal) => self.eval_literal(literal)?,
Expression::Variable(var_index) => self.vars[*var_index]
.as_ref()
.expect("type checking should ensure that unitialized variables are not read")
.clone(),
Expression::Call(Call::Function(f, args)) => f
.run(
args.iter()
.map(|arg| self.eval_expression(arg))
.collect::<Result<_, _>>()?,
)
.map_err(Error::Function)?,
Expression::Call(Call::Template(tmpl_index, arg)) => {
let mut res = VecDrain::default();
write_tmpl(
&mut res,
self.tmpls,
*tmpl_index,
arg.iter()
.map(|(name, expr)| {
Ok::<_, Error>((name.clone(), self.eval_expression(expr)?))
})
.collect::<Result<_, _>>()?,
)?;
Value::ArrayOrTuple(res.0)
}
Expression::If {
condition,
then,
otherwise,
} => {
let condition = self.eval_expression(condition)?;
self.eval_expression(match condition {
Value::Bool(true) => then,
Value::Bool(false) => otherwise,
_ => panic!(
"non bool condition ({condition:?}) in if expression; {WRONG_TYPE_MESSAGE}"
),
})?
}
Expression::MemberAccess { expression, access } => {
let mut value = self.eval_expression(expression)?;
match value {
Value::ArrayOrTuple(ref mut values) => {
assert!(*access < values.len(), "value ({value:?}) in member access expression did not have the requested value ({access}); {WRONG_TYPE_MESSAGE}");
values.swap_remove(*access)
}
Value::Struct(ref values) => {
values.values().nth(*access).unwrap_or_else(|| panic!("value ({value:?}) in member access expression did not have the requested value ({access}); {WRONG_TYPE_MESSAGE}")).clone()
}
_ => panic!("value ({value:?}) has wrong type for member access expression; {WRONG_TYPE_MESSAGE}"),
}
}
Expression::Each {
assignment_variable,
array_expression,
body,
} => {
let array_values = self.eval_expression(array_expression)?;
match array_values {
Value::ArrayOrTuple(values) => {
let mut result_values = Vec::with_capacity(values.len());
for value in values {
self.vars[*assignment_variable] = Some(value);
result_values.push(self.eval_expression(body)?);
}
Value::ArrayOrTuple(result_values)
}
_ => panic!("value ({array_values:?}) has wrong type for each expression; {WRONG_TYPE_MESSAGE}"),
}
}
Expression::Select {
select_expression,
empty_text_fallback,
variable,
arms,
} => {
let val = self.eval_expression(select_expression)?;
self.vars[*variable] = Some(val.clone());
for (arm_ty, arm_expr) in arms {
if !arm_ty.matches(&val) {
continue;
}
return self.eval_expression(arm_expr);
}
if *empty_text_fallback {
Value::Text(String::new())
} else {
panic!("no select arms triggered on a non empty text fallback select expression; {WRONG_TYPE_MESSAGE}");
}
}
})
}
}
pub(crate) fn write_tmpl<W: fmt::Write, Esc: Escaper>(
writer: impl TemplateContentDrain<W>,
tmpls: &Templates<Esc>,
index: usize,
params: BTreeMap<String, Value>,
) -> Result<(), Error> {
let tmpl = &tmpls.tmpls[index];
if !tmpl.params.matches(&Value::Struct(params.clone())) {
return Err(Error::WrongParameters {
got: Value::Struct(params.clone()),
expected: tmpl.params.clone(),
});
}
let remaining_var_count = tmpl.variable_count - params.len();
let mut vars = params
.into_values()
.map(Some)
.chain(iter::repeat_n(None, remaining_var_count))
.collect_vec();
Context {
_w: PhantomData,
drain: writer,
tmpls,
vars: vars.as_mut_slice(),
}
.run_template_body(&tmpl.body)
}