use crate::decorator::DecoratorPlugin;
use crate::module::ModuleResolver;
use crate::native_fn::RelonFunction;
use crate::value::Value;
use relon_parser::Node;
use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicU64;
use std::sync::{Arc, Mutex};
pub use relon_cap::{
Capabilities, CapabilityBit, NativeFnGate, ResourceBudget, ResourceBudgetProfile,
};
pub struct GatedNativeFn {
pub func: Arc<dyn RelonFunction>,
pub gate: NativeFnGate,
}
pub struct Context {
pub root_node: Option<Arc<Node>>,
pub decorators: HashMap<String, Arc<dyn DecoratorPlugin>>,
pub functions: HashMap<String, GatedNativeFn>,
pub native_methods: HashMap<String, HashMap<String, GatedNativeFn>>,
pub schemas: HashMap<String, Value>,
module_resolvers: Vec<Arc<dyn ModuleResolver>>,
pub path_cache: Mutex<HashMap<String, Value>>,
pub module_cache: Mutex<HashMap<String, Value>>,
pub iter_cursors: Mutex<HashMap<u64, usize>>,
pub iter_id_counter: AtomicU64,
pub loading_modules: Mutex<HashMap<String, usize>>,
pub evaluating_paths: Mutex<HashSet<String>>,
pub step_counter: AtomicU64,
pub closure_call_counter: AtomicU64,
analyzed: Option<Arc<relon_analyzer::AnalyzedTree>>,
pub workspace: Option<Arc<relon_analyzer::WorkspaceTree>>,
capabilities: Capabilities,
pub sandboxed_flag: bool,
pub backend_prepared: bool,
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
impl Context {
pub fn new() -> Self {
Self {
root_node: None,
decorators: HashMap::new(),
functions: HashMap::new(),
native_methods: HashMap::new(),
schemas: HashMap::new(),
module_resolvers: Vec::new(),
path_cache: Mutex::new(HashMap::new()),
module_cache: Mutex::new(HashMap::new()),
iter_cursors: Mutex::new(HashMap::new()),
iter_id_counter: AtomicU64::new(0),
loading_modules: Mutex::new(HashMap::new()),
evaluating_paths: Mutex::new(HashSet::new()),
step_counter: AtomicU64::new(0),
closure_call_counter: AtomicU64::new(0),
analyzed: None,
workspace: None,
capabilities: Capabilities::default(),
sandboxed_flag: false,
backend_prepared: false,
}
}
pub fn sandboxed() -> Self {
let mut this = Self::new();
this.sandboxed_flag = true;
this
}
pub fn with_root(mut self, node: Node) -> Self {
self.root_node = Some(Arc::new(node));
self
}
pub fn with_analyzed(mut self, tree: Arc<relon_analyzer::AnalyzedTree>) -> Self {
self.analyzed = Some(tree);
self
}
pub fn with_workspace(mut self, workspace: Arc<relon_analyzer::WorkspaceTree>) -> Self {
if let Some(entry) = workspace.modules.get(&workspace.entry_id) {
self.analyzed = Some(Arc::clone(entry));
}
self.workspace = Some(workspace);
self
}
pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
self.capabilities = capabilities;
self
}
pub fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
pub fn analyzed(&self) -> Option<&Arc<relon_analyzer::AnalyzedTree>> {
self.analyzed.as_ref()
}
pub fn module_resolvers(&self) -> &[Arc<dyn ModuleResolver>] {
&self.module_resolvers
}
pub fn prepend_module_resolver(&mut self, resolver: Arc<dyn ModuleResolver>) {
self.module_resolvers.insert(0, resolver);
}
pub fn append_module_resolver(&mut self, resolver: Arc<dyn ModuleResolver>) {
self.module_resolvers.push(resolver);
}
pub fn register_fn<S: Into<String>>(
&mut self,
name: S,
gate: NativeFnGate,
func: Arc<dyn RelonFunction>,
) {
self.functions
.insert(name.into(), GatedNativeFn { func, gate });
}
pub fn register_pure_fn<S: Into<String>>(&mut self, name: S, func: Arc<dyn RelonFunction>) {
self.register_fn(name, NativeFnGate::default(), func);
}
pub fn register_method<S: Into<String>, M: Into<String>>(
&mut self,
schema: S,
method: M,
gate: NativeFnGate,
func: Arc<dyn RelonFunction>,
) {
self.native_methods
.entry(schema.into())
.or_default()
.insert(method.into(), GatedNativeFn { func, gate });
}
pub fn register_pure_method<S: Into<String>, M: Into<String>>(
&mut self,
schema: S,
method: M,
func: Arc<dyn RelonFunction>,
) {
self.register_method(schema, method, NativeFnGate::default(), func);
}
pub fn register_decorator<S: Into<String>>(
&mut self,
name: S,
plugin: Arc<dyn DecoratorPlugin>,
) {
self.decorators.insert(name.into(), plugin);
}
pub fn register_schema<S: Into<String>>(&mut self, name: S, schema: Value) {
self.schemas.insert(name.into(), schema);
}
pub fn enter_loading_module(&self, id: String) -> LoadingModuleGuard<'_> {
*self
.loading_modules
.lock()
.unwrap()
.entry(id.clone())
.or_insert(0) += 1;
LoadingModuleGuard {
context: self,
module_id: id,
}
}
pub fn analyzer_target(&self, id: relon_parser::NodeId) -> Option<Node> {
self.analyzed()
.and_then(|tree| tree.node(id).map(|arc| (**arc).clone()))
}
pub fn next_iter_id(&self) -> u64 {
use std::sync::atomic::Ordering;
let id = self.iter_id_counter.fetch_add(1, Ordering::Relaxed);
self.iter_cursors.lock().unwrap().insert(id, 0);
id
}
pub fn iter_cursor_fetch_and_inc(&self, iter_id: u64, len: usize) -> Option<usize> {
let mut cursors = self.iter_cursors.lock().unwrap();
let cursor_slot = cursors.get_mut(&iter_id)?;
if *cursor_slot < len {
let idx = *cursor_slot;
*cursor_slot += 1;
Some(idx)
} else {
None
}
}
}
pub struct LoadingModuleGuard<'a> {
context: &'a Context,
module_id: String,
}
impl Drop for LoadingModuleGuard<'_> {
fn drop(&mut self) {
let mut loading = self.context.loading_modules.lock().unwrap();
if let Some(count) = loading.get_mut(&self.module_id) {
*count -= 1;
if *count == 0 {
loading.remove(&self.module_id);
}
}
}
}
#[cfg(test)]
mod cap_bit_tests {
use super::*;
#[test]
fn cap_bit_indices_are_stable() {
assert_eq!(CapabilityBit::ReadsFs.bit_index(), 0);
assert_eq!(CapabilityBit::WritesFs.bit_index(), 1);
assert_eq!(CapabilityBit::Network.bit_index(), 2);
assert_eq!(CapabilityBit::ReadsClock.bit_index(), 3);
assert_eq!(CapabilityBit::ReadsEnv.bit_index(), 4);
assert_eq!(CapabilityBit::UsesRng.bit_index(), 5);
}
}