use std::borrow::Cow;
use std::cell::Cell;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::{Arc, Mutex};
use crate::compiler::instructions::Instructions;
use crate::environment::Environment;
use crate::error::{Error, ErrorKind};
use crate::output::Output;
use crate::template::Template;
use crate::utils::{AutoEscape, UndefinedBehavior};
use crate::value::{ArgType, Object, Value};
use crate::vm::context::Context;
#[cfg(feature = "fuel")]
use crate::vm::fuel::FuelTracker;
#[cfg(feature = "macros")]
static STATE_ID: std::sync::atomic::AtomicIsize = std::sync::atomic::AtomicIsize::new(0);
pub struct State<'template, 'env> {
pub(crate) ctx: Context<'env>,
pub(crate) current_block: Option<&'env str>,
pub(crate) auto_escape: Cell<AutoEscape>,
pub(crate) instructions: &'template Instructions<'env>,
pub(crate) temps: Arc<Mutex<BTreeMap<Box<str>, Value>>>,
pub(crate) blocks: BTreeMap<&'env str, BlockStack<'template, 'env>>,
#[allow(unused)]
pub(crate) loaded_templates: BTreeSet<&'env str>,
#[cfg(feature = "macros")]
pub(crate) id: isize,
#[cfg(feature = "macros")]
pub(crate) macros: std::sync::Arc<Vec<(&'template Instructions<'env>, u32)>>,
#[cfg(feature = "macros")]
pub(crate) closure_tracker: std::sync::Arc<crate::vm::closure_object::ClosureTracker>,
#[cfg(feature = "fuel")]
pub(crate) fuel_tracker: Option<std::sync::Arc<FuelTracker>>,
}
impl fmt::Debug for State<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ds = f.debug_struct("State");
ds.field("name", &self.instructions.name());
ds.field("current_block", &self.current_block);
ds.field("auto_escape", &self.auto_escape.get());
ds.field("ctx", &self.ctx);
ds.field("env", &self.env());
ds.finish()
}
}
impl<'template, 'env> State<'template, 'env> {
pub(crate) fn new(
ctx: Context<'env>,
auto_escape: AutoEscape,
instructions: &'template Instructions<'env>,
blocks: BTreeMap<&'env str, BlockStack<'template, 'env>>,
) -> State<'template, 'env> {
State {
#[cfg(feature = "macros")]
id: STATE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
current_block: None,
auto_escape: Cell::new(auto_escape),
instructions,
blocks,
temps: Default::default(),
loaded_templates: BTreeSet::new(),
#[cfg(feature = "macros")]
macros: Default::default(),
#[cfg(feature = "macros")]
closure_tracker: Default::default(),
#[cfg(feature = "fuel")]
fuel_tracker: ctx.env().fuel().map(FuelTracker::new),
ctx,
}
}
pub(crate) fn new_for_env(env: &'env Environment) -> State<'env, 'env> {
State::new(
Context::new(env),
AutoEscape::None,
&crate::compiler::instructions::EMPTY_INSTRUCTIONS,
BTreeMap::new(),
)
}
#[inline(always)]
pub fn env(&self) -> &'env Environment<'env> {
self.ctx.env()
}
pub fn name(&self) -> &str {
self.instructions.name()
}
#[inline(always)]
pub fn auto_escape(&self) -> AutoEscape {
self.auto_escape.get()
}
pub(crate) fn with_auto_escape<R>(
&self,
auto_escape: AutoEscape,
f: impl FnOnce(&State<'template, 'env>) -> R,
) -> R {
if self.auto_escape.get() == auto_escape {
return f(self);
}
let old = self.auto_escape.replace(auto_escape);
let rv = f(self);
self.auto_escape.set(old);
rv
}
#[inline(always)]
pub fn undefined_behavior(&self) -> UndefinedBehavior {
self.env().undefined_behavior()
}
#[inline(always)]
pub fn current_block(&self) -> Option<&str> {
self.current_block
}
#[inline(always)]
pub fn lookup(&self, name: &str) -> Option<Value> {
self.ctx.load(name)
}
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub fn call_macro(&self, name: &str, args: &[Value]) -> Result<String, Error> {
let f = ok!(self.lookup(name).ok_or_else(|| Error::new(
crate::error::ErrorKind::UnknownFunction,
"macro not found"
)));
f.call(self, args).map(Into::into)
}
#[cfg(feature = "multi_template")]
#[cfg_attr(docsrs, doc(cfg(feature = "multi_template")))]
pub fn render_block(&mut self, block: &str) -> Result<String, Error> {
let mut buf = String::new();
crate::vm::Vm::new(self.env())
.call_block(block, self, &mut Output::new(&mut buf))
.map(|_| buf)
}
#[cfg(feature = "multi_template")]
#[cfg_attr(docsrs, doc(cfg(feature = "multi_template")))]
pub fn render_block_to_write<W>(&mut self, block: &str, w: W) -> Result<(), Error>
where
W: std::io::Write,
{
let mut wrapper = crate::output::WriteWrapper { w, err: None };
crate::vm::Vm::new(self.env())
.call_block(block, self, &mut Output::new(&mut wrapper))
.map(|_| ())
.map_err(|err| wrapper.take_err(err))
}
pub fn exports(&self) -> Vec<&str> {
self.ctx.exports().keys().copied().collect()
}
pub fn known_variables(&self) -> Vec<Cow<'_, str>> {
Vec::from_iter(self.ctx.known_variables(true))
}
pub fn get_template(&self, name: &str) -> Result<Template<'env, 'env>, Error> {
self.env()
.get_template(&self.env().join_template_path(name, self.name()))
}
pub fn apply_filter(&self, filter: &str, args: &[Value]) -> Result<Value, Error> {
match self.env().get_filter(filter) {
Some(filter) => filter.call(self, args),
None => Err(Error::from(ErrorKind::UnknownFilter)),
}
}
pub fn perform_test(&self, test: &str, args: &[Value]) -> Result<bool, Error> {
match self.env().get_test(test) {
Some(test) => test.call(self, args).map(|x| x.is_true()),
None => Err(Error::from(ErrorKind::UnknownTest)),
}
}
pub fn format(&self, value: Value) -> Result<String, Error> {
let mut rv = String::new();
let mut out = Output::new(&mut rv);
self.env().format(&value, self, &mut out).map(|_| rv)
}
#[cfg(feature = "fuel")]
#[cfg_attr(docsrs, doc(cfg(feature = "fuel")))]
pub fn fuel_levels(&self) -> Option<(u64, u64)> {
self.fuel_tracker
.as_ref()
.map(|x| (x.consumed(), x.remaining()))
}
pub fn get_temp(&self, name: &str) -> Option<Value> {
self.temps.lock().unwrap().get(name).cloned()
}
pub fn set_temp(&self, name: &str, value: Value) -> Option<Value> {
self.temps
.lock()
.unwrap()
.insert(name.to_owned().into(), value)
}
pub fn get_or_set_temp_object<O, F>(&self, name: &str, f: F) -> Arc<O>
where
O: Object + 'static,
F: FnOnce() -> O,
{
self.get_temp(name)
.unwrap_or_else(|| {
let rv = Value::from_object(f());
self.set_temp(name, rv.clone());
rv
})
.downcast_object()
.expect("downcast unexpectedly failed. Name conflict?")
}
#[cfg(feature = "debug")]
pub(crate) fn make_debug_info(
&self,
pc: u32,
instructions: &Instructions<'_>,
) -> crate::debug::DebugInfo {
crate::debug::DebugInfo {
template_source: Some(instructions.source().to_string()),
referenced_locals: instructions
.get_referenced_names(pc)
.into_iter()
.filter_map(|n| Some((n.to_string(), some!(self.lookup(n)))))
.collect(),
}
}
}
impl<'a> ArgType<'a> for &State<'_, '_> {
type Output = &'a State<'a, 'a>;
fn from_value(_value: Option<&'a Value>) -> Result<Self::Output, Error> {
Err(Error::new(
ErrorKind::InvalidOperation,
"cannot use state type in this position",
))
}
fn from_state_and_value(
state: Option<&'a State>,
_value: Option<&'a Value>,
) -> Result<(Self::Output, usize), Error> {
match state {
None => Err(Error::new(ErrorKind::InvalidOperation, "state unavailable")),
Some(state) => Ok((state, 0)),
}
}
}
#[derive(Default)]
pub(crate) struct BlockStack<'template, 'env> {
instructions: Vec<&'template Instructions<'env>>,
depth: usize,
}
impl<'template, 'env> BlockStack<'template, 'env> {
pub fn new(instructions: &'template Instructions<'env>) -> BlockStack<'template, 'env> {
BlockStack {
instructions: vec![instructions],
depth: 0,
}
}
pub fn instructions(&self) -> &'template Instructions<'env> {
self.instructions.get(self.depth).copied().unwrap()
}
pub fn push(&mut self) -> bool {
if self.depth + 1 < self.instructions.len() {
self.depth += 1;
true
} else {
false
}
}
#[track_caller]
pub fn pop(&mut self) {
self.depth = self.depth.checked_sub(1).unwrap()
}
#[cfg(feature = "multi_template")]
pub fn append_instructions(&mut self, instructions: &'template Instructions<'env>) {
self.instructions.push(instructions);
}
}