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'''
16CommentParsing
17'''
18
19##
20# Import Modules
21#
22import re
23
24from Library.String import GetSplitValueList
25from Library.String import CleanString2
26from Library.DataType import HEADER_COMMENT_NOT_STARTED
27from Library.DataType import TAB_COMMENT_SPLIT
28from Library.DataType import HEADER_COMMENT_LICENSE
29from Library.DataType import HEADER_COMMENT_ABSTRACT
30from Library.DataType import HEADER_COMMENT_COPYRIGHT
31from Library.DataType import HEADER_COMMENT_DESCRIPTION
32from Library.DataType import TAB_SPACE_SPLIT
33from Library.DataType import TAB_COMMA_SPLIT
34from Library.DataType import SUP_MODULE_LIST
35from Library.DataType import TAB_VALUE_SPLIT
36from Library.DataType import TAB_PCD_VALIDRANGE
37from Library.DataType import TAB_PCD_VALIDLIST
38from Library.DataType import TAB_PCD_EXPRESSION
39from Library.DataType import TAB_PCD_PROMPT
40from Library.DataType import TAB_CAPHEX_START
41from Library.DataType import TAB_HEX_START
42from Library.DataType import PCD_ERR_CODE_MAX_SIZE
43from Library.ExpressionValidate import IsValidRangeExpr
44from Library.ExpressionValidate import IsValidListExpr
45from Library.ExpressionValidate import IsValidLogicalExpr
46from Object.POM.CommonObject import TextObject
47from Object.POM.CommonObject import PcdErrorObject
48import Logger.Log as Logger
49from Logger.ToolError import FORMAT_INVALID
50from Logger.ToolError import FORMAT_NOT_SUPPORTED
51from 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#
61def 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#
160def _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#
176def 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#
203def 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#
235def 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#
381def 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#
422def _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#
435def _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#
447def _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#
460def 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#
471def _ValidateCopyright(Line):
472    if Line:
473        pass
474    Result = True
475    ErrMsg = ''
476
477    return Result, ErrMsg
478
479def 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#
496def 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