1## @file
2# This file is used to define helper class and function for DEC parser
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'''
15DecParserMisc
16'''
17
18## Import modules
19#
20import os
21import Logger.Log as Logger
22from Logger.ToolError import FILE_PARSE_FAILURE
23from Logger import StringTable as ST
24from Library.DataType import TAB_COMMENT_SPLIT
25from Library.DataType import TAB_COMMENT_EDK1_SPLIT
26from Library.ExpressionValidate import IsValidBareCString
27from Library.ParserValidate import IsValidCFormatGuid
28from Library.ExpressionValidate import IsValidFeatureFlagExp
29from Library.ExpressionValidate import IsValidLogicalExpr
30from Library.ExpressionValidate import IsValidStringTest
31from Library.Misc import CheckGuidRegFormat
32
33TOOL_NAME = 'DecParser'
34VERSION_PATTERN = '[0-9]+(\.[0-9]+)?'
35CVAR_PATTERN = '[_a-zA-Z][a-zA-Z0-9_]*'
36PCD_TOKEN_PATTERN = '(0[xX]0*[a-fA-F0-9]{1,8})|([0-9]+)'
37MACRO_PATTERN = '[A-Z][_A-Z0-9]*'
38
39## FileContent
40# Class to hold DEC file information
41#
42class FileContent:
43    def __init__(self, Filename, FileContent2):
44        self.Filename = Filename
45        self.PackagePath, self.PackageFile = os.path.split(Filename)
46        self.LineIndex = 0
47        self.CurrentLine = ''
48        self.NextLine = ''
49        self.HeadComment = []
50        self.TailComment = []
51        self.CurrentScope = None
52        self.Content = FileContent2
53        self.Macros = {}
54        self.FileLines = len(FileContent2)
55
56    def GetNextLine(self):
57        if self.LineIndex >= self.FileLines:
58            return ''
59        Line = self.Content[self.LineIndex]
60        self.LineIndex += 1
61        return Line
62
63    def UndoNextLine(self):
64        if self.LineIndex > 0:
65            self.LineIndex -= 1
66
67    def ResetNext(self):
68        self.HeadComment = []
69        self.TailComment = []
70        self.NextLine = ''
71
72    def SetNext(self, Line, HeadComment, TailComment):
73        self.NextLine = Line
74        self.HeadComment = HeadComment
75        self.TailComment = TailComment
76
77    def IsEndOfFile(self):
78        return self.LineIndex >= self.FileLines
79
80
81## StripRoot
82#
83# Strip root path
84#
85# @param Root: Root must be absolute path
86# @param Path: Path to be stripped
87#
88def StripRoot(Root, Path):
89    OrigPath = Path
90    Root = os.path.normpath(Root)
91    Path = os.path.normpath(Path)
92    if not os.path.isabs(Root):
93        return OrigPath
94    if Path.startswith(Root):
95        Path = Path[len(Root):]
96        if Path and Path[0] == os.sep:
97            Path = Path[1:]
98        return Path
99    return OrigPath
100
101## CleanString
102#
103# Split comments in a string
104# Remove spaces
105#
106# @param Line:              The string to be cleaned
107# @param CommentCharacter:  Comment char, used to ignore comment content,
108#                           default is DataType.TAB_COMMENT_SPLIT
109#
110def CleanString(Line, CommentCharacter=TAB_COMMENT_SPLIT, \
111                AllowCppStyleComment=False):
112    #
113    # remove whitespace
114    #
115    Line = Line.strip()
116    #
117    # Replace EDK1's comment character
118    #
119    if AllowCppStyleComment:
120        Line = Line.replace(TAB_COMMENT_EDK1_SPLIT, CommentCharacter)
121    #
122    # separate comments and statements
123    #
124    Comment = ''
125    InQuote = False
126    for Index in range(0, len(Line)):
127        if Line[Index] == '"':
128            InQuote = not InQuote
129            continue
130        if Line[Index] == CommentCharacter and not InQuote:
131            Comment = Line[Index:].strip()
132            Line = Line[0:Index].strip()
133            break
134
135    return Line, Comment
136
137
138## IsValidNumValUint8
139#
140# Check if Token is NumValUint8: <NumValUint8> ::= {<ShortNum>} {<UINT8>} {<Expression>}
141#
142# @param Token: Token to be checked
143#
144def IsValidNumValUint8(Token):
145    Valid = True
146    Cause = ""
147    TokenValue = None
148    Token = Token.strip()
149    if Token.lower().startswith('0x'):
150        Base = 16
151    else:
152        Base = 10
153    try:
154        TokenValue = long(Token, Base)
155    except BaseException:
156        Valid, Cause = IsValidLogicalExpr(Token, True)
157        if Cause:
158            pass
159    if not Valid:
160        return False
161    if TokenValue and (TokenValue < 0 or TokenValue > 0xFF):
162        return False
163    else:
164        return True
165
166## IsValidNList
167#
168# Check if Value has the format of <NumValUint8> ["," <NumValUint8>]{0,}
169# <NumValUint8> ::= {<ShortNum>} {<UINT8>} {<Expression>}
170#
171# @param Value: Value to be checked
172#
173def IsValidNList(Value):
174    Par = ParserHelper(Value)
175    if Par.End():
176        return False
177    while not Par.End():
178        Token = Par.GetToken(',')
179        if not IsValidNumValUint8(Token):
180            return False
181        if Par.Expect(','):
182            if Par.End():
183                return False
184            continue
185        else:
186            break
187    return Par.End()
188
189## IsValidCArray
190#
191# check Array is valid
192#
193# @param Array:    The input Array
194#
195def IsValidCArray(Array):
196    Par = ParserHelper(Array)
197    if not Par.Expect('{'):
198        return False
199    if Par.End():
200        return False
201    while not Par.End():
202        Token = Par.GetToken(',}')
203        #
204        # ShortNum, UINT8, Expression
205        #
206        if not IsValidNumValUint8(Token):
207            return False
208        if Par.Expect(','):
209            if Par.End():
210                return False
211            continue
212        elif Par.Expect('}'):
213            #
214            # End of C array
215            #
216            break
217        else:
218            return False
219    return Par.End()
220
221## IsValidPcdDatum
222#
223# check PcdDatum is valid
224#
225# @param Type:    The pcd Type
226# @param Value:    The pcd Value
227#
228def IsValidPcdDatum(Type, Value):
229    if not Value:
230        return False, ST.ERR_DECPARSE_PCD_VALUE_EMPTY
231    Valid = True
232    Cause = ""
233    if Type not in ["UINT8", "UINT16", "UINT32", "UINT64", "VOID*", "BOOLEAN"]:
234        return False, ST.ERR_DECPARSE_PCD_TYPE
235    if Type == "VOID*":
236        if not ((Value.startswith('L"') or Value.startswith('"') and \
237                 Value.endswith('"'))
238                or (IsValidCArray(Value)) or (IsValidCFormatGuid(Value)) \
239                or (IsValidNList(Value)) or (CheckGuidRegFormat(Value))
240               ):
241            return False, ST.ERR_DECPARSE_PCD_VOID % (Value, Type)
242        RealString = Value[Value.find('"') + 1 :-1]
243        if RealString:
244            if not IsValidBareCString(RealString):
245                return False, ST.ERR_DECPARSE_PCD_VOID % (Value, Type)
246    elif Type == 'BOOLEAN':
247        if Value in ['TRUE', 'FALSE', 'true', 'false', 'True', 'False',
248                     '0x1', '0x01', '1', '0x0', '0x00', '0']:
249            return True, ""
250        Valid, Cause = IsValidStringTest(Value, True)
251        if not Valid:
252            Valid, Cause = IsValidFeatureFlagExp(Value, True)
253        if not Valid:
254            return False, Cause
255    else:
256        if Value and (Value[0] == '-' or Value[0] == '+'):
257            return False, ST.ERR_DECPARSE_PCD_INT_NEGTIVE % (Value, Type)
258        try:
259            StrVal = Value
260            if Value and not Value.startswith('0x') \
261                and not Value.startswith('0X'):
262                Value = Value.lstrip('0')
263                if not Value:
264                    return True, ""
265            Value = long(Value, 0)
266            TypeLenMap = {
267                #
268                # 0x00 - 0xff
269                #
270                'UINT8'  : 2,
271                #
272                # 0x0000 - 0xffff
273                #
274                'UINT16' : 4,
275                #
276                # 0x00000000 - 0xffffffff
277                #
278                'UINT32' : 8,
279                #
280                # 0x0 - 0xffffffffffffffff
281                #
282                'UINT64' : 16
283            }
284            HexStr = hex(Value)
285            #
286            # First two chars of HexStr are 0x and tail char is L
287            #
288            if TypeLenMap[Type] < len(HexStr) - 3:
289                return False, ST.ERR_DECPARSE_PCD_INT_EXCEED % (StrVal, Type)
290        except BaseException:
291            Valid, Cause = IsValidLogicalExpr(Value, True)
292        if not Valid:
293            return False, Cause
294
295    return True, ""
296
297## ParserHelper
298#
299class ParserHelper:
300    def __init__(self, String, File=''):
301        self._String = String
302        self._StrLen = len(String)
303        self._Index = 0
304        self._File = File
305
306    ## End
307    #
308    # End
309    #
310    def End(self):
311        self.__SkipWhitespace()
312        return self._Index >= self._StrLen
313
314    ## __SkipWhitespace
315    #
316    # Skip whitespace
317    #
318    def __SkipWhitespace(self):
319        for Char in self._String[self._Index:]:
320            if Char not in ' \t':
321                break
322            self._Index += 1
323
324    ## Expect
325    #
326    # Expect char in string
327    #
328    # @param ExpectChar: char expected in index of string
329    #
330    def Expect(self, ExpectChar):
331        self.__SkipWhitespace()
332        for Char in self._String[self._Index:]:
333            if Char != ExpectChar:
334                return False
335            else:
336                self._Index += 1
337                return True
338        #
339        # Index out of bound of String
340        #
341        return False
342
343    ## GetToken
344    #
345    # Get token until encounter StopChar, front whitespace is consumed
346    #
347    # @param StopChar: Get token until encounter char in StopChar
348    # @param StkipPair: Only can be ' or ", StopChar in SkipPair are skipped
349    #
350    def GetToken(self, StopChar='.,|\t ', SkipPair='"'):
351        self.__SkipWhitespace()
352        PreIndex = self._Index
353        InQuote = False
354        LastChar = ''
355        for Char in self._String[self._Index:]:
356            if Char == SkipPair and LastChar != '\\':
357                InQuote = not InQuote
358            if Char in StopChar and not InQuote:
359                break
360            self._Index += 1
361            if Char == '\\' and LastChar == '\\':
362                LastChar = ''
363            else:
364                LastChar = Char
365        return self._String[PreIndex:self._Index]
366
367    ## AssertChar
368    #
369    # Assert char at current index of string is AssertChar, or will report
370    # error message
371    #
372    # @param AssertChar: AssertChar
373    # @param ErrorString: ErrorString
374    # @param ErrorLineNum: ErrorLineNum
375    #
376    def AssertChar(self, AssertChar, ErrorString, ErrorLineNum):
377        if not self.Expect(AssertChar):
378            Logger.Error(TOOL_NAME, FILE_PARSE_FAILURE, File=self._File,
379                         Line=ErrorLineNum, ExtraData=ErrorString)
380
381    ## AssertEnd
382    #
383    # @param ErrorString: ErrorString
384    # @param ErrorLineNum: ErrorLineNum
385    #
386    def AssertEnd(self, ErrorString, ErrorLineNum):
387        self.__SkipWhitespace()
388        if self._Index != self._StrLen:
389            Logger.Error(TOOL_NAME, FILE_PARSE_FAILURE, File=self._File,
390                         Line=ErrorLineNum, ExtraData=ErrorString)
391