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    /// Optional permission set for compile-time capability checking.
36    /// When set, the compiler will deny imports that require permissions
37    /// not present in this set.
38    pub(crate) permission_set: Option<shape_abi_v1::PermissionSet>,
39    /// When true, the compiler allows `__intrinsic_*` calls from user code.
40    /// Used by test helpers that inline stdlib source as top-level code.
41    pub allow_internal_builtins: bool,
42}
43
44impl Default for BytecodeExecutor {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl BytecodeExecutor {
51    /// Get the registered extension modules.
52    pub fn extensions(&self) -> &[shape_runtime::module_exports::ModuleExports] {
53        &self.extensions
54    }
55
56    /// Create a new executor
57    pub fn new() -> Self {
58        let mut executor = Self {
59            extensions: Vec::new(),
60            virtual_modules: HashMap::new(),
61            compiled_module_paths: HashSet::new(),
62            interrupt: Arc::new(AtomicU8::new(0)),
63            bytecode_cache: None,
64            dependency_paths: HashMap::new(),
65            native_resolution_context: None,
66            root_package_key: None,
67            module_loader: None,
68            permission_set: None,
69            allow_internal_builtins: false,
70        };
71        executor.register_stdlib_modules();
72
73        // Always initialize a module loader so that graph-based compilation
74        // can resolve imports via the embedded stdlib modules.
75        let mut loader = shape_runtime::module_loader::ModuleLoader::new();
76        executor.register_extension_artifacts_in_loader(&mut loader);
77        executor.module_loader = Some(loader);
78
79        executor
80    }
81
82    /// Register the VM-native stdlib modules (regex, http, crypto, env, json, etc.)
83    /// so the compiler discovers their exports and emits correct module bindings.
84    fn register_stdlib_modules(&mut self) {
85        // shape-runtime canonical registry covers all non-VM modules.
86        self.extensions
87            .extend(shape_runtime::stdlib::all_stdlib_modules());
88        // VM-side modules (state, transport, remote) live in shape-vm.
89        self.extensions
90            .push(crate::executor::state_builtins::create_state_module());
91        self.extensions
92            .push(crate::executor::create_transport_module_exports());
93        self.extensions
94            .push(crate::executor::create_remote_module_exports());
95    }
96
97    /// Register an external/user extension module (e.g. loaded from a .so plugin).
98    /// It will be available in all subsequent executions.
99    /// Bundled Shape sources are kept for legacy virtual-module imports.
100    pub fn register_extension(&mut self, module: shape_runtime::module_exports::ModuleExports) {
101        // Register bundled module artifacts for import-based resolution.
102        // Compilation is deferred to unified module loading so extension module
103        // code compiles with the same context as normal modules.
104        let module_name = module.name.clone();
105        for artifact in &module.module_artifacts {
106            let Some(source) = artifact.source.as_ref() else {
107                continue;
108            };
109            let module_path = artifact.module_path.clone();
110            self.virtual_modules
111                .entry(module_path)
112                .or_insert_with(|| source.clone());
113        }
114
115        // Register shape_sources under the module's canonical name only.
116        // No legacy std::loaders:: paths — extensions use module_artifacts now.
117        for (_filename, source) in &module.shape_sources {
118            self.virtual_modules
119                .entry(module_name.clone())
120                .or_insert_with(|| source.clone());
121        }
122        self.extensions.push(module);
123    }
124
125    /// Return all currently registered extension/extension module schemas.
126    pub fn module_schemas(&self) -> Vec<shape_runtime::extensions::ParsedModuleSchema> {
127        self.extensions
128            .iter()
129            .map(|module| {
130                let mut functions = Vec::with_capacity(module.schemas.len());
131                for (name, schema) in &module.schemas {
132                    if !module.is_export_public_surface(name, false) {
133                        continue;
134                    }
135                    functions.push(shape_runtime::extensions::ParsedModuleFunction {
136                        name: name.clone(),
137                        description: schema.description.clone(),
138                        params: schema.params.iter().map(|p| p.type_name.clone()).collect(),
139                        return_type: schema.return_type.clone(),
140                    });
141                }
142                let artifacts = module
143                    .module_artifacts
144                    .iter()
145                    .map(|artifact| shape_runtime::extensions::ParsedModuleArtifact {
146                        module_path: artifact.module_path.clone(),
147                        source: artifact.source.clone(),
148                        compiled: artifact.compiled.clone(),
149                    })
150                    .collect();
151                shape_runtime::extensions::ParsedModuleSchema {
152                    module_name: module.name.clone(),
153                    functions,
154                    artifacts,
155                }
156            })
157            .collect()
158    }
159
160    /// Enable bytecode caching. Compiled programs will be stored as .shapec files
161    /// and reused on subsequent runs if the source hasn't changed.
162    /// Returns false if the cache directory could not be created.
163    pub fn enable_bytecode_cache(&mut self) -> bool {
164        match crate::bytecode_cache::BytecodeCache::new() {
165            Some(cache) => {
166                self.bytecode_cache = Some(cache);
167                true
168            }
169            None => false,
170        }
171    }
172
173    /// Set the interrupt flag (shared with Ctrl+C handler).
174    pub fn set_interrupt(&mut self, flag: Arc<AtomicU8>) {
175        self.interrupt = flag;
176    }
177
178    /// Set resolved dependency paths from shape.toml [dependencies].
179    ///
180    /// These are mirrored into the module loader so import resolution
181    /// matches runtime behavior.
182    pub fn set_dependency_paths(&mut self, paths: HashMap<String, std::path::PathBuf>) {
183        self.dependency_paths = paths.clone();
184        if let Some(loader) = self.module_loader.as_mut() {
185            loader.set_dependency_paths(paths);
186        }
187    }
188
189    /// Install the package-scoped native library resolutions for the current host.
190    pub fn set_native_resolution_context(
191        &mut self,
192        resolutions: shape_runtime::native_resolution::NativeResolutionSet,
193        root_package_key: Option<String>,
194    ) {
195        self.native_resolution_context = Some(resolutions);
196        self.root_package_key = root_package_key;
197    }
198
199    /// Clear any previously configured native resolution context.
200    pub fn clear_native_resolution_context(&mut self) {
201        self.native_resolution_context = None;
202        self.root_package_key = None;
203    }
204
205    /// Set the permission set for compile-time capability checking.
206    ///
207    /// When set, the compiler will deny imports that require permissions
208    /// not present in this set. Pass `None` to disable checking (default).
209    pub fn set_permission_set(&mut self, permissions: Option<shape_abi_v1::PermissionSet>) {
210        self.permission_set = permissions;
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_new_executor_has_module_loader() {
220        let executor = BytecodeExecutor::new();
221        assert!(
222            executor.module_loader.is_some(),
223            "new() should initialize module_loader"
224        );
225    }
226}