importargparseimportosimportreLIBRARY_ROOT=os.path.abspath(os.path.dirname(__file__))LIBRARY_PARENT=os.path.dirname(LIBRARY_ROOT)INCLUDE_PATHS=('.',LIBRARY_ROOT,LIBRARY_PARENT)GITHUB_SOURCE_ROOT='https://github.com/m1une/m1une_library/blob/main'visited=set()skipped_defined_macros={'LOCAL'}defline_marker_name(display_name):"""
Builds a diagnostic filename that is safe for syntax highlighters.
Ace misparses a #line filename containing a path separator and can treat the
following source line as part of a string. The filename is only used for
diagnostics, so keep its path context with a separator that cannot trigger
that tokenizer bug.
"""returnre.sub(r'[/\\]+','::',display_name).replace('"',"'")defresolve_include(header,current_file_dir):"""
Finds the absolute path for a given header file.
It first checks relative to the current file's directory,
then checks the global include paths.
"""relative_path=os.path.join(current_file_dir,header)ifos.path.isfile(relative_path):returnos.path.abspath(relative_path)forpathinINCLUDE_PATHS:full_path=os.path.join(path,header)ifos.path.isfile(full_path):returnos.path.abspath(full_path)returnNonedeflibrary_source_url(path):"""Returns the GitHub URL for a file inside this library repository."""relative_path=os.path.relpath(os.path.abspath(path),LIBRARY_ROOT)ifrelative_path==os.pardirorrelative_path.startswith(os.pardir+os.sep):returnNonereturnf'{GITHUB_SOURCE_ROOT}/{relative_path.replace(os.sep,"/")}'defexpand_file(path,display_name=None,mention_source=False):"""
Recursively expands a C++ file by inlining its local #include directives.
It removes include guards and skips blocks guarded by a macro configured as
unavailable for the generated submission.
"""abs_path=os.path.abspath(path)ifabs_pathinvisited:returnvisited.add(abs_path)ifdisplay_nameisNone:display_name=os.path.relpath(path,LIBRARY_ROOT)marker_name=line_marker_name(display_name)print(f'// BEGIN: {display_name}')ifmention_source:source_url=library_source_url(abs_path)ifsource_url:print(f'// Source: {source_url}')withopen(path,encoding='utf-8')asf:lines=f.readlines()# --- Pre-processing Step: Identify lines to skip ---
lines_to_skip=set()# Identify and mark include guards for removal
iflines:# Method 1: #pragma once
fori,lineinenumerate(lines):ifline.strip()=="#pragma once":lines_to_skip.add(i)break# Method 2: #ifndef/#define/#endif guards
ifnotlines_to_skipandlen(lines)>=2:first_line_idx,first_line=-1,""fori,lineinenumerate(lines):ifline.strip():first_line_idx,first_line=i,linebreaksecond_line_idx,second_line=-1,""iffirst_line_idx!=-1:foriinrange(first_line_idx+1,len(lines)):iflines[i].strip():second_line_idx,second_line=i,lines[i]breaklast_endif_idx=-1foriinrange(len(lines)-1,-1,-1):iflines[i].strip().startswith('#endif'):last_endif_idx=ibreakiffirst_line_idx!=-1andsecond_line_idx!=-1andlast_endif_idx!=-1:ifndef_match=re.match(r'#\s*ifndef\s+([A-Z0-9_]+)\s*',first_line)ififndef_match:guard_macro=ifndef_match.group(1)define_pattern=r'#\s*define\s+'+re.escape(guard_macro)ifre.match(define_pattern,second_line.strip()):lines_to_skip.add(first_line_idx)lines_to_skip.add(second_line_idx)lines_to_skip.add(last_endif_idx)# --- Main Processing Loop ---
first_line_emitted=Falseconditional_depth=0skipped_block_depth=Noneskipping_guarded_branch=Falsecurrent_file_dir=os.path.dirname(path)fori,lineinenumerate(lines):stripped_line=line.strip()is_conditional_start=re.match(r'#\s*(?:if|ifdef|ifndef)\b',stripped_line)is_skipped_start=any(re.match(r'#\s*(?:ifdef\s+'+re.escape(macro)+r'\b|if\s+defined\s*(?:\(\s*'+re.escape(macro)+r'\s*\)|'+re.escape(macro)+r'\b))',stripped_line,)formacroinskipped_defined_macros)ifis_conditional_start:ifskipped_block_depthisNoneandis_skipped_start:skipped_block_depth=conditional_depthskipping_guarded_branch=Trueconditional_depth+=1continueconditional_depth+=1elif(re.match(r'#\s*else\b',stripped_line)andskipped_block_depthisnotNoneandconditional_depth==skipped_block_depth+1):skipping_guarded_branch=Falsecontinueelifre.match(r'#\s*endif\b',stripped_line):if(skipped_block_depthisnotNoneandconditional_depth==skipped_block_depth+1):conditional_depth-=1skipped_block_depth=Noneskipping_guarded_branch=Falsecontinueconditional_depth=max(0,conditional_depth-1)ifskipping_guarded_branch:continueifiinlines_to_skip:continueifnotfirst_line_emitted:print(f'#line {i+1} "{marker_name}"')first_line_emitted=Truem=re.match(r'#\s*include\s*"([^"]+)"',stripped_line)ifm:header=m.group(1)resolved=resolve_include(header,current_file_dir)ifresolved:expand_file(resolved,header,mention_source=True)print(f'#line {i+2} "{marker_name}"')else:print(f'// [warning] include not found: {header}')print(line,end='')else:print(line,end='')print(f'// END: {display_name}')if__name__=='__main__':parser=argparse.ArgumentParser(description='Inline local C++ headers into a submission source.')parser.add_argument('--no-x86-simd',action='store_true',help='omit code guarded by M1UNE_FPS_HAS_X86_SIMD',)parser.add_argument('main_file',help='the C++ source file to expand')arguments=parser.parse_args()ifarguments.no_x86_simd:skipped_defined_macros.add('M1UNE_FPS_HAS_X86_SIMD')print('#define M1UNE_FPS_DISABLE_X86_SIMD 1')visited.clear()expand_file(arguments.main_file)ifarguments.no_x86_simd:print('#undef M1UNE_FPS_DISABLE_X86_SIMD')