Skip to main content

just_engine/runner/plugin/
registry.rs

1//! Built-in registry for managing built-in objects and plugins.
2
3use std::collections::HashMap;
4use std::path::Path;
5
6use super::types::{BuiltInFn, BuiltInObject, PluginInfo};
7use super::config::PluginConfig;
8use crate::runner::std_lib::register_core_builtins;
9
10/// Error type for plugin operations.
11#[derive(Debug)]
12pub enum PluginError {
13    /// Plugin not found at the specified path.
14    NotFound(String),
15    /// Plugin failed to load.
16    LoadError(String),
17    /// Plugin registration failed.
18    RegistrationError(String),
19    /// Configuration error.
20    ConfigError(String),
21    /// Object not found in registry.
22    ObjectNotFound(String),
23    /// Method not found on object.
24    MethodNotFound(String, String),
25}
26
27impl std::fmt::Display for PluginError {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            PluginError::NotFound(path) => write!(f, "Plugin not found: {}", path),
31            PluginError::LoadError(msg) => write!(f, "Plugin load error: {}", msg),
32            PluginError::RegistrationError(msg) => write!(f, "Plugin registration error: {}", msg),
33            PluginError::ConfigError(msg) => write!(f, "Plugin config error: {}", msg),
34            PluginError::ObjectNotFound(name) => write!(f, "Object not found: {}", name),
35            PluginError::MethodNotFound(obj, method) => {
36                write!(f, "Method not found: {}.{}", obj, method)
37            }
38        }
39    }
40}
41
42impl std::error::Error for PluginError {}
43
44/// Loaded plugin information.
45pub struct LoadedPlugin {
46    pub info: PluginInfo,
47    pub source: PluginSource,
48}
49
50/// Source of a loaded plugin.
51pub enum PluginSource {
52    /// Built-in (compiled with the runtime).
53    BuiltIn,
54    /// Native dynamic library.
55    Native(String),
56    /// JavaScript file.
57    JavaScript(String),
58}
59
60/// Registry for built-in objects and methods.
61///
62/// Manages all built-in JavaScript objects (Math, console, Array, etc.)
63/// and their methods. Supports plugin loading and method overrides.
64///
65/// # Examples
66///
67/// ```
68/// use just::runner::plugin::registry::BuiltInRegistry;
69/// use just::runner::plugin::types::EvalContext;
70///
71/// // Create registry with core built-ins
72/// let registry = BuiltInRegistry::with_core();
73///
74/// // Use with evaluation context
75/// let mut ctx = EvalContext::new();
76/// ctx.install_core_builtins(registry);
77/// ```
78pub struct BuiltInRegistry {
79    /// All registered built-in objects.
80    objects: HashMap<String, BuiltInObject>,
81
82    /// Loaded plugins.
83    plugins: Vec<LoadedPlugin>,
84
85    /// Override chain (object.method -> list of override sources).
86    overrides: HashMap<String, Vec<String>>,
87}
88
89impl BuiltInRegistry {
90    /// Create an empty registry.
91    ///
92    /// Use [`with_core`](Self::with_core) instead to get a registry
93    /// with standard built-in objects.
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// use just::runner::plugin::registry::BuiltInRegistry;
99    ///
100    /// let registry = BuiltInRegistry::new();
101    /// // Registry is empty, no built-ins registered
102    /// ```
103    pub fn new() -> Self {
104        BuiltInRegistry {
105            objects: HashMap::new(),
106            plugins: Vec::new(),
107            overrides: HashMap::new(),
108        }
109    }
110
111    /// Create a registry with core built-in objects.
112    ///
113    /// Includes: Math, console, JSON, String, Number, Array, Object, Error types.
114    /// This is the most common way to create a registry.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use just::parser::JsParser;
120    /// use just::runner::plugin::registry::BuiltInRegistry;
121    /// use just::runner::plugin::types::EvalContext;
122    /// use just::runner::eval::statement::execute_statement;
123    ///
124    /// let mut ctx = EvalContext::new();
125    /// ctx.install_core_builtins(BuiltInRegistry::with_core());
126    ///
127    /// // Now you can use Math, console, etc.
128    /// let code = "var x = Math.abs(-42);";
129    /// let ast = JsParser::parse_to_ast_from_str(code).unwrap();
130    /// for stmt in &ast.body {
131    ///     execute_statement(stmt, &mut ctx).unwrap();
132    /// }
133    /// ```
134    pub fn with_core() -> Self {
135        let mut registry = Self::new();
136
137        // Register core built-ins from std_lib
138        register_core_builtins(&mut registry);
139
140        // Mark that we have core built-ins loaded
141        registry.plugins.push(LoadedPlugin {
142            info: PluginInfo::new("core", "0.1.0")
143                .with_provides(vec![
144                    "console".to_string(),
145                    "Object".to_string(),
146                    "Array".to_string(),
147                    "String".to_string(),
148                    "Number".to_string(),
149                    "Math".to_string(),
150                    "JSON".to_string(),
151                    "Error".to_string(),
152                ]),
153            source: PluginSource::BuiltIn,
154        });
155
156        registry
157    }
158
159    /// Register a custom built-in object.
160    ///
161    /// Use this to add your own built-in objects to the registry.
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use just::runner::plugin::registry::BuiltInRegistry;
167    /// use just::runner::plugin::types::BuiltInObject;
168    ///
169    /// let mut registry = BuiltInRegistry::new();
170    /// let my_object = BuiltInObject::new("MyObject");
171    /// registry.register_object(my_object);
172    /// ```
173    pub fn register_object(&mut self, obj: BuiltInObject) {
174        self.objects.insert(obj.name.clone(), obj);
175    }
176
177    /// Get a registered object by name.
178    ///
179    /// Returns `None` if the object is not registered.
180    ///
181    /// # Examples
182    ///
183    /// ```
184    /// use just::runner::plugin::registry::BuiltInRegistry;
185    ///
186    /// let registry = BuiltInRegistry::with_core();
187    /// let math = registry.get_object("Math");
188    /// assert!(math.is_some());
189    /// ```
190    pub fn get_object(&self, name: &str) -> Option<&BuiltInObject> {
191        self.objects.get(name)
192    }
193
194    /// Get a mutable reference to a registered object.
195    pub fn get_object_mut(&mut self, name: &str) -> Option<&mut BuiltInObject> {
196        self.objects.get_mut(name)
197    }
198
199    /// Override an existing built-in method.
200    pub fn override_method(
201        &mut self,
202        object: &str,
203        method: &str,
204        func: BuiltInFn,
205    ) -> Result<(), PluginError> {
206        let obj = self
207            .objects
208            .get_mut(object)
209            .ok_or_else(|| PluginError::ObjectNotFound(object.to_string()))?;
210
211        obj.methods.insert(method.to_string(), func);
212
213        // Track the override
214        let key = format!("{}.{}", object, method);
215        self.overrides
216            .entry(key)
217            .or_insert_with(Vec::new)
218            .push("manual".to_string());
219
220        Ok(())
221    }
222
223    /// Get a built-in function for execution.
224    pub fn get_method(&self, object: &str, method: &str) -> Option<&BuiltInFn> {
225        self.objects
226            .get(object)
227            .and_then(|obj| obj.methods.get(method))
228    }
229
230    /// Get a constructor function for an object.
231    pub fn get_constructor(&self, object: &str) -> Option<&BuiltInFn> {
232        self.objects
233            .get(object)
234            .and_then(|obj| obj.constructor.as_ref())
235    }
236
237    /// Check if an object exists in the registry.
238    pub fn has_object(&self, name: &str) -> bool {
239        self.objects.contains_key(name)
240    }
241
242    /// Check if a method exists on an object.
243    pub fn has_method(&self, object: &str, method: &str) -> bool {
244        self.objects
245            .get(object)
246            .map(|obj| obj.methods.contains_key(method))
247            .unwrap_or(false)
248    }
249
250    /// Load native plugin from dynamic library.
251    /// Note: This is a placeholder - actual implementation requires unsafe FFI code
252    /// and the `libloading` crate for cross-platform support.
253    pub fn load_native_plugin(&mut self, path: &Path) -> Result<PluginInfo, PluginError> {
254        // TODO: Implement using libloading crate
255        // The plugin must export:
256        // - plugin_info() -> PluginInfo
257        // - plugin_register(&mut BuiltInRegistry)
258        Err(PluginError::LoadError(format!(
259            "Native plugin loading not yet implemented: {}",
260            path.display()
261        )))
262    }
263
264    /// Load JavaScript plugin from file.
265    /// Note: This requires the runtime to be functional to execute the JS code.
266    pub fn load_js_plugin(&mut self, path: &Path) -> Result<PluginInfo, PluginError> {
267        // TODO: Implement after runtime is complete
268        // The JS file should export __plugin__ metadata and object definitions
269        Err(PluginError::LoadError(format!(
270            "JavaScript plugin loading not yet implemented: {}",
271            path.display()
272        )))
273    }
274
275    /// Load plugins from config file.
276    pub fn load_from_config(&mut self, config_path: &Path) -> Result<(), PluginError> {
277        let config = PluginConfig::load(config_path)?;
278
279        for native_plugin in &config.native_plugins {
280            if native_plugin.enabled {
281                self.load_native_plugin(Path::new(&native_plugin.path))?;
282            }
283        }
284
285        for js_plugin in &config.js_plugins {
286            if js_plugin.enabled {
287                self.load_js_plugin(Path::new(&js_plugin.path))?;
288            }
289        }
290
291        Ok(())
292    }
293
294    /// Get list of all registered object names.
295    pub fn object_names(&self) -> Vec<&String> {
296        self.objects.keys().collect()
297    }
298
299    /// Get list of all loaded plugins.
300    pub fn loaded_plugins(&self) -> &[LoadedPlugin] {
301        &self.plugins
302    }
303}
304
305impl Default for BuiltInRegistry {
306    fn default() -> Self {
307        Self::with_core()
308    }
309}