mod metadata;
mod resolution;
mod scope_stack;
mod substitution;
mod util;
#[cfg(feature = "compat")]
use self::metadata::PropertyMetadata;
use self::util::*;
use super::interpreter::constants::BUILTIN_CONSTANTS;
#[cfg(test)]
use crate::extensions::core::default_extensions;
use crate::extensions::ExtensionHandler;
use core::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropertyScope {
Local,
Parent,
Global,
}
pub(crate) struct EvalContext<const MAX_SUBSTITUTION_DEPTH: usize = 100> {
raw_properties: RefCell<HashMap<String, String>>,
evaluated_cache: RefCell<HashMap<String, String>>,
resolution_stack: RefCell<Vec<String>>,
scope_stack: RefCell<Vec<HashMap<String, String>>>,
args: Rc<RefCell<HashMap<String, String>>>,
extensions: Rc<Vec<Box<dyn ExtensionHandler>>>,
#[cfg(feature = "yaml")]
yaml_tag_handler_registry: Rc<crate::eval::yaml_tag_handler::YamlTagHandlerRegistry>,
current_location: RefCell<Option<crate::eval::LocationContext>>,
#[cfg(feature = "compat")]
use_python_compat: bool,
#[cfg(feature = "compat")]
property_metadata: RefCell<HashMap<String, PropertyMetadata>>,
}
impl<const MAX_SUBSTITUTION_DEPTH: usize> EvalContext<MAX_SUBSTITUTION_DEPTH> {
pub fn new_with_extensions(
args: Rc<RefCell<HashMap<String, String>>>,
extensions: Rc<Vec<Box<dyn ExtensionHandler>>>,
#[cfg(feature = "yaml")] yaml_tag_handlers: Rc<
crate::eval::yaml_tag_handler::YamlTagHandlerRegistry,
>,
) -> Self {
Self {
raw_properties: RefCell::new(HashMap::new()),
evaluated_cache: RefCell::new(HashMap::new()),
resolution_stack: RefCell::new(Vec::new()),
scope_stack: RefCell::new(Vec::new()),
args,
extensions,
#[cfg(feature = "yaml")]
yaml_tag_handler_registry: yaml_tag_handlers,
current_location: RefCell::new(None),
#[cfg(feature = "compat")]
use_python_compat: true, #[cfg(feature = "compat")]
property_metadata: RefCell::new(HashMap::new()),
}
}
#[cfg(feature = "compat")]
fn add_raw_property_with_metadata(
&self,
name: String,
value: String,
metadata: PropertyMetadata,
) {
if BUILTIN_CONSTANTS.iter().any(|(k, _)| *k == name.as_str()) {
log::warn!(
"Property '{}' overrides built-in math constant. \
This may cause unexpected behavior. \
Consider using a different name.",
name
);
}
self.property_metadata
.borrow_mut()
.insert(name.clone(), metadata);
self.raw_properties.borrow_mut().insert(name.clone(), value);
self.evaluated_cache.borrow_mut().remove(&name);
}
#[cfg(not(feature = "compat"))]
fn add_raw_property_with_metadata(
&self,
name: String,
value: String,
_metadata: (),
) {
if BUILTIN_CONSTANTS.iter().any(|(k, _)| *k == name.as_str()) {
log::warn!(
"Property '{}' overrides built-in math constant. \
This may cause unexpected behavior. \
Consider using a different name.",
name
);
}
self.raw_properties.borrow_mut().insert(name.clone(), value);
self.evaluated_cache.borrow_mut().remove(&name);
}
pub fn define_property(
&self,
name: String,
value: String,
scope: PropertyScope,
) {
#[cfg(feature = "compat")]
let metadata = self.compute_property_metadata(&value);
match scope {
PropertyScope::Local => {
let mut stack = self.scope_stack.borrow_mut();
let scope_depth = stack.len();
if let Some(top) = stack.last_mut() {
#[cfg(feature = "compat")]
{
let scoped_key = format!("{}:{}", scope_depth, name);
self.property_metadata
.borrow_mut()
.insert(scoped_key, metadata);
}
top.insert(name, value);
} else {
drop(stack);
#[cfg(feature = "compat")]
{
self.add_raw_property_with_metadata(name, value, metadata);
}
#[cfg(not(feature = "compat"))]
{
self.add_raw_property_with_metadata(name, value, ());
}
}
}
PropertyScope::Parent => {
let mut stack = self.scope_stack.borrow_mut();
if stack.len() >= 2 {
let parent_idx = stack.len() - 2;
let scope_depth = parent_idx + 1; #[cfg(feature = "compat")]
{
let scoped_key = format!("{}:{}", scope_depth, name);
self.property_metadata
.borrow_mut()
.insert(scoped_key, metadata);
}
stack[parent_idx].insert(name, value);
} else if stack.len() == 1 {
drop(stack);
#[cfg(feature = "compat")]
{
self.add_raw_property_with_metadata(name, value, metadata);
}
#[cfg(not(feature = "compat"))]
{
self.add_raw_property_with_metadata(name, value, ());
}
} else {
drop(stack);
log::warn!(
"Property '{}': cannot use scope='parent' at global scope (no parent exists), \
falling back to global scope",
name
);
#[cfg(feature = "compat")]
{
self.add_raw_property_with_metadata(name, value, metadata);
}
#[cfg(not(feature = "compat"))]
{
self.add_raw_property_with_metadata(name, value, ());
}
}
}
PropertyScope::Global => {
#[cfg(feature = "compat")]
{
self.add_raw_property_with_metadata(name, value, metadata);
}
#[cfg(not(feature = "compat"))]
{
self.add_raw_property_with_metadata(name, value, ());
}
}
}
}
}
#[cfg(test)]
impl<const MAX_SUBSTITUTION_DEPTH: usize> EvalContext<MAX_SUBSTITUTION_DEPTH> {
#[allow(clippy::new_without_default)]
pub(crate) fn new() -> Self {
Self::new_with_args(Rc::new(RefCell::new(HashMap::new())))
}
pub(crate) fn new_with_args(args: Rc<RefCell<HashMap<String, String>>>) -> Self {
let extensions = Rc::new(default_extensions());
Self::new_with_extensions(
args,
extensions,
#[cfg(feature = "yaml")]
Rc::new(crate::eval::yaml_tag_handler::YamlTagHandlerRegistry::new()),
)
}
pub(crate) fn add_raw_property(
&self,
name: String,
value: String,
) {
#[cfg(feature = "compat")]
{
let metadata = self.compute_property_metadata(&value);
self.add_raw_property_with_metadata(name, value, metadata);
}
#[cfg(not(feature = "compat"))]
{
self.add_raw_property_with_metadata(name, value, ());
}
}
pub(crate) fn has_property(
&self,
name: &str,
) -> bool {
self.lookup_raw_value(name).is_some()
}
}