1use crate::configuration::BytecodeExecutor;
7
8use shape_ast::Program;
9use shape_ast::ast::{ExportItem, Item};
10use shape_ast::parser::parse_program;
11use shape_runtime::module_loader::ModuleCode;
12
13pub(crate) fn hidden_annotation_import_module_name(module_path: &str) -> String {
14 use std::hash::{Hash, Hasher};
15
16 let mut hasher = std::collections::hash_map::DefaultHasher::new();
17 module_path.hash(&mut hasher);
18 format!("__annimport__{:016x}", hasher.finish())
19}
20
21pub(crate) fn is_hidden_annotation_import_module_name(name: &str) -> bool {
22 name.starts_with("__annimport__")
23}
24
25pub fn build_graph_and_stdlib_names(
34 program: &Program,
35 loader: &mut shape_runtime::module_loader::ModuleLoader,
36 extensions: &[shape_runtime::module_exports::ModuleExports],
37) -> std::result::Result<
38 (
39 std::sync::Arc<crate::module_graph::ModuleGraph>,
40 std::collections::HashSet<String>,
41 Vec<String>,
42 ),
43 shape_ast::error::ShapeError,
44> {
45 let prelude_imports = crate::module_graph::collect_prelude_import_paths(loader);
46 let graph =
47 crate::module_graph::build_module_graph(program, loader, extensions, &prelude_imports)
48 .map_err(|e| shape_ast::error::ShapeError::ModuleError {
49 message: e.to_string(),
50 module_path: None,
51 })?;
52 let graph = std::sync::Arc::new(graph);
53
54 let mut stdlib_names = std::collections::HashSet::new();
55 for prelude_path in &prelude_imports {
56 if let Some(dep_id) = graph.id_for_path(prelude_path) {
57 let dep_node = graph.node(dep_id);
58 for export_name in dep_node.interface.exports.keys() {
59 stdlib_names.insert(export_name.clone());
60 stdlib_names.insert(format!("{}::{}", prelude_path, export_name));
61 }
62 }
63 }
64
65 Ok((graph, stdlib_names, prelude_imports))
66}
67
68pub(crate) fn annotate_program_native_abi_package_key(
70 program: &mut Program,
71 package_key: Option<&str>,
72) {
73 let Some(package_key) = package_key else {
74 return;
75 };
76 for item in &mut program.items {
77 annotate_item_native_abi_package_key(item, package_key);
78 }
79}
80
81fn annotate_item_native_abi_package_key(item: &mut Item, package_key: &str) {
82 match item {
83 Item::ForeignFunction(def, _) => {
84 if let Some(native) = def.native_abi.as_mut()
85 && native.package_key.is_none()
86 {
87 native.package_key = Some(package_key.to_string());
88 }
89 }
90 Item::Export(export, _) => {
91 if let ExportItem::ForeignFunction(def) = &mut export.item
92 && let Some(native) = def.native_abi.as_mut()
93 && native.package_key.is_none()
94 {
95 native.package_key = Some(package_key.to_string());
96 }
97 }
98 Item::Module(module, _) => {
99 for nested in &mut module.items {
100 annotate_item_native_abi_package_key(nested, package_key);
101 }
102 }
103 _ => {}
104 }
105}
106
107
108impl BytecodeExecutor {
109 pub fn set_module_loader(&mut self, mut loader: shape_runtime::module_loader::ModuleLoader) {
114 if !self.dependency_paths.is_empty() {
115 loader.set_dependency_paths(self.dependency_paths.clone());
116 }
117 self.register_extension_artifacts_in_loader(&mut loader);
118 self.module_loader = Some(loader);
119 }
120
121 pub(crate) fn register_extension_artifacts_in_loader(
122 &self,
123 loader: &mut shape_runtime::module_loader::ModuleLoader,
124 ) {
125 for module in &self.extensions {
126 for artifact in &module.module_artifacts {
127 let code = match (&artifact.source, &artifact.compiled) {
128 (Some(source), Some(compiled)) => ModuleCode::Both {
129 source: std::sync::Arc::from(source.as_str()),
130 compiled: std::sync::Arc::from(compiled.clone()),
131 },
132 (Some(source), None) => {
133 ModuleCode::Source(std::sync::Arc::from(source.as_str()))
134 }
135 (None, Some(compiled)) => {
136 ModuleCode::Compiled(std::sync::Arc::from(compiled.clone()))
137 }
138 (None, None) => continue,
139 };
140 loader.register_extension_module(artifact.module_path.clone(), code);
141 }
142
143 for (_filename, source) in &module.shape_sources {
145 if !loader.has_extension_module(&module.name) {
146 loader.register_extension_module(
147 module.name.clone(),
148 ModuleCode::Source(std::sync::Arc::from(source.as_str())),
149 );
150 }
151 }
152 }
153 }
154
155 pub fn module_loader_mut(&mut self) -> Option<&mut shape_runtime::module_loader::ModuleLoader> {
157 self.module_loader.as_mut()
158 }
159
160 pub fn resolve_file_imports_with_context(
168 &mut self,
169 program: &Program,
170 context_dir: Option<&std::path::Path>,
171 ) {
172 use shape_ast::ast::Item;
173
174 let loader = match self.module_loader.as_mut() {
175 Some(l) => l,
176 None => return,
177 };
178 let context_dir = context_dir.map(std::path::Path::to_path_buf);
179
180 let import_paths: Vec<String> = program
182 .items
183 .iter()
184 .filter_map(|item| {
185 if let Item::Import(import_stmt, _) = item {
186 Some(import_stmt.from.clone())
187 } else {
188 None
189 }
190 })
191 .filter(|path| !path.is_empty())
192 .collect();
193
194 for module_path in &import_paths {
195 let _ = loader.load_module_with_context(module_path, context_dir.as_ref());
199 }
200
201 let mut loaded_module_paths: Vec<String> = loader
204 .loaded_modules()
205 .into_iter()
206 .map(str::to_string)
207 .collect();
208 loaded_module_paths.sort();
209
210 for module_path in loaded_module_paths {
211 self.compiled_module_paths.insert(module_path);
212 }
213 }
214
215 pub fn resolve_file_imports(&mut self, program: &Program) {
217 self.resolve_file_imports_with_context(program, None);
218 }
219
220 pub fn resolve_file_imports_from_source(
222 &mut self,
223 source: &str,
224 context_dir: Option<&std::path::Path>,
225 ) {
226 match parse_program(source) {
227 Ok(program) => self.resolve_file_imports_with_context(&program, context_dir),
228 Err(e) => eprintln!(
229 "Warning: failed to parse source for import pre-resolution: {}",
230 e
231 ),
232 }
233 }
234
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::VMConfig;
241 use crate::compiler::BytecodeCompiler;
242 use crate::executor::VirtualMachine;
243 use crate::module_graph;
244
245 fn compile_program_with_graph(
247 source: &str,
248 extra_paths: &[std::path::PathBuf],
249 ) -> shape_ast::error::Result<crate::bytecode::BytecodeProgram> {
250 let program = shape_ast::parser::parse_program(source)?;
251 let mut loader = shape_runtime::module_loader::ModuleLoader::new();
252 for p in extra_paths {
253 loader.add_module_path(p.clone());
254 }
255 let prelude_imports = module_graph::collect_prelude_import_paths(&mut loader);
256 let graph = module_graph::build_module_graph(&program, &mut loader, &[], &prelude_imports)
257 .map_err(|e| shape_ast::error::ShapeError::ModuleError {
258 message: e.to_string(),
259 module_path: None,
260 })?;
261 let graph = std::sync::Arc::new(graph);
262
263 let mut stdlib_names = std::collections::HashSet::new();
264 for prelude_path in &prelude_imports {
265 if let Some(dep_id) = graph.id_for_path(prelude_path) {
266 let dep_node = graph.node(dep_id);
267 for export_name in dep_node.interface.exports.keys() {
268 stdlib_names.insert(export_name.clone());
269 stdlib_names.insert(format!("{}::{}", prelude_path, export_name));
270 }
271 }
272 }
273
274 let mut compiler = BytecodeCompiler::new();
275 compiler.stdlib_function_names = stdlib_names;
276 compiler.compile_with_graph_and_prelude(&program, graph, &prelude_imports)
277 }
278
279 #[test]
280 fn test_graph_prelude_provides_stdlib_definitions() {
281 let bytecode = compile_program_with_graph("let x = 42\nx", &[])
283 .expect("compile with graph prelude should succeed");
284 assert!(
285 !bytecode.functions.is_empty(),
286 "bytecode should contain prelude-compiled functions"
287 );
288 }
289
290 #[test]
291 fn test_graph_prelude_includes_math_functions() {
292 let program = shape_ast::parser::parse_program("let x = 1\nx").expect("parse");
294 let mut loader = shape_runtime::module_loader::ModuleLoader::new();
295 let prelude_imports = module_graph::collect_prelude_import_paths(&mut loader);
296 let graph =
297 module_graph::build_module_graph(&program, &mut loader, &[], &prelude_imports)
298 .expect("graph build");
299
300 let math_id = graph.id_for_path("std::core::math");
302 assert!(math_id.is_some(), "graph should contain std::core::math");
303
304 let math_node = graph.node(math_id.unwrap());
305 assert!(
306 math_node.interface.exports.contains_key("sum"),
307 "std::core::math should export 'sum'"
308 );
309 }
310
311 #[test]
312 fn test_graph_compiles_with_engine() {
313 let mut executor = crate::configuration::BytecodeExecutor::new();
315 let mut engine =
316 shape_runtime::engine::ShapeEngine::new().expect("engine creation failed");
317 engine.load_stdlib().expect("load stdlib");
318
319 let program = shape_ast::parser::parse_program("let x = 42\nx").expect("parse");
320 let bytecode = executor
321 .compile_program_for_inspection(&mut engine, &program)
322 .expect("compile with graph pipeline should succeed");
323
324 assert!(
325 !bytecode.functions.is_empty(),
326 "bytecode should contain prelude-compiled functions"
327 );
328 }
329
330 #[test]
331 fn test_graph_file_dependency_named_import() {
332 let tmp = tempfile::tempdir().expect("temp dir");
334 let mod_dir = tmp.path().join("mymod");
335 std::fs::create_dir_all(&mod_dir).expect("create mymod dir");
336 std::fs::write(
337 mod_dir.join("index.shape"),
338 r#"
339pub fn alpha() -> int { 1 }
340pub fn beta() -> int { 2 }
341pub fn gamma() -> int { 3 }
342"#,
343 )
344 .expect("write index.shape");
345
346 let source = r#"
347from mymod use { alpha, beta, gamma }
348alpha() + beta() + gamma()
349"#;
350 let bytecode = compile_program_with_graph(source, &[tmp.path().to_path_buf()])
351 .expect("named import from file dependency should compile");
352
353 let mut vm = VirtualMachine::new(VMConfig::default());
354 vm.load_program(bytecode);
355 let result = vm.execute(None).expect("execute");
356 assert_eq!(result.as_number_coerce().unwrap(), 6.0);
357 }
358
359 #[test]
360 fn test_graph_namespace_import_enables_qualified_calls() {
361 let tmp = tempfile::tempdir().expect("temp dir");
362 let mod_dir = tmp.path().join("mymod");
363 std::fs::create_dir_all(&mod_dir).expect("create module dir");
364 std::fs::write(
365 mod_dir.join("index.shape"),
366 r#"
367pub fn alpha() -> int { 1 }
368pub fn beta() -> int { alpha() + 1 }
369"#,
370 )
371 .expect("write index.shape");
372
373 let bytecode = compile_program_with_graph(
374 r#"
375use mymod
376mymod::beta()
377"#,
378 &[tmp.path().to_path_buf()],
379 )
380 .expect("namespace call should compile");
381
382 let mut vm = VirtualMachine::new(VMConfig::default());
383 vm.load_program(bytecode);
384 let result = vm.execute(None).expect("execute");
385 assert_eq!(result.as_number_coerce().unwrap(), 2.0);
386 }
387
388 #[test]
389 fn test_graph_cycle_detection() {
390 let tmp = tempfile::tempdir().expect("temp dir");
392 std::fs::write(
393 tmp.path().join("a.shape"),
394 "use b\npub fn fa() -> int { 1 }\n",
395 )
396 .expect("write a.shape");
397 std::fs::write(
398 tmp.path().join("b.shape"),
399 "use a\npub fn fb() -> int { 2 }\n",
400 )
401 .expect("write b.shape");
402
403 let source = "use a\na::fa()\n";
404 let result = compile_program_with_graph(source, &[tmp.path().to_path_buf()]);
405 assert!(
406 result.is_err(),
407 "circular import should produce an error"
408 );
409 let err_msg = format!("{}", result.unwrap_err());
410 assert!(
411 err_msg.to_lowercase().contains("circular")
412 || err_msg.to_lowercase().contains("cyclic"),
413 "error should mention circularity, got: {}",
414 err_msg
415 );
416 }
417
418 #[test]
419 fn test_graph_stdlib_names_include_qualified() {
420 let program = shape_ast::parser::parse_program("1").expect("parse");
422 let mut loader = shape_runtime::module_loader::ModuleLoader::new();
423 let prelude_imports = module_graph::collect_prelude_import_paths(&mut loader);
424 let graph =
425 module_graph::build_module_graph(&program, &mut loader, &[], &prelude_imports)
426 .expect("graph build");
427
428 let mut stdlib_names = std::collections::HashSet::new();
429 for prelude_path in &prelude_imports {
430 if let Some(dep_id) = graph.id_for_path(prelude_path) {
431 let dep_node = graph.node(dep_id);
432 for export_name in dep_node.interface.exports.keys() {
433 stdlib_names.insert(export_name.clone());
434 stdlib_names.insert(format!("{}::{}", prelude_path, export_name));
435 }
436 }
437 }
438
439 assert!(
440 stdlib_names.contains("sum"),
441 "stdlib_names should contain bare name 'sum'"
442 );
443 assert!(
444 stdlib_names.contains("std::core::math::sum"),
445 "stdlib_names should contain qualified name 'std::core::math::sum'"
446 );
447 }
448
449 #[test]
455 fn test_type_alias_forward_reference_under_graph_compilation() {
456 let bytecode = compile_program_with_graph(
459 r#"
460 fn make_val() -> MyInt { 42 }
461 type MyInt = int
462 make_val()
463 "#,
464 &[],
465 )
466 .expect("compile with forward type alias should succeed");
467 let mut vm = VirtualMachine::new(VMConfig::default());
468 vm.load_program(bytecode);
469 let result = vm.execute(None).expect("execute failed");
470 assert_eq!(result.as_i64(), Some(42));
471 }
472}