#!/usr/bin/env python3
"""A small, dependency-free C++ source minifier for contest submissions.
The minifier removes comments and whitespace without changing the C++ token
stream. By default it also shortens file-owned variables, fields, types,
namespaces, and unqualified functions, and introduces aliases when their
declaration cost is profitable. Macros, reserved names, external qualified
names and member calls, contract functions, and labels remain protected.
"""from__future__importannotationsimportargparseimportreimportsysfromcollectionsimportCounterfromdataclassesimportdataclassfrompathlibimportPathCPP_KEYWORDS={"alignas","alignof","and","and_eq","asm","auto","bitand","bitor","bool","break","case","catch","char","char8_t","char16_t","char32_t","class","compl","concept","const","consteval","constexpr","constinit","const_cast","continue","co_await","co_return","co_yield","decltype","default","delete","do","double","dynamic_cast","else","enum","explicit","export","extern","false","float","for","friend","goto","if","inline","int","long","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","private","protected","public","register","reinterpret_cast","requires","return","short","signed","sizeof","static","static_assert","static_cast","struct","switch","template","this","thread_local","throw","true","try","typedef","typeid","typename","union","unsigned","using","virtual","void","volatile","wchar_t","while","xor","xor_eq","import","module",}BUILTIN_TYPES={"auto","bool","char","char8_t","char16_t","char32_t","double","float","int","long","short","signed","unsigned","void","wchar_t","size_t","ptrdiff_t","int8_t","int16_t","int32_t","int64_t","uint8_t","uint16_t","uint32_t","uint64_t","__int128","__int128_t","__uint128_t",}COMMON_TYPES={"array","bitset","deque","function","initializer_list","list","map","multimap","multiset","optional","pair","priority_queue","queue","set","span","stack","string","string_view","tuple","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector",}CV_AND_POINTER={"const","volatile","constexpr","static","mutable","*","&","&&"}CONVENTIONAL_EXTERNAL_DOT_MEMBERS={"first","second"}CONVENTIONAL_EXTERNAL_QUALIFIED_MEMBERS={"type","value"}TYPE_ALIAS_PATTERNS=(("unsigned","long","long","int"),("signed","long","long","int"),("unsigned","long","long"),("signed","long","long"),("long","long","int"),("long","long"),("unsigned","long","int"),("signed","long","int"),("unsigned","short","int"),("signed","short","int"),("short","int"),("long","int"),("unsigned","char"),("signed","char"),("unsigned","long"),("signed","long"),("unsigned","short"),("signed","short"),("unsigned","int"),("signed","int"),("long","double"),("void",),("bool",),("char",),("char8_t",),("char16_t",),("char32_t",),("wchar_t",),("short",),("int",),("long",),("signed",),("unsigned",),("float",),("double",),("__int128",),("__int128_t",),("__uint128_t",),)TYPE_SPECIFIER_WORDS={wordforpatterninTYPE_ALIAS_PATTERNSforwordinpattern}STD_PACK_ALIAS_TEMPLATES={"allocator":"memory","basic_string":"string","basic_string_view":"string_view","complex":"complex","deque":"deque","forward_list":"forward_list","function":"functional","list":"list","map":"map","multimap":"map","multiset":"set","optional":"optional","pair":"utility","priority_queue":"queue","queue":"queue","set":"set","shared_ptr":"memory","stack":"stack","tuple":"tuple","unique_ptr":"memory","unordered_map":"unordered_map","unordered_multimap":"unordered_map","unordered_multiset":"unordered_set","unordered_set":"unordered_set","valarray":"valarray","variant":"variant","vector":"vector","weak_ptr":"memory",}STD_NAMESPACE_HEADERS=set(STD_PACK_ALIAS_TEMPLATES.values())|{"algorithm","any","array","atomic","barrier","bit","bitset","charconv","chrono","compare","concepts","condition_variable","coroutine","exception","execution","filesystem","format","fstream","future","initializer_list","iomanip","ios","iosfwd","iostream","istream","iterator","latch","limits","locale","mutex","new","numbers","numeric","ostream","random","ranges","ratio","regex","scoped_allocator","semaphore","source_location","span","sstream","stdexcept","stop_token","streambuf","syncstream","system_error","thread","type_traits","typeindex","typeinfo",}PUNCTUATORS=("%:%:","<<=",">>=","<=>","->*","...","##","::",".*","->","++","--","<<",">>","<=",">=","==","!=","&&","||","*=","/=","%=","+=","-=","&=","^=","|=","<:",":>","<%","%>","%:","{","}","[","]","(",")","#",";",":","?",".","+","-","*","/","%","^","&","|","~","!","=","<",">",",",)@dataclass(frozen=True)classToken:kind:strtext:str@dataclass(frozen=True)classAlias:kind:stroriginal:tuple[str,...]name:strpositions:frozenset[int]parameter:str=""insertion:int=-1def_short_names():alphabet="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"length=1whileTrue:indices=[0]*lengthwhileTrue:yield"".join(alphabet[index]forindexinindices)position=length-1whileposition>=0andindices[position]==len(alphabet)-1:indices[position]=0position-=1ifposition<0:breakindices[position]+=1length+=1def_is_identifier_start(ch:str)->bool:# '$' is a widely supported GCC/Clang extension and is useful to recognize
# even though portable C++ identifiers do not contain it.
returnchin"_$"orch.isalpha()or(ord(ch)>=128andnotch.isspace())def_is_identifier_continue(ch:str)->bool:return_is_identifier_start(ch)orch.isdigit()def_number_end(source:str,start:int)->int:"""Return the end of a preprocessing number starting at ``start``."""i=start+1whilei<len(source):current=source[i]if_is_identifier_continue(current)orcurrent==".":i+=1elif(current=="'"andi+1<len(source)and_is_identifier_continue(source[i+1])):i+=2elifcurrentin"+-"andsource[i-1]in"eEpP":i+=1else:breakreturnidef_raw_literal_bounds(source:str,start:int)->tuple[int,int,int]|None:"""Return ``(open_paren, close_paren, end)`` for a raw literal."""prefixes=("u8R\"","uR\"","UR\"","LR\"","R\"")raw_prefix=next((pforpinprefixesifsource.startswith(p,start)),None)ifraw_prefixisNone:returnNonedelimiter_start=start+len(raw_prefix)open_paren=source.find("(",delimiter_start)ifopen_paren==-1oropen_paren-delimiter_start>16:returnNonedelimiter=source[delimiter_start:open_paren]ifany(ch.isspace()orchin"()\\"forchindelimiter):returnNoneclose_paren=source.find(")"+delimiter+"\"",open_paren+1)ifclose_paren==-1:returnNoneend=close_paren+len(delimiter)+2whileend<len(source)and_is_identifier_continue(source[end]):end+=1returnopen_paren,close_paren,enddef_quoted_end(source:str,start:int)->int|None:"""Return the end of a string/character/raw literal starting at start.
An immediately adjacent user-defined-literal suffix is part of the token.
"""raw_bounds=_raw_literal_bounds(source,start)ifraw_boundsisnotNone:returnraw_bounds[2]ifany(source.startswith(p,start)forpin("u8R\"","uR\"","UR\"","LR\"","R\"")):# It looks like a raw literal but is malformed or unterminated. The
# compiler will diagnose it; treating the remainder as one token keeps
# the minifier from compounding the error.
returnlen(source)prefixes=("u8\"","u\"","U\"","L\"","\"","u'","U'","L'","'")prefix=next((pforpinprefixesifsource.startswith(p,start)),None)ifprefixisNone:returnNonequote=prefix[-1]i=start+len(prefix)whilei<len(source):ifsource[i]=="\\":i+=2elifsource[i]==quote:end=i+1whileend<len(source)and_is_identifier_continue(source[end]):end+=1returnendelse:i+=1returnlen(source)def_splice_lines(source:str)->str:"""Perform phase-2 line splicing while restoring raw-string contents."""spliced:list[str]=[]origins:list[int]=[]i=0whilei<len(source):ifsource[i]=="\\":ifsource.startswith("\r\n",i+1):i+=3continueifi+1<len(source)andsource[i+1]in"\r\n":i+=2continuespliced.append(source[i])origins.append(i)i+=1phase_two="".join(spliced)output:list[str]=[]copied_until=0i=0whilei<len(phase_two):ifphase_two.startswith("//",i):newline=phase_two.find("\n",i+2)i=len(phase_two)ifnewline==-1elsenewlinecontinueifphase_two.startswith("/*",i):close=phase_two.find("*/",i+2)i=len(phase_two)ifclose==-1elseclose+2continueifphase_two[i].isdigit()or(phase_two[i]=="."andi+1<len(phase_two)andphase_two[i+1].isdigit()):i=_number_end(phase_two,i)continueraw_bounds=_raw_literal_bounds(phase_two,i)ifraw_boundsisnotNone:open_paren,close_paren,end=raw_boundsoutput.append(phase_two[copied_until:open_paren+1])original_open=origins[open_paren]original_close=origins[close_paren]output.append(source[original_open+1:original_close])output.append(phase_two[close_paren:end])copied_until=endi=endcontinueliteral_end=_quoted_end(phase_two,i)ifliteral_endisnotNone:i=literal_endcontinueif_is_identifier_start(phase_two[i]):i+=1whilei<len(phase_two)and_is_identifier_continue(phase_two[i]):i+=1continuei+=1output.append(phase_two[copied_until:])return"".join(output)def_prepare_source(source:str)->str:"""Apply the translation phases needed before preprocessing-token lexing."""# Line splicing precedes comment recognition. C++ restores transformations
# within raw-string contents after recognizing the raw literal.
source=source.replace("\r\n","\n").replace("\r","\n")source=_splice_lines(source)# Comments are replaced by one space in translation phase 3. In
# particular, newlines *inside* a block comment do not terminate a
# preprocessing directive.
output:list[str]=[]i=0whilei<len(source):ifsource[i].isdigit()or(source[i]=="."andi+1<len(source)andsource[i+1].isdigit()):end=_number_end(source,i)output.append(source[i:end])i=endcontinueliteral_end=_quoted_end(source,i)ifliteral_endisnotNone:output.append(source[i:literal_end])i=literal_endcontinueifsource.startswith("//",i):output.append(" ")newline=source.find("\n",i+2)i=len(source)ifnewline==-1elsenewlinecontinueifsource.startswith("/*",i):output.append(" ")close=source.find("*/",i+2)i=len(source)ifclose==-1elseclose+2continueoutput.append(source[i])i+=1return"".join(output)def_directive_end(source:str,start:int)->int:"""Return the end of a preprocessing directive, including its newline."""i=startwhilei<len(source):ifsource[i]=="\n":returni+1ifsource[i].isdigit()or(source[i]=="."andi+1<len(source)andsource[i+1].isdigit()):i=_number_end(source,i)continueliteral_end=_quoted_end(source,i)ifliteral_endisnotNone:i=literal_endcontinueif_is_identifier_start(source[i]):i+=1whilei<len(source)and_is_identifier_continue(source[i]):i+=1continuei+=1returnlen(source)deftokenize(source:str)->list[Token]:source=_prepare_source(source)tokens:list[Token]=[]i=0at_line_start=Truen=len(source)whilei<n:ch=source[i]ifchin" \t\v\f\r":i+=1continueifch=="\n":at_line_start=Truei+=1continueifat_line_startand(ch=="#"orsource.startswith("%:",i)):start=ii=_directive_end(source,i)tokens.append(Token("directive",source[start:i].rstrip()))at_line_start=Truecontinueat_line_start=Falseliteral_end=_quoted_end(source,i)ifliteral_endisnotNone:tokens.append(Token("literal",source[i:literal_end]))i=literal_endcontinueif_is_identifier_start(ch):j=i+1whilej<nand_is_identifier_continue(source[j]):j+=1tokens.append(Token("identifier",source[i:j]))i=jcontinueifch.isdigit()or(ch=="."andi+1<nandsource[i+1].isdigit()):j=_number_end(source,i)tokens.append(Token("number",source[i:j]))i=jcontinuepunctuator=next((pforpinPUNCTUATORSifsource.startswith(p,i)),ch)tokens.append(Token("punct",punctuator))i+=len(punctuator)returntokensdef_matching_left(tokens:list[Token],right:int,opening:str,closing:str)->int|None:depth=0foriinrange(right,-1,-1):iftokens[i].text==closing:depth+=1eliftokens[i].text==opening:depth-=1ifdepth==0:returnireturnNonedef_matching_template_left(tokens:list[Token],right:int)->int|None:"""Match a closing template bracket, including lexed ``>>`` tokens."""depth=0foriinrange(right,-1,-1):iftokens[i].text==">":depth+=1eliftokens[i].text==">>":depth+=2eliftokens[i].text=="<":depth-=1ifdepth==0:returnireturnNonedef_qualified_owner_name(tokens:list[Token],member:int)->str:"""Return the identifier naming the qualifier before ``::member``."""owner=member-2ifowner<0:return""iftokens[owner].textin{">",">>"}:left=_matching_template_left(tokens,owner)owner=-1ifleftisNoneelseleft-1ifowner>=0andtokens[owner].kind=="identifier":returntokens[owner].textreturn""def_known_types(tokens:list[Token])->set[str]:known=set(BUILTIN_TYPES)|set(COMMON_TYPES)|_declared_type_names(tokens)fori,tokeninenumerate(tokens):iftoken.kind!="identifier":continueprevious=tokens[i-1].textifielse""ifpreviousin{"class","struct","union","enum","typename"}:known.add(token.text)ifprevious=="using"andi+1<len(tokens)andtokens[i+1].text=="=":known.add(token.text)# In a typedef declaration, the final identifier is the new type name.
start=0fori,tokeninenumerate(tokens):iftoken.textin{";","{","}"}:statement=tokens[start:i]ifstatementandstatement[0].text=="typedef":identifiers=[t.textfortinstatementift.kind=="identifier"]ifidentifiers:known.add(identifiers[-1])start=i+1returnknowndef_declared_type_names(tokens:list[Token])->set[str]:"""Collect types declared by this translation unit and safe to rename."""result:set[str]=set()fori,tokeninenumerate(tokens):iftoken.kind!="identifier":continueprevious=tokens[i-1].textifielse""following=tokens[i+1].textifi+1<len(tokens)else""ifpreviousin{"class","struct","union","enum"}:result.add(token.text)ifprevious=="using"andfollowing=="=":result.add(token.text)start=0fori,tokeninenumerate(tokens):iftoken.textin{";","{","}"}:statement=tokens[start:i]ifstatementandstatement[0].text=="typedef":identifiers=[t.textfortinstatementift.kind=="identifier"]ifidentifiers:result.add(identifiers[-1])start=i+1returnresultdef_declared_namespace_names(tokens:list[Token])->set[str]:"""Collect namespace definitions owned by the translation unit."""result:set[str]=set()fori,tokeninenumerate(tokens[:-1]):iftoken.text!="namespace":continuej=i+1components:list[str]=[]whilej<len(tokens):iftokens[j].kind=="identifier":components.append(tokens[j].text)j+=1ifj<len(tokens)andtokens[j].text=="::":j+=1continuebreak# A namespace alias such as ``namespace s=std`` does not own members of
# its target and must not make ``s::external_name`` eligible to rename.
ifj<len(tokens)andtokens[j].text=="{":result.update(components)result.discard("std")returnresultdef_direct_class_context(tokens:list[Token])->list[bool]:"""Mark tokens whose innermost brace is a class/struct/union body."""result=[False]*len(tokens)stack:list[str]=[]statement_start=0fori,tokeninenumerate(tokens):result[i]=bool(stackandstack[-1]=="class")iftoken.text=="{":prefix=tokens[statement_start:i]class_keywords=[jforj,iteminenumerate(prefix)ifitem.textin{"class","struct","union"}]is_class=bool(class_keywords)andnotany(item.text==")"foriteminprefix[class_keywords[-1]+1:])kind="class"ifis_classelse"other"stack.append(kind)statement_start=i+1eliftoken.text=="}":ifstack:stack.pop()statement_start=i+1eliftoken.text==";":statement_start=i+1returnresultdef_direct_namespace_context(tokens:list[Token])->list[bool]:"""Mark tokens whose innermost brace is an owned namespace body."""result=[False]*len(tokens)stack:list[str]=[]statement_start=0fori,tokeninenumerate(tokens):result[i]=bool(stackandstack[-1]=="namespace")iftoken.text=="{":prefix=tokens[statement_start:i]is_namespace=any(item.text=="namespace"foriteminprefix)stack.append("namespace"ifis_namespaceelse"other")statement_start=i+1eliftoken.text=="}":ifstack:stack.pop()statement_start=i+1eliftoken.text==";":statement_start=i+1returnresultdef_parenthesis_depth(tokens:list[Token])->list[int]:result:list[int]=[]depth=0fortokenintokens:result.append(depth)iftoken.text=="(":depth+=1eliftoken.text==")":depth=max(0,depth-1)returnresultdef_declared_enum_members(tokens:list[Token])->set[str]:"""Collect enumerators declared by enums in this translation unit."""result:set[str]=set()fori,tokeninenumerate(tokens):iftoken.text!="enum":continueopening=i+1whileopening<len(tokens)andtokens[opening].textnotin{"{",";"}:opening+=1ifopening>=len(tokens)ortokens[opening].text!="{":continuebrace=paren=bracket=0expect_name=Truej=openingwhilej<len(tokens):text=tokens[j].textiftext=="{":brace+=1eliftext=="}":brace-=1ifbrace==0:breakelifbrace==1:iftext=="(":paren+=1eliftext==")":paren=max(0,paren-1)eliftext=="[":bracket+=1eliftext=="]":bracket=max(0,bracket-1)eliftext==","andparen==bracket==0:expect_name=Trueelif(expect_nameandtokens[j].kind=="identifier"andtextnotinCPP_KEYWORDS):result.add(text)expect_name=Falsej+=1returnresultdef_type_before(tokens:list[Token],index:int,known_types:set[str])->bool:j=index-1whilej>=0andtokens[j].textinCV_AND_POINTER:j-=1ifj<0:returnFalseiftokens[j].textinBUILTIN_TYPESortokens[j].textinknown_types:returnTrueiftokens[j].textin{">",">>"}:left=_matching_template_left(tokens,j)returnleftisnotNoneandleft>0andtokens[left-1].kind=="identifier"if(tokens[j].kind=="identifier"andj>0andtokens[j-1].text=="::"):returnTrueiftokens[j].text==")":left=_matching_left(tokens,j,"(",")")returnleftisnotNoneandleft>0andtokens[left-1].text=="decltype"returnFalsedef_directive_identifiers(tokens:list[Token])->set[str]:result:set[str]=set()fortokenintokens:iftoken.kind=="directive":result.update(re.findall(r"(?:[^\W\d]|[_$])(?:\w|[$])*",token.text))returnresultdef_directive_macro_names(tokens:list[Token])->set[str]:result:set[str]=set()fortokenintokens:iftoken.kind!="directive":continuematch=re.match(r"(?:#|%:)\s*(?:define|undef)\s+"r"((?:[^\W\d]|[_$])(?:\w|[$])*)",token.text,)ifmatchisnotNone:result.add(match.group(1))returnresultdef_is_line_control_directive(token:Token)->bool:iftoken.kind!="directive":returnFalsereturnre.match(r"(?:#|%:)\s*(?:line\b|[0-9]+(?:\s|$))",token.text)isnotNonedef_directive_keyword(text:str)->str:match=re.match(r"(?:#|%:)\s*([A-Za-z_][A-Za-z_0-9]*)",text)returnmatch.group(1)ifmatchelse""def_direct_include_operand(text:str)->str|None:match=re.match(r'(?:#|%:)\s*include\s*(<[^>\n]+>|"(?:\\.|[^"\n])+")\s*$',text,)returnmatch.group(1)ifmatchelseNonedef_filter_directives(tokens:list[Token])->list[Token]:"""Drop line controls and duplicate unconditional direct includes."""result:list[Token]=[]seen_includes:set[str]=set()conditional_depth=0fortokenintokens:if_is_line_control_directive(token):continueiftoken.kind!="directive":result.append(token)continuekeyword=_directive_keyword(token.text)ifkeyword=="endif":conditional_depth=max(0,conditional_depth-1)ifkeyword=="include"andconditional_depth==0:operand=_direct_include_operand(token.text)ifoperandisnotNone:ifoperandinseen_includes:continueseen_includes.add(operand)result.append(token)ifkeywordin{"if","ifdef","ifndef"}:conditional_depth+=1returnresultdef_strip_assertions(tokens:list[Token])->list[Token]:"""Remove assert/static_assert calls for source-size-constrained builds.
Runtime assertions become ``(void)0`` so the surrounding expression keeps
its type and grammar. Static assertions, including their trailing
semicolon, can be dropped entirely after a successful normal build.
"""result:list[Token]=[]i=0whilei<len(tokens):token=tokens[i]if(token.kind!="directive"andtoken.textin{"assert","static_assert"}andi+1<len(tokens)andtokens[i+1].text=="("):depth=0end=i+1whileend<len(tokens):iftokens[end].text=="(":depth+=1eliftokens[end].text==")":depth-=1ifdepth==0:breakend+=1ifend==len(tokens):result.append(token)i+=1continueiftoken.text=="assert":result.extend(tokenize("(void)0"))elifend+1<len(tokens)andtokens[end+1].text==";":end+=1i=end+1continueresult.append(token)i+=1returnresultdef_hoist_direct_includes(tokens:list[Token])->tuple[list[Token],int]:"""Move direct includes to the front and return their resulting count.
Extreme mode targets one known judge platform, so making a conditional
include unconditional is intentional: it lets generated keyword macros be
defined after every system header without polluting those headers.
"""includes:list[Token]=[]body:list[Token]=[]seen:set[str]=set()fortokenintokens:operand=(_direct_include_operand(token.text)iftoken.kind=="directive"elseNone)ifoperandisNone:body.append(token)elifoperandnotinseen:seen.add(operand)includes.append(token)returnincludes+body,len(includes)def_keyword_macro_compression(tokens:list[Token],insertion:int,)->tuple[list[Token],dict[str,str]]:"""Alias profitable C++ keywords after all includes with short macros."""used:set[str]={token.textfortokenintokensiftoken.kind=="identifier"}fortokenintokens:iftoken.kind=="directive":used.update(re.findall(r"[A-Za-z_][A-Za-z_0-9]*",token.text))names=(namefornamein_short_names()ifnamenotinusedandnamenotinCPP_KEYWORDSandnotname.startswith("_"))counts=Counter(token.textfortokenintokens[insertion:]iftoken.kind!="directive"andtoken.textinCPP_KEYWORDS)ordered=sorted(counts.items(),key=lambdaitem:(-item[1]*max(0,len(item[0])-2),item[0]),)aliases:dict[str,str]={}candidate=next(names)forkeyword,countinordered:declaration_size=len(f"#define {candidate}{keyword}\n")saving=count*(len(keyword)-len(candidate))-declaration_sizeifsaving<=0:continuealiases[keyword]=candidatecandidate=next(names)definitions=[Token("directive",f"#define {alias}{keyword}")forkeyword,aliasinaliases.items()]result=tokens[:insertion]+definitions+tokens[insertion:]first_code=insertion+len(definitions)foriinrange(first_code,len(result)):token=result[i]iftoken.kind!="directive"andtoken.textinaliases:result[i]=Token(token.kind,aliases[token.text])returnresult,aliasesdef_variable_rename_plan(tokens:list[Token],)->tuple[dict[str,str],frozenset[int]]:"""Build replacements and positions that refer to external names."""known_types=_known_types(tokens)declared_types=_declared_type_names(tokens)declared_namespaces=_declared_namespace_names(tokens)enum_members=_declared_enum_members(tokens)class_context=_direct_class_context(tokens)namespace_context=_direct_namespace_context(tokens)parenthesis_depth=_parenthesis_depth(tokens)candidates:set[str]=set()declaration_indices:list[int]=[]field_names:set[str]=set()fori,tokeninenumerate(tokens):iftoken.kind=="identifier"andtoken.textnotinCPP_KEYWORDS:if_type_before(tokens,i,known_types):candidates.add(token.text)declaration_indices.append(i)if(class_context[i]andparenthesis_depth[i]==0and(i+1>=len(tokens)ortokens[i+1].text!="(")):field_names.add(token.text)# Structured bindings: auto [long_name, other_name].
iftoken.text=="[":j=i-1whilej>=0andtokens[j].textinCV_AND_POINTER:j-=1ifj>=0andtokens[j].text=="auto":close=next((kforkinrange(i+1,len(tokens))iftokens[k].text=="]"),None)ifcloseisnotNone:foritemintokens[i+1:close]:ifitem.kind=="identifier"anditem.textnotinCPP_KEYWORDS:candidates.add(item.text)candidates.update(declared_types)candidates.update(declared_namespaces)candidates.update(enum_members)function_names={tokens[i].textforiindeclaration_indicesifi+1<len(tokens)andtokens[i+1].text=="("}namespace_variables={tokens[i].textforiindeclaration_indicesifnamespace_context[i]and(i+1>=len(tokens)ortokens[i+1].text!="(")}# Add later declarators in declarations such as: int first = 0, second = 1;
fordeclarationindeclaration_indices:paren=bracket=brace=0i=declaration+1whilei<len(tokens):text=tokens[i].textiftext=="(":paren+=1eliftext==")":ifparen==0:breakparen-=1eliftext=="[":bracket+=1eliftext=="]":bracket=max(0,bracket-1)eliftext=="{":brace+=1eliftext=="}":ifbrace==0:breakbrace-=1eliftext==";"andparen==bracket==brace==0:breakeliftext==","andparen==bracket==brace==0:j=i+1whilej<len(tokens)andtokens[j].textinCV_AND_POINTER:j+=1ifj<len(tokens)andtokens[j].kind=="identifier":name=tokens[j].textifnamenotinknown_typesandnamenotinCPP_KEYWORDS:candidates.add(name)ifclass_context[declaration]:field_names.add(name)i+=1protected=(_directive_identifiers(tokens)|(known_types-declared_types)|CPP_KEYWORDS|{"main"})protected.update(field_names&(CONVENTIONAL_EXTERNAL_DOT_MEMBERS|CONVENTIONAL_EXTERNAL_QUALIFIED_MEMBERS))protected.update((declared_types|enum_members)&CONVENTIONAL_EXTERNAL_QUALIFIED_MEMBERS)excluded_positions:set[int]=set()fori,tokeninenumerate(tokens):iftoken.kind!="identifier":continueprevious=tokens[i-1].textifielse""following=tokens[i+1].textifi+1<len(tokens)else""owned_type=token.textindeclared_typesowned_namespace=token.textindeclared_namespacesowned_field=token.textinfield_namesowned_member=owned_fieldortoken.textinenum_membersowned_function=token.textinfunction_namesifpreviousin{".","->"}and(token.textinCONVENTIONAL_EXTERNAL_DOT_MEMBERSornot(owned_fieldorowned_type)):excluded_positions.add(i)ifpreviousin{".","->"}andfollowing=="(":excluded_positions.add(i)ifowned_function:# Without receiver-type information, a locally declared method
# cannot be distinguished from an external method at call sites.
protected.add(token.text)ifprevious=="::":qualifier=_qualified_owner_name(tokens,i)qualifier_owned=(qualifierindeclared_typesorqualifierindeclared_namespaces)conventional_external=(token.textinCONVENTIONAL_EXTERNAL_QUALIFIED_MEMBERSandqualifiernotindeclared_namespaces)ifconventional_externalornotqualifier_ownedornot(owned_typeorowned_namespaceorowned_memberorowned_functionortoken.textinnamespace_variables):excluded_positions.add(i)ifpreviousin{"goto","typedef"}:protected.add(token.text)ifpreviousin{"class","struct","union","enum"}andnotowned_type:excluded_positions.add(i)ifprevious=="namespace"andnotowned_namespace:excluded_positions.add(i)ifprevious=="using"andnot(owned_typeorowned_namespace):excluded_positions.add(i)iffollowing=="::"andnot(owned_typeorowned_namespace):excluded_positions.add(i)iffollowing==":"andtoken.textnotincandidates:protected.add(token.text)iffollowing=="("andnot(owned_typeorowned_memberorowned_functionortoken.textincandidates):excluded_positions.add(i)iftoken.text.startswith("__")or(token.text.startswith("_")andlen(token.text)>1andtoken.text[1].isupper()):protected.add(token.text)# Keep declarations whose spelling participates in an external contract.
foriindeclaration_indices:start=i-1whilestart>=0andtokens[start].textnotin{";","{","}"}:start-=1prefix=tokens[start+1:i]ifany(token.text=="extern"fortokeninprefix):protected.add(tokens[i].text)ifi+1>=len(tokens)ortokens[i+1].text!="(":continueend=i+1whileend<len(tokens)andtokens[end].textnotin{";","{"}:end+=1declaration=tokens[start+1:end]ifany(token.textin{"override","final"}fortokenindeclaration):protected.add(tokens[i].text)candidates-=protectedcounts:dict[str,int]={}fori,tokeninenumerate(tokens):if(inotinexcluded_positionsandtoken.kind=="identifier"andtoken.textincandidates):counts[token.text]=counts.get(token.text,0)+1unavailable={token.textfortokenintokensiftoken.kind=="identifier"}|CPP_KEYWORDSnames=_short_names()renames:dict[str,str]={}foroldinsorted(candidates,key=lambdaname:(-counts.get(name,0),-len(name),name)):new=next(names)whilenewinunavailableornewinrenames.values():new=next(names)iflen(new)<len(old):renames[old]=newreturnrenames,frozenset(excluded_positions)defvariable_renames(tokens:list[Token])->dict[str,str]:"""Return the spelling map for file-owned C++ names."""return_variable_rename_plan(tokens)[0]def_pattern_positions(tokens:list[Token],pattern:tuple[str,...])->list[int]:"""Find maximal builtin type spellings equal to ``pattern``."""result:list[int]=[]width=len(pattern)foriinrange(len(tokens)-width+1):iftuple(token.textfortokenintokens[i:i+width])!=pattern:continueprevious=tokens[i-1].textifielse""following=tokens[i+width].textifi+width<len(tokens)else""ifpreviousinTYPE_SPECIFIER_WORDSorfollowinginTYPE_SPECIFIER_WORDS:continueresult.append(i)returnresultdef_qualified_template_positions(tokens:list[Token],name:str)->list[int]:pattern=("std","::",name,"<")return[iforiinrange(len(tokens)-len(pattern)+1)iftuple(token.textfortokenintokens[i:i+len(pattern)])==pattern]def_standard_header_insertions(tokens:list[Token])->dict[str,int]:"""Return safe global insertion points after unconditional angle includes."""result:dict[str,int]={}conditional_depth=0brace_depth=0fori,tokeninenumerate(tokens):iftoken.kind=="directive":keyword=_directive_keyword(token.text)ifkeyword=="endif":conditional_depth=max(0,conditional_depth-1)if(keyword=="include"andconditional_depth==0andbrace_depth==0):operand=_direct_include_operand(token.text)ifoperandisnotNoneandoperand.startswith("<"):result.setdefault(operand[1:-1],i+1)ifkeywordin{"if","ifdef","ifndef"}:conditional_depth+=1continueiftoken.text=="{":brace_depth+=1eliftoken.text=="}":brace_depth=max(0,brace_depth-1)returnresultdef_leading_directive_end(tokens:list[Token])->tuple[int,bool,bool]:"""Return the initial directive-block end and namespace-alias safety."""i=0saw_include=Falseconditional=Falsewhilei<len(tokens)andtokens[i].kind=="directive":directive=tokens[i].textmatch=re.match(r"(?:#|%:)\s*([A-Za-z_][A-Za-z_0-9]*)",directive)keyword=match.group(1)ifmatchelse""saw_include|=keyword=="include"conditional|=keywordin{"if","ifdef","ifndef","elif","else","endif"}i+=1returni,saw_includeandnotconditional,notconditionaldef_compression_aliases(tokens:list[Token],renames:dict[str,str])->tuple[list[Alias],int,int]:"""Choose profitable type aliases and well-known namespace aliases."""# Declarations before/among module directives have strict placement rules.
ifany(token.text=="module"fortokenintokens):return[],0,0directive_names=_directive_identifiers(tokens)macro_names=_directive_macro_names(tokens)candidates:list[tuple[str,tuple[str,...],list[int],int,int,str,int]]=[]forpatterninTYPE_ALIAS_PATTERNS:ifany(wordindirective_namesforwordinpattern):continuepositions=_pattern_positions(tokens,pattern)ifnotpositions:continuespelling_length=sum(map(len,pattern))+len(pattern)-1candidates.append(("type",pattern,positions,spelling_length,7+spelling_length,"",-1,))leading_end,_,type_aliases_safe=_leading_directive_end(tokens)header_insertions=_standard_header_insertions(tokens)umbrella_insertions=[header_insertions[header]forheaderin("bits/stdc++.h","bits/extc++.h")ifheaderinheader_insertions]parameter=next(namefornamein("T","U","V","X","_")ifnamenotindirective_names)fortemplate_name,headerinSTD_PACK_ALIAS_TEMPLATES.items():iftemplate_nameinmacro_namesor"std"inmacro_names:continueinsertions=list(umbrella_insertions)ifheaderinheader_insertions:insertions.append(header_insertions[header])ifnotinsertions:continueinsertion=min(insertions)positions=[positionforpositionin_qualified_template_positions(tokens,template_name)ifposition>=insertion]ifnotpositions:continueoriginal=("std","::",template_name)spelling_length=len("std::")+len(template_name)declaration_without_name=(f"template<class...{parameter}>using "f"=std::{template_name}<{parameter}...>;")candidates.append(("template",original,positions,spelling_length,len(declaration_without_name),parameter,insertion,))namespace_insertions={"std":[insertionforheader,insertioninheader_insertions.items()ifheaderinSTD_NAMESPACE_HEADERSorheaderin{"bits/stdc++.h","bits/extc++.h"}],"boost":[insertionforheader,insertioninheader_insertions.items()ifheader.startswith("boost/")],}fornamespacein("std","boost"):ifnamespaceinmacro_namesornotnamespace_insertions[namespace]:continueall_positions=[ifori,tokeninenumerate(tokens)iftoken.kind=="identifier"andtoken.text==namespace]ifnotall_positionsorany(i+1>=len(tokens)ortokens[i+1].text!="::"foriinall_positions):continueinsertion=min(namespace_insertions[namespace])positions=[positionforpositioninall_positionsifposition>=insertion]ifnotpositions:continuecandidates.append(("namespace",(namespace,),positions,len(namespace),12+len(namespace),"",insertion,))occupied={token.textfortokenintokensiftoken.kind=="identifier"}|directive_names|CPP_KEYWORDS|set(renames.values())names=_short_names()aliases:list[Alias]=[]whilecandidates:name=next(names)whilenameinoccupied:name=next(names)best_index=-1best_saving=0fori,candidateinenumerate(candidates):_,_,positions,spelling_length,declaration_base,_,_=candidatesaving=(len(positions)*(spelling_length-len(name))-declaration_base-len(name))ifsaving>best_saving:best_saving=savingbest_index=iifbest_index==-1:breakkind,original,positions,_,_,parameter,insertion=candidates.pop(best_index)aliases.append(Alias(kind,original,name,frozenset(positions),parameter,insertion))occupied.add(name)template_positions={positionforaliasinaliasesifalias.kind=="template"forpositioninalias.positions}adjusted:list[Alias]=[]foraliasinaliases:ifalias.kind!="namespace"oralias.original!=("std",):adjusted.append(alias)continuepositions=alias.positions-template_positionsdeclaration_length=12+len(alias.name)+len(alias.original[0])saving=len(positions)*(len("std")-len(alias.name))ifsaving>declaration_length:adjusted.append(Alias(alias.kind,alias.original,alias.name,positions,alias.parameter,alias.insertion,))aliases=adjustedtype_insert=leading_endiftype_aliases_safeelse0namespace_insert=leading_endreturnaliases,type_insert,namespace_insertdef_alias_declaration(alias:Alias)->list[Token]:original=" ".join(alias.original)ifalias.kind=="namespace":returntokenize(f"namespace {alias.name}={original};")ifalias.kind=="template":template_name=alias.original[-1]parameter=alias.parameterreturntokenize(f"template<class...{parameter}>using {alias.name}="f"std::{template_name}<{parameter}...>;")returntokenize(f"using {alias.name}={original};")def_alias_key(alias:Alias)->str:ifalias.kind=="template":return"".join(alias.original)return" ".join(alias.original)def_transform_tokens(tokens:list[Token],renames:dict[str,str],aliases:list[Alias],type_insert:int,namespace_insert:int,rename_exclusions:frozenset[int]=frozenset(),)->list[Token]:injections:dict[int,list[Token]]={}foraliasinaliases:index=(alias.insertionifalias.insertion>=0elsenamespace_insertifalias.kindin{"namespace","template"}elsetype_insert)injections.setdefault(index,[]).extend(_alias_declaration(alias))type_at:dict[int,Alias]={}namespace_at:dict[int,Alias]={}foraliasinaliases:destination=namespace_atifalias.kind=="namespace"elsetype_atforpositioninalias.positions:destination[position]=aliasresult:list[Token]=[]i=0whilei<len(tokens):result.extend(injections.get(i,()))ifiintype_at:alias=type_at[i]result.append(Token("identifier",alias.name))i+=len(alias.original)continuetoken=tokens[i]ifiinnamespace_at:token=Token("identifier",namespace_at[i].name)elif(inotinrename_exclusionsandtoken.kind=="identifier"andtoken.textinrenames):token=Token(token.kind,renames[token.text])result.append(token)i+=1result.extend(injections.get(len(tokens),()))returnresultdef_needs_space(previous:Token,current:Token)->bool:ifprevious.kind=="directive"orcurrent.kind=="directive":returnFalse# Re-lexing the boundary is more reliable than maintaining a growing list
# of special cases for pp-numbers, literal prefixes, digraphs, comments,
# and multi-character punctuators. Each token participates in at most two
# such checks, so the total amount of text examined remains linear.
combined=tokenize(previous.text+current.text)returncombined!=[previous,current]defminify(source:str,rename:bool=True,extreme:bool=False,)->tuple[str,dict[str,str]]:tokens=_filter_directives(tokenize(source))ifextreme:tokens=_strip_assertions(tokens)tokens,include_count=_hoist_direct_includes(tokens)else:include_count=0rename_exclusions:frozenset[int]=frozenset()ifrename:renames,rename_exclusions=_variable_rename_plan(tokens)else:renames={}aliases:list[Alias]=[]type_insert=namespace_insert=0ifrename:aliases,type_insert,namespace_insert=_compression_aliases(tokens,renames)tokens=_transform_tokens(tokens,renames,aliases,type_insert,namespace_insert,rename_exclusions,)foraliasinaliases:renames[_alias_key(alias)]=alias.nameifextreme:tokens,keyword_aliases=_keyword_macro_compression(tokens,include_count,)forkeyword,aliasinkeyword_aliases.items():renames[f"keyword:{keyword}"]=aliasoutput:list[str]=[]previous:Token|None=Nonefortokenintokens:iftoken.kind=="directive":ifoutputandnotoutput[-1].endswith("\n"):output.append("\n")output.append(token.text+"\n")previous=NonecontinueifpreviousisnotNoneand_needs_space(previous,token):output.append(" ")output.append(token.text)previous=tokenresult="".join(output).rstrip()return(result+"\n"ifresultelse""),renamesdefmain()->int:parser=argparse.ArgumentParser(description="Minify C++ source code for contest submissions.")parser.add_argument("input",nargs="?",type=Path,help="input file (default: stdin)")parser.add_argument("-o","--output",type=Path,help="output file (default: stdout)")parser.add_argument("--no-rename",action="store_true",help="only remove comments and whitespace")parser.add_argument("--extreme",action="store_true",help=("target-specific size mode: hoist direct includes, remove assertions, ""and alias profitable C++ keywords with macros"),)parser.add_argument("--stats",action="store_true",help="print size and name-compression statistics to stderr")args=parser.parse_args()source=args.input.read_text(encoding="utf-8")ifargs.inputelsesys.stdin.read()result,renames=minify(source,rename=notargs.no_rename,extreme=args.extreme,)ifargs.output:args.output.write_text(result,encoding="utf-8")else:sys.stdout.write(result)ifargs.stats:source_size=len(source.encode("utf-8"))result_size=len(result.encode("utf-8"))reduction=0.0ifnotsource_sizeelse100.0*(source_size-result_size)/source_sizeprint(f"{source_size} -> {result_size} bytes ({reduction:.1f}% smaller), "f"renamed/aliased {len(renames)} names",file=sys.stderr,)return0if__name__=="__main__":raiseSystemExit(main())