@@ -697,6 +697,8 @@ impl Compiler {
697697 "let" => return self . compile_let ( & items[ 1 ..] , dest, tail_pos) ,
698698 "fn" | "lambda" => return self . compile_fn ( & items[ 1 ..] , dest) ,
699699 "do" => return self . compile_do ( & items[ 1 ..] , dest, tail_pos) ,
700+ "module" => return self . compile_module ( & items[ 1 ..] , dest) ,
701+ "import" => return self . compile_import ( & items[ 1 ..] , dest) ,
700702 _ => { }
701703 }
702704 }
@@ -1248,6 +1250,181 @@ impl Compiler {
12481250 self . compile_expr ( & args[ args. len ( ) - 1 ] , dest, tail_pos)
12491251 }
12501252
1253+ /// Compile a module declaration:
1254+ /// (module name (export sym1 sym2 ...) body...)
1255+ ///
1256+ /// This creates a module with the specified exports and stores it as a global.
1257+ /// Definitions inside the module body are stored as qualified globals (module/name).
1258+ fn compile_module ( & mut self , args : & [ Value ] , dest : Reg ) -> Result < ( ) , String > {
1259+ if args. len ( ) < 2 {
1260+ return Err ( "module expects at least 2 arguments: name and (export ...)" . to_string ( ) ) ;
1261+ }
1262+
1263+ // Parse module name
1264+ let module_name = args[ 0 ]
1265+ . as_symbol ( )
1266+ . ok_or ( "module name must be a symbol" ) ?;
1267+
1268+ // Parse export list: (export sym1 sym2 ...)
1269+ let export_list = args[ 1 ]
1270+ . as_list ( )
1271+ . ok_or ( "module expects (export ...) as second argument" ) ?;
1272+
1273+ if export_list. is_empty ( ) || export_list[ 0 ] . as_symbol ( ) != Some ( "export" ) {
1274+ return Err ( "module expects (export sym1 sym2 ...) as second argument" . to_string ( ) ) ;
1275+ }
1276+
1277+ let exports: Vec < String > = export_list[ 1 ..]
1278+ . iter ( )
1279+ . map ( |v| {
1280+ v. as_symbol ( )
1281+ . ok_or_else ( || "export list must contain symbols" . to_string ( ) )
1282+ . map ( |s| s. to_string ( ) )
1283+ } )
1284+ . collect :: < Result < Vec < _ > , _ > > ( ) ?;
1285+
1286+ // Body expressions (definitions)
1287+ let body = & args[ 2 ..] ;
1288+
1289+ // We'll build the module by:
1290+ // 1. Compiling each body expression
1291+ // 2. For each def, we store as qualified global (module/name)
1292+ // 3. At the end, we create a Module value and store it
1293+
1294+ // Compile body expressions - definitions will be stored as module/name globals
1295+ // We need to track definitions made in this module
1296+ let mut defined_names: Vec < String > = Vec :: new ( ) ;
1297+
1298+ for expr in body {
1299+ // Check if this is a def expression to track the name
1300+ if let Some ( items) = expr. as_list ( ) {
1301+ if !items. is_empty ( ) {
1302+ if let Some ( "def" ) = items[ 0 ] . as_symbol ( ) {
1303+ if items. len ( ) >= 2 {
1304+ if let Some ( name) = items[ 1 ] . as_symbol ( ) {
1305+ defined_names. push ( name. to_string ( ) ) ;
1306+ }
1307+ }
1308+ }
1309+ }
1310+ }
1311+
1312+ // Create a transformed expression with qualified name for def
1313+ let transformed = self . transform_module_def ( expr, module_name) ?;
1314+ let temp = self . alloc_reg ( ) ;
1315+ self . compile_expr ( & transformed, temp, false ) ?;
1316+ self . free_reg ( ) ;
1317+ }
1318+
1319+ // Now create the Module object
1320+ // First, load all arguments
1321+
1322+ // Module name as string
1323+ let name_idx = self . add_constant ( Value :: string ( module_name) ) ;
1324+ let name_reg = self . alloc_reg ( ) ;
1325+ self . emit ( Op :: load_const ( name_reg, name_idx) ) ;
1326+
1327+ // Export names as strings
1328+ for export_name in & exports {
1329+ let export_idx = self . add_constant ( Value :: string ( export_name) ) ;
1330+ let export_reg = self . alloc_reg ( ) ;
1331+ self . emit ( Op :: load_const ( export_reg, export_idx) ) ;
1332+ }
1333+
1334+ // Call __make_module__
1335+ let fn_name_idx = self . add_constant ( Value :: symbol ( "__make_module__" ) ) ;
1336+ if fn_name_idx <= 255 {
1337+ self . emit ( Op :: call_global ( dest, fn_name_idx as u8 , ( 1 + exports. len ( ) ) as u8 ) ) ;
1338+ } else {
1339+ return Err ( "Too many constants" . to_string ( ) ) ;
1340+ }
1341+
1342+ // Free temp registers
1343+ for _ in 0 ..=exports. len ( ) {
1344+ self . free_reg ( ) ;
1345+ }
1346+
1347+ // Store the module as a global
1348+ let module_global_idx = self . add_constant ( Value :: symbol ( module_name) ) ;
1349+ self . emit ( Op :: set_global ( module_global_idx, dest) ) ;
1350+
1351+ Ok ( ( ) )
1352+ }
1353+
1354+ /// Transform a def expression inside a module to use qualified name
1355+ fn transform_module_def ( & self , expr : & Value , module_name : & str ) -> Result < Value , String > {
1356+ if let Some ( items) = expr. as_list ( ) {
1357+ if !items. is_empty ( ) {
1358+ if let Some ( "def" ) = items[ 0 ] . as_symbol ( ) {
1359+ if items. len ( ) >= 2 {
1360+ if let Some ( name) = items[ 1 ] . as_symbol ( ) {
1361+ // Transform (def name value) to (def module/name value)
1362+ let qualified_name = format ! ( "{}/{}" , module_name, name) ;
1363+ let mut new_items = vec ! [ items[ 0 ] . clone( ) , Value :: symbol( & qualified_name) ] ;
1364+ new_items. extend ( items[ 2 ..] . iter ( ) . cloned ( ) ) ;
1365+ return Ok ( Value :: list ( new_items) ) ;
1366+ }
1367+ }
1368+ }
1369+ }
1370+ }
1371+ // Not a def or malformed - return as-is
1372+ Ok ( expr. clone ( ) )
1373+ }
1374+
1375+ /// Compile an import statement:
1376+ /// (import module) - import all exports, access as module/name
1377+ /// (import module (sym1 sym2)) - import specific symbols into local scope
1378+ fn compile_import ( & mut self , args : & [ Value ] , dest : Reg ) -> Result < ( ) , String > {
1379+ if args. is_empty ( ) {
1380+ return Err ( "import expects at least 1 argument" . to_string ( ) ) ;
1381+ }
1382+
1383+ let module_name = args[ 0 ]
1384+ . as_symbol ( )
1385+ . ok_or ( "import expects a module name symbol" ) ?;
1386+
1387+ if args. len ( ) == 1 {
1388+ // (import module) - just load the module to verify it exists
1389+ // The qualified names (module/name) are already available as globals
1390+ let module_idx = self . add_constant ( Value :: symbol ( module_name) ) ;
1391+ self . emit ( Op :: get_global ( dest, module_idx) ) ;
1392+ // Result is the module value (for chaining or inspection)
1393+ } else if args. len ( ) == 2 {
1394+ // (import module (sym1 sym2 ...)) - import specific symbols
1395+ let symbols = args[ 1 ]
1396+ . as_list ( )
1397+ . ok_or ( "import expects a list of symbols as second argument" ) ?;
1398+
1399+ for sym in symbols {
1400+ let sym_name = sym
1401+ . as_symbol ( )
1402+ . ok_or ( "import symbol list must contain symbols" ) ?;
1403+
1404+ // Get the qualified name from the module
1405+ let qualified_name = format ! ( "{}/{}" , module_name, sym_name) ;
1406+ let qualified_idx = self . add_constant ( Value :: symbol ( & qualified_name) ) ;
1407+
1408+ // Load the value
1409+ let temp = self . alloc_reg ( ) ;
1410+ self . emit ( Op :: get_global ( temp, qualified_idx) ) ;
1411+
1412+ // Store as unqualified global
1413+ let unqualified_idx = self . add_constant ( Value :: symbol ( sym_name) ) ;
1414+ self . emit ( Op :: set_global ( unqualified_idx, temp) ) ;
1415+
1416+ self . free_reg ( ) ;
1417+ }
1418+
1419+ // Return nil
1420+ self . emit ( Op :: load_nil ( dest) ) ;
1421+ } else {
1422+ return Err ( "import expects 1 or 2 arguments" . to_string ( ) ) ;
1423+ }
1424+
1425+ Ok ( ( ) )
1426+ }
1427+
12511428 /// Try to compile a binary operation using specialized opcodes
12521429 /// Returns Some(true) if compiled, Some(false) if not applicable, Err on error
12531430 fn try_compile_binary_op ( & mut self , op : & str , args : & [ Value ] , dest : Reg ) -> Result < Option < bool > , String > {
@@ -1414,11 +1591,15 @@ impl Compiler {
14141591
14151592 fn compile_call ( & mut self , items : & [ Value ] , dest : Reg , tail_pos : bool ) -> Result < ( ) , String > {
14161593 // Try constant folding for the entire call expression (including pure user functions)
1417- let call_expr = Value :: list ( items. to_vec ( ) ) ;
1418- if let Some ( folded) = try_const_eval_with_fns ( & call_expr, & self . pure_fns ) {
1419- let idx = self . add_constant ( folded) ;
1420- self . emit ( Op :: load_const ( dest, idx) ) ;
1421- return Ok ( ( ) ) ;
1594+ // NOTE: Don't constant-fold qualified names (module/function) to preserve module privacy
1595+ let is_module_call = items[ 0 ] . as_symbol ( ) . map_or ( false , |s| s. contains ( '/' ) ) ;
1596+ if !is_module_call {
1597+ let call_expr = Value :: list ( items. to_vec ( ) ) ;
1598+ if let Some ( folded) = try_const_eval_with_fns ( & call_expr, & self . pure_fns ) {
1599+ let idx = self . add_constant ( folded) ;
1600+ self . emit ( Op :: load_const ( dest, idx) ) ;
1601+ return Ok ( ( ) ) ;
1602+ }
14221603 }
14231604
14241605 // Try to compile as specialized binary operation
@@ -1465,6 +1646,8 @@ impl Compiler {
14651646 // Try inlining small non-recursive functions
14661647 // This eliminates call overhead for thin wrappers like:
14671648 // (def reverse (fn (lst) (reverse-acc lst (list))))
1649+ // NOTE: Don't inline qualified names (module/function) to preserve module privacy
1650+ if !op. contains ( '/' ) {
14681651 if let Some ( fn_def) = self . inline_candidates . get ( op) . cloned ( ) {
14691652 // Check: correct number of arguments
14701653 if args. len ( ) == fn_def. params . len ( ) {
@@ -1486,6 +1669,7 @@ impl Compiler {
14861669 }
14871670 }
14881671 }
1672+ } // end !op.contains('/')
14891673 }
14901674
14911675 // Check if calling a global symbol (optimization: use CallGlobal/TailCallGlobal)
0 commit comments