1## @file 2# Trim files preprocessed by compiler 3# 4# Copyright (c) 2007 - 2014, Intel Corporation. All rights reserved.<BR> 5# This program and the accompanying materials 6# are licensed and made available under the terms and conditions of the BSD License 7# which accompanies this distribution. The full text of the license may be found at 8# http://opensource.org/licenses/bsd-license.php 9# 10# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 11# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 12# 13 14## 15# Import Modules 16# 17import Common.LongFilePathOs as os 18import sys 19import re 20 21from optparse import OptionParser 22from optparse import make_option 23from Common.BuildToolError import * 24from Common.Misc import * 25from Common.BuildVersion import gBUILD_VERSION 26import Common.EdkLogger as EdkLogger 27from Common.LongFilePathSupport import OpenLongFilePath as open 28 29# Version and Copyright 30__version_number__ = ("0.10" + " " + gBUILD_VERSION) 31__version__ = "%prog Version " + __version_number__ 32__copyright__ = "Copyright (c) 2007-2010, Intel Corporation. All rights reserved." 33 34## Regular expression for matching Line Control directive like "#line xxx" 35gLineControlDirective = re.compile('^\s*#(?:line)?\s+([0-9]+)\s+"*([^"]*)"') 36## Regular expression for matching "typedef struct" 37gTypedefPattern = re.compile("^\s*typedef\s+struct(\s+\w+)?\s*[{]*$", re.MULTILINE) 38## Regular expression for matching "#pragma pack" 39gPragmaPattern = re.compile("^\s*#pragma\s+pack", re.MULTILINE) 40 41# 42# The following number pattern match will only match if following criteria is met: 43# There is leading non-(alphanumeric or _) character, and no following alphanumeric or _ 44# as the pattern is greedily match, so it is ok for the gDecNumberPattern or gHexNumberPattern to grab the maximum match 45# 46## Regular expression for matching HEX number 47gHexNumberPattern = re.compile("(?<=[^a-zA-Z0-9_])(0[xX])([0-9a-fA-F]+)(U(?=$|[^a-zA-Z0-9_]))?") 48## Regular expression for matching decimal number with 'U' postfix 49gDecNumberPattern = re.compile("(?<=[^a-zA-Z0-9_])([0-9]+)U(?=$|[^a-zA-Z0-9_])") 50## Regular expression for matching constant with 'ULL' 'LL' postfix 51gLongNumberPattern = re.compile("(?<=[^a-zA-Z0-9_])(0[xX][0-9a-fA-F]+|[0-9]+)U?LL(?=$|[^a-zA-Z0-9_])") 52 53## Regular expression for matching "Include ()" in asl file 54gAslIncludePattern = re.compile("^(\s*)[iI]nclude\s*\(\"?([^\"\(\)]+)\"\)", re.MULTILINE) 55## Regular expression for matching C style #include "XXX.asl" in asl file 56gAslCIncludePattern = re.compile(r'^(\s*)#include\s*[<"]\s*([-\\/\w.]+)\s*([>"])', re.MULTILINE) 57## Patterns used to convert EDK conventions to EDK2 ECP conventions 58gImportCodePatterns = [ 59 [ 60 re.compile('^(\s*)\(\*\*PeiServices\)\.PciCfg\s*=\s*([^;\s]+);', re.MULTILINE), 61 '''\\1{ 62\\1 STATIC EFI_PEI_PPI_DESCRIPTOR gEcpPeiPciCfgPpiList = { 63\\1 (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), 64\\1 &gEcpPeiPciCfgPpiGuid, 65\\1 \\2 66\\1 }; 67\\1 (**PeiServices).InstallPpi (PeiServices, &gEcpPeiPciCfgPpiList); 68\\1}''' 69 ], 70 71 [ 72 re.compile('^(\s*)\(\*PeiServices\)->PciCfg\s*=\s*([^;\s]+);', re.MULTILINE), 73 '''\\1{ 74\\1 STATIC EFI_PEI_PPI_DESCRIPTOR gEcpPeiPciCfgPpiList = { 75\\1 (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), 76\\1 &gEcpPeiPciCfgPpiGuid, 77\\1 \\2 78\\1 }; 79\\1 (**PeiServices).InstallPpi (PeiServices, &gEcpPeiPciCfgPpiList); 80\\1}''' 81 ], 82 83 [ 84 re.compile("(\s*).+->Modify[\s\n]*\(", re.MULTILINE), 85 '\\1PeiLibPciCfgModify (' 86 ], 87 88 [ 89 re.compile("(\W*)gRT->ReportStatusCode[\s\n]*\(", re.MULTILINE), 90 '\\1EfiLibReportStatusCode (' 91 ], 92 93 [ 94 re.compile('#include\s+EFI_GUID_DEFINITION\s*\(FirmwareFileSystem\)', re.MULTILINE), 95 '#include EFI_GUID_DEFINITION (FirmwareFileSystem)\n#include EFI_GUID_DEFINITION (FirmwareFileSystem2)' 96 ], 97 98 [ 99 re.compile('gEfiFirmwareFileSystemGuid', re.MULTILINE), 100 'gEfiFirmwareFileSystem2Guid' 101 ], 102 103 [ 104 re.compile('EFI_FVH_REVISION', re.MULTILINE), 105 'EFI_FVH_PI_REVISION' 106 ], 107 108 [ 109 re.compile("(\s*)\S*CreateEvent\s*\([\s\n]*EFI_EVENT_SIGNAL_READY_TO_BOOT[^,]*,((?:[^;]+\n)+)(\s*\));", re.MULTILINE), 110 '\\1EfiCreateEventReadyToBoot (\\2\\3;' 111 ], 112 113 [ 114 re.compile("(\s*)\S*CreateEvent\s*\([\s\n]*EFI_EVENT_SIGNAL_LEGACY_BOOT[^,]*,((?:[^;]+\n)+)(\s*\));", re.MULTILINE), 115 '\\1EfiCreateEventLegacyBoot (\\2\\3;' 116 ], 117# [ 118# re.compile("(\W)(PEI_PCI_CFG_PPI)(\W)", re.MULTILINE), 119# '\\1ECP_\\2\\3' 120# ] 121] 122 123## file cache to avoid circular include in ASL file 124gIncludedAslFile = [] 125 126## Trim preprocessed source code 127# 128# Remove extra content made by preprocessor. The preprocessor must enable the 129# line number generation option when preprocessing. 130# 131# @param Source File to be trimmed 132# @param Target File to store the trimmed content 133# @param Convert If True, convert standard HEX format to MASM format 134# 135def TrimPreprocessedFile(Source, Target, ConvertHex, TrimLong): 136 CreateDirectory(os.path.dirname(Target)) 137 try: 138 f = open (Source, 'r') 139 except: 140 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source) 141 142 # read whole file 143 Lines = f.readlines() 144 f.close() 145 146 PreprocessedFile = "" 147 InjectedFile = "" 148 LineIndexOfOriginalFile = None 149 NewLines = [] 150 LineControlDirectiveFound = False 151 for Index in range(len(Lines)): 152 Line = Lines[Index] 153 # 154 # Find out the name of files injected by preprocessor from the lines 155 # with Line Control directive 156 # 157 MatchList = gLineControlDirective.findall(Line) 158 if MatchList != []: 159 MatchList = MatchList[0] 160 if len(MatchList) == 2: 161 LineNumber = int(MatchList[0], 0) 162 InjectedFile = MatchList[1] 163 # The first injetcted file must be the preprocessed file itself 164 if PreprocessedFile == "": 165 PreprocessedFile = InjectedFile 166 LineControlDirectiveFound = True 167 continue 168 elif PreprocessedFile == "" or InjectedFile != PreprocessedFile: 169 continue 170 171 if LineIndexOfOriginalFile == None: 172 # 173 # Any non-empty lines must be from original preprocessed file. 174 # And this must be the first one. 175 # 176 LineIndexOfOriginalFile = Index 177 EdkLogger.verbose("Found original file content starting from line %d" 178 % (LineIndexOfOriginalFile + 1)) 179 180 if TrimLong: 181 Line = gLongNumberPattern.sub(r"\1", Line) 182 # convert HEX number format if indicated 183 if ConvertHex: 184 Line = gHexNumberPattern.sub(r"0\2h", Line) 185 else: 186 Line = gHexNumberPattern.sub(r"\1\2", Line) 187 188 # convert Decimal number format 189 Line = gDecNumberPattern.sub(r"\1", Line) 190 191 if LineNumber != None: 192 EdkLogger.verbose("Got line directive: line=%d" % LineNumber) 193 # in case preprocessor removed some lines, like blank or comment lines 194 if LineNumber <= len(NewLines): 195 # possible? 196 NewLines[LineNumber - 1] = Line 197 else: 198 if LineNumber > (len(NewLines) + 1): 199 for LineIndex in range(len(NewLines), LineNumber-1): 200 NewLines.append(os.linesep) 201 NewLines.append(Line) 202 LineNumber = None 203 EdkLogger.verbose("Now we have lines: %d" % len(NewLines)) 204 else: 205 NewLines.append(Line) 206 207 # in case there's no line directive or linemarker found 208 if (not LineControlDirectiveFound) and NewLines == []: 209 NewLines = Lines 210 211 # save to file 212 try: 213 f = open (Target, 'wb') 214 except: 215 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target) 216 f.writelines(NewLines) 217 f.close() 218 219## Trim preprocessed VFR file 220# 221# Remove extra content made by preprocessor. The preprocessor doesn't need to 222# enable line number generation option when preprocessing. 223# 224# @param Source File to be trimmed 225# @param Target File to store the trimmed content 226# 227def TrimPreprocessedVfr(Source, Target): 228 CreateDirectory(os.path.dirname(Target)) 229 230 try: 231 f = open (Source,'r') 232 except: 233 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source) 234 # read whole file 235 Lines = f.readlines() 236 f.close() 237 238 FoundTypedef = False 239 Brace = 0 240 TypedefStart = 0 241 TypedefEnd = 0 242 for Index in range(len(Lines)): 243 Line = Lines[Index] 244 # don't trim the lines from "formset" definition to the end of file 245 if Line.strip() == 'formset': 246 break 247 248 if FoundTypedef == False and (Line.find('#line') == 0 or Line.find('# ') == 0): 249 # empty the line number directive if it's not aomong "typedef struct" 250 Lines[Index] = "\n" 251 continue 252 253 if FoundTypedef == False and gTypedefPattern.search(Line) == None: 254 # keep "#pragram pack" directive 255 if gPragmaPattern.search(Line) == None: 256 Lines[Index] = "\n" 257 continue 258 elif FoundTypedef == False: 259 # found "typedef struct", keept its position and set a flag 260 FoundTypedef = True 261 TypedefStart = Index 262 263 # match { and } to find the end of typedef definition 264 if Line.find("{") >= 0: 265 Brace += 1 266 elif Line.find("}") >= 0: 267 Brace -= 1 268 269 # "typedef struct" must end with a ";" 270 if Brace == 0 and Line.find(";") >= 0: 271 FoundTypedef = False 272 TypedefEnd = Index 273 # keep all "typedef struct" except to GUID, EFI_PLABEL and PAL_CALL_RETURN 274 if Line.strip("} ;\r\n") in ["GUID", "EFI_PLABEL", "PAL_CALL_RETURN"]: 275 for i in range(TypedefStart, TypedefEnd+1): 276 Lines[i] = "\n" 277 278 # save all lines trimmed 279 try: 280 f = open (Target,'w') 281 except: 282 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target) 283 f.writelines(Lines) 284 f.close() 285 286## Read the content ASL file, including ASL included, recursively 287# 288# @param Source File to be read 289# @param Indent Spaces before the Include() statement 290# @param IncludePathList The list of external include file 291# @param LocalSearchPath If LocalSearchPath is specified, this path will be searched 292# first for the included file; otherwise, only the path specified 293# in the IncludePathList will be searched. 294# 295def DoInclude(Source, Indent='', IncludePathList=[], LocalSearchPath=None): 296 NewFileContent = [] 297 298 try: 299 # 300 # Search LocalSearchPath first if it is specified. 301 # 302 if LocalSearchPath: 303 SearchPathList = [LocalSearchPath] + IncludePathList 304 else: 305 SearchPathList = IncludePathList 306 307 for IncludePath in SearchPathList: 308 IncludeFile = os.path.join(IncludePath, Source) 309 if os.path.isfile(IncludeFile): 310 F = open(IncludeFile, "r") 311 break 312 else: 313 EdkLogger.error("Trim", "Failed to find include file %s" % Source) 314 except: 315 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source) 316 317 318 # avoid A "include" B and B "include" A 319 IncludeFile = os.path.abspath(os.path.normpath(IncludeFile)) 320 if IncludeFile in gIncludedAslFile: 321 EdkLogger.warn("Trim", "Circular include", 322 ExtraData= "%s -> %s" % (" -> ".join(gIncludedAslFile), IncludeFile)) 323 return [] 324 gIncludedAslFile.append(IncludeFile) 325 326 for Line in F: 327 LocalSearchPath = None 328 Result = gAslIncludePattern.findall(Line) 329 if len(Result) == 0: 330 Result = gAslCIncludePattern.findall(Line) 331 if len(Result) == 0 or os.path.splitext(Result[0][1])[1].lower() not in [".asl", ".asi"]: 332 NewFileContent.append("%s%s" % (Indent, Line)) 333 continue 334 # 335 # We should first search the local directory if current file are using pattern #include "XXX" 336 # 337 if Result[0][2] == '"': 338 LocalSearchPath = os.path.dirname(IncludeFile) 339 CurrentIndent = Indent + Result[0][0] 340 IncludedFile = Result[0][1] 341 NewFileContent.extend(DoInclude(IncludedFile, CurrentIndent, IncludePathList, LocalSearchPath)) 342 NewFileContent.append("\n") 343 344 gIncludedAslFile.pop() 345 F.close() 346 347 return NewFileContent 348 349 350## Trim ASL file 351# 352# Replace ASL include statement with the content the included file 353# 354# @param Source File to be trimmed 355# @param Target File to store the trimmed content 356# @param IncludePathFile The file to log the external include path 357# 358def TrimAslFile(Source, Target, IncludePathFile): 359 CreateDirectory(os.path.dirname(Target)) 360 361 SourceDir = os.path.dirname(Source) 362 if SourceDir == '': 363 SourceDir = '.' 364 365 # 366 # Add source directory as the first search directory 367 # 368 IncludePathList = [SourceDir] 369 370 # 371 # If additional include path file is specified, append them all 372 # to the search directory list. 373 # 374 if IncludePathFile: 375 try: 376 LineNum = 0 377 for Line in open(IncludePathFile,'r'): 378 LineNum += 1 379 if Line.startswith("/I") or Line.startswith ("-I"): 380 IncludePathList.append(Line[2:].strip()) 381 else: 382 EdkLogger.warn("Trim", "Invalid include line in include list file.", IncludePathFile, LineNum) 383 except: 384 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=IncludePathFile) 385 386 Lines = DoInclude(Source, '', IncludePathList) 387 388 # 389 # Undef MIN and MAX to avoid collision in ASL source code 390 # 391 Lines.insert(0, "#undef MIN\n#undef MAX\n") 392 393 # save all lines trimmed 394 try: 395 f = open (Target,'w') 396 except: 397 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target) 398 399 f.writelines(Lines) 400 f.close() 401 402## Trim EDK source code file(s) 403# 404# 405# @param Source File or directory to be trimmed 406# @param Target File or directory to store the trimmed content 407# 408def TrimEdkSources(Source, Target): 409 if os.path.isdir(Source): 410 for CurrentDir, Dirs, Files in os.walk(Source): 411 if '.svn' in Dirs: 412 Dirs.remove('.svn') 413 elif "CVS" in Dirs: 414 Dirs.remove("CVS") 415 416 for FileName in Files: 417 Dummy, Ext = os.path.splitext(FileName) 418 if Ext.upper() not in ['.C', '.H']: continue 419 if Target == None or Target == '': 420 TrimEdkSourceCode( 421 os.path.join(CurrentDir, FileName), 422 os.path.join(CurrentDir, FileName) 423 ) 424 else: 425 TrimEdkSourceCode( 426 os.path.join(CurrentDir, FileName), 427 os.path.join(Target, CurrentDir[len(Source)+1:], FileName) 428 ) 429 else: 430 TrimEdkSourceCode(Source, Target) 431 432## Trim one EDK source code file 433# 434# Do following replacement: 435# 436# (**PeiServices\).PciCfg = <*>; 437# => { 438# STATIC EFI_PEI_PPI_DESCRIPTOR gEcpPeiPciCfgPpiList = { 439# (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), 440# &gEcpPeiPciCfgPpiGuid, 441# <*> 442# }; 443# (**PeiServices).InstallPpi (PeiServices, &gEcpPeiPciCfgPpiList); 444# 445# <*>Modify(<*>) 446# => PeiLibPciCfgModify (<*>) 447# 448# gRT->ReportStatusCode (<*>) 449# => EfiLibReportStatusCode (<*>) 450# 451# #include <LoadFile\.h> 452# => #include <FvLoadFile.h> 453# 454# CreateEvent (EFI_EVENT_SIGNAL_READY_TO_BOOT, <*>) 455# => EfiCreateEventReadyToBoot (<*>) 456# 457# CreateEvent (EFI_EVENT_SIGNAL_LEGACY_BOOT, <*>) 458# => EfiCreateEventLegacyBoot (<*>) 459# 460# @param Source File to be trimmed 461# @param Target File to store the trimmed content 462# 463def TrimEdkSourceCode(Source, Target): 464 EdkLogger.verbose("\t%s -> %s" % (Source, Target)) 465 CreateDirectory(os.path.dirname(Target)) 466 467 try: 468 f = open (Source,'rb') 469 except: 470 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Source) 471 # read whole file 472 Lines = f.read() 473 f.close() 474 475 NewLines = None 476 for Re,Repl in gImportCodePatterns: 477 if NewLines == None: 478 NewLines = Re.sub(Repl, Lines) 479 else: 480 NewLines = Re.sub(Repl, NewLines) 481 482 # save all lines if trimmed 483 if Source == Target and NewLines == Lines: 484 return 485 486 try: 487 f = open (Target,'wb') 488 except: 489 EdkLogger.error("Trim", FILE_OPEN_FAILURE, ExtraData=Target) 490 f.write(NewLines) 491 f.close() 492 493 494## Parse command line options 495# 496# Using standard Python module optparse to parse command line option of this tool. 497# 498# @retval Options A optparse.Values object containing the parsed options 499# @retval InputFile Path of file to be trimmed 500# 501def Options(): 502 OptionList = [ 503 make_option("-s", "--source-code", dest="FileType", const="SourceCode", action="store_const", 504 help="The input file is preprocessed source code, including C or assembly code"), 505 make_option("-r", "--vfr-file", dest="FileType", const="Vfr", action="store_const", 506 help="The input file is preprocessed VFR file"), 507 make_option("-a", "--asl-file", dest="FileType", const="Asl", action="store_const", 508 help="The input file is ASL file"), 509 make_option("-8", "--Edk-source-code", dest="FileType", const="EdkSourceCode", action="store_const", 510 help="The input file is source code for Edk to be trimmed for ECP"), 511 512 make_option("-c", "--convert-hex", dest="ConvertHex", action="store_true", 513 help="Convert standard hex format (0xabcd) to MASM format (abcdh)"), 514 515 make_option("-l", "--trim-long", dest="TrimLong", action="store_true", 516 help="Remove postfix of long number"), 517 make_option("-i", "--include-path-file", dest="IncludePathFile", 518 help="The input file is include path list to search for ASL include file"), 519 make_option("-o", "--output", dest="OutputFile", 520 help="File to store the trimmed content"), 521 make_option("-v", "--verbose", dest="LogLevel", action="store_const", const=EdkLogger.VERBOSE, 522 help="Run verbosely"), 523 make_option("-d", "--debug", dest="LogLevel", type="int", 524 help="Run with debug information"), 525 make_option("-q", "--quiet", dest="LogLevel", action="store_const", const=EdkLogger.QUIET, 526 help="Run quietly"), 527 make_option("-?", action="help", help="show this help message and exit"), 528 ] 529 530 # use clearer usage to override default usage message 531 UsageString = "%prog [-s|-r|-a] [-c] [-v|-d <debug_level>|-q] [-i <include_path_file>] [-o <output_file>] <input_file>" 532 533 Parser = OptionParser(description=__copyright__, version=__version__, option_list=OptionList, usage=UsageString) 534 Parser.set_defaults(FileType="Vfr") 535 Parser.set_defaults(ConvertHex=False) 536 Parser.set_defaults(LogLevel=EdkLogger.INFO) 537 538 Options, Args = Parser.parse_args() 539 540 # error check 541 if len(Args) == 0: 542 EdkLogger.error("Trim", OPTION_MISSING, ExtraData=Parser.get_usage()) 543 if len(Args) > 1: 544 EdkLogger.error("Trim", OPTION_NOT_SUPPORTED, ExtraData=Parser.get_usage()) 545 546 InputFile = Args[0] 547 return Options, InputFile 548 549## Entrance method 550# 551# This method mainly dispatch specific methods per the command line options. 552# If no error found, return zero value so the caller of this tool can know 553# if it's executed successfully or not. 554# 555# @retval 0 Tool was successful 556# @retval 1 Tool failed 557# 558def Main(): 559 try: 560 EdkLogger.Initialize() 561 CommandOptions, InputFile = Options() 562 if CommandOptions.LogLevel < EdkLogger.DEBUG_9: 563 EdkLogger.SetLevel(CommandOptions.LogLevel + 1) 564 else: 565 EdkLogger.SetLevel(CommandOptions.LogLevel) 566 except FatalError, X: 567 return 1 568 569 try: 570 if CommandOptions.FileType == "Vfr": 571 if CommandOptions.OutputFile == None: 572 CommandOptions.OutputFile = os.path.splitext(InputFile)[0] + '.iii' 573 TrimPreprocessedVfr(InputFile, CommandOptions.OutputFile) 574 elif CommandOptions.FileType == "Asl": 575 if CommandOptions.OutputFile == None: 576 CommandOptions.OutputFile = os.path.splitext(InputFile)[0] + '.iii' 577 TrimAslFile(InputFile, CommandOptions.OutputFile, CommandOptions.IncludePathFile) 578 elif CommandOptions.FileType == "EdkSourceCode": 579 TrimEdkSources(InputFile, CommandOptions.OutputFile) 580 else : 581 if CommandOptions.OutputFile == None: 582 CommandOptions.OutputFile = os.path.splitext(InputFile)[0] + '.iii' 583 TrimPreprocessedFile(InputFile, CommandOptions.OutputFile, CommandOptions.ConvertHex, CommandOptions.TrimLong) 584 except FatalError, X: 585 import platform 586 import traceback 587 if CommandOptions != None and CommandOptions.LogLevel <= EdkLogger.DEBUG_9: 588 EdkLogger.quiet("(Python %s on %s) " % (platform.python_version(), sys.platform) + traceback.format_exc()) 589 return 1 590 except: 591 import traceback 592 import platform 593 EdkLogger.error( 594 "\nTrim", 595 CODE_ERROR, 596 "Unknown fatal error when trimming [%s]" % InputFile, 597 ExtraData="\n(Please send email to edk2-devel@lists.sourceforge.net for help, attaching following call stack trace!)\n", 598 RaiseError=False 599 ) 600 EdkLogger.quiet("(Python %s on %s) " % (platform.python_version(), sys.platform) + traceback.format_exc()) 601 return 1 602 603 return 0 604 605if __name__ == '__main__': 606 r = Main() 607 ## 0-127 is a safe return range, and 1 is a standard default error 608 if r < 0 or r > 127: r = 1 609 sys.exit(r) 610 611