Skip to main content

shape_vm/
configuration.rs

1//! BytecodeExecutor struct definition, constructors, and configuration.
2//!
3//! Extension 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    /// Module loader for resolving file-based imports.
28    /// When set, imports that don't match virtual modules are resolved via the loader.
29    pub(crate) module_loader: Option<shape_runtime::module_loader::ModuleLoader>,
30}
31
32impl Default for BytecodeExecutor {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl BytecodeExecutor {
39    /// Create a new executor
40    pub fn new() -> Self {
41        let mut executor = Self {
42            extensions: Vec::new(),
43            virtual_modules: HashMap::new(),
44            compiled_module_paths: HashSet::new(),
45            interrupt: Arc::new(AtomicU8::new(0)),
46            bytecode_cache: None,
47            dependency_paths: HashMap::new(),
48            module_loader: None,
49        };
50        executor.register_stdlib_extensions();
51
52        // Always initialize a module loader so that append_imported_module_items()
53        // can resolve imports via the embedded stdlib modules.
54        let mut loader = shape_runtime::module_loader::ModuleLoader::new();
55        executor.register_extension_artifacts_in_loader(&mut loader);
56        executor.module_loader = Some(loader);
57
58        executor
59    }
60
61    /// Register the VM-native stdlib modules (regex, http, crypto, env, log, json)
62    /// so the compiler discovers their exports and emits correct module bindings.
63    fn register_stdlib_extensions(&mut self) {
64        self.extensions
65            .push(shape_runtime::stdlib::regex::create_regex_module());
66        self.extensions
67            .push(shape_runtime::stdlib::http::create_http_module());
68        self.extensions
69            .push(shape_runtime::stdlib::crypto::create_crypto_module());
70        self.extensions
71            .push(shape_runtime::stdlib::env::create_env_module());
72        self.extensions
73            .push(shape_runtime::stdlib::log::create_log_module());
74        self.extensions
75            .push(shape_runtime::stdlib::json::create_json_module());
76        self.extensions
77            .push(shape_runtime::stdlib::toml_module::create_toml_module());
78        self.extensions
79            .push(shape_runtime::stdlib::yaml::create_yaml_module());
80        self.extensions
81            .push(shape_runtime::stdlib::xml::create_xml_module());
82        self.extensions
83            .push(shape_runtime::stdlib::compress::create_compress_module());
84        self.extensions
85            .push(shape_runtime::stdlib::archive::create_archive_module());
86        self.extensions
87            .push(shape_runtime::stdlib::unicode::create_unicode_module());
88    }
89
90    /// Register an extension module. It will be available in all subsequent executions.
91    /// Bundled Shape sources are kept for legacy virtual-module imports.
92    pub fn register_extension(&mut self, module: shape_runtime::module_exports::ModuleExports) {
93        // Register bundled module artifacts for import-based resolution.
94        // Compilation is deferred to unified module loading so extension module
95        // code compiles with the same context as normal modules.
96        let module_name = module.name.clone();
97        for artifact in &module.module_artifacts {
98            let Some(source) = artifact.source.as_ref() else {
99                continue;
100            };
101            let module_path = artifact.module_path.clone();
102            self.virtual_modules
103                .entry(module_path)
104                .or_insert_with(|| source.clone());
105        }
106
107        // Legacy compatibility path mappings for shape_sources.
108        let mut registered_primary_path = false;
109        for (filename, source) in &module.shape_sources {
110            // Backward-compatible import path.
111            let legacy_path = format!("std::loaders::{}", module_name);
112            self.virtual_modules
113                .entry(legacy_path)
114                .or_insert_with(|| source.clone());
115
116            // Primary resolver path for extension modules (`use duckdb`, `from duckdb use { ... }`).
117            if !registered_primary_path {
118                self.virtual_modules
119                    .entry(module_name.clone())
120                    .or_insert_with(|| source.clone());
121                registered_primary_path = true;
122            } else if let Some(stem) = std::path::Path::new(filename)
123                .file_stem()
124                .and_then(|s| s.to_str())
125            {
126                let extra_path = format!("{}::{}", module_name, stem);
127                self.virtual_modules
128                    .entry(extra_path)
129                    .or_insert_with(|| source.clone());
130            }
131
132        }
133        self.extensions.push(module);
134    }
135
136    /// Return all currently registered extension/extension module schemas.
137    pub fn module_schemas(&self) -> Vec<shape_runtime::extensions::ParsedModuleSchema> {
138        self.extensions
139            .iter()
140            .map(|module| {
141                let mut functions = Vec::with_capacity(module.schemas.len());
142                for (name, schema) in &module.schemas {
143                    if !module.is_export_public_surface(name, false) {
144                        continue;
145                    }
146                    functions.push(shape_runtime::extensions::ParsedModuleFunction {
147                        name: name.clone(),
148                        description: schema.description.clone(),
149                        params: schema.params.iter().map(|p| p.type_name.clone()).collect(),
150                        return_type: schema.return_type.clone(),
151                    });
152                }
153                let artifacts = module
154                    .module_artifacts
155                    .iter()
156                    .map(|artifact| shape_runtime::extensions::ParsedModuleArtifact {
157                        module_path: artifact.module_path.clone(),
158                        source: artifact.source.clone(),
159                        compiled: artifact.compiled.clone(),
160                    })
161                    .collect();
162                shape_runtime::extensions::ParsedModuleSchema {
163                    module_name: module.name.clone(),
164                    functions,
165                    artifacts,
166                }
167            })
168            .collect()
169    }
170
171    /// Enable bytecode caching. Compiled programs will be stored as .shapec files
172    /// and reused on subsequent runs if the source hasn't changed.
173    /// Returns false if the cache directory could not be created.
174    pub fn enable_bytecode_cache(&mut self) -> bool {
175        match crate::bytecode_cache::BytecodeCache::new() {
176            Some(cache) => {
177                self.bytecode_cache = Some(cache);
178                true
179            }
180            None => false,
181        }
182    }
183
184    /// Set the interrupt flag (shared with Ctrl+C handler).
185    pub fn set_interrupt(&mut self, flag: Arc<AtomicU8>) {
186        self.interrupt = flag;
187    }
188
189    /// Set resolved dependency paths from shape.toml [dependencies].
190    ///
191    /// These are mirrored into the module loader so import resolution
192    /// matches runtime behavior.
193    pub fn set_dependency_paths(&mut self, paths: HashMap<String, std::path::PathBuf>) {
194        self.dependency_paths = paths.clone();
195        if let Some(loader) = self.module_loader.as_mut() {
196            loader.set_dependency_paths(paths);
197        }
198    }
199
200}
201
202#[cfg(test)]
203mod tests {
204    use super::*;
205
206    #[test]
207    fn test_new_executor_has_module_loader() {
208        let executor = BytecodeExecutor::new();
209        assert!(
210            executor.module_loader.is_some(),
211            "new() should initialize module_loader"
212        );
213    }
214}