]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/Python/UPT/Library/CommentParsing.py
250ba2dd5e8dc94746d7776c469ac4f5d0705bbb
[mirror_edk2.git] / BaseTools / Source / Python / UPT / Library / CommentParsing.py
1 ## @file
2 # This file is used to define comment parsing interface
3 #
4 # Copyright (c) 2011 - 2018, 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.StringUtils import GetSplitValueList
25 from Library.StringUtils 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 range(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 = int(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 ErrorCode = '0x%x' % ErrorCode
217 return ErrorCode
218 except ValueError as XStr:
219 if XStr:
220 pass
221 Logger.Error('Parser',
222 FORMAT_NOT_SUPPORTED,
223 "The format %s of ErrorCode is not valid, should be UNIT32 type or long type" % Value,
224 File = ContainerFile,
225 Line = LineNum)
226
227 ## ParseDecPcdGenericComment
228 #
229 # @param GenericComment: Generic comment list, element of (CommentLine,
230 # LineNum)
231 # @param ContainerFile: Input value for filename of Dec file
232 #
233 def ParseDecPcdGenericComment (GenericComment, ContainerFile, TokenSpaceGuidCName, CName, MacroReplaceDict):
234 HelpStr = ''
235 PromptStr = ''
236 PcdErr = None
237 PcdErrList = []
238 ValidValueNum = 0
239 ValidRangeNum = 0
240 ExpressionNum = 0
241
242 for (CommentLine, LineNum) in GenericComment:
243 Comment = CleanString2(CommentLine)[1]
244 #
245 # To replace Macro
246 #
247 MACRO_PATTERN = '[\t\s]*\$\([A-Z][_A-Z0-9]*\)'
248 MatchedStrs = re.findall(MACRO_PATTERN, Comment)
249 for MatchedStr in MatchedStrs:
250 if MatchedStr:
251 Macro = MatchedStr.strip().lstrip('$(').rstrip(')').strip()
252 if Macro in MacroReplaceDict:
253 Comment = Comment.replace(MatchedStr, MacroReplaceDict[Macro])
254 if Comment.startswith(TAB_PCD_VALIDRANGE):
255 if ValidValueNum > 0 or ExpressionNum > 0:
256 Logger.Error('Parser',
257 FORMAT_NOT_SUPPORTED,
258 ST.WRN_MULTI_PCD_RANGES,
259 File = ContainerFile,
260 Line = LineNum)
261 else:
262 PcdErr = PcdErrorObject()
263 PcdErr.SetTokenSpaceGuidCName(TokenSpaceGuidCName)
264 PcdErr.SetCName(CName)
265 PcdErr.SetFileLine(Comment)
266 PcdErr.SetLineNum(LineNum)
267 ValidRangeNum += 1
268 ValidRange = Comment.replace(TAB_PCD_VALIDRANGE, "", 1).strip()
269 Valid, Cause = _CheckRangeExpression(ValidRange)
270 if Valid:
271 ValueList = ValidRange.split(TAB_VALUE_SPLIT)
272 if len(ValueList) > 1:
273 PcdErr.SetValidValueRange((TAB_VALUE_SPLIT.join(ValueList[1:])).strip())
274 PcdErr.SetErrorNumber(ParsePcdErrorCode(ValueList[0], ContainerFile, LineNum))
275 else:
276 PcdErr.SetValidValueRange(ValidRange)
277 PcdErrList.append(PcdErr)
278 else:
279 Logger.Error("Parser",
280 FORMAT_NOT_SUPPORTED,
281 Cause,
282 ContainerFile,
283 LineNum)
284 elif Comment.startswith(TAB_PCD_VALIDLIST):
285 if ValidRangeNum > 0 or ExpressionNum > 0:
286 Logger.Error('Parser',
287 FORMAT_NOT_SUPPORTED,
288 ST.WRN_MULTI_PCD_RANGES,
289 File = ContainerFile,
290 Line = LineNum)
291 elif ValidValueNum > 0:
292 Logger.Error('Parser',
293 FORMAT_NOT_SUPPORTED,
294 ST.WRN_MULTI_PCD_VALIDVALUE,
295 File = ContainerFile,
296 Line = LineNum)
297 else:
298 PcdErr = PcdErrorObject()
299 PcdErr.SetTokenSpaceGuidCName(TokenSpaceGuidCName)
300 PcdErr.SetCName(CName)
301 PcdErr.SetFileLine(Comment)
302 PcdErr.SetLineNum(LineNum)
303 ValidValueNum += 1
304 ValidValueExpr = Comment.replace(TAB_PCD_VALIDLIST, "", 1).strip()
305 Valid, Cause = _CheckListExpression(ValidValueExpr)
306 if Valid:
307 ValidValue = Comment.replace(TAB_PCD_VALIDLIST, "", 1).replace(TAB_COMMA_SPLIT, TAB_SPACE_SPLIT)
308 ValueList = ValidValue.split(TAB_VALUE_SPLIT)
309 if len(ValueList) > 1:
310 PcdErr.SetValidValue((TAB_VALUE_SPLIT.join(ValueList[1:])).strip())
311 PcdErr.SetErrorNumber(ParsePcdErrorCode(ValueList[0], ContainerFile, LineNum))
312 else:
313 PcdErr.SetValidValue(ValidValue)
314 PcdErrList.append(PcdErr)
315 else:
316 Logger.Error("Parser",
317 FORMAT_NOT_SUPPORTED,
318 Cause,
319 ContainerFile,
320 LineNum)
321 elif Comment.startswith(TAB_PCD_EXPRESSION):
322 if ValidRangeNum > 0 or ValidValueNum > 0:
323 Logger.Error('Parser',
324 FORMAT_NOT_SUPPORTED,
325 ST.WRN_MULTI_PCD_RANGES,
326 File = ContainerFile,
327 Line = LineNum)
328 else:
329 PcdErr = PcdErrorObject()
330 PcdErr.SetTokenSpaceGuidCName(TokenSpaceGuidCName)
331 PcdErr.SetCName(CName)
332 PcdErr.SetFileLine(Comment)
333 PcdErr.SetLineNum(LineNum)
334 ExpressionNum += 1
335 Expression = Comment.replace(TAB_PCD_EXPRESSION, "", 1).strip()
336 Valid, Cause = _CheckExpression(Expression)
337 if Valid:
338 ValueList = Expression.split(TAB_VALUE_SPLIT)
339 if len(ValueList) > 1:
340 PcdErr.SetExpression((TAB_VALUE_SPLIT.join(ValueList[1:])).strip())
341 PcdErr.SetErrorNumber(ParsePcdErrorCode(ValueList[0], ContainerFile, LineNum))
342 else:
343 PcdErr.SetExpression(Expression)
344 PcdErrList.append(PcdErr)
345 else:
346 Logger.Error("Parser",
347 FORMAT_NOT_SUPPORTED,
348 Cause,
349 ContainerFile,
350 LineNum)
351 elif Comment.startswith(TAB_PCD_PROMPT):
352 if PromptStr:
353 Logger.Error('Parser',
354 FORMAT_NOT_SUPPORTED,
355 ST.WRN_MULTI_PCD_PROMPT,
356 File = ContainerFile,
357 Line = LineNum)
358 PromptStr = Comment.replace(TAB_PCD_PROMPT, "", 1).strip()
359 else:
360 if Comment:
361 HelpStr += Comment + '\n'
362
363 #
364 # remove the last EOL if the comment is of format 'FOO\n'
365 #
366 if HelpStr.endswith('\n'):
367 if HelpStr != '\n' and not HelpStr.endswith('\n\n'):
368 HelpStr = HelpStr[:-1]
369
370 return HelpStr, PcdErrList, PromptStr
371
372 ## ParseDecPcdTailComment
373 #
374 # @param TailCommentList: Tail comment list of Pcd, item of format (Comment, LineNum)
375 # @param ContainerFile: Input value for filename of Dec file
376 # @retVal SupModuleList: The supported module type list detected
377 # @retVal HelpStr: The generic help text string detected
378 #
379 def ParseDecPcdTailComment (TailCommentList, ContainerFile):
380 assert(len(TailCommentList) == 1)
381 TailComment = TailCommentList[0][0]
382 LineNum = TailCommentList[0][1]
383
384 Comment = TailComment.lstrip(" #")
385
386 ReFindFirstWordRe = re.compile(r"""^([^ #]*)""", re.DOTALL)
387
388 #
389 # get first word and compare with SUP_MODULE_LIST
390 #
391 MatchObject = ReFindFirstWordRe.match(Comment)
392 if not (MatchObject and MatchObject.group(1) in SUP_MODULE_LIST):
393 return None, Comment
394
395 #
396 # parse line, it must have supported module type specified
397 #
398 if Comment.find(TAB_COMMENT_SPLIT) == -1:
399 Comment += TAB_COMMENT_SPLIT
400 SupMode, HelpStr = GetSplitValueList(Comment, TAB_COMMENT_SPLIT, 1)
401 SupModuleList = []
402 for Mod in GetSplitValueList(SupMode, TAB_SPACE_SPLIT):
403 if not Mod:
404 continue
405 elif Mod not in SUP_MODULE_LIST:
406 Logger.Error("UPT",
407 FORMAT_INVALID,
408 ST.WRN_INVALID_MODULE_TYPE%Mod,
409 ContainerFile,
410 LineNum)
411 else:
412 SupModuleList.append(Mod)
413
414 return SupModuleList, HelpStr
415
416 ## _CheckListExpression
417 #
418 # @param Expression: Pcd value list expression
419 #
420 def _CheckListExpression(Expression):
421 ListExpr = ''
422 if TAB_VALUE_SPLIT in Expression:
423 ListExpr = Expression[Expression.find(TAB_VALUE_SPLIT)+1:]
424 else:
425 ListExpr = Expression
426
427 return IsValidListExpr(ListExpr)
428
429 ## _CheckExpression
430 #
431 # @param Expression: Pcd value expression
432 #
433 def _CheckExpression(Expression):
434 Expr = ''
435 if TAB_VALUE_SPLIT in Expression:
436 Expr = Expression[Expression.find(TAB_VALUE_SPLIT)+1:]
437 else:
438 Expr = Expression
439 return IsValidLogicalExpr(Expr, True)
440
441 ## _CheckRangeExpression
442 #
443 # @param Expression: Pcd range expression
444 #
445 def _CheckRangeExpression(Expression):
446 RangeExpr = ''
447 if TAB_VALUE_SPLIT in Expression:
448 RangeExpr = Expression[Expression.find(TAB_VALUE_SPLIT)+1:]
449 else:
450 RangeExpr = Expression
451
452 return IsValidRangeExpr(RangeExpr)
453
454 ## ValidateCopyright
455 #
456 #
457 #
458 def ValidateCopyright(Result, ErrType, FileName, LineNo, ErrMsg):
459 if not Result:
460 Logger.Warn("\nUPT", ErrType, FileName, LineNo, ErrMsg)
461
462 ## _ValidateCopyright
463 #
464 # @param Line: Line that contains copyright information, # stripped
465 #
466 # @retval Result: True if line is conformed to Spec format, False else
467 # @retval ErrMsg: the detailed error description
468 #
469 def _ValidateCopyright(Line):
470 if Line:
471 pass
472 Result = True
473 ErrMsg = ''
474
475 return Result, ErrMsg
476
477 def GenerateTokenList (Comment):
478 #
479 # Tokenize Comment using '#' and ' ' as token separators
480 #
481 ReplacedComment = None
482 while Comment != ReplacedComment:
483 ReplacedComment = Comment
484 Comment = Comment.replace('##', '#').replace(' ', ' ').replace(' ', '#').strip('# ')
485 return Comment.split('#')
486
487
488 #
489 # Comment - Comment to parse
490 # TypeTokens - A dictionary of type token synonyms
491 # RemoveTokens - A list of tokens to remove from help text
492 # ParseVariable - True for parsing [Guids]. Otherwise False
493 #
494 def ParseComment (Comment, UsageTokens, TypeTokens, RemoveTokens, ParseVariable):
495 #
496 # Initialize return values
497 #
498 Usage = None
499 Type = None
500 String = None
501
502 Comment = Comment[0]
503
504 NumTokens = 2
505 if ParseVariable:
506 #
507 # Remove white space around first instance of ':' from Comment if 'Variable'
508 # is in front of ':' and Variable is the 1st or 2nd token in Comment.
509 #
510 List = Comment.split(':', 1)
511 if len(List) > 1:
512 SubList = GenerateTokenList (List[0].strip())
513 if len(SubList) in [1, 2] and SubList[-1] == 'Variable':
514 if List[1].strip().find('L"') == 0:
515 Comment = List[0].strip() + ':' + List[1].strip()
516
517 #
518 # Remove first instance of L"<VariableName> from Comment and put into String
519 # if and only if L"<VariableName>" is the 1st token, the 2nd token. Or
520 # L"<VariableName>" is the third token immediately following 'Variable:'.
521 #
522 End = -1
523 Start = Comment.find('Variable:L"')
524 if Start >= 0:
525 String = Comment[Start + 9:]
526 End = String[2:].find('"')
527 else:
528 Start = Comment.find('L"')
529 if Start >= 0:
530 String = Comment[Start:]
531 End = String[2:].find('"')
532 if End >= 0:
533 SubList = GenerateTokenList (Comment[:Start])
534 if len(SubList) < 2:
535 Comment = Comment[:Start] + String[End + 3:]
536 String = String[:End + 3]
537 Type = 'Variable'
538 NumTokens = 1
539
540 #
541 # Initialize HelpText to Comment.
542 # Content will be remove from HelpText as matching tokens are found
543 #
544 HelpText = Comment
545
546 #
547 # Tokenize Comment using '#' and ' ' as token separators
548 #
549 List = GenerateTokenList (Comment)
550
551 #
552 # Search first two tokens for Usage and Type and remove any matching tokens
553 # from HelpText
554 #
555 for Token in List[0:NumTokens]:
556 if Usage is None and Token in UsageTokens:
557 Usage = UsageTokens[Token]
558 HelpText = HelpText.replace(Token, '')
559 if Usage is not None or not ParseVariable:
560 for Token in List[0:NumTokens]:
561 if Type is None and Token in TypeTokens:
562 Type = TypeTokens[Token]
563 HelpText = HelpText.replace(Token, '')
564 if Usage is not None:
565 for Token in List[0:NumTokens]:
566 if Token in RemoveTokens:
567 HelpText = HelpText.replace(Token, '')
568
569 #
570 # If no Usage token is present and set Usage to UNDEFINED
571 #
572 if Usage is None:
573 Usage = 'UNDEFINED'
574
575 #
576 # If no Type token is present and set Type to UNDEFINED
577 #
578 if Type is None:
579 Type = 'UNDEFINED'
580
581 #
582 # If Type is not 'Variable:', then set String to None
583 #
584 if Type != 'Variable':
585 String = None
586
587 #
588 # Strip ' ' and '#' from the beginning of HelpText
589 # If HelpText is an empty string after all parsing is
590 # complete then set HelpText to None
591 #
592 HelpText = HelpText.lstrip('# ')
593 if HelpText == '':
594 HelpText = None
595
596 #
597 # Return parsing results
598 #
599 return Usage, Type, String, HelpText