1use crate::configuration::BytecodeExecutor;
7
8use shape_ast::Program;
9use shape_ast::ast::{DestructurePattern, ExportItem, Item};
10use shape_ast::error::Result;
11use shape_ast::parser::parse_program;
12use shape_runtime::module_loader::ModuleCode;
13
14pub(crate) fn should_include_item(item: &Item, names: &std::collections::HashSet<&str>) -> bool {
18 match item {
19 Item::Function(func_def, _) => names.contains(func_def.name.as_str()),
20 Item::Export(export, _) => match &export.item {
21 ExportItem::Function(f) => names.contains(f.name.as_str()),
22 ExportItem::Enum(e) => names.contains(e.name.as_str()),
23 ExportItem::Struct(s) => names.contains(s.name.as_str()),
24 ExportItem::Trait(t) => names.contains(t.name.as_str()),
25 ExportItem::TypeAlias(a) => names.contains(a.name.as_str()),
26 ExportItem::Interface(i) => names.contains(i.name.as_str()),
27 ExportItem::ForeignFunction(f) => names.contains(f.name.as_str()),
28 ExportItem::Named(specs) => specs.iter().any(|s| names.contains(s.name.as_str())),
29 },
30 Item::StructType(def, _) => names.contains(def.name.as_str()),
31 Item::Enum(def, _) => names.contains(def.name.as_str()),
32 Item::Trait(def, _) => names.contains(def.name.as_str()),
33 Item::TypeAlias(def, _) => names.contains(def.name.as_str()),
34 Item::Interface(def, _) => names.contains(def.name.as_str()),
35 Item::VariableDecl(decl, _) => {
36 if let DestructurePattern::Identifier(name, _) = &decl.pattern {
37 names.contains(name.as_str())
38 } else {
39 false
40 }
41 }
42 Item::Impl(..) | Item::Extend(..) => true,
44 Item::Import(..) => true,
46 _ => false,
47 }
48}
49
50pub(crate) fn collect_function_names_from_items(
52 items: &[Item],
53) -> std::collections::HashSet<String> {
54 let mut names = std::collections::HashSet::new();
55 for item in items {
56 match item {
57 Item::Function(func_def, _) => {
58 names.insert(func_def.name.clone());
59 }
60 Item::Export(export, _) => {
61 if let ExportItem::Function(f) = &export.item {
62 names.insert(f.name.clone());
63 } else if let ExportItem::ForeignFunction(f) = &export.item {
64 names.insert(f.name.clone());
65 }
66 }
67 _ => {}
68 }
69 }
70 names
71}
72
73pub(crate) fn annotate_program_native_abi_package_key(
75 program: &mut Program,
76 package_key: Option<&str>,
77) {
78 let Some(package_key) = package_key else {
79 return;
80 };
81 for item in &mut program.items {
82 annotate_item_native_abi_package_key(item, package_key);
83 }
84}
85
86fn annotate_item_native_abi_package_key(item: &mut Item, package_key: &str) {
87 match item {
88 Item::ForeignFunction(def, _) => {
89 if let Some(native) = def.native_abi.as_mut()
90 && native.package_key.is_none()
91 {
92 native.package_key = Some(package_key.to_string());
93 }
94 }
95 Item::Export(export, _) => {
96 if let ExportItem::ForeignFunction(def) = &mut export.item
97 && let Some(native) = def.native_abi.as_mut()
98 && native.package_key.is_none()
99 {
100 native.package_key = Some(package_key.to_string());
101 }
102 }
103 Item::Module(module, _) => {
104 for nested in &mut module.items {
105 annotate_item_native_abi_package_key(nested, package_key);
106 }
107 }
108 _ => {}
109 }
110}
111
112pub fn prepend_prelude_items(program: &mut Program) -> std::collections::HashSet<String> {
126 use shape_ast::ast::ImportItems;
127 use std::sync::OnceLock;
128
129 for item in &program.items {
131 if let Item::Import(import_stmt, _) = item {
132 if import_stmt.from == "std::core::prelude" || import_stmt.from == "std::prelude" {
133 return std::collections::HashSet::new();
134 }
135 }
136 }
137
138 static RESOLVED_PRELUDE: OnceLock<(Vec<Item>, std::collections::HashSet<String>)> =
139 OnceLock::new();
140
141 let (items, stdlib_names) = RESOLVED_PRELUDE.get_or_init(|| {
142 let mut loader = shape_runtime::module_loader::ModuleLoader::new();
143
144 let prelude = match loader.load_module("std::core::prelude") {
146 Ok(m) => m,
147 Err(_) => return (Vec::new(), std::collections::HashSet::new()),
148 };
149
150 let mut all_items = Vec::new();
151 let mut seen = std::collections::HashSet::new();
152
153 for item in &prelude.ast.items {
156 if let Item::Import(import_stmt, _) = item {
157 let module_path = &import_stmt.from;
158 if seen.insert(module_path.clone()) {
159 if let Ok(module) = loader.load_module(module_path) {
160 let named_filter: Option<std::collections::HashSet<&str>> =
162 match &import_stmt.items {
163 ImportItems::Named(specs) => {
164 Some(specs.iter().map(|s| s.name.as_str()).collect())
165 }
166 ImportItems::Namespace { .. } => None,
167 };
168
169 if let Some(ref names) = named_filter {
170 for ast_item in &module.ast.items {
171 if should_include_item(ast_item, names) {
172 all_items.push(ast_item.clone());
173 }
174 }
175 } else {
176 all_items.extend(module.ast.items.clone());
177 }
178 }
179 }
180 }
181 }
182
183 let stdlib_names = collect_function_names_from_items(&all_items);
184 (all_items, stdlib_names)
185 });
186
187 if !items.is_empty() {
188 let mut prelude_items = items.clone();
189 prelude_items.extend(std::mem::take(&mut program.items));
190 program.items = prelude_items;
191 }
192
193 stdlib_names.clone()
194}
195
196impl BytecodeExecutor {
197 pub fn set_module_loader(&mut self, mut loader: shape_runtime::module_loader::ModuleLoader) {
202 if !self.dependency_paths.is_empty() {
203 loader.set_dependency_paths(self.dependency_paths.clone());
204 }
205 self.register_extension_artifacts_in_loader(&mut loader);
206 self.module_loader = Some(loader);
207 }
208
209 pub(crate) fn register_extension_artifacts_in_loader(
210 &self,
211 loader: &mut shape_runtime::module_loader::ModuleLoader,
212 ) {
213 for module in &self.extensions {
214 for artifact in &module.module_artifacts {
215 let code = match (&artifact.source, &artifact.compiled) {
216 (Some(source), Some(compiled)) => ModuleCode::Both {
217 source: std::sync::Arc::from(source.as_str()),
218 compiled: std::sync::Arc::from(compiled.clone()),
219 },
220 (Some(source), None) => {
221 ModuleCode::Source(std::sync::Arc::from(source.as_str()))
222 }
223 (None, Some(compiled)) => {
224 ModuleCode::Compiled(std::sync::Arc::from(compiled.clone()))
225 }
226 (None, None) => continue,
227 };
228 loader.register_extension_module(artifact.module_path.clone(), code);
229 }
230
231 if !module.shape_sources.is_empty() {
233 let legacy_path = format!("std::loaders::{}", module.name);
234 if !loader.has_extension_module(&legacy_path) {
235 let source = &module.shape_sources[0].1;
236 loader.register_extension_module(
237 legacy_path,
238 ModuleCode::Source(std::sync::Arc::from(source.as_str())),
239 );
240 }
241 if !loader.has_extension_module(&module.name) {
242 let source = &module.shape_sources[0].1;
243 loader.register_extension_module(
244 module.name.clone(),
245 ModuleCode::Source(std::sync::Arc::from(source.as_str())),
246 );
247 }
248 }
249 }
250 }
251
252 pub fn module_loader_mut(&mut self) -> Option<&mut shape_runtime::module_loader::ModuleLoader> {
254 self.module_loader.as_mut()
255 }
256
257 pub fn resolve_file_imports_with_context(
265 &mut self,
266 program: &Program,
267 context_dir: Option<&std::path::Path>,
268 ) {
269 use shape_ast::ast::Item;
270
271 let loader = match self.module_loader.as_mut() {
272 Some(l) => l,
273 None => return,
274 };
275 let context_dir = context_dir.map(std::path::Path::to_path_buf);
276
277 let import_paths: Vec<String> = program
279 .items
280 .iter()
281 .filter_map(|item| {
282 if let Item::Import(import_stmt, _) = item {
283 Some(import_stmt.from.clone())
284 } else {
285 None
286 }
287 })
288 .filter(|path| !path.is_empty())
289 .collect();
290
291 for module_path in &import_paths {
292 match loader.load_module_with_context(module_path, context_dir.as_ref()) {
293 Ok(_) => {}
294 Err(e) => {
295 eprintln!(
298 "Warning: module loader could not resolve '{}': {}",
299 module_path, e
300 );
301 }
302 }
303 }
304
305 let mut loaded_module_paths: Vec<String> = loader
308 .loaded_modules()
309 .into_iter()
310 .map(str::to_string)
311 .collect();
312 loaded_module_paths.sort();
313
314 for module_path in loaded_module_paths {
315 self.compiled_module_paths.insert(module_path);
316 }
317 }
318
319 pub fn resolve_file_imports(&mut self, program: &Program) {
321 self.resolve_file_imports_with_context(program, None);
322 }
323
324 pub fn resolve_file_imports_from_source(
326 &mut self,
327 source: &str,
328 context_dir: Option<&std::path::Path>,
329 ) {
330 match parse_program(source) {
331 Ok(program) => self.resolve_file_imports_with_context(&program, context_dir),
332 Err(e) => eprintln!(
333 "Warning: failed to parse source for import pre-resolution: {}",
334 e
335 ),
336 }
337 }
338
339 pub(crate) fn append_imported_module_items(
346 &mut self,
347 program: &mut Program,
348 ) -> Result<std::collections::HashSet<String>> {
349 use shape_ast::ast::ImportItems;
350 let mut inlined_names: std::collections::HashMap<
353 String,
354 Option<std::collections::HashSet<String>>,
355 > = std::collections::HashMap::new();
356 let mut stdlib_names = std::collections::HashSet::new();
357
358 loop {
359 let mut module_items = Vec::new();
360 let mut found_new = false;
361
362 let mut merged: std::collections::HashMap<
367 String,
368 Option<std::collections::HashSet<String>>,
369 > = std::collections::HashMap::new();
370
371 for item in program.items.iter() {
372 let Item::Import(import_stmt, _) = item else {
373 continue;
374 };
375 let module_path = import_stmt.from.as_str();
376 if module_path.is_empty() {
377 continue;
378 }
379
380 if matches!(inlined_names.get(module_path), Some(None)) {
382 continue;
383 }
384
385 let named_filter: Option<std::collections::HashSet<String>> =
386 match &import_stmt.items {
387 ImportItems::Named(specs) => {
388 Some(specs.iter().map(|s| s.name.clone()).collect())
389 }
390 ImportItems::Namespace { .. } => None,
391 };
392
393 let new_filter = match &named_filter {
395 None => {
396 if matches!(inlined_names.get(module_path), Some(None)) {
398 continue;
399 }
400 None
401 }
402 Some(names) => {
403 let mut new_names = names.clone();
404 if let Some(Some(already)) = inlined_names.get(module_path) {
405 new_names.retain(|n| !already.contains(n));
406 }
407 if new_names.is_empty() {
408 continue;
409 }
410 Some(new_names)
411 }
412 };
413
414 let entry = merged
416 .entry(module_path.to_string())
417 .or_insert_with(|| Some(std::collections::HashSet::new()));
418 match new_filter {
419 None => {
420 *entry = None;
422 }
423 Some(ref new) => {
424 if let Some(existing) = entry {
425 existing.extend(new.iter().cloned());
426 }
427 }
429 }
430 }
431
432 for (module_path, merged_filter) in &merged {
433 found_new = true;
434 let is_std = module_path.starts_with("std::");
435
436 let ast_items: Option<Vec<Item>> = if let Some(loader) = self.module_loader.as_mut()
438 {
439 if let Some(module) = loader.get_module(module_path) {
440 Some(module.ast.items.clone())
441 } else {
442 Some(loader.load_module(module_path)?.ast.items.clone())
443 }
444 } else {
445 None
446 };
447
448 let ast_items = match ast_items {
449 Some(items) => Some(items),
450 None => match self.virtual_modules.get(module_path.as_str()) {
451 Some(source) => Some(parse_program(source)?.items),
452 None => None,
453 },
454 };
455
456 if let Some(items) = ast_items {
457 if is_std {
458 stdlib_names.extend(collect_function_names_from_items(&items));
459 }
460 if let Some(names) = merged_filter {
461 let names_ref: std::collections::HashSet<&str> =
462 names.iter().map(|s| s.as_str()).collect();
463 for ast_item in items {
464 if should_include_item(&ast_item, &names_ref) {
465 module_items.push(ast_item);
466 }
467 }
468 let entry = inlined_names
470 .entry(module_path.clone())
471 .or_insert_with(|| Some(std::collections::HashSet::new()));
472 if let Some(existing) = entry {
473 existing.extend(names.iter().cloned());
474 }
475 } else {
476 module_items.extend(items);
477 inlined_names.insert(module_path.clone(), None);
479 }
480 }
481 }
482
483 if !module_items.is_empty() {
484 module_items.extend(std::mem::take(&mut program.items));
485 program.items = module_items;
486 }
487
488 if !found_new {
489 break;
490 }
491 }
492
493 Ok(stdlib_names)
494 }
495
496 pub fn create_program_from_imports(
498 module_binding_registry: &std::sync::Arc<
499 std::sync::RwLock<shape_runtime::ModuleBindingRegistry>,
500 >,
501 ) -> shape_runtime::error::Result<Program> {
502 let registry = module_binding_registry.read().unwrap();
503 let items = Vec::new();
504
505 for name in registry.names() {
507 if let Some(value) = registry.get_by_name(name) {
508 if value.as_closure().is_some() {
509 }
512 }
513 }
514 Ok(Program {
515 items,
516 docs: shape_ast::ast::ProgramDocs::default(),
517 })
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 fn test_prepend_prelude_items_injects_definitions() {
527 let mut program = Program {
528 items: vec![],
529 docs: shape_ast::ast::ProgramDocs::default(),
530 };
531 prepend_prelude_items(&mut program);
532 assert!(
534 !program.items.is_empty(),
535 "prepend_prelude_items should add items to the program"
536 );
537 }
538
539 #[test]
540 fn test_prepend_prelude_items_skips_when_already_imported() {
541 use shape_ast::ast::{ImportItems, ImportStmt, Item, Span};
542 let import = ImportStmt {
543 from: "std::core::prelude".to_string(),
544 items: ImportItems::Named(vec![]),
545 };
546 let mut program = Program {
547 items: vec![Item::Import(import, Span::DUMMY)],
548 docs: shape_ast::ast::ProgramDocs::default(),
549 };
550 let count_before = program.items.len();
551 prepend_prelude_items(&mut program);
552 assert_eq!(
553 program.items.len(),
554 count_before,
555 "should not inject prelude when already imported"
556 );
557 }
558
559 #[test]
560 fn test_prepend_prelude_items_idempotent() {
561 let mut program = Program {
562 items: vec![],
563 docs: shape_ast::ast::ProgramDocs::default(),
564 };
565 prepend_prelude_items(&mut program);
566 let count_after_first = program.items.len();
567 prepend_prelude_items(&mut program);
571 assert!(count_after_first > 0);
576 }
577
578 #[test]
579 fn test_prelude_compiles_with_stdlib_definitions() {
580 let mut executor = crate::configuration::BytecodeExecutor::new();
584 let mut engine = shape_runtime::engine::ShapeEngine::new().expect("engine creation failed");
585 engine.load_stdlib().expect("load stdlib");
586
587 let program = shape_ast::parser::parse_program("let x = 42\nx").expect("parse");
589 let bytecode = executor
590 .compile_program_for_inspection(&mut engine, &program)
591 .expect("compile with prelude should succeed");
592
593 assert!(
597 !bytecode.functions.is_empty(),
598 "bytecode should contain prelude-injected functions"
599 );
600 }
601
602 #[test]
603 fn test_prelude_injects_math_trig_definitions() {
604 let mut program = Program {
606 items: vec![],
607 docs: shape_ast::ast::ProgramDocs::default(),
608 };
609 prepend_prelude_items(&mut program);
610
611 let has_fn_defs = program.items.iter().any(|item| {
613 matches!(
614 item,
615 shape_ast::ast::Item::Function(..)
616 | shape_ast::ast::Item::Export(..)
617 | shape_ast::ast::Item::Statement(..)
618 )
619 });
620 assert!(
621 has_fn_defs,
622 "prelude should inject function/statement definitions from stdlib modules"
623 );
624 }
625}