use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::Arc;
use crate::compiler::instructions::Instructions;
use crate::environment::Environment;
use crate::error::{Error, ErrorKind};
use crate::utils::{AutoEscape, UndefinedBehavior};
use crate::value::{ArgType, Value};
use crate::vm::context::Context;
#[cfg(feature = "fuel")]
use crate::vm::fuel::FuelTracker;
pub struct State<'vm, 'env> {
pub(crate) env: &'env Environment<'env>,
pub(crate) ctx: Context<'env>,
pub(crate) current_block: Option<&'env str>,
pub(crate) current_call: Option<&'env str>,
pub(crate) auto_escape: AutoEscape,
pub(crate) instructions: &'vm Instructions<'env>,
pub(crate) blocks: BTreeMap<&'env str, BlockStack<'vm, 'env>>,
#[allow(unused)]
pub(crate) loaded_templates: BTreeSet<&'env str>,
#[cfg(feature = "macros")]
pub(crate) macros: std::sync::Arc<Vec<(&'vm Instructions<'env>, usize)>>,
#[cfg(feature = "fuel")]
pub(crate) fuel_tracker: Option<std::sync::Arc<FuelTracker>>,
}
impl<'vm, 'env> fmt::Debug for State<'vm, 'env> {
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("current_call", &self.current_call);
ds.field("auto_escape", &self.auto_escape);
ds.field("ctx", &self.ctx);
ds.field("env", &self.env);
ds.finish()
}
}
impl<'vm, 'env> State<'vm, 'env> {
pub(crate) fn new(
env: &'env Environment,
ctx: Context<'env>,
auto_escape: AutoEscape,
instructions: &'vm Instructions<'env>,
blocks: BTreeMap<&'env str, BlockStack<'vm, 'env>>,
) -> State<'vm, 'env> {
State {
env,
ctx,
current_block: None,
current_call: None,
auto_escape,
instructions,
blocks,
loaded_templates: BTreeSet::new(),
#[cfg(feature = "macros")]
macros: Arc::new(Vec::new()),
#[cfg(feature = "fuel")]
fuel_tracker: env.fuel().map(FuelTracker::new),
}
}
#[inline(always)]
pub fn env(&self) -> &Environment<'_> {
self.env
}
pub fn name(&self) -> &str {
self.instructions.name()
}
#[inline(always)]
pub fn auto_escape(&self) -> AutoEscape {
self.auto_escape
}
#[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 current_call(&self) -> Option<&str> {
self.current_call
}
#[inline(always)]
pub fn lookup(&self, name: &str) -> Option<Value> {
self.ctx.load(self.env, name)
}
#[cfg(any(test, feature = "testutils"))]
pub(crate) fn with_dummy<R, F: FnOnce(&State) -> R>(env: &'env Environment<'env>, f: F) -> R {
f(&State::new(
env,
Context::default(),
AutoEscape::None,
&Instructions::new("<unknown>", ""),
BTreeMap::new(),
))
}
#[cfg(feature = "debug")]
pub(crate) fn make_debug_info(
&self,
pc: usize,
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<'vm, 'env> {
instructions: Vec<&'vm Instructions<'env>>,
depth: usize,
}
impl<'vm, 'env> BlockStack<'vm, 'env> {
pub fn new(instructions: &'vm Instructions<'env>) -> BlockStack<'vm, 'env> {
BlockStack {
instructions: vec![instructions],
depth: 0,
}
}
pub fn instructions(&self) -> &'vm 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: &'vm Instructions<'env>) {
self.instructions.push(instructions);
}
}