1 ## @file 2 # This file is used to define comment parsing interface 3 # 4 # Copyright (c) 2011 - 2014, Intel Corporation. All rights reserved.<BR> 5 # 6 # This program and the accompanying materials are licensed and made available 7 # under the terms and conditions of the BSD License which accompanies this 8 # distribution. The full text of the license may be found at 9 # http://opensource.org/licenses/bsd-license.php 10 # 11 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 13 # 14 15 ''' 16 CommentParsing 17 ''' 18 19 ## 20 # Import Modules 21 # 22 import re 23 24 from Library.String import GetSplitValueList 25 from Library.String import CleanString2 26 from Library.DataType import HEADER_COMMENT_NOT_STARTED 27 from Library.DataType import TAB_COMMENT_SPLIT 28 from Library.DataType import HEADER_COMMENT_LICENSE 29 from Library.DataType import HEADER_COMMENT_ABSTRACT 30 from Library.DataType import HEADER_COMMENT_COPYRIGHT 31 from Library.DataType import HEADER_COMMENT_DESCRIPTION 32 from Library.DataType import TAB_SPACE_SPLIT 33 from Library.DataType import TAB_COMMA_SPLIT 34 from Library.DataType import SUP_MODULE_LIST 35 from Library.DataType import TAB_VALUE_SPLIT 36 from Library.DataType import TAB_PCD_VALIDRANGE 37 from Library.DataType import TAB_PCD_VALIDLIST 38 from Library.DataType import TAB_PCD_EXPRESSION 39 from Library.DataType import TAB_PCD_PROMPT 40 from Library.DataType import TAB_CAPHEX_START 41 from Library.DataType import TAB_HEX_START 42 from Library.DataType import PCD_ERR_CODE_MAX_SIZE 43 from Library.ExpressionValidate import IsValidRangeExpr 44 from Library.ExpressionValidate import IsValidListExpr 45 from Library.ExpressionValidate import IsValidLogicalExpr 46 from Object.POM.CommonObject import TextObject 47 from Object.POM.CommonObject import PcdErrorObject 48 import Logger.Log as Logger 49 from Logger.ToolError import FORMAT_INVALID 50 from Logger.ToolError import FORMAT_NOT_SUPPORTED 51 from Logger import StringTable as ST 52 53 ## ParseHeaderCommentSection 54 # 55 # Parse Header comment section lines, extract Abstract, Description, Copyright 56 # , License lines 57 # 58 # @param CommentList: List of (Comment, LineNumber) 59 # @param FileName: FileName of the comment 60 # 61 def ParseHeaderCommentSection(CommentList, FileName = None, IsBinaryHeader = False): 62 Abstract = '' 63 Description = '' 64 Copyright = '' 65 License = '' 66 EndOfLine = "\n" 67 if IsBinaryHeader: 68 STR_HEADER_COMMENT_START = "@BinaryHeader" 69 else: 70 STR_HEADER_COMMENT_START = "@file" 71 HeaderCommentStage = HEADER_COMMENT_NOT_STARTED 72 73 # 74 # first find the last copyright line 75 # 76 Last = 0 77 for Index in xrange(len(CommentList)-1, 0, -1): 78 Line = CommentList[Index][0] 79 if _IsCopyrightLine(Line): 80 Last = Index 81 break 82 83 for Item in CommentList: 84 Line = Item[0] 85 LineNo = Item[1] 86 87 if not Line.startswith(TAB_COMMENT_SPLIT) and Line: 88 Logger.Error("\nUPT", FORMAT_INVALID, ST.ERR_INVALID_COMMENT_FORMAT, FileName, Item[1]) 89 Comment = CleanString2(Line)[1] 90 Comment = Comment.strip() 91 # 92 # if there are blank lines between License or Description, keep them as they would be 93 # indication of different block; or in the position that Abstract should be, also keep it 94 # as it indicates that no abstract 95 # 96 if not Comment and HeaderCommentStage not in [HEADER_COMMENT_LICENSE, \ 97 HEADER_COMMENT_DESCRIPTION, HEADER_COMMENT_ABSTRACT]: 98 continue 99 100 if HeaderCommentStage == HEADER_COMMENT_NOT_STARTED: 101 if Comment.startswith(STR_HEADER_COMMENT_START): 102 HeaderCommentStage = HEADER_COMMENT_ABSTRACT 103 else: 104 License += Comment + EndOfLine 105 else: 106 if HeaderCommentStage == HEADER_COMMENT_ABSTRACT: 107 # 108 # in case there is no abstract and description 109 # 110 if not Comment: 111 HeaderCommentStage = HEADER_COMMENT_DESCRIPTION 112 elif _IsCopyrightLine(Comment): 113 Result, ErrMsg = _ValidateCopyright(Comment) 114 ValidateCopyright(Result, ST.WRN_INVALID_COPYRIGHT, FileName, LineNo, ErrMsg) 115 Copyright += Comment + EndOfLine 116 HeaderCommentStage = HEADER_COMMENT_COPYRIGHT 117 else: 118 Abstract += Comment + EndOfLine 119 HeaderCommentStage = HEADER_COMMENT_DESCRIPTION 120 elif HeaderCommentStage == HEADER_COMMENT_DESCRIPTION: 121 # 122 # in case there is no description 123 # 124 if _IsCopyrightLine(Comment): 125 Result, ErrMsg = _ValidateCopyright(Comment) 126 ValidateCopyright(Result, ST.WRN_INVALID_COPYRIGHT, FileName, LineNo, ErrMsg) 127 Copyright += Comment + EndOfLine 128 HeaderCommentStage = HEADER_COMMENT_COPYRIGHT 129 else: 130 Description += Comment + EndOfLine 131 elif HeaderCommentStage == HEADER_COMMENT_COPYRIGHT: 132 if _IsCopyrightLine(Comment): 133 Result, ErrMsg = _ValidateCopyright(Comment) 134 ValidateCopyright(Result, ST.WRN_INVALID_COPYRIGHT, FileName, LineNo, ErrMsg) 135 Copyright += Comment + EndOfLine 136 else: 137 # 138 # Contents after copyright line are license, those non-copyright lines in between 139 # copyright line will be discarded 140 # 141 if LineNo > Last: 142 if License: 143 License += EndOfLine 144 License += Comment + EndOfLine 145 HeaderCommentStage = HEADER_COMMENT_LICENSE 146 else: 147 if not Comment and not License: 148 continue 149 License += Comment + EndOfLine 150 151 return Abstract.strip(), Description.strip(), Copyright.strip(), License.strip() 152 153 ## _IsCopyrightLine 154 # check whether current line is copyright line, the criteria is whether there is case insensitive keyword "Copyright" 155 # followed by zero or more white space characters followed by a "(" character 156 # 157 # @param LineContent: the line need to be checked 158 # @return: True if current line is copyright line, False else 159 # 160 def _IsCopyrightLine (LineContent): 161 LineContent = LineContent.upper() 162 Result = False 163 164 ReIsCopyrightRe = re.compile(r"""(^|\s)COPYRIGHT *\(""", re.DOTALL) 165 if ReIsCopyrightRe.search(LineContent): 166 Result = True 167 168 return Result 169 170 ## ParseGenericComment 171 # 172 # @param GenericComment: Generic comment list, element of 173 # (CommentLine, LineNum) 174 # @param ContainerFile: Input value for filename of Dec file 175 # 176 def ParseGenericComment (GenericComment, ContainerFile=None, SkipTag=None): 177 if ContainerFile: 178 pass 179 HelpTxt = None 180 HelpStr = '' 181 182 for Item in GenericComment: 183 CommentLine = Item[0] 184 Comment = CleanString2(CommentLine)[1] 185 if SkipTag is not None and Comment.startswith(SkipTag): 186 Comment = Comment.replace(SkipTag, '', 1) 187 HelpStr += Comment + '\n' 188 189 if HelpStr: 190 HelpTxt = TextObject() 191 if HelpStr.endswith('\n') and not HelpStr.endswith('\n\n') and HelpStr != '\n': 192 HelpStr = HelpStr[:-1] 193 HelpTxt.SetString(HelpStr) 194 195 return HelpTxt 196 197 ## ParsePcdErrorCode 198 # 199 # @param Value: original ErrorCode value 200 # @param ContainerFile: Input value for filename of Dec file 201 # @param LineNum: Line Num 202 # 203 def ParsePcdErrorCode (Value = None, ContainerFile = None, LineNum = None): 204 try: 205 if Value.strip().startswith((TAB_HEX_START, TAB_CAPHEX_START)): 206 Base = 16 207 else: 208 Base = 10 209 ErrorCode = long(Value, Base) 210 if ErrorCode > PCD_ERR_CODE_MAX_SIZE or ErrorCode < 0: 211 Logger.Error('Parser', 212 FORMAT_NOT_SUPPORTED, 213 "The format %s of ErrorCode is not valid, should be UNIT32 type or long type" % Value, 214 File = ContainerFile, 215 Line = LineNum) 216 # 217 # To delete the tailing 'L' 218 # 219 return hex(ErrorCode)[:-1] 220 except ValueError, XStr: 221 if XStr: 222 pass 223 Logger.Error('Parser', 224 FORMAT_NOT_SUPPORTED, 225 "The format %s of ErrorCode is not valid, should be UNIT32 type or long type" % Value, 226 File = ContainerFile, 227 Line = LineNum) 228 229 ## ParseDecPcdGenericComment 230 # 231 # @param GenericComment: Generic comment list, element of (CommentLine, 232 # LineNum) 233 # @param ContainerFile: Input value for filename of Dec file 234 # 235 def ParseDecPcdGenericComment (GenericComment, ContainerFile, TokenSpaceGuidCName, CName, MacroReplaceDict): 236 HelpStr = '' 237 PromptStr = '' 238 PcdErr = None 239 PcdErrList = [] 240 ValidValueNum = 0 241 ValidRangeNum = 0 242 ExpressionNum = 0 243 244 for (CommentLine, LineNum) in GenericComment: 245 Comment = CleanString2(CommentLine)[1] 246 # 247 # To replace Macro 248 # 249 MACRO_PATTERN = '[\t\s]*\$\([A-Z][_A-Z0-9]*\)' 250 MatchedStrs = re.findall(MACRO_PATTERN, Comment) 251 for MatchedStr in MatchedStrs: 252 if MatchedStr: 253 Macro = MatchedStr.strip().lstrip('$(').rstrip(')').strip() 254 if Macro in MacroReplaceDict: 255 Comment = Comment.replace(MatchedStr, MacroReplaceDict[Macro]) 256 if Comment.startswith(TAB_PCD_VALIDRANGE): 257 if ValidValueNum > 0 or ExpressionNum > 0: 258 Logger.Error('Parser', 259 FORMAT_NOT_SUPPORTED, 260 ST.WRN_MULTI_PCD_RANGES, 261 File = ContainerFile, 262 Line = LineNum) 263 else: 264 PcdErr = PcdErrorObject() 265 PcdErr.SetTokenSpaceGuidCName(TokenSpaceGuidCName) 266 PcdErr.SetCName(CName) 267 PcdErr.SetFileLine(Comment) 268 PcdErr.SetLineNum(LineNum) 269 ValidRangeNum += 1 270 ValidRange = Comment.replace(TAB_PCD_VALIDRANGE, "", 1).strip() 271 Valid, Cause = _CheckRangeExpression(ValidRange) 272 if Valid: 273 ValueList = ValidRange.split(TAB_VALUE_SPLIT) 274 if len(ValueList) > 1: 275 PcdErr.SetValidValueRange((TAB_VALUE_SPLIT.join(ValueList[1:])).strip()) 276 PcdErr.SetErrorNumber(ParsePcdErrorCode(ValueList[0], ContainerFile, LineNum)) 277 else: 278 PcdErr.SetValidValueRange(ValidRange) 279 PcdErrList.append(PcdErr) 280 else: 281 Logger.Error("Parser", 282 FORMAT_NOT_SUPPORTED, 283 Cause, 284 ContainerFile, 285 LineNum) 286 elif Comment.startswith(TAB_PCD_VALIDLIST): 287 if ValidRangeNum > 0 or ExpressionNum > 0: 288 Logger.Error('Parser', 289 FORMAT_NOT_SUPPORTED, 290 ST.WRN_MULTI_PCD_RANGES, 291 File = ContainerFile, 292 Line = LineNum) 293 elif ValidValueNum > 0: 294 Logger.Error('Parser', 295 FORMAT_NOT_SUPPORTED, 296 ST.WRN_MULTI_PCD_VALIDVALUE, 297 File = ContainerFile, 298 Line = LineNum) 299 else: 300 PcdErr = PcdErrorObject() 301 PcdErr.SetTokenSpaceGuidCName(TokenSpaceGuidCName) 302 PcdErr.SetCName(CName) 303 PcdErr.SetFileLine(Comment) 304 PcdErr.SetLineNum(LineNum) 305 ValidValueNum += 1 306 ValidValueExpr = Comment.replace(TAB_PCD_VALIDLIST, "", 1).strip() 307 Valid, Cause = _CheckListExpression(ValidValueExpr) 308 if Valid: 309 ValidValue = Comment.replace(TAB_PCD_VALIDLIST, "", 1).replace(TAB_COMMA_SPLIT, TAB_SPACE_SPLIT) 310 ValueList = ValidValue.split(TAB_VALUE_SPLIT) 311 if len(ValueList) > 1: 312 PcdErr.SetValidValue((TAB_VALUE_SPLIT.join(ValueList[1:])).strip()) 313 PcdErr.SetErrorNumber(ParsePcdErrorCode(ValueList[0], ContainerFile, LineNum)) 314 else: 315 PcdErr.SetValidValue(ValidValue) 316 PcdErrList.append(PcdErr) 317 else: 318 Logger.Error("Parser", 319 FORMAT_NOT_SUPPORTED, 320 Cause, 321 ContainerFile, 322 LineNum) 323 elif Comment.startswith(TAB_PCD_EXPRESSION): 324 if ValidRangeNum > 0 or ValidValueNum > 0: 325 Logger.Error('Parser', 326 FORMAT_NOT_SUPPORTED, 327 ST.WRN_MULTI_PCD_RANGES, 328 File = ContainerFile, 329 Line = LineNum) 330 else: 331 PcdErr = PcdErrorObject() 332 PcdErr.SetTokenSpaceGuidCName(TokenSpaceGuidCName) 333 PcdErr.SetCName(CName) 334 PcdErr.SetFileLine(Comment) 335 PcdErr.SetLineNum(LineNum) 336 ExpressionNum += 1 337 Expression = Comment.replace(TAB_PCD_EXPRESSION, "", 1).strip() 338 Valid, Cause = _CheckExpression(Expression) 339 if Valid: 340 ValueList = Expression.split(TAB_VALUE_SPLIT) 341 if len(ValueList) > 1: 342 PcdErr.SetExpression((TAB_VALUE_SPLIT.join(ValueList[1:])).strip()) 343 PcdErr.SetErrorNumber(ParsePcdErrorCode(ValueList[0], ContainerFile, LineNum)) 344 else: 345 PcdErr.SetExpression(Expression) 346 PcdErrList.append(PcdErr) 347 else: 348 Logger.Error("Parser", 349 FORMAT_NOT_SUPPORTED, 350 Cause, 351 ContainerFile, 352 LineNum) 353 elif Comment.startswith(TAB_PCD_PROMPT): 354 if PromptStr: 355 Logger.Error('Parser', 356 FORMAT_NOT_SUPPORTED, 357 ST.WRN_MULTI_PCD_PROMPT, 358 File = ContainerFile, 359 Line = LineNum) 360 PromptStr = Comment.replace(TAB_PCD_PROMPT, "", 1).strip() 361 else: 362 if Comment: 363 HelpStr += Comment + '\n' 364 365 # 366 # remove the last EOL if the comment is of format 'FOO\n' 367 # 368 if HelpStr.endswith('\n'): 369 if HelpStr != '\n' and not HelpStr.endswith('\n\n'): 370 HelpStr = HelpStr[:-1] 371 372 return HelpStr, PcdErrList, PromptStr 373 374 ## ParseDecPcdTailComment 375 # 376 # @param TailCommentList: Tail comment list of Pcd, item of format (Comment, LineNum) 377 # @param ContainerFile: Input value for filename of Dec file 378 # @retVal SupModuleList: The supported module type list detected 379 # @retVal HelpStr: The generic help text string detected 380 # 381 def ParseDecPcdTailComment (TailCommentList, ContainerFile): 382 assert(len(TailCommentList) == 1) 383 TailComment = TailCommentList[0][0] 384 LineNum = TailCommentList[0][1] 385 386 Comment = TailComment.lstrip(" #") 387 388 ReFindFirstWordRe = re.compile(r"""^([^ #]*)""", re.DOTALL) 389 390 # 391 # get first word and compare with SUP_MODULE_LIST 392 # 393 MatchObject = ReFindFirstWordRe.match(Comment) 394 if not (MatchObject and MatchObject.group(1) in SUP_MODULE_LIST): 395 return None, Comment 396 397 # 398 # parse line, it must have supported module type specified 399 # 400 if Comment.find(TAB_COMMENT_SPLIT) == -1: 401 Comment += TAB_COMMENT_SPLIT 402 SupMode, HelpStr = GetSplitValueList(Comment, TAB_COMMENT_SPLIT, 1) 403 SupModuleList = [] 404 for Mod in GetSplitValueList(SupMode, TAB_SPACE_SPLIT): 405 if not Mod: 406 continue 407 elif Mod not in SUP_MODULE_LIST: 408 Logger.Error("UPT", 409 FORMAT_INVALID, 410 ST.WRN_INVALID_MODULE_TYPE%Mod, 411 ContainerFile, 412 LineNum) 413 else: 414 SupModuleList.append(Mod) 415 416 return SupModuleList, HelpStr 417 418 ## _CheckListExpression 419 # 420 # @param Expression: Pcd value list expression 421 # 422 def _CheckListExpression(Expression): 423 ListExpr = '' 424 if TAB_VALUE_SPLIT in Expression: 425 ListExpr = Expression[Expression.find(TAB_VALUE_SPLIT)+1:] 426 else: 427 ListExpr = Expression 428 429 return IsValidListExpr(ListExpr) 430 431 ## _CheckExpreesion 432 # 433 # @param Expression: Pcd value expression 434 # 435 def _CheckExpression(Expression): 436 Expr = '' 437 if TAB_VALUE_SPLIT in Expression: 438 Expr = Expression[Expression.find(TAB_VALUE_SPLIT)+1:] 439 else: 440 Expr = Expression 441 return IsValidLogicalExpr(Expr, True) 442 443 ## _CheckRangeExpression 444 # 445 # @param Expression: Pcd range expression 446 # 447 def _CheckRangeExpression(Expression): 448 RangeExpr = '' 449 if TAB_VALUE_SPLIT in Expression: 450 RangeExpr = Expression[Expression.find(TAB_VALUE_SPLIT)+1:] 451 else: 452 RangeExpr = Expression 453 454 return IsValidRangeExpr(RangeExpr) 455 456 ## ValidateCopyright 457 # 458 # 459 # 460 def ValidateCopyright(Result, ErrType, FileName, LineNo, ErrMsg): 461 if not Result: 462 Logger.Warn("\nUPT", ErrType, FileName, LineNo, ErrMsg) 463 464 ## _ValidateCopyright 465 # 466 # @param Line: Line that contains copyright information, # stripped 467 # 468 # @retval Result: True if line is conformed to Spec format, False else 469 # @retval ErrMsg: the detailed error description 470 # 471 def _ValidateCopyright(Line): 472 if Line: 473 pass 474 Result = True 475 ErrMsg = '' 476 477 return Result, ErrMsg 478 479 def GenerateTokenList (Comment): 480 # 481 # Tokenize Comment using '#' and ' ' as token seperators 482 # 483 RelplacedComment = None 484 while Comment != RelplacedComment: 485 RelplacedComment = Comment 486 Comment = Comment.replace('##', '#').replace(' ', ' ').replace(' ', '#').strip('# ') 487 return Comment.split('#') 488 489 490 # 491 # Comment - Comment to parse 492 # TypeTokens - A dictionary of type token synonyms 493 # RemoveTokens - A list of tokens to remove from help text 494 # ParseVariable - True for parsing [Guids]. Otherwise False 495 # 496 def ParseComment (Comment, UsageTokens, TypeTokens, RemoveTokens, ParseVariable): 497 # 498 # Initialize return values 499 # 500 Usage = None 501 Type = None 502 String = None 503 504 Comment = Comment[0] 505 506 NumTokens = 2 507 if ParseVariable: 508 # 509 # Remove white space around first instance of ':' from Comment if 'Variable' 510 # is in front of ':' and Variable is the 1st or 2nd token in Comment. 511 # 512 List = Comment.split(':', 1) 513 if len(List) > 1: 514 SubList = GenerateTokenList (List[0].strip()) 515 if len(SubList) in [1, 2] and SubList[-1] == 'Variable': 516 if List[1].strip().find('L"') == 0: 517 Comment = List[0].strip() + ':' + List[1].strip() 518 519 # 520 # Remove first instance of L"<VariableName> from Comment and put into String 521 # if and only if L"<VariableName>" is the 1st token, the 2nd token. Or 522 # L"<VariableName>" is the third token immediately following 'Variable:'. 523 # 524 End = -1 525 Start = Comment.find('Variable:L"') 526 if Start >= 0: 527 String = Comment[Start + 9:] 528 End = String[2:].find('"') 529 else: 530 Start = Comment.find('L"') 531 if Start >= 0: 532 String = Comment[Start:] 533 End = String[2:].find('"') 534 if End >= 0: 535 SubList = GenerateTokenList (Comment[:Start]) 536 if len(SubList) < 2: 537 Comment = Comment[:Start] + String[End + 3:] 538 String = String[:End + 3] 539 Type = 'Variable' 540 NumTokens = 1 541 542 # 543 # Initialze HelpText to Comment. 544 # Content will be remove from HelpText as matching tokens are found 545 # 546 HelpText = Comment 547 548 # 549 # Tokenize Comment using '#' and ' ' as token seperators 550 # 551 List = GenerateTokenList (Comment) 552 553 # 554 # Search first two tokens for Usage and Type and remove any matching tokens 555 # from HelpText 556 # 557 for Token in List[0:NumTokens]: 558 if Usage == None and Token in UsageTokens: 559 Usage = UsageTokens[Token] 560 HelpText = HelpText.replace(Token, '') 561 if Usage != None or not ParseVariable: 562 for Token in List[0:NumTokens]: 563 if Type == None and Token in TypeTokens: 564 Type = TypeTokens[Token] 565 HelpText = HelpText.replace(Token, '') 566 if Usage != None: 567 for Token in List[0:NumTokens]: 568 if Token in RemoveTokens: 569 HelpText = HelpText.replace(Token, '') 570 571 # 572 # If no Usage token is present and set Usage to UNDEFINED 573 # 574 if Usage == None: 575 Usage = 'UNDEFINED' 576 577 # 578 # If no Type token is present and set Type to UNDEFINED 579 # 580 if Type == None: 581 Type = 'UNDEFINED' 582 583 # 584 # If Type is not 'Variable:', then set String to None 585 # 586 if Type != 'Variable': 587 String = None 588 589 # 590 # Strip ' ' and '#' from the beginning of HelpText 591 # If HelpText is an empty string after all parsing is 592 # complete then set HelpText to None 593 # 594 HelpText = HelpText.lstrip('# ') 595 if HelpText == '': 596 HelpText = None 597 598 # 599 # Return parsing results 600 # 601 return Usage, Type, String, HelpText 602