mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-02 13:03:28 +01:00
b1264ef1e3
Signed-off-by: Sergey Isakov <isakov-sl@bk.ru>
365 lines
10 KiB
Python
Executable File
365 lines
10 KiB
Python
Executable File
## @file
|
|
# This file is used to define helper class and function for DEC parser
|
|
#
|
|
# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
|
|
#
|
|
# 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: <NumValUint8> ::= {<ShortNum>} {<UINT8>} {<Expression>}
|
|
#
|
|
# @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 <NumValUint8> ["," <NumValUint8>]{0,}
|
|
# <NumValUint8> ::= {<ShortNum>} {<UINT8>} {<Expression>}
|
|
#
|
|
# @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)
|