33Generate embedded core modules for moonbit-eval interpreter.
44This script reads all modules from moonbitlang/core and generates
55RuntimePackage definitions in the format expected by core_modules.mbt.
6+ It honors the `options.targets` directive in each package's moon.pkg
7+ to include only files relevant for the js target.
68"""
79
810import os
11+ import re
912from pathlib import Path
10- from typing import Dict , List
13+ from typing import Dict , List , Optional , Set , Tuple
1114
1215# Core library path
1316CORE_PATH = "/Users/oboard/.moon/lib/core"
1417OUTPUT_FILE = "interpreter/core/core_modules.mbt"
1518
16- # Removed extract_all_items function - no longer needed with new format
19+ # Target we are building for
20+ BUILD_TARGET = "js"
1721
18- def read_module_info (module_dir : Path ) -> Dict :
22+ def parse_targets (moon_pkg_content : str ) -> Dict [str , List [str ]]:
23+ """Parse options(targets: { ... }) from moon.pkg content.
24+
25+ Returns a dict mapping filename -> list of target strings.
26+ Files not listed in targets are included for all targets.
1927 """
20- Read module information from a directory.
21- Returns dict with module name, dependencies, and concatenated code content.
28+ targets = {}
29+ # Match options(targets: { ... }) block
30+ m = re .search (r'targets\s*:\s*\{([^}]*)\}' , moon_pkg_content , re .DOTALL )
31+ if not m :
32+ return targets
33+
34+ block = m .group (1 )
35+ # Parse each entry: "filename": [ "target1", "target2" ]
36+ for entry_m in re .finditer (r'"([^"]+)"\s*:\s*\[([^\]]*)\]' , block ):
37+ filename = entry_m .group (1 )
38+ targets_str = entry_m .group (2 )
39+ target_list = [t .strip ().strip ('"' ) for t in targets_str .split (',' ) if t .strip ().strip ('"' )]
40+ targets [filename ] = target_list
41+
42+ return targets
43+
44+ def should_include_file (filename : str , targets : Dict [str , List [str ]], build_target : str ) -> bool :
45+ """Determine if a file should be included for the given build target.
46+
47+ Rules:
48+ - If file is not in targets dict, include for all targets.
49+ - If the target list starts with "not", include for targets NOT in the list.
50+ - Otherwise, include only for targets IN the list.
2251 """
52+ if filename not in targets :
53+ return True
54+
55+ target_list = targets [filename ]
56+ if not target_list :
57+ return True
58+
59+ if target_list [0 ] == "not" :
60+ # Exclude for listed targets
61+ return build_target not in target_list [1 :]
62+ else :
63+ # Include only for listed targets
64+ return build_target in target_list
65+
66+ def read_module_info (module_dir : Path ) -> Optional [Dict ]:
67+ """Read module information from a directory."""
2368 pkg = "moonbitlang/core/" + module_dir .relative_to (CORE_PATH ).as_posix ()
2469 alias = module_dir .relative_to (CORE_PATH ).as_posix ()
2570 module_name = pkg .replace ('/' , '_' )
2671
27- # Keep embedded package deps empty to avoid introducing definition cycles.
28- # The generated file is used as a flat registry of embedded packages.
2972 dependencies = []
3073
74+ # Read moon.pkg to get targets
75+ moon_pkg_path = module_dir / "moon.pkg"
76+ file_targets = {}
77+ if moon_pkg_path .exists ():
78+ try :
79+ with open (moon_pkg_path , 'r' , encoding = 'utf-8' ) as f :
80+ pkg_content = f .read ()
81+ file_targets = parse_targets (pkg_content )
82+ except (UnicodeDecodeError , FileNotFoundError ):
83+ pass
84+
3185 # Find all .mbt files (excluding test files)
3286 mbt_files = [f for f in module_dir .glob ("*.mbt" )
3387 if not f .name .endswith ('_test.mbt' ) and not f .name .endswith ('_wbtest.mbt' )]
3488
35- # Collect file contents with their names
89+ # Collect file contents with their names, filtering by target
3690 file_contents = {}
3791
38- for mbt_file in sorted (mbt_files ): # Sort for consistent ordering
92+ for mbt_file in sorted (mbt_files ):
93+ if not should_include_file (mbt_file .name , file_targets , BUILD_TARGET ):
94+ continue
3995 try :
4096 with open (mbt_file , 'r' , encoding = 'utf-8' ) as f :
4197 content = f .read ().strip ()
42- if content : # Only add non-empty content
98+ if content :
4399 file_contents [mbt_file .name ] = content
44100 except (UnicodeDecodeError , FileNotFoundError ):
45101 continue
46102
47- # Join all content with double newlines for backward compatibility
103+ if not file_contents :
104+ return None
105+
48106 concatenated_code = '\n \n ' .join (file_contents .values ())
49107
50108 return {
@@ -56,19 +114,14 @@ def read_module_info(module_dir: Path) -> Dict:
56114 'files' : file_contents
57115 }
58116
59- # Removed has_non_self_parameters function - no longer needed with new format
60-
61117def generate_module_code (module_info : Dict ) -> str :
62- """
63- Generate RuntimePackage code for a single module.
64- """
118+ """Generate RuntimePackage code for a single module."""
65119 pkg = module_info ['pkg' ]
66120 module_name = module_info ['name' ]
67- code = module_info ['code' ]
68121 dependencies = module_info ['dependencies' ]
69122 files = module_info ['files' ]
70123
71- if not code . strip () :
124+ if not files :
72125 return ""
73126
74127 # Generate dependencies map
@@ -85,22 +138,18 @@ def generate_module_code(module_info: Dict) -> str:
85138 # Generate files map
86139 files_map_entries = []
87140 for filename , content in files .items ():
88- # Format the code with #| prefix for each line, filtering out comments and empty lines
89141 code_lines = content .split ('\n ' )
90142 formatted_code_lines = []
91143 for line in code_lines :
92- # Skip comment lines and empty lines to reduce file size
93144 stripped_line = line .strip ()
94- if not stripped_line or stripped_line .startswith ('//' ) or stripped_line .startswith ('///' ) or stripped_line . startswith ( '///' ) :
145+ if not stripped_line or stripped_line .startswith ('//' ) or stripped_line .startswith ('///' ):
95146 continue
96- # Escape quotes and backslashes for the string literal
97147 formatted_code_lines .append (f' #|{ line } ' )
98148
99149 if formatted_code_lines :
100150 formatted_content = '\n ' .join (formatted_code_lines )
101151 files_map_entries .append (f' "{ filename } ": (\n { formatted_content } \n )' )
102152 else :
103- # For empty files, generate empty string
104153 files_map_entries .append (f' "{ filename } ": ""' )
105154
106155 files_map_str = ',\n ' .join (files_map_entries )
@@ -117,9 +166,7 @@ def generate_module_code(module_info: Dict) -> str:
117166 return module_code
118167
119168def generate_core_modules_map (modules : List [dict ]) -> str :
120- """
121- Generate the core_modules map declaration.
122- """
169+ """Generate the core_modules map declaration."""
123170 entries = []
124171 for module in modules :
125172 entries .append (f'"{ module ["alias" ]} ": { module ["name" ]} _module' )
@@ -130,19 +177,17 @@ def generate_core_modules_map(modules: List[dict]) -> str:
130177let core_modules : Map[String, RuntimePackage] = {{ { map_content } }}'''
131178
132179def main ():
133- """
134- Main function to generate all core modules.
135- """
180+ """Main function to generate all core modules."""
136181 core_path = Path (CORE_PATH )
137182
138183 if not core_path .exists ():
139184 print (f"Error: Core path { CORE_PATH } does not exist" )
140185 return
186+
141187 # Get all module directories recursively
142188 module_dirs = []
143189 for root , dirs , _ in os .walk (core_path ):
144190 root_path = Path (root )
145- # Skip hidden directories and build artifacts.
146191 dirs [:] = [
147192 d
148193 for d in dirs
@@ -155,21 +200,20 @@ def main():
155200 generated_modules = []
156201 module_codes = []
157202
158- # Process each module
159203 for module_dir in sorted (module_dirs ):
160204 print (f"Processing module: { module_dir .name } " )
161205
162206 module_info = read_module_info (module_dir )
163207
164- if module_info ['code' ].strip ():
208+ if module_info and module_info ['code' ].strip ():
165209 module_code = generate_module_code (module_info )
166210 if module_code :
167211 module_codes .append (module_code )
168212 generated_modules .append (module_info )
169213 code_lines = len ([line for line in module_info ['code' ].split ('\n ' ) if line .strip ()])
170214 print (f" Generated module with { code_lines } lines of code" )
171215 else :
172- print (f" No code content found" )
216+ print (f" No code content found (filtered by target) " )
173217
174218 # Generate the complete file content
175219 header = '''///|
@@ -185,17 +229,14 @@ def main():
185229 }
186230}'''
187231
188- # Generate core_modules map
189232 core_modules_map = generate_core_modules_map (generated_modules )
190233
191- # Combine all parts
192234 full_content = "\n \n " .join ([
193235 header ,
194236 core_modules_map ,
195237 dummy_loc
196238 ] + module_codes )
197239
198- # Write to output file
199240 output_path = Path (OUTPUT_FILE )
200241 with open (output_path , 'w' , encoding = 'utf-8' ) as f :
201242 f .write (full_content )
0 commit comments