#!/usr/bin/env python3.14# -*- coding: utf-8 -*-"""Runner framework for Project Euler solutions with benchmarking and validation."""from__future__importannotations__all__=['get_text_file','main','parse_int','parse_list','show']fromastimportliteral_evalfromfunctoolsimportwrapsfrompathlibimportPathfromsysimportargv,stderrfromtimeimportperf_counterfromtypingimportCallable,ProtocolclassSolve(Protocol):"""Protocol for a solver function."""def__call__(self,*args:str)->str:...defget_text_file(src:str)->str:"""Read a file from the 'resources' directory next to the solution script. Resolved from the script's own location (argv[0]) so it works regardless of the working directory — `__file__` now points at the runner module, not the solution. """local_filename:str='resources/'+src.split('/')[-1].split('?')[0]return(Path(argv[0]).resolve().parent/local_filename).read_text()show:bool=False# set in maindefmain(solve:Solve)->Callable[[],int]:"""Decorator to add benchmarking and validation to a solver function."""@wraps(solve)defwrapper()->int:"""Execute `solve` with benchmarking and validation."""globalshowtry:runs_arg:str=next(argforarginargv[1:]ifarg.startswith('--runs='))runs:int=int(runs_arg.split('=',1)[1])assertruns>0except(AssertionError,StopIteration,ValueError):runs=1if'--show'inargv:show=Trueargs:list[str]=[argforarginargv[1:]ifnotarg.startswith('--')]elapsed:list[float]=[]result:str|None=Nonerc:int=0errors:list[str]=[]for_inrange(runs):_start,_result,_stop=perf_counter(),solve(*args),perf_counter()# <= timed callelapsed.append(_stop-_start)ifresultisnotNoneand_result!=result:errors.append(f"Expected consistent result, got {_result} previous result={result}")result=_resultifresultisNone:errors.append("Expected a result, got None")average:float=sum(elapsed)/len(elapsed)iferrors:print('\n'.join(errors),file=stderr)rc=1print(f'{runs}{average}{result}')returnrcreturnwrapperdefparse_int(token:str)->int:"""Parse an integer supporting power notation and underscores."""token=token.strip()if'**'intoken:base,_,exp=token.partition('**')returnint(int(base)**int(exp))if'_'intoken:returnint(token.replace('_',''))returnint(token)defparse_list(token:str)->list[int]:"""Parse a list literal such as '[1, 2, 3]' into a list of ints."""result:list[int]=literal_eval(token)returnresult