1use crate::type_schema::{TypeSchema, TypeSchemaRegistry};
7use shape_value::ValueWord;
8use std::collections::HashMap;
9use std::ffi::c_void;
10use std::future::Future;
11use std::pin::Pin;
12use std::sync::Arc;
13
14#[derive(Clone, Copy)]
21pub struct RawCallableInvoker {
22 pub ctx: *mut c_void,
23 pub invoke: unsafe fn(*mut c_void, &ValueWord, &[ValueWord]) -> Result<ValueWord, String>,
24}
25
26impl RawCallableInvoker {
27 pub unsafe fn call(
33 &self,
34 callable: &ValueWord,
35 args: &[ValueWord],
36 ) -> Result<ValueWord, String> {
37 unsafe { (self.invoke)(self.ctx, callable, args) }
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct FrameInfo {
44 pub function_id: Option<u16>,
45 pub function_name: String,
46 pub blob_hash: Option<[u8; 32]>,
47 pub local_ip: usize,
48 pub locals: Vec<ValueWord>,
49 pub upvalues: Option<Vec<ValueWord>>,
50 pub args: Vec<ValueWord>,
51}
52
53pub trait VmStateAccessor: Send + Sync {
55 fn current_frame(&self) -> Option<FrameInfo>;
56 fn all_frames(&self) -> Vec<FrameInfo>;
57 fn caller_frame(&self) -> Option<FrameInfo>;
58 fn current_args(&self) -> Vec<ValueWord>;
59 fn current_locals(&self) -> Vec<(String, ValueWord)>;
60 fn module_bindings(&self) -> Vec<(String, ValueWord)>;
61 fn instruction_count(&self) -> usize {
63 0
64 }
65}
66
67pub struct ModuleContext<'a> {
72 pub schemas: &'a TypeSchemaRegistry,
74
75 pub invoke_callable: Option<&'a dyn Fn(&ValueWord, &[ValueWord]) -> Result<ValueWord, String>>,
77
78 pub raw_invoker: Option<RawCallableInvoker>,
82
83 pub function_hashes: Option<&'a [Option<[u8; 32]>]>,
87
88 pub vm_state: Option<&'a dyn VmStateAccessor>,
91
92 pub granted_permissions: Option<shape_abi_v1::PermissionSet>,
96
97 pub scope_constraints: Option<shape_abi_v1::ScopeConstraints>,
100
101 pub set_pending_resume: Option<&'a dyn Fn(ValueWord)>,
105
106 pub set_pending_frame_resume: Option<&'a dyn Fn(usize, Vec<ValueWord>)>,
110}
111
112pub fn check_permission(
118 ctx: &ModuleContext,
119 permission: shape_abi_v1::Permission,
120) -> Result<(), String> {
121 if let Some(ref granted) = ctx.granted_permissions {
122 if !granted.contains(&permission) {
123 return Err(format!(
124 "Permission denied: {} ({})",
125 permission.description(),
126 permission.name()
127 ));
128 }
129 }
130 Ok(())
131}
132
133pub type ModuleFn = Arc<
139 dyn for<'ctx> Fn(&[ValueWord], &ModuleContext<'ctx>) -> Result<ValueWord, String> + Send + Sync,
140>;
141
142pub type AsyncModuleFn = Arc<
150 dyn Fn(&[ValueWord]) -> Pin<Box<dyn Future<Output = Result<ValueWord, String>> + Send>>
151 + Send
152 + Sync,
153>;
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum ModuleExportVisibility {
158 Public,
160 ComptimeOnly,
162 Internal,
164}
165
166impl Default for ModuleExportVisibility {
167 fn default() -> Self {
168 Self::Public
169 }
170}
171
172#[derive(Debug, Clone)]
175pub struct ModuleParam {
176 pub name: String,
177 pub type_name: String,
178 pub required: bool,
179 pub description: String,
180 pub default_snippet: Option<String>,
181 pub allowed_values: Option<Vec<String>>,
182 pub nested_params: Option<Vec<ModuleParam>>,
183}
184
185impl Default for ModuleParam {
186 fn default() -> Self {
187 Self {
188 name: String::new(),
189 type_name: "any".to_string(),
190 required: false,
191 description: String::new(),
192 default_snippet: None,
193 allowed_values: None,
194 nested_params: None,
195 }
196 }
197}
198
199#[derive(Debug, Clone)]
202pub struct ModuleFunction {
203 pub description: String,
204 pub params: Vec<ModuleParam>,
205 pub return_type: Option<String>,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct ModuleArtifact {
211 pub module_path: String,
213 pub source: Option<String>,
215 pub compiled: Option<Vec<u8>>,
217}
218
219#[derive(Clone)]
221pub struct ModuleExports {
222 pub name: String,
224 pub description: String,
226 pub exports: HashMap<String, ModuleFn>,
228 pub async_exports: HashMap<String, AsyncModuleFn>,
230 pub schemas: HashMap<String, ModuleFunction>,
232 pub export_visibility: HashMap<String, ModuleExportVisibility>,
234 pub shape_sources: Vec<(String, String)>,
240 pub module_artifacts: Vec<ModuleArtifact>,
242 pub method_intrinsics: HashMap<String, HashMap<String, ModuleFn>>,
247 pub type_schemas: Vec<TypeSchema>,
251}
252
253impl ModuleExports {
254 pub fn new(name: impl Into<String>) -> Self {
256 Self {
257 name: name.into(),
258 description: String::new(),
259 exports: HashMap::new(),
260 async_exports: HashMap::new(),
261 schemas: HashMap::new(),
262 export_visibility: HashMap::new(),
263 shape_sources: Vec::new(),
264 module_artifacts: Vec::new(),
265 method_intrinsics: HashMap::new(),
266 type_schemas: Vec::new(),
267 }
268 }
269
270 pub fn add_function<F>(&mut self, name: impl Into<String>, f: F) -> &mut Self
272 where
273 F: for<'ctx> Fn(&[ValueWord], &ModuleContext<'ctx>) -> Result<ValueWord, String>
274 + Send
275 + Sync
276 + 'static,
277 {
278 let name = name.into();
279 self.exports.insert(name.clone(), Arc::new(f));
280 self.export_visibility.entry(name).or_default();
281 self
282 }
283
284 pub fn add_function_with_schema<F>(
286 &mut self,
287 name: impl Into<String>,
288 f: F,
289 schema: ModuleFunction,
290 ) -> &mut Self
291 where
292 F: for<'ctx> Fn(&[ValueWord], &ModuleContext<'ctx>) -> Result<ValueWord, String>
293 + Send
294 + Sync
295 + 'static,
296 {
297 let name = name.into();
298 self.exports.insert(name.clone(), Arc::new(f));
299 self.schemas.insert(name.clone(), schema);
300 self.export_visibility.entry(name).or_default();
301 self
302 }
303
304 pub fn add_async_function<F, Fut>(&mut self, name: impl Into<String>, f: F) -> &mut Self
306 where
307 F: Fn(Vec<ValueWord>) -> Fut + Send + Sync + 'static,
308 Fut: Future<Output = Result<ValueWord, String>> + Send + 'static,
309 {
310 let name = name.into();
311 self.async_exports.insert(
312 name.clone(),
313 Arc::new(move |args: &[ValueWord]| {
314 let owned_args = args.to_vec();
315 Box::pin(f(owned_args))
316 }),
317 );
318 self.export_visibility.entry(name).or_default();
319 self
320 }
321
322 pub fn add_async_function_with_schema<F, Fut>(
324 &mut self,
325 name: impl Into<String>,
326 f: F,
327 schema: ModuleFunction,
328 ) -> &mut Self
329 where
330 F: Fn(Vec<ValueWord>) -> Fut + Send + Sync + 'static,
331 Fut: Future<Output = Result<ValueWord, String>> + Send + 'static,
332 {
333 let name = name.into();
334 self.async_exports.insert(
335 name.clone(),
336 Arc::new(move |args: &[ValueWord]| {
337 let owned_args = args.to_vec();
338 Box::pin(f(owned_args))
339 }),
340 );
341 self.schemas.insert(name.clone(), schema);
342 self.export_visibility.entry(name).or_default();
343 self
344 }
345
346 pub fn set_export_visibility(
348 &mut self,
349 name: impl Into<String>,
350 visibility: ModuleExportVisibility,
351 ) -> &mut Self {
352 self.export_visibility.insert(name.into(), visibility);
353 self
354 }
355
356 pub fn export_visibility(&self, name: &str) -> ModuleExportVisibility {
358 self.export_visibility
359 .get(name)
360 .copied()
361 .unwrap_or_default()
362 }
363
364 pub fn is_export_available(&self, name: &str, comptime_mode: bool) -> bool {
366 match self.export_visibility(name) {
367 ModuleExportVisibility::Public => true,
368 ModuleExportVisibility::ComptimeOnly => comptime_mode,
369 ModuleExportVisibility::Internal => true,
370 }
371 }
372
373 pub fn is_export_public_surface(&self, name: &str, comptime_mode: bool) -> bool {
375 match self.export_visibility(name) {
376 ModuleExportVisibility::Public => true,
377 ModuleExportVisibility::ComptimeOnly => comptime_mode,
378 ModuleExportVisibility::Internal => false,
379 }
380 }
381
382 pub fn export_names_available(&self, comptime_mode: bool) -> Vec<&str> {
384 self.export_names()
385 .into_iter()
386 .filter(|name| self.is_export_available(name, comptime_mode))
387 .collect()
388 }
389
390 pub fn export_names_public_surface(&self, comptime_mode: bool) -> Vec<&str> {
392 self.export_names()
393 .into_iter()
394 .filter(|name| self.is_export_public_surface(name, comptime_mode))
395 .collect()
396 }
397
398 pub fn add_shape_source(&mut self, filename: &str, source: &str) -> &mut Self {
401 self.module_artifacts.push(ModuleArtifact {
402 module_path: filename.to_string(),
403 source: Some(source.to_string()),
404 compiled: None,
405 });
406 self.shape_sources
407 .push((filename.to_string(), source.to_string()));
408 self
409 }
410
411 pub fn add_shape_artifact(
413 &mut self,
414 module_path: impl Into<String>,
415 source: Option<String>,
416 compiled: Option<Vec<u8>>,
417 ) -> &mut Self {
418 self.module_artifacts.push(ModuleArtifact {
419 module_path: module_path.into(),
420 source,
421 compiled,
422 });
423 self
424 }
425
426 pub fn add_intrinsic<F>(&mut self, type_name: &str, method_name: &str, f: F) -> &mut Self
429 where
430 F: for<'ctx> Fn(&[ValueWord], &ModuleContext<'ctx>) -> Result<ValueWord, String>
431 + Send
432 + Sync
433 + 'static,
434 {
435 self.method_intrinsics
436 .entry(type_name.to_string())
437 .or_default()
438 .insert(method_name.to_string(), Arc::new(f));
439 self
440 }
441
442 pub fn add_type_schema(&mut self, schema: TypeSchema) -> crate::type_schema::SchemaId {
445 let id = schema.id;
446 self.type_schemas.push(schema);
447 id
448 }
449
450 pub fn has_export(&self, name: &str) -> bool {
452 self.exports.contains_key(name) || self.async_exports.contains_key(name)
453 }
454
455 pub fn get_export(&self, name: &str) -> Option<&ModuleFn> {
457 self.exports.get(name)
458 }
459
460 pub fn get_async_export(&self, name: &str) -> Option<&AsyncModuleFn> {
462 self.async_exports.get(name)
463 }
464
465 pub fn is_async(&self, name: &str) -> bool {
467 self.async_exports.contains_key(name)
468 }
469
470 pub fn get_schema(&self, name: &str) -> Option<&ModuleFunction> {
472 self.schemas.get(name)
473 }
474
475 pub fn export_names(&self) -> Vec<&str> {
477 let mut names: Vec<&str> = self
478 .exports
479 .keys()
480 .chain(self.async_exports.keys())
481 .map(|s| s.as_str())
482 .collect();
483 names.sort_unstable();
484 names.dedup();
485 names
486 }
487
488 pub fn to_parsed_schema(&self) -> crate::extensions::ParsedModuleSchema {
491 let functions = self
492 .schemas
493 .iter()
494 .filter(|(name, _)| self.is_export_public_surface(name, false))
495 .map(|(name, schema)| crate::extensions::ParsedModuleFunction {
496 name: name.clone(),
497 description: schema.description.clone(),
498 params: schema.params.iter().map(|p| p.type_name.clone()).collect(),
499 return_type: schema.return_type.clone(),
500 })
501 .collect();
502 crate::extensions::ParsedModuleSchema {
503 module_name: self.name.clone(),
504 functions,
505 artifacts: Vec::new(),
506 }
507 }
508
509 pub fn stdlib_module_schemas() -> Vec<crate::extensions::ParsedModuleSchema> {
513 vec![
514 crate::stdlib::regex::create_regex_module().to_parsed_schema(),
515 crate::stdlib::http::create_http_module().to_parsed_schema(),
516 crate::stdlib::crypto::create_crypto_module().to_parsed_schema(),
517 crate::stdlib::env::create_env_module().to_parsed_schema(),
518 crate::stdlib::json::create_json_module().to_parsed_schema(),
519 crate::stdlib::toml_module::create_toml_module().to_parsed_schema(),
520 crate::stdlib::yaml::create_yaml_module().to_parsed_schema(),
521 crate::stdlib::xml::create_xml_module().to_parsed_schema(),
522 crate::stdlib::compress::create_compress_module().to_parsed_schema(),
523 crate::stdlib::archive::create_archive_module().to_parsed_schema(),
524 crate::stdlib::parallel::create_parallel_module().to_parsed_schema(),
525 crate::stdlib::unicode::create_unicode_module().to_parsed_schema(),
526 crate::stdlib::csv_module::create_csv_module().to_parsed_schema(),
527 crate::stdlib::msgpack_module::create_msgpack_module().to_parsed_schema(),
528 ]
529 }
530}
531
532impl std::fmt::Debug for ModuleExports {
533 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534 f.debug_struct("ModuleExports")
535 .field("name", &self.name)
536 .field("description", &self.description)
537 .field("exports", &self.exports.keys().collect::<Vec<_>>())
538 .field(
539 "async_exports",
540 &self.async_exports.keys().collect::<Vec<_>>(),
541 )
542 .field("schemas", &self.schemas.keys().collect::<Vec<_>>())
543 .field(
544 "shape_sources",
545 &self
546 .shape_sources
547 .iter()
548 .map(|(f, _)| f)
549 .collect::<Vec<_>>(),
550 )
551 .field(
552 "method_intrinsics",
553 &self.method_intrinsics.keys().collect::<Vec<_>>(),
554 )
555 .finish()
556 }
557}
558
559#[derive(Default)]
563pub struct ModuleExportRegistry {
564 modules: HashMap<String, ModuleExports>,
565}
566
567impl ModuleExportRegistry {
568 pub fn new() -> Self {
570 Self {
571 modules: HashMap::new(),
572 }
573 }
574
575 pub fn register(&mut self, module: ModuleExports) {
577 self.modules.insert(module.name.clone(), module);
578 }
579
580 pub fn get(&self, name: &str) -> Option<&ModuleExports> {
582 self.modules.get(name)
583 }
584
585 pub fn has(&self, name: &str) -> bool {
587 self.modules.contains_key(name)
588 }
589
590 pub fn module_names(&self) -> Vec<&str> {
592 self.modules.keys().map(|s| s.as_str()).collect()
593 }
594
595 pub fn modules(&self) -> &HashMap<String, ModuleExports> {
597 &self.modules
598 }
599}
600
601impl std::fmt::Debug for ModuleExportRegistry {
602 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
603 f.debug_struct("ModuleExportRegistry")
604 .field("modules", &self.modules.keys().collect::<Vec<_>>())
605 .finish()
606 }
607}
608
609#[cfg(test)]
610#[path = "module_exports_tests.rs"]
611mod tests;