@@ -133,6 +133,9 @@ class SystemInfo:
133133 inline_subnamespaces : Set [str ] = field (
134134 default_factory = set
135135 ) # Track inline subnamespaces (e.g., "si2019", "codata2018")
136+ imported_systems : Set [str ] = field (
137+ default_factory = set
138+ ) # Track systems imported via using declarations (e.g., {"si"})
136139
137140
138141class SystemsParser :
@@ -146,6 +149,15 @@ def __init__(self, systems_dir: Path):
146149 # systems_dir is src/systems/include/mp-units/systems, so we need to go up 5 levels to get to repo root
147150 self .source_root = systems_dir .parent .parent .parent .parent .parent
148151
152+ @staticmethod
153+ def _strip_comments (content : str ) -> str :
154+ """Remove C++ comments (both // and /* */) from source code"""
155+ # Remove multi-line comments /* ... */
156+ content = re .sub (r"/\*.*?\*/" , "" , content , flags = re .DOTALL )
157+ # Remove single-line comments //...
158+ content = re .sub (r"//.*?$" , "" , content , flags = re .MULTILINE )
159+ return content
160+
149161 def parse_all_systems (self ):
150162 """Parse all system header files, following include order"""
151163 # First, parse core framework entities
@@ -228,13 +240,14 @@ def _parse_core_framework(self):
228240 unit_path = self .source_root / "src/core/include/mp-units/framework/unit.h"
229241 if unit_path .exists ():
230242 try :
231- content = unit_path .read_text ()
232- # Only parse content after "common dimensionless units" comment
233- # and before "Common unit" comment to avoid parsing examples
234- start_marker = content .find ("// common dimensionless units" )
235- end_marker = content .find ("// Common unit" )
243+ raw_content = unit_path .read_text ()
244+ # Find markers before stripping comments
245+ start_marker = raw_content .find ("// common dimensionless units" )
246+ end_marker = raw_content .find ("// Common unit" )
236247 if start_marker != - 1 and end_marker != - 1 :
237- content = content [start_marker :end_marker ]
248+ # Extract the section, then strip comments
249+ section_content = raw_content [start_marker :end_marker ]
250+ content = self ._strip_comments (section_content )
238251 # Parse units at mp_units namespace level (no sub-namespace)
239252 self ._parse_units (
240253 content , core_system , str (unit_path ), namespace_to_search = None
@@ -249,7 +262,7 @@ def _parse_core_framework(self):
249262
250263 def parse_system_with_includes (self , main_header : Path ):
251264 """Parse a system header and all its includes in order"""
252- content = main_header .read_text ()
265+ content = self . _strip_comments ( main_header .read_text () )
253266
254267 # Extract includes from this header
255268 include_pattern = r"#include\s+<mp-units/systems/([^>]+)>"
@@ -298,7 +311,7 @@ def parse_system_header(self, header_file: Path):
298311 return
299312
300313 self .parsed_files .add (header_file )
301- content = header_file .read_text ()
314+ content = self . _strip_comments ( header_file .read_text () )
302315
303316 # Extract namespace
304317 namespace_match = re .search (r"namespace\s+mp_units::(\w+)" , content )
@@ -1306,11 +1319,17 @@ def _parse_using_declarations(self, content: str, system: SystemInfo, file: str)
13061319 if "std" in full_namespace :
13071320 continue
13081321
1322+ # Extract the source system from the namespace
1323+ # e.g., "si" from "si" or "si::non_si" from "si::non_si"
1324+ source_system = full_namespace .split ("::" )[0 ]
1325+ system .imported_systems .add (source_system )
1326+
13091327 unit = Unit (
13101328 name = unit_name ,
13111329 symbol = f"(imported from { full_namespace } )" ,
13121330 definition = f"using { full_namespace } ::{ unit_name } " ,
13131331 namespace = f"mp_units::{ system .namespace } " ,
1332+ origin_namespace = f"mp_units::{ full_namespace } " ,
13141333 file = file ,
13151334 is_alias = True ,
13161335 )
@@ -2667,6 +2686,34 @@ def _linkify_definition(self, definition: str, current_system: SystemInfo) -> st
26672686 # Collect all possible references from all systems
26682687 all_refs = {} # name -> (system_namespace, anchor_name)
26692688
2689+ # Build priority-aware reference map
2690+ # For the current system, prioritize units from imported systems
2691+ priority_refs = (
2692+ {}
2693+ ) # name -> (system_namespace, anchor_name) for imported systems
2694+
2695+ if current_system .imported_systems :
2696+ for imported_sys in current_system .imported_systems :
2697+ if imported_sys in self .parser .systems :
2698+ imported_system = self .parser .systems [imported_sys ]
2699+ for unit in imported_system .units :
2700+ if not unit .is_alias :
2701+ subns_prefix = None
2702+ if unit .origin_namespace :
2703+ parts = unit .origin_namespace .replace (
2704+ "mp_units::" , ""
2705+ ).split ("::" )
2706+ if len (parts ) > 1 :
2707+ subns_prefix = parts [- 1 ]
2708+ elif unit .subnamespace :
2709+ subns_prefix = unit .subnamespace
2710+ anchor_id = (
2711+ f"{ subns_prefix } -{ unit .name } "
2712+ if subns_prefix
2713+ else unit .name
2714+ )
2715+ priority_refs [unit .name ] = (imported_sys , anchor_id )
2716+
26702717 for sys_ns , system in self .parser .systems .items ():
26712718 # Add units
26722719 for unit in system .units :
@@ -2681,8 +2728,11 @@ def _linkify_definition(self, definition: str, current_system: SystemInfo) -> st
26812728
26822729 anchor_id = f"{ subns_prefix } -{ unit .name } " if subns_prefix else unit .name
26832730
2684- all_refs [unit .name ] = (sys_ns , anchor_id )
2685- # Also add qualified names
2731+ # For unqualified names, only add if not an alias and not already present
2732+ # This prevents aliases and redefinitions from overwriting the primary definition
2733+ if not unit .is_alias and unit .name not in all_refs :
2734+ all_refs [unit .name ] = (sys_ns , anchor_id )
2735+ # Always add qualified names (these are system-specific)
26862736 all_refs [f"{ sys_ns } ::{ unit .name } " ] = (sys_ns , anchor_id )
26872737 # If there's a subnamespace, also add subnamespace::name format
26882738 if subns_prefix :
@@ -2694,15 +2744,19 @@ def _linkify_definition(self, definition: str, current_system: SystemInfo) -> st
26942744
26952745 # Add point origins
26962746 for origin in system .point_origins :
2697- all_refs [origin .name ] = (sys_ns , origin .name )
2747+ # Only add unqualified name if not already present (first system wins)
2748+ if origin .name not in all_refs :
2749+ all_refs [origin .name ] = (sys_ns , origin .name )
26982750 all_refs [f"{ sys_ns } ::{ origin .name } " ] = (sys_ns , origin .name )
26992751 if origin .secondary_namespaces :
27002752 for sec_ns in origin .secondary_namespaces :
27012753 all_refs [f"{ sec_ns } ::{ origin .name } " ] = (sys_ns , origin .name )
27022754
27032755 # Add quantities
27042756 for qty in system .quantities :
2705- all_refs [qty .name ] = (sys_ns , qty .name )
2757+ # Only add unqualified name if not already present (first system wins)
2758+ if qty .name not in all_refs :
2759+ all_refs [qty .name ] = (sys_ns , qty .name )
27062760 all_refs [f"{ sys_ns } ::{ qty .name } " ] = (sys_ns , qty .name )
27072761 if qty .secondary_namespaces :
27082762 for sec_ns in qty .secondary_namespaces :
@@ -2852,6 +2906,14 @@ def make_link(text, url):
28522906 target_sys , anchor = all_refs ["dimensionless" ]
28532907 return make_link (display_text , f"{ target_sys } .md#{ anchor } " )
28542908
2909+ # Check priority refs first (imported systems)
2910+ if unqualified_check in priority_refs :
2911+ target_sys , anchor = priority_refs [unqualified_check ]
2912+ if target_sys == current_system .namespace :
2913+ return make_link (display_text , f"#{ anchor } " )
2914+ else :
2915+ return make_link (display_text , f"{ target_sys } .md#{ anchor } " )
2916+
28552917 if current_sys_key in all_refs :
28562918 # Found in current system
28572919 target_sys , anchor = all_refs [current_sys_key ]
@@ -3201,59 +3263,73 @@ def extract_metadata(self):
32013263
32023264 def _generate_cpp_program (self ) -> str :
32033265 """Generate C++ program that outputs metadata for all quantities"""
3204- lines = [
3205- "// Auto-generated program to extract quantity metadata" ,
3206- "#include <mp-units/systems/isq.h>" ,
3207- "#include <mp-units/systems/isq_angle.h>" ,
3208- "#include <mp-units/systems/angular.h>" ,
3209- "#include <mp-units/systems/natural.h>" ,
3210- "#include <mp-units/systems/iec80000.h>" ,
3211- "#include <iostream>" ,
3212- "" ,
3213- "using namespace mp_units;" ,
3214- "" ,
3215- "constexpr std::string_view get_parent(QuantitySpec auto qs)" ,
3216- "{" ,
3217- " if constexpr (requires { qs._parent_; })" ,
3218- " return detail::type_name<std::remove_const_t<decltype(qs._parent_)>>();" ,
3219- " else" ,
3220- ' return "<root>";' ,
3221- "}" ,
3222- "" ,
3223- "constexpr std::string_view character_to_string(quantity_character ch)" ,
3224- "{" ,
3225- " switch (ch) {" ,
3226- " case quantity_character::real_scalar:" ,
3227- ' return "Real";' ,
3228- " case quantity_character::complex_scalar:" ,
3229- ' return "Complex";' ,
3230- " case quantity_character::vector:" ,
3231- ' return "Vector";' ,
3232- " case quantity_character::tensor:" ,
3233- ' return "Tensor";' ,
3234- " default:" ,
3235- ' return "Unknown";' ,
3236- " }" ,
3237- "}" ,
3238- "" ,
3239- "template<QuantitySpec QS>" ,
3240- "void print_quantity(std::string_view namespace_name, std::string_view name, QS qs)" ,
3241- "{" ,
3242- ' std::cout << namespace_name << "," // namespace' ,
3243- ' << name << "," // quantity name' ,
3244- ' << character_to_string(qs.character) << "," // character' ,
3245- ' << dimension_symbol(qs.dimension) << "," // dimension' ,
3246- ' << std::boolalpha << (qs == detail::get_kind_tree_root(qs)) << "," // is kind' ,
3247- ' << detail::type_name<decltype(get_kind(qs))>() << "," // kind quantity' ,
3248- ' << get_parent(qs) << "," // parent quantity' ,
3249- ' << detail::type_name<decltype(detail::get_hierarchy_root(qs))>() << "\\ n"; // hierarchy root' ,
3250- "}" ,
3251- "" ,
3252- "#define PRINT_QTY(ns, qty) print_quantity(#ns, #qty, ns::qty)" ,
3253- "" ,
3254- "int main()" ,
3255- "{" ,
3256- ]
3266+ # Discover all system header files dynamically
3267+ systems_include_dir = (
3268+ self .source_dir / "src" / "systems" / "include" / "mp-units" / "systems"
3269+ )
3270+ system_headers = sorted (
3271+ [
3272+ f .name
3273+ for f in systems_include_dir .iterdir ()
3274+ if f .is_file () and f .suffix == ".h"
3275+ ]
3276+ )
3277+
3278+ lines = ["// Auto-generated program to extract quantity metadata" ]
3279+
3280+ # Add includes for all discovered system headers
3281+ for header in system_headers :
3282+ lines .append (f"#include <mp-units/systems/{ header } >" )
3283+
3284+ lines .extend (
3285+ [
3286+ "#include <iostream>" ,
3287+ "" ,
3288+ "using namespace mp_units;" ,
3289+ "" ,
3290+ "constexpr std::string_view get_parent(QuantitySpec auto qs)" ,
3291+ "{" ,
3292+ " if constexpr (requires { qs._parent_; })" ,
3293+ " return detail::type_name<std::remove_const_t<decltype(qs._parent_)>>();" ,
3294+ " else" ,
3295+ ' return "<root>";' ,
3296+ "}" ,
3297+ "" ,
3298+ "constexpr std::string_view character_to_string(quantity_character ch)" ,
3299+ "{" ,
3300+ " switch (ch) {" ,
3301+ " case quantity_character::real_scalar:" ,
3302+ ' return "Real";' ,
3303+ " case quantity_character::complex_scalar:" ,
3304+ ' return "Complex";' ,
3305+ " case quantity_character::vector:" ,
3306+ ' return "Vector";' ,
3307+ " case quantity_character::tensor:" ,
3308+ ' return "Tensor";' ,
3309+ " default:" ,
3310+ ' return "Unknown";' ,
3311+ " }" ,
3312+ "}" ,
3313+ "" ,
3314+ "template<QuantitySpec QS>" ,
3315+ "void print_quantity(std::string_view namespace_name, std::string_view name, QS qs)" ,
3316+ "{" ,
3317+ ' std::cout << namespace_name << "," // namespace' ,
3318+ ' << name << "," // quantity name' ,
3319+ ' << character_to_string(qs.character) << "," // character' ,
3320+ ' << dimension_symbol(qs.dimension) << "," // dimension' ,
3321+ ' << std::boolalpha << (qs == detail::get_kind_tree_root(qs)) << "," // is kind' ,
3322+ ' << detail::type_name<decltype(get_kind(qs))>() << "," // kind quantity' ,
3323+ ' << get_parent(qs) << "," // parent quantity' ,
3324+ ' << detail::type_name<decltype(detail::get_hierarchy_root(qs))>() << "\\ n"; // hierarchy root' ,
3325+ "}" ,
3326+ "" ,
3327+ "#define PRINT_QTY(ns, qty) print_quantity(#ns, #qty, ns::qty)" ,
3328+ "" ,
3329+ "int main()" ,
3330+ "{" ,
3331+ ]
3332+ )
32573333
32583334 # Add dimensionless (special case - no namespace)
32593335 lines .append (' print_quantity("", "dimensionless", dimensionless);' )
0 commit comments