## @file # This file is used to define helper class and function for DEC parser # # Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.
# # SPDX-License-Identifier: BSD-2-Clause-Patent ''' DecParserMisc ''' ## Import modules # import os import Logger.Log as Logger from Logger.ToolError import FILE_PARSE_FAILURE from Logger import StringTable as ST from Library.DataType import TAB_COMMENT_SPLIT from Library.DataType import TAB_COMMENT_EDK1_SPLIT from Library.ExpressionValidate import IsValidBareCString from Library.ParserValidate import IsValidCFormatGuid from Library.ExpressionValidate import IsValidFeatureFlagExp from Library.ExpressionValidate import IsValidLogicalExpr from Library.ExpressionValidate import IsValidStringTest from Library.Misc import CheckGuidRegFormat TOOL_NAME = 'DecParser' VERSION_PATTERN = '[0-9]+(\.[0-9]+)?' CVAR_PATTERN = '[_a-zA-Z][a-zA-Z0-9_]*' PCD_TOKEN_PATTERN = '(0[xX]0*[a-fA-F0-9]{1,8})|([0-9]+)' MACRO_PATTERN = '[A-Z][_A-Z0-9]*' ## FileContent # Class to hold DEC file information # class FileContent: def __init__(self, Filename, FileContent2): self.Filename = Filename self.PackagePath, self.PackageFile = os.path.split(Filename) self.LineIndex = 0 self.CurrentLine = '' self.NextLine = '' self.HeadComment = [] self.TailComment = [] self.CurrentScope = None self.Content = FileContent2 self.Macros = {} self.FileLines = len(FileContent2) def GetNextLine(self): if self.LineIndex >= self.FileLines: return '' Line = self.Content[self.LineIndex] self.LineIndex += 1 return Line def UndoNextLine(self): if self.LineIndex > 0: self.LineIndex -= 1 def ResetNext(self): self.HeadComment = [] self.TailComment = [] self.NextLine = '' def SetNext(self, Line, HeadComment, TailComment): self.NextLine = Line self.HeadComment = HeadComment self.TailComment = TailComment def IsEndOfFile(self): return self.LineIndex >= self.FileLines ## StripRoot # # Strip root path # # @param Root: Root must be absolute path # @param Path: Path to be stripped # def StripRoot(Root, Path): OrigPath = Path Root = os.path.normpath(Root) Path = os.path.normpath(Path) if not os.path.isabs(Root): return OrigPath if Path.startswith(Root): Path = Path[len(Root):] if Path and Path[0] == os.sep: Path = Path[1:] return Path return OrigPath ## CleanString # # Split comments in a string # Remove spaces # # @param Line: The string to be cleaned # @param CommentCharacter: Comment char, used to ignore comment content, # default is DataType.TAB_COMMENT_SPLIT # def CleanString(Line, CommentCharacter=TAB_COMMENT_SPLIT, \ AllowCppStyleComment=False): # # remove whitespace # Line = Line.strip() # # Replace EDK1's comment character # if AllowCppStyleComment: Line = Line.replace(TAB_COMMENT_EDK1_SPLIT, CommentCharacter) # # separate comments and statements # Comment = '' InQuote = False for Index in range(0, len(Line)): if Line[Index] == '"': InQuote = not InQuote continue if Line[Index] == CommentCharacter and not InQuote: Comment = Line[Index:].strip() Line = Line[0:Index].strip() break return Line, Comment ## IsValidNumValUint8 # # Check if Token is NumValUint8: ::= {} {} {} # # @param Token: Token to be checked # def IsValidNumValUint8(Token): Valid = True Cause = "" TokenValue = None Token = Token.strip() if Token.lower().startswith('0x'): Base = 16 else: Base = 10 try: TokenValue = int(Token, Base) except BaseException: Valid, Cause = IsValidLogicalExpr(Token, True) if Cause: pass if not Valid: return False if TokenValue and (TokenValue < 0 or TokenValue > 0xFF): return False else: return True ## IsValidNList # # Check if Value has the format of ["," ]{0,} # ::= {} {} {} # # @param Value: Value to be checked # def IsValidNList(Value): Par = ParserHelper(Value) if Par.End(): return False while not Par.End(): Token = Par.GetToken(',') if not IsValidNumValUint8(Token): return False if Par.Expect(','): if Par.End(): return False continue else: break return Par.End() ## IsValidCArray # # check Array is valid # # @param Array: The input Array # def IsValidCArray(Array): Par = ParserHelper(Array) if not Par.Expect('{'): return False if Par.End(): return False while not Par.End(): Token = Par.GetToken(',}') # # ShortNum, UINT8, Expression # if not IsValidNumValUint8(Token): return False if Par.Expect(','): if Par.End(): return False continue elif Par.Expect('}'): # # End of C array # break else: return False return Par.End() ## IsValidPcdDatum # # check PcdDatum is valid # # @param Type: The pcd Type # @param Value: The pcd Value # def IsValidPcdDatum(Type, Value): if not Value: return False, ST.ERR_DECPARSE_PCD_VALUE_EMPTY Valid = True Cause = "" if Type not in ["UINT8", "UINT16", "UINT32", "UINT64", "VOID*", "BOOLEAN"]: return False, ST.ERR_DECPARSE_PCD_TYPE if Type == "VOID*": if not ((Value.startswith('L"') or Value.startswith('"') and \ Value.endswith('"')) or (IsValidCArray(Value)) or (IsValidCFormatGuid(Value)) \ or (IsValidNList(Value)) or (CheckGuidRegFormat(Value)) ): return False, ST.ERR_DECPARSE_PCD_VOID % (Value, Type) RealString = Value[Value.find('"') + 1 :-1] if RealString: if not IsValidBareCString(RealString): return False, ST.ERR_DECPARSE_PCD_VOID % (Value, Type) elif Type == 'BOOLEAN': if Value in ['TRUE', 'FALSE', 'true', 'false', 'True', 'False', '0x1', '0x01', '1', '0x0', '0x00', '0']: return True, "" Valid, Cause = IsValidStringTest(Value, True) if not Valid: Valid, Cause = IsValidFeatureFlagExp(Value, True) if not Valid: return False, Cause else: if Value and (Value[0] == '-' or Value[0] == '+'): return False, ST.ERR_DECPARSE_PCD_INT_NEGTIVE % (Value, Type) try: StrVal = Value if Value and not Value.startswith('0x') \ and not Value.startswith('0X'): Value = Value.lstrip('0') if not Value: return True, "" Value = int(Value, 0) MAX_VAL_TYPE = {"BOOLEAN": 0x01, 'UINT8': 0xFF, 'UINT16': 0xFFFF, 'UINT32': 0xFFFFFFFF, 'UINT64': 0xFFFFFFFFFFFFFFFF} if Value > MAX_VAL_TYPE[Type]: return False, ST.ERR_DECPARSE_PCD_INT_EXCEED % (StrVal, Type) except BaseException: Valid, Cause = IsValidLogicalExpr(Value, True) if not Valid: return False, Cause return True, "" ## ParserHelper # class ParserHelper: def __init__(self, String, File=''): self._String = String self._StrLen = len(String) self._Index = 0 self._File = File ## End # # End # def End(self): self.__SkipWhitespace() return self._Index >= self._StrLen ## __SkipWhitespace # # Skip whitespace # def __SkipWhitespace(self): for Char in self._String[self._Index:]: if Char not in ' \t': break self._Index += 1 ## Expect # # Expect char in string # # @param ExpectChar: char expected in index of string # def Expect(self, ExpectChar): self.__SkipWhitespace() for Char in self._String[self._Index:]: if Char != ExpectChar: return False else: self._Index += 1 return True # # Index out of bound of String # return False ## GetToken # # Get token until encounter StopChar, front whitespace is consumed # # @param StopChar: Get token until encounter char in StopChar # @param StkipPair: Only can be ' or ", StopChar in SkipPair are skipped # def GetToken(self, StopChar='.,|\t ', SkipPair='"'): self.__SkipWhitespace() PreIndex = self._Index InQuote = False LastChar = '' for Char in self._String[self._Index:]: if Char == SkipPair and LastChar != '\\': InQuote = not InQuote if Char in StopChar and not InQuote: break self._Index += 1 if Char == '\\' and LastChar == '\\': LastChar = '' else: LastChar = Char return self._String[PreIndex:self._Index] ## AssertChar # # Assert char at current index of string is AssertChar, or will report # error message # # @param AssertChar: AssertChar # @param ErrorString: ErrorString # @param ErrorLineNum: ErrorLineNum # def AssertChar(self, AssertChar, ErrorString, ErrorLineNum): if not self.Expect(AssertChar): Logger.Error(TOOL_NAME, FILE_PARSE_FAILURE, File=self._File, Line=ErrorLineNum, ExtraData=ErrorString) ## AssertEnd # # @param ErrorString: ErrorString # @param ErrorLineNum: ErrorLineNum # def AssertEnd(self, ErrorString, ErrorLineNum): self.__SkipWhitespace() if self._Index != self._StrLen: Logger.Error(TOOL_NAME, FILE_PARSE_FAILURE, File=self._File, Line=ErrorLineNum, ExtraData=ErrorString)