1use crate::configuration::BytecodeExecutor;
7
8use shape_ast::Program;
9use shape_ast::ast::{DestructurePattern, ExportItem, Item};
10use shape_ast::parser::parse_program;
11use shape_runtime::module_loader::ModuleCode;
12
13fn should_include_item(item: &Item, names: &std::collections::HashSet<&str>) -> bool {
17 match item {
18 Item::Function(func_def, _) => names.contains(func_def.name.as_str()),
19 Item::Export(export, _) => match &export.item {
20 ExportItem::Function(f) => names.contains(f.name.as_str()),
21 ExportItem::Enum(e) => names.contains(e.name.as_str()),
22 ExportItem::Struct(s) => names.contains(s.name.as_str()),
23 ExportItem::Trait(t) => names.contains(t.name.as_str()),
24 ExportItem::TypeAlias(a) => names.contains(a.name.as_str()),
25 ExportItem::Interface(i) => names.contains(i.name.as_str()),
26 ExportItem::ForeignFunction(f) => names.contains(f.name.as_str()),
27 ExportItem::Named(specs) => specs.iter().any(|s| names.contains(s.name.as_str())),
28 },
29 Item::StructType(def, _) => names.contains(def.name.as_str()),
30 Item::Enum(def, _) => names.contains(def.name.as_str()),
31 Item::Trait(def, _) => names.contains(def.name.as_str()),
32 Item::TypeAlias(def, _) => names.contains(def.name.as_str()),
33 Item::Interface(def, _) => names.contains(def.name.as_str()),
34 Item::VariableDecl(decl, _) => {
35 if let DestructurePattern::Identifier(name, _) = &decl.pattern {
36 names.contains(name.as_str())
37 } else {
38 false
39 }
40 }
41 Item::Impl(..) | Item::Extend(..) => true,
43 Item::Import(..) => true,
45 _ => false,
46 }
47}
48
49pub fn prepend_prelude_items(program: &mut Program) {
60 use shape_ast::ast::ImportItems;
61 use std::sync::OnceLock;
62
63 for item in &program.items {
65 if let Item::Import(import_stmt, _) = item {
66 if import_stmt.from == "std::core::prelude" || import_stmt.from == "std::prelude" {
67 return;
68 }
69 }
70 }
71
72 static RESOLVED_PRELUDE: OnceLock<Vec<Item>> = OnceLock::new();
73
74 let items = RESOLVED_PRELUDE.get_or_init(|| {
75 let mut loader = shape_runtime::module_loader::ModuleLoader::new();
76
77 let prelude = match loader.load_module("std::core::prelude") {
79 Ok(m) => m,
80 Err(_) => return Vec::new(),
81 };
82
83 let mut all_items = Vec::new();
84 let mut seen = std::collections::HashSet::new();
85
86 for item in &prelude.ast.items {
89 if let Item::Import(import_stmt, _) = item {
90 let module_path = &import_stmt.from;
91 if seen.insert(module_path.clone()) {
92 if let Ok(module) = loader.load_module(module_path) {
93 let named_filter: Option<std::collections::HashSet<&str>> =
95 match &import_stmt.items {
96 ImportItems::Named(specs) => {
97 Some(specs.iter().map(|s| s.name.as_str()).collect())
98 }
99 ImportItems::Namespace { .. } => None,
100 };
101
102 if let Some(ref names) = named_filter {
103 for ast_item in &module.ast.items {
104 if should_include_item(ast_item, names) {
105 all_items.push(ast_item.clone());
106 }
107 }
108 } else {
109 all_items.extend(module.ast.items.clone());
110 }
111 }
112 }
113 }
114 }
115
116 all_items
117 });
118
119 if !items.is_empty() {
120 let mut prelude_items = items.clone();
121 prelude_items.extend(std::mem::take(&mut program.items));
122 program.items = prelude_items;
123 }
124}
125
126impl BytecodeExecutor {
127 pub fn set_module_loader(&mut self, mut loader: shape_runtime::module_loader::ModuleLoader) {
132 if !self.dependency_paths.is_empty() {
133 loader.set_dependency_paths(self.dependency_paths.clone());
134 }
135 self.register_extension_artifacts_in_loader(&mut loader);
136 self.module_loader = Some(loader);
137 }
138
139 pub(crate) fn register_extension_artifacts_in_loader(
140 &self,
141 loader: &mut shape_runtime::module_loader::ModuleLoader,
142 ) {
143 for module in &self.extensions {
144 for artifact in &module.module_artifacts {
145 let code = match (&artifact.source, &artifact.compiled) {
146 (Some(source), Some(compiled)) => ModuleCode::Both {
147 source: std::sync::Arc::from(source.as_str()),
148 compiled: std::sync::Arc::from(compiled.clone()),
149 },
150 (Some(source), None) => {
151 ModuleCode::Source(std::sync::Arc::from(source.as_str()))
152 }
153 (None, Some(compiled)) => {
154 ModuleCode::Compiled(std::sync::Arc::from(compiled.clone()))
155 }
156 (None, None) => continue,
157 };
158 loader.register_extension_module(artifact.module_path.clone(), code);
159 }
160
161 if !module.shape_sources.is_empty() {
163 let legacy_path = format!("std::loaders::{}", module.name);
164 if !loader.has_extension_module(&legacy_path) {
165 let source = &module.shape_sources[0].1;
166 loader.register_extension_module(
167 legacy_path,
168 ModuleCode::Source(std::sync::Arc::from(source.as_str())),
169 );
170 }
171 if !loader.has_extension_module(&module.name) {
172 let source = &module.shape_sources[0].1;
173 loader.register_extension_module(
174 module.name.clone(),
175 ModuleCode::Source(std::sync::Arc::from(source.as_str())),
176 );
177 }
178 }
179 }
180 }
181
182 pub fn module_loader_mut(&mut self) -> Option<&mut shape_runtime::module_loader::ModuleLoader> {
184 self.module_loader.as_mut()
185 }
186
187 pub fn resolve_file_imports_with_context(
195 &mut self,
196 program: &Program,
197 context_dir: Option<&std::path::Path>,
198 ) {
199 use shape_ast::ast::Item;
200
201 let loader = match self.module_loader.as_mut() {
202 Some(l) => l,
203 None => return,
204 };
205 let context_dir = context_dir.map(std::path::Path::to_path_buf);
206
207 let import_paths: Vec<String> = program
209 .items
210 .iter()
211 .filter_map(|item| {
212 if let Item::Import(import_stmt, _) = item {
213 Some(import_stmt.from.clone())
214 } else {
215 None
216 }
217 })
218 .filter(|path| !path.is_empty())
219 .collect();
220
221 for module_path in &import_paths {
222 match loader.load_module_with_context(module_path, context_dir.as_ref()) {
223 Ok(_) => {}
224 Err(e) => {
225 eprintln!(
228 "Warning: module loader could not resolve '{}': {}",
229 module_path, e
230 );
231 }
232 }
233 }
234
235 let mut loaded_module_paths: Vec<String> = loader
238 .loaded_modules()
239 .into_iter()
240 .map(str::to_string)
241 .collect();
242 loaded_module_paths.sort();
243
244 for module_path in loaded_module_paths {
245 self.compiled_module_paths.insert(module_path);
246 }
247 }
248
249 pub fn resolve_file_imports(&mut self, program: &Program) {
251 self.resolve_file_imports_with_context(program, None);
252 }
253
254 pub fn resolve_file_imports_from_source(
256 &mut self,
257 source: &str,
258 context_dir: Option<&std::path::Path>,
259 ) {
260 match parse_program(source) {
261 Ok(program) => self.resolve_file_imports_with_context(&program, context_dir),
262 Err(e) => eprintln!(
263 "Warning: failed to parse source for import pre-resolution: {}",
264 e
265 ),
266 }
267 }
268
269 pub(crate) fn append_imported_module_items(&self, program: &mut Program) {
270 use shape_ast::ast::ImportItems;
271 let mut module_items = Vec::new();
272 let mut seen_paths = std::collections::HashSet::new();
273
274 for item in &program.items {
275 let Item::Import(import_stmt, _) = item else {
276 continue;
277 };
278 let module_path = import_stmt.from.as_str();
279 if module_path.is_empty() || !seen_paths.insert(module_path.to_string()) {
280 continue;
281 }
282
283 let named_filter: Option<std::collections::HashSet<&str>> =
285 match &import_stmt.items {
286 ImportItems::Named(specs) => {
287 Some(specs.iter().map(|s| s.name.as_str()).collect())
288 }
289 ImportItems::Namespace { .. } => None,
290 };
291
292 let ast_items: Option<Vec<Item>> =
293 if let Some(loader) = self.module_loader.as_ref()
294 && let Some(module) = loader.get_module(module_path)
295 {
296 Some(module.ast.items.clone())
297 } else if let Some(source) = self.virtual_modules.get(module_path)
298 && let Ok(parsed) = parse_program(source)
299 {
300 Some(parsed.items)
301 } else {
302 None
303 };
304
305 if let Some(items) = ast_items {
306 if let Some(ref names) = named_filter {
307 for ast_item in items {
308 if should_include_item(&ast_item, names) {
309 module_items.push(ast_item);
310 }
311 }
312 } else {
313 module_items.extend(items);
314 }
315 }
316 }
317
318 if !module_items.is_empty() {
319 module_items.extend(std::mem::take(&mut program.items));
320 program.items = module_items;
321 }
322 }
323
324 pub fn create_program_from_imports(
326 module_binding_registry: &std::sync::Arc<
327 std::sync::RwLock<shape_runtime::ModuleBindingRegistry>,
328 >,
329 ) -> shape_runtime::error::Result<Program> {
330 let registry = module_binding_registry.read().unwrap();
331 let items = Vec::new();
332
333 for name in registry.names() {
335 if let Some(value) = registry.get_by_name(name) {
336 if value.as_closure().is_some() {
337 }
340 }
341 }
342 Ok(Program { items })
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn test_prepend_prelude_items_injects_definitions() {
352 let mut program = Program { items: vec![] };
353 prepend_prelude_items(&mut program);
354 assert!(
356 !program.items.is_empty(),
357 "prepend_prelude_items should add items to the program"
358 );
359 }
360
361 #[test]
362 fn test_prepend_prelude_items_skips_when_already_imported() {
363 use shape_ast::ast::{ImportItems, ImportStmt, Item, Span};
364 let import = ImportStmt {
365 from: "std::core::prelude".to_string(),
366 items: ImportItems::Named(vec![]),
367 };
368 let mut program = Program {
369 items: vec![Item::Import(import, Span::DUMMY)],
370 };
371 let count_before = program.items.len();
372 prepend_prelude_items(&mut program);
373 assert_eq!(
374 program.items.len(),
375 count_before,
376 "should not inject prelude when already imported"
377 );
378 }
379
380 #[test]
381 fn test_prepend_prelude_items_idempotent() {
382 let mut program = Program { items: vec![] };
383 prepend_prelude_items(&mut program);
384 let count_after_first = program.items.len();
385 prepend_prelude_items(&mut program);
389 assert!(count_after_first > 0);
394 }
395
396 #[test]
397 fn test_prelude_compiles_with_stdlib_definitions() {
398 let executor = crate::configuration::BytecodeExecutor::new();
402 let mut engine =
403 shape_runtime::engine::ShapeEngine::new().expect("engine creation failed");
404 engine.load_stdlib().expect("load stdlib");
405
406 let program = shape_ast::parser::parse_program("let x = 42\nx").expect("parse");
408 let bytecode = executor
409 .compile_program_for_inspection(&mut engine, &program)
410 .expect("compile with prelude should succeed");
411
412 assert!(
416 !bytecode.functions.is_empty(),
417 "bytecode should contain prelude-injected functions"
418 );
419 }
420
421 #[test]
422 fn test_prelude_injects_math_trig_definitions() {
423 let mut program = Program { items: vec![] };
425 prepend_prelude_items(&mut program);
426
427 let has_fn_defs = program.items.iter().any(|item| {
429 matches!(
430 item,
431 shape_ast::ast::Item::Function(..)
432 | shape_ast::ast::Item::Export(..)
433 | shape_ast::ast::Item::Statement(..)
434 )
435 });
436 assert!(
437 has_fn_defs,
438 "prelude should inject function/statement definitions from stdlib modules"
439 );
440 }
441}