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