Skip to main content

shape_vm/
configuration.rs

1//! BytecodeExecutor struct definition, constructors, and configuration.
2//!
3//! Stdlib module registration, module schema export, bytecode caching,
4//! interrupt handling, and dependency path wiring live here.
5
6use std::collections::{HashMap, HashSet};
7use std::sync::Arc;
8use std::sync::atomic::AtomicU8;
9
10/// Bytecode executor for compiling and running Shape programs.
11///
12/// Core stdlib is loaded via prelude injection (AST inlining) rather than
13/// bytecode merging.
14pub struct BytecodeExecutor {
15    /// Extension modules to register with each VM instance
16    pub(crate) extensions: Vec<shape_runtime::module_exports::ModuleExports>,
17    /// Virtual module sources for import-based resolution (module_path → source)
18    pub(crate) virtual_modules: HashMap<String, String>,
19    /// Module paths tracked during file-based import resolution.
20    pub(crate) compiled_module_paths: HashSet<String>,
21    /// Interrupt flag shared with Ctrl+C handler
22    pub(crate) interrupt: Arc<AtomicU8>,
23    /// Optional bytecode cache for .shapec files
24    pub(crate) bytecode_cache: Option<crate::bytecode_cache::BytecodeCache>,
25    /// Resolved dependency paths from shape.toml, mirrored into the module loader.
26    pub(crate) dependency_paths: HashMap<String, std::path::PathBuf>,
27    /// Package-scoped native library resolutions for the current host.
28    pub(crate) native_resolution_context:
29        Option<shape_runtime::native_resolution::NativeResolutionSet>,
30    /// Root package identity for the currently configured execution context.
31    pub(crate) root_package_key: Option<String>,
32    /// Module loader for resolving file-based imports.
33    /// When set, imports that don't match virtual modules are resolved via the loader.
34    pub(crate) module_loader: Option<shape_runtime::module_loader::ModuleLoader>,
35}
36
37impl Default for BytecodeExecutor {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl BytecodeExecutor {
44    /// Create a new executor
45    pub fn new() -> Self {
46        let mut executor = Self {
47            extensions: Vec::new(),
48            virtual_modules: HashMap::new(),
49            compiled_module_paths: HashSet::new(),
50            interrupt: Arc::new(AtomicU8::new(0)),
51            bytecode_cache: None,
52            dependency_paths: HashMap::new(),
53            native_resolution_context: None,
54            root_package_key: None,
55            module_loader: None,
56        };
57        executor.register_stdlib_modules();
58
59        // Always initialize a module loader so that append_imported_module_items()
60        // can resolve imports via the embedded stdlib modules.
61        let mut loader = shape_runtime::module_loader::ModuleLoader::new();
62        executor.register_extension_artifacts_in_loader(&mut loader);
63        executor.module_loader = Some(loader);
64
65        executor
66    }
67
68    /// Register the VM-native stdlib modules (regex, http, crypto, env, json, etc.)
69    /// so the compiler discovers their exports and emits correct module bindings.
70    fn register_stdlib_modules(&mut self) {
71        self.extensions
72            .push(shape_runtime::stdlib::regex::create_regex_module());
73        self.extensions
74            .push(shape_runtime::stdlib::http::create_http_module());
75        self.extensions
76            .push(shape_runtime::stdlib::crypto::create_crypto_module());
77        self.extensions
78            .push(shape_runtime::stdlib::env::create_env_module());
79        self.extensions
80            .push(shape_runtime::stdlib::json::create_json_module());
81        self.extensions
82            .push(shape_runtime::stdlib::toml_module::create_toml_module());
83        self.extensions
84            .push(shape_runtime::stdlib::yaml::create_yaml_module());
85        self.extensions
86            .push(shape_runtime::stdlib::xml::create_xml_module());
87        self.extensions
88            .push(shape_runtime::stdlib::compress::create_compress_module());
89        self.extensions
90            .push(shape_runtime::stdlib::archive::create_archive_module());
91        self.extensions
92            .push(shape_runtime::stdlib::unicode::create_unicode_module());
93        self.extensions
94            .push(shape_runtime::stdlib::csv_module::create_csv_module());
95        self.extensions
96            .push(shape_runtime::stdlib::msgpack_module::create_msgpack_module());
97        self.extensions
98            .push(shape_runtime::stdlib::set_module::create_set_module());
99        self.extensions
100            .push(crate::executor::create_transport_module_exports());
101        self.extensions
102            .push(crate::executor::create_remote_module_exports());
103    }
104
105    /// Register an external/user extension module (e.g. loaded from a .so plugin).
106    /// It will be available in all subsequent executions.
107    /// Bundled Shape sources are kept for legacy virtual-module imports.
108    pub fn register_extension(&mut self, module: shape_runtime::module_exports::ModuleExports) {
109        // Register bundled module artifacts for import-based resolution.
110        // Compilation is deferred to unified module loading so extension module
111        // code compiles with the same context as normal modules.
112        let module_name = module.name.clone();
113        for artifact in &module.module_artifacts {
114            let Some(source) = artifact.source.as_ref() else {
115                continue;
116            };
117            let module_path = artifact.module_path.clone();
118            self.virtual_modules
119                .entry(module_path)
120                .or_insert_with(|| source.clone());
121        }
122
123        // Legacy compatibility path mappings for shape_sources.
124        let mut registered_primary_path = false;
125        for (filename, source) in &module.shape_sources {
126            // Backward-compatible import path.
127            let legacy_path = format!("std::loaders::{}", module_name);
128            self.virtual_modules
129                .entry(legacy_path)
130                .or_insert_with(|| source.clone());
131
132            // Primary resolver path for extension modules (`use duckdb`, `from duckdb use { ... }`).
133            if !registered_primary_path {
134                self.virtual_modules
135                    .entry(module_name.clone())
136                    .or_insert_with(|| source.clone());
137                registered_primary_path = true;
138            } else if let Some(stem) = std::path::Path::new(filename)
139                .file_stem()
140                .and_then(|s| s.to_str())
141            {
142                let extra_path = format!("{}::{}", module_name, stem);
143                self.virtual_modules
144                    .entry(extra_path)
145                    .or_insert_with(|| source.clone());
146            }
147        }
148        self.extensions.push(module);
149    }
150
151    /// Return all currently registered extension/extension module schemas.
152    pub fn module_schemas(&self) -> Vec<shape_runtime::extensions::ParsedModuleSchema> {
153        self.extensions
154            .iter()
155            .map(|module| {
156                let mut functions = Vec::with_capacity(module.schemas.len());
157                for (name, schema) in &module.schemas {
158                    if !module.is_export_public_surface(name, false) {
159                        continue;
160                    }
161                    functions.push(shape_runtime::extensions::ParsedModuleFunction {
162                        name: name.clone(),
163                        description: schema.description.clone(),
164                        params: schema.params.iter().map(|p| p.type_name.clone()).collect(),
165                        return_type: schema.return_type.clone(),
166                    });
167                }
168                let artifacts = module
169                    .module_artifacts
170                    .iter()
171                    .map(|artifact| shape_runtime::extensions::ParsedModuleArtifact {
172                        module_path: artifact.module_path.clone(),
173                        source: artifact.source.clone(),
174                        compiled: artifact.compiled.clone(),
175                    })
176                    .collect();
177                shape_runtime::extensions::ParsedModuleSchema {
178                    module_name: module.name.clone(),
179                    functions,
180                    artifacts,
181                }
182            })
183            .collect()
184    }
185
186    /// Enable bytecode caching. Compiled programs will be stored as .shapec files
187    /// and reused on subsequent runs if the source hasn't changed.
188    /// Returns false if the cache directory could not be created.
189    pub fn enable_bytecode_cache(&mut self) -> bool {
190        match crate::bytecode_cache::BytecodeCache::new() {
191            Some(cache) => {
192                self.bytecode_cache = Some(cache);
193                true
194            }
195            None => false,
196        }
197    }
198
199    /// Set the interrupt flag (shared with Ctrl+C handler).
200    pub fn set_interrupt(&mut self, flag: Arc<AtomicU8>) {
201        self.interrupt = flag;
202    }
203
204    /// Set resolved dependency paths from shape.toml [dependencies].
205    ///
206    /// These are mirrored into the module loader so import resolution
207    /// matches runtime behavior.
208    pub fn set_dependency_paths(&mut self, paths: HashMap<String, std::path::PathBuf>) {
209        self.dependency_paths = paths.clone();
210        if let Some(loader) = self.module_loader.as_mut() {
211            loader.set_dependency_paths(paths);
212        }
213    }
214
215    /// Install the package-scoped native library resolutions for the current host.
216    pub fn set_native_resolution_context(
217        &mut self,
218        resolutions: shape_runtime::native_resolution::NativeResolutionSet,
219        root_package_key: Option<String>,
220    ) {
221        self.native_resolution_context = Some(resolutions);
222        self.root_package_key = root_package_key;
223    }
224
225    /// Clear any previously configured native resolution context.
226    pub fn clear_native_resolution_context(&mut self) {
227        self.native_resolution_context = None;
228        self.root_package_key = None;
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_new_executor_has_module_loader() {
238        let executor = BytecodeExecutor::new();
239        assert!(
240            executor.module_loader.is_some(),
241            "new() should initialize module_loader"
242        );
243    }
244}