use crate::value::Value;
use relon_parser::Node;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard};
pub struct ListContext {
pub index: Mutex<usize>,
pub elements: Arc<[Arc<Thunk>]>,
pub iter_binding: Mutex<Option<(Arc<str>, Value)>>,
}
impl ListContext {
pub fn fixed(index: usize, elements: Arc<[Arc<Thunk>]>) -> Self {
Self {
index: Mutex::new(index),
elements,
iter_binding: Mutex::new(None),
}
}
pub fn empty(elements: Arc<[Arc<Thunk>]>) -> Self {
Self {
index: Mutex::new(0),
elements,
iter_binding: Mutex::new(None),
}
}
pub fn current_index(&self) -> usize {
*self.index.lock().unwrap()
}
}
#[derive(Clone)]
pub struct RootRef {
pub node: Arc<Node>,
pub scope: Option<Arc<Scope>>,
pub parent_fallback: Option<Arc<Scope>>,
}
impl RootRef {
pub fn new(node: Arc<Node>) -> Self {
Self {
node,
scope: None,
parent_fallback: None,
}
}
}
pub type Locals = Mutex<HashMap<Arc<str>, Value>>;
pub type Thunks = Mutex<HashMap<Arc<str>, Arc<Thunk>>>;
#[derive(Default)]
pub struct Scope {
pub parent: Option<Arc<Scope>>,
pub path_node: Option<Arc<str>>,
pub locals: Locals,
pub current_dir: Arc<str>,
pub cache_namespace: Arc<str>,
pub root_ref: Option<RootRef>,
pub list_context: Option<Arc<ListContext>>,
pub thunks: Thunks,
}
impl std::fmt::Debug for Scope {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Scope")
.field("path_node", &self.path_node)
.field("current_dir", &self.current_dir)
.field("cache_namespace", &self.cache_namespace)
.field("has_root_ref", &self.root_ref.is_some())
.field(
"index",
&self.list_context.as_ref().map(|c| c.current_index()),
)
.finish()
}
}
impl Clone for Scope {
fn clone(&self) -> Self {
Self {
parent: self.parent.clone(),
path_node: self.path_node.clone(),
locals: Mutex::new(self.locals.lock().unwrap().clone()),
current_dir: self.current_dir.clone(),
cache_namespace: self.cache_namespace.clone(),
root_ref: self.root_ref.clone(),
list_context: self.list_context.clone(),
thunks: Mutex::new(self.thunks.lock().unwrap().clone()),
}
}
}
impl Scope {
pub fn get_local(&self, name: &str) -> Option<Value> {
if let Some(ctx) = &self.list_context {
if let Some((bound_name, bound_val)) = ctx.iter_binding.lock().unwrap().as_ref() {
if bound_name.as_ref() == name {
return Some(bound_val.clone());
}
}
}
if let Some(v) = self.locals.lock().unwrap().get(name) {
Some(v.clone())
} else if let Some(parent) = &self.parent {
parent.get_local(name)
} else {
None
}
}
pub fn current_iter_binding(&self) -> Option<(Arc<str>, Value)> {
self.list_context
.as_ref()
.and_then(|c| c.iter_binding.lock().unwrap().clone())
}
pub fn get_thunk(&self, name: &str) -> Option<Arc<Thunk>> {
if let Some(thunk) = self.thunks.lock().unwrap().get(name) {
Some(Arc::clone(thunk))
} else if let Some(parent) = &self.parent {
parent.get_thunk(name)
} else {
None
}
}
pub fn get_own_thunk(&self, name: &str) -> Option<Arc<Thunk>> {
self.thunks.lock().unwrap().get(name).map(Arc::clone)
}
pub fn locals_for_write(&self) -> MutexGuard<'_, HashMap<Arc<str>, Value>> {
self.locals.lock().unwrap()
}
pub fn thunks_for_write(&self) -> MutexGuard<'_, HashMap<Arc<str>, Arc<Thunk>>> {
self.thunks.lock().unwrap()
}
pub fn full_path(&self) -> Vec<String> {
let mut path = Vec::new();
let mut current = Some(self);
while let Some(scope) = current {
if let Some(node) = &scope.path_node {
path.push(node.to_string());
}
current = scope.parent.as_deref();
}
path.reverse();
path
}
pub fn path_cache_key(&self, path: &[String]) -> String {
let namespace = if self.cache_namespace.is_empty() {
&self.current_dir
} else {
&self.cache_namespace
};
let encoded_path = path
.iter()
.map(|s| format!("{}:{}", s.len(), s))
.collect::<Vec<_>>()
.join("/");
format!("{namespace}::{encoded_path}")
}
pub fn child(self: &Arc<Self>) -> Arc<Self> {
Arc::new(Self {
parent: Some(Arc::clone(self)),
path_node: None,
locals: Mutex::new(HashMap::new()),
current_dir: self.current_dir.clone(),
cache_namespace: self.cache_namespace.clone(),
root_ref: self.root_ref.clone(),
list_context: self.list_context.clone(),
thunks: Mutex::new(HashMap::new()),
})
}
pub fn with_local(self: &Arc<Self>, name: impl Into<Arc<str>>, val: Value) -> Arc<Self> {
let child = self.child();
child.locals_for_write().insert(name.into(), val);
child
}
pub fn with_locals(self: &Arc<Self>, new_locals: HashMap<Arc<str>, Value>) -> Arc<Self> {
let mut child = self.child();
Arc::get_mut(&mut child)
.expect("freshly built child has no aliases")
.locals = Mutex::new(new_locals);
child
}
pub fn with_path(self: &Arc<Self>, node: impl Into<Arc<str>>) -> Arc<Self> {
let mut child = self.child();
Arc::get_mut(&mut child)
.expect("freshly built child has no aliases")
.path_node = Some(node.into());
child
}
pub fn with_list_context(
self: &Arc<Self>,
index: usize,
elements: Arc<[Arc<Thunk>]>,
) -> Arc<Self> {
let mut child = self.child();
let unique = Arc::get_mut(&mut child).expect("freshly built child has no aliases");
unique.path_node = Some(Arc::<str>::from(index.to_string()));
unique.list_context = Some(Arc::new(ListContext::fixed(index, elements)));
child
}
pub fn with_iter_loop(self: &Arc<Self>, elements: Arc<[Arc<Thunk>]>) -> Arc<Self> {
let mut child = self.child();
let unique = Arc::get_mut(&mut child).expect("freshly built child has no aliases");
unique.list_context = Some(Arc::new(ListContext::empty(elements)));
child
}
pub fn set_iter_binding(&self, name: Arc<str>, value: Value, index: usize) {
let ctx = self
.list_context
.as_ref()
.expect("set_iter_binding called on a scope without an iter loop");
*ctx.iter_binding.lock().unwrap() = Some((name, value));
*ctx.index.lock().unwrap() = index;
}
}
pub struct Thunk {
pub node: Node,
pub scope: Arc<Scope>,
pub path: Vec<String>,
pub cache_key: String,
pub value: Mutex<Option<Value>>,
}
impl Thunk {
pub fn new(node: Node, scope: Arc<Scope>, path: Vec<String>, cache_key: String) -> Self {
Self {
node,
scope,
path,
cache_key,
value: Mutex::new(None),
}
}
}