Skip to main content

just_engine/runner/plugin/
super_global.rs

1//! Super-global environment — the bottom of the scope chain.
2//!
3//! This environment sits below the global scope and lazily resolves
4//! built-in and plugin-provided objects on first access. Objects are
5//! cached after first resolution so each name is materialized at most once.
6//!
7//! ## How It Works
8//!
9//! When JavaScript code references a name that isn't found in the lexical
10//! environment chain or global scope, the super-global environment is consulted:
11//!
12//! ```text
13//! JavaScript: Math.abs(-5)
14//!      ↓
15//! 1. Check local scope → not found
16//! 2. Check outer scopes → not found  
17//! 3. Check global scope → not found
18//! 4. Check super-global → "Math" found!
19//!      ↓
20//! 5. Query resolvers: Does anyone provide "Math"?
21//! 6. CorePluginResolver says "yes"
22//! 7. Cache the result
23//! 8. Dispatch Math.abs() via resolver
24//! ```
25//!
26//! ## Caching Strategy
27//!
28//! - **First lookup**: Query all resolvers in order, cache which one owns the name
29//! - **Subsequent lookups**: Use cached resolver index directly
30//! - **Method calls**: Can bypass object materialization entirely
31//!
32//! ## Example
33//!
34//! ```
35//! use just::runner::plugin::super_global::SuperGlobalEnvironment;
36//! use just::runner::plugin::resolver::PluginResolver;
37//! use just::runner::plugin::types::EvalContext;
38//! use just::runner::ds::value::{JsValue, JsNumberType};
39//! use just::runner::ds::error::JErrorType;
40//!
41//! struct MyPlugin;
42//!
43//! impl PluginResolver for MyPlugin {
44//!     fn has_binding(&self, name: &str) -> bool {
45//!         name == "MyObject"
46//!     }
47//!     
48//!     fn resolve(&self, _name: &str, _ctx: &mut EvalContext) -> Result<JsValue, JErrorType> {
49//!         Ok(JsValue::Number(JsNumberType::Integer(42)))
50//!     }
51//!     
52//!     fn call_method(&self, _obj: &str, _method: &str, _ctx: &mut EvalContext,
53//!                    _this: JsValue, _args: Vec<JsValue>) -> Option<Result<JsValue, JErrorType>> {
54//!         None
55//!     }
56//!     
57//!     fn name(&self) -> &str { "my_plugin" }
58//! }
59//!
60//! let mut sg = SuperGlobalEnvironment::new();
61//! sg.add_resolver(Box::new(MyPlugin));
62//!
63//! // Now "MyObject" is available in the super-global scope
64//! ```
65
66use std::collections::HashMap;
67
68use crate::runner::ds::error::JErrorType;
69use crate::runner::ds::value::JsValue;
70use crate::runner::plugin::resolver::PluginResolver;
71use crate::runner::plugin::types::EvalContext;
72
73/// The super-global environment for lazy resolution of built-in objects.
74///
75/// This is the outermost scope in the resolution chain, sitting below even
76/// the global scope. It provides built-in objects (Math, console, etc.) and
77/// plugin-provided objects through a lazy resolution mechanism.
78///
79/// ## Key Features
80///
81/// - **Lazy Resolution**: Objects are only materialized when first accessed
82/// - **Caching**: Once resolved, values are cached for fast subsequent access
83/// - **Read-Only**: JavaScript code cannot create or modify super-global bindings
84/// - **Plugin System**: Multiple resolvers can be registered, queried in order
85/// - **Method Dispatch**: Efficient method calls without object materialization
86///
87/// ## Resolution Order
88///
89/// When a name is looked up:
90/// 1. Check if it's in the cache → return cached value
91/// 2. Query each resolver's `has_binding()` in registration order
92/// 3. First resolver that claims the name wins
93/// 4. Call resolver's `resolve()` to materialize the value
94/// 5. Cache the value and resolver index
95/// 6. Return the value
96///
97/// ## Method Call Optimization
98///
99/// For method calls like `Math.abs(-5)`, the super-global can dispatch directly
100/// to the resolver's `call_method()` without materializing the Math object,
101/// providing better performance for built-in method calls.
102///
103/// ## Thread Safety
104///
105/// This struct is typically wrapped in `Rc<RefCell<>>` (see [`SharedSuperGlobal`](super::types::SharedSuperGlobal))
106/// to allow shared mutable access across the evaluation context.
107pub struct SuperGlobalEnvironment {
108    /// Registered plugin resolvers, queried in order.
109    resolvers: Vec<Box<dyn PluginResolver>>,
110    /// Cache of already-resolved bindings (name → value).
111    cache: HashMap<String, JsValue>,
112    /// Cache of which resolver index owns which name.
113    resolver_map: HashMap<String, usize>,
114}
115
116impl SuperGlobalEnvironment {
117    pub fn new() -> Self {
118        SuperGlobalEnvironment {
119            resolvers: Vec::new(),
120            cache: HashMap::new(),
121            resolver_map: HashMap::new(),
122        }
123    }
124
125    /// Register a plugin resolver. Resolvers are queried in registration order.
126    pub fn add_resolver(&mut self, resolver: Box<dyn PluginResolver>) {
127        self.resolvers.push(resolver);
128    }
129
130    /// Find which resolver (if any) provides the given name.
131    fn find_resolver_index(&self, name: &str) -> Option<usize> {
132        // Check the cache first
133        if let Some(&idx) = self.resolver_map.get(name) {
134            return Some(idx);
135        }
136        // Query resolvers in order
137        for (i, resolver) in self.resolvers.iter().enumerate() {
138            if resolver.has_binding(name) {
139                return Some(i);
140            }
141        }
142        None
143    }
144
145    /// Call a method on a super-global object, dispatching to the owning resolver.
146    ///
147    /// Returns `None` if no resolver owns `object_name` or the resolver
148    /// doesn't provide the method (allowing fallback to property lookup).
149    pub fn call_method(
150        &self,
151        object_name: &str,
152        method_name: &str,
153        ctx: &mut EvalContext,
154        this: JsValue,
155        args: Vec<JsValue>,
156    ) -> Option<Result<JsValue, JErrorType>> {
157        let idx = if let Some(&idx) = self.resolver_map.get(object_name) {
158            idx
159        } else {
160            self.find_resolver_index(object_name)?
161        };
162        self.resolvers[idx].call_method(object_name, method_name, ctx, this, args)
163    }
164
165    /// Call a constructor on a super-global object.
166    pub fn call_constructor(
167        &self,
168        object_name: &str,
169        ctx: &mut EvalContext,
170        args: Vec<JsValue>,
171    ) -> Option<Result<JsValue, JErrorType>> {
172        let idx = if let Some(&idx) = self.resolver_map.get(object_name) {
173            idx
174        } else {
175            self.find_resolver_index(object_name)?
176        };
177        self.resolvers[idx].call_constructor(object_name, ctx, args)
178    }
179
180    /// Check if any resolver provides the given name.
181    pub fn has_name(&self, name: &str) -> bool {
182        self.cache.contains_key(name) || self.find_resolver_index(name).is_some()
183    }
184
185    /// Resolve a name, caching the result.
186    /// `ctx` is needed because some resolvers may need it during materialization.
187    pub fn resolve_binding(
188        &mut self,
189        name: &str,
190        ctx: &mut EvalContext,
191    ) -> Result<JsValue, JErrorType> {
192        // Return cached value if available
193        if let Some(val) = self.cache.get(name) {
194            return Ok(val.clone());
195        }
196
197        // Find the owning resolver
198        if let Some(idx) = self.find_resolver_index(name) {
199            let value = self.resolvers[idx].resolve(name, ctx)?;
200            self.cache.insert(name.to_string(), value.clone());
201            self.resolver_map.insert(name.to_string(), idx);
202            Ok(value)
203        } else {
204            Err(JErrorType::ReferenceError(format!(
205                "{} is not defined",
206                name
207            )))
208        }
209    }
210
211    /// Get a reference to the resolvers (for inspection/testing).
212    pub fn resolvers(&self) -> &[Box<dyn PluginResolver>] {
213        &self.resolvers
214    }
215}