1## @file 2# This file is used to parse and evaluate expression in directive or PCD value. 3# 4# Copyright (c) 2011, Intel Corporation. All rights reserved.<BR> 5# This program and the accompanying materials 6# are licensed and made available under the terms and conditions of the BSD License 7# which accompanies this distribution. The full text of the license may be found at 8# http://opensource.org/licenses/bsd-license.php 9# 10# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, 11# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. 12 13## Import Modules 14# 15from Common.GlobalData import * 16from CommonDataClass.Exceptions import BadExpression 17from CommonDataClass.Exceptions import WrnExpression 18from Misc import GuidStringToGuidStructureString 19 20ERR_STRING_EXPR = 'This operator cannot be used in string expression: [%s].' 21ERR_SNYTAX = 'Syntax error, the rest of expression cannot be evaluated: [%s].' 22ERR_MATCH = 'No matching right parenthesis.' 23ERR_STRING_TOKEN = 'Bad string token: [%s].' 24ERR_MACRO_TOKEN = 'Bad macro token: [%s].' 25ERR_EMPTY_TOKEN = 'Empty token is not allowed.' 26ERR_PCD_RESOLVE = 'PCD token cannot be resolved: [%s].' 27ERR_VALID_TOKEN = 'No more valid token found from rest of string: [%s].' 28ERR_EXPR_TYPE = 'Different types found in expression.' 29ERR_OPERATOR_UNSUPPORT = 'Unsupported operator: [%s]' 30ERR_REL_NOT_IN = 'Expect "IN" after "not" operator.' 31WRN_BOOL_EXPR = 'Operand of boolean type cannot be used in arithmetic expression.' 32WRN_EQCMP_STR_OTHERS = '== Comparison between Operand of string type and Boolean/Number Type always return False.' 33WRN_NECMP_STR_OTHERS = '!= Comparison between Operand of string type and Boolean/Number Type always return True.' 34ERR_RELCMP_STR_OTHERS = 'Operator taking Operand of string type and Boolean/Number Type is not allowed: [%s].' 35ERR_STRING_CMP = 'Unicode string and general string cannot be compared: [%s %s %s]' 36ERR_ARRAY_TOKEN = 'Bad C array or C format GUID token: [%s].' 37ERR_ARRAY_ELE = 'This must be HEX value for NList or Array: [%s].' 38ERR_EMPTY_EXPR = 'Empty expression is not allowed.' 39ERR_IN_OPERAND = 'Macro after IN operator can only be: $(FAMILY), $(ARCH), $(TOOL_CHAIN_TAG) and $(TARGET).' 40 41## SplitString 42# Split string to list according double quote 43# For example: abc"de\"f"ghi"jkl"mn will be: ['abc', '"de\"f"', 'ghi', '"jkl"', 'mn'] 44# 45def SplitString(String): 46 # There might be escaped quote: "abc\"def\\\"ghi" 47 Str = String.replace('\\\\', '//').replace('\\\"', '\\\'') 48 RetList = [] 49 InQuote = False 50 Item = '' 51 for i, ch in enumerate(Str): 52 if ch == '"': 53 InQuote = not InQuote 54 if not InQuote: 55 Item += String[i] 56 RetList.append(Item) 57 Item = '' 58 continue 59 if Item: 60 RetList.append(Item) 61 Item = '' 62 Item += String[i] 63 if InQuote: 64 raise BadExpression(ERR_STRING_TOKEN % Item) 65 if Item: 66 RetList.append(Item) 67 return RetList 68 69## ReplaceExprMacro 70# 71def ReplaceExprMacro(String, Macros, ExceptionList = None): 72 StrList = SplitString(String) 73 for i, String in enumerate(StrList): 74 InQuote = False 75 if String.startswith('"'): 76 InQuote = True 77 MacroStartPos = String.find('$(') 78 if MacroStartPos < 0: 79 continue 80 RetStr = '' 81 while MacroStartPos >= 0: 82 RetStr = String[0:MacroStartPos] 83 MacroEndPos = String.find(')', MacroStartPos) 84 if MacroEndPos < 0: 85 raise BadExpression(ERR_MACRO_TOKEN % String[MacroStartPos:]) 86 Macro = String[MacroStartPos+2:MacroEndPos] 87 if Macro not in Macros: 88 # From C reference manual: 89 # If an undefined macro name appears in the constant-expression of 90 # !if or !elif, it is replaced by the integer constant 0. 91 RetStr += '0' 92 elif not InQuote: 93 Tklst = RetStr.split() 94 if Tklst and Tklst[-1] in ['IN', 'in'] and ExceptionList and Macro not in ExceptionList: 95 raise BadExpression(ERR_IN_OPERAND) 96 # Make sure the macro in exception list is encapsulated by double quote 97 # For example: DEFINE ARCH = IA32 X64 98 # $(ARCH) is replaced with "IA32 X64" 99 if ExceptionList and Macro in ExceptionList: 100 RetStr += '"' + Macros[Macro] + '"' 101 elif Macros[Macro].strip(): 102 RetStr += Macros[Macro] 103 else: 104 RetStr += '""' 105 else: 106 RetStr += Macros[Macro] 107 RetStr += String[MacroEndPos+1:] 108 String = RetStr 109 MacroStartPos = String.find('$(') 110 StrList[i] = RetStr 111 return ''.join(StrList) 112 113SupportedInMacroList = ['TARGET', 'TOOL_CHAIN_TAG', 'ARCH', 'FAMILY'] 114 115class ValueExpression(object): 116 # Logical operator mapping 117 LogicalOperators = { 118 '&&' : 'and', '||' : 'or', 119 '!' : 'not', 'AND': 'and', 120 'OR' : 'or' , 'NOT': 'not', 121 'XOR': '^' , 'xor': '^', 122 'EQ' : '==' , 'NE' : '!=', 123 'GT' : '>' , 'LT' : '<', 124 'GE' : '>=' , 'LE' : '<=', 125 'IN' : 'in' 126 } 127 128 NonLetterOpLst = ['+', '-', '&', '|', '^', '!', '=', '>', '<'] 129 130 PcdPattern = re.compile(r'[_a-zA-Z][0-9A-Za-z_]*\.[_a-zA-Z][0-9A-Za-z_]*$') 131 HexPattern = re.compile(r'0[xX][0-9a-fA-F]+$') 132 RegGuidPattern = re.compile(r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}') 133 134 SymbolPattern = re.compile("(" 135 "\$\([A-Z][A-Z0-9_]*\)|\$\(\w+\.\w+\)|\w+\.\w+|" 136 "&&|\|\||!(?!=)|" 137 "(?<=\W)AND(?=\W)|(?<=\W)OR(?=\W)|(?<=\W)NOT(?=\W)|(?<=\W)XOR(?=\W)|" 138 "(?<=\W)EQ(?=\W)|(?<=\W)NE(?=\W)|(?<=\W)GT(?=\W)|(?<=\W)LT(?=\W)|(?<=\W)GE(?=\W)|(?<=\W)LE(?=\W)" 139 ")") 140 141 @staticmethod 142 def Eval(Operator, Oprand1, Oprand2 = None): 143 WrnExp = None 144 145 if Operator not in ["==", "!=", ">=", "<=", ">", "<", "in", "not in"] and \ 146 (type(Oprand1) == type('') or type(Oprand2) == type('')): 147 raise BadExpression(ERR_STRING_EXPR % Operator) 148 149 TypeDict = { 150 type(0) : 0, 151 type(0L) : 0, 152 type('') : 1, 153 type(True) : 2 154 } 155 156 EvalStr = '' 157 if Operator in ["!", "NOT", "not"]: 158 if type(Oprand1) == type(''): 159 raise BadExpression(ERR_STRING_EXPR % Operator) 160 EvalStr = 'not Oprand1' 161 else: 162 if Operator in ["+", "-"] and (type(True) in [type(Oprand1), type(Oprand2)]): 163 # Boolean in '+'/'-' will be evaluated but raise warning 164 WrnExp = WrnExpression(WRN_BOOL_EXPR) 165 elif type('') in [type(Oprand1), type(Oprand2)] and type(Oprand1)!= type(Oprand2): 166 # == between string and number/boolean will always return False, != return True 167 if Operator == "==": 168 WrnExp = WrnExpression(WRN_EQCMP_STR_OTHERS) 169 WrnExp.result = False 170 raise WrnExp 171 elif Operator == "!=": 172 WrnExp = WrnExpression(WRN_NECMP_STR_OTHERS) 173 WrnExp.result = True 174 raise WrnExp 175 else: 176 raise BadExpression(ERR_RELCMP_STR_OTHERS % Operator) 177 elif TypeDict[type(Oprand1)] != TypeDict[type(Oprand2)]: 178 if Operator in ["==", "!=", ">=", "<=", ">", "<"] and set((TypeDict[type(Oprand1)], TypeDict[type(Oprand2)])) == set((TypeDict[type(True)], TypeDict[type(0)])): 179 # comparison between number and boolean is allowed 180 pass 181 elif Operator in ['&', '|', '^', "and", "or"] and set((TypeDict[type(Oprand1)], TypeDict[type(Oprand2)])) == set((TypeDict[type(True)], TypeDict[type(0)])): 182 # bitwise and logical operation between number and boolean is allowed 183 pass 184 else: 185 raise BadExpression(ERR_EXPR_TYPE) 186 if type(Oprand1) == type('') and type(Oprand2) == type(''): 187 if (Oprand1.startswith('L"') and not Oprand2.startswith('L"')) or \ 188 (not Oprand1.startswith('L"') and Oprand2.startswith('L"')): 189 raise BadExpression(ERR_STRING_CMP % (Oprand1, Operator, Oprand2)) 190 if 'in' in Operator and type(Oprand2) == type(''): 191 Oprand2 = Oprand2.split() 192 EvalStr = 'Oprand1 ' + Operator + ' Oprand2' 193 194 # Local symbols used by built in eval function 195 Dict = { 196 'Oprand1' : Oprand1, 197 'Oprand2' : Oprand2 198 } 199 try: 200 Val = eval(EvalStr, {}, Dict) 201 except Exception, Excpt: 202 raise BadExpression(str(Excpt)) 203 204 if Operator in ['and', 'or']: 205 if Val: 206 Val = True 207 else: 208 Val = False 209 210 if WrnExp: 211 WrnExp.result = Val 212 raise WrnExp 213 return Val 214 215 def __init__(self, Expression, SymbolTable={}): 216 self._NoProcess = False 217 if type(Expression) != type(''): 218 self._Expr = Expression 219 self._NoProcess = True 220 return 221 222 self._Expr = ReplaceExprMacro(Expression.strip(), 223 SymbolTable, 224 SupportedInMacroList) 225 226 if not self._Expr.strip(): 227 raise BadExpression(ERR_EMPTY_EXPR) 228 229 # 230 # The symbol table including PCD and macro mapping 231 # 232 self._Symb = SymbolTable 233 self._Symb.update(self.LogicalOperators) 234 self._Idx = 0 235 self._Len = len(self._Expr) 236 self._Token = '' 237 self._WarnExcept = None 238 239 # Literal token without any conversion 240 self._LiteralToken = '' 241 242 # Public entry for this class 243 # @param RealValue: False: only evaluate if the expression is true or false, used for conditional expression 244 # True : return the evaluated str(value), used for PCD value 245 # 246 # @return: True or False if RealValue is False 247 # Evaluated value of string format if RealValue is True 248 # 249 def __call__(self, RealValue=False, Depth=0): 250 if self._NoProcess: 251 return self._Expr 252 253 self._Depth = Depth 254 255 self._Expr = self._Expr.strip() 256 if RealValue and Depth == 0: 257 self._Token = self._Expr 258 if self.__IsNumberToken(): 259 return self._Expr 260 261 try: 262 Token = self._GetToken() 263 if type(Token) == type('') and Token.startswith('{') and Token.endswith('}') and self._Idx >= self._Len: 264 return self._Expr 265 except BadExpression: 266 pass 267 268 self._Idx = 0 269 self._Token = '' 270 271 Val = self._OrExpr() 272 RealVal = Val 273 if type(Val) == type(''): 274 if Val == 'L""': 275 Val = False 276 elif not Val: 277 Val = False 278 RealVal = '""' 279 elif not Val.startswith('L"') and not Val.startswith('{'): 280 Val = True 281 RealVal = '"' + RealVal + '"' 282 283 # The expression has been parsed, but the end of expression is not reached 284 # It means the rest does not comply EBNF of <Expression> 285 if self._Idx != self._Len: 286 raise BadExpression(ERR_SNYTAX % self._Expr[self._Idx:]) 287 288 if RealValue: 289 RetVal = str(RealVal) 290 elif Val: 291 RetVal = True 292 else: 293 RetVal = False 294 295 if self._WarnExcept: 296 self._WarnExcept.result = RetVal 297 raise self._WarnExcept 298 else: 299 return RetVal 300 301 # Template function to parse binary operators which have same precedence 302 # Expr [Operator Expr]* 303 def _ExprFuncTemplate(self, EvalFunc, OpLst): 304 Val = EvalFunc() 305 while self._IsOperator(OpLst): 306 Op = self._Token 307 try: 308 Val = self.Eval(Op, Val, EvalFunc()) 309 except WrnExpression, Warn: 310 self._WarnExcept = Warn 311 Val = Warn.result 312 return Val 313 314 # A [|| B]* 315 def _OrExpr(self): 316 return self._ExprFuncTemplate(self._AndExpr, ["OR", "or", "||"]) 317 318 # A [&& B]* 319 def _AndExpr(self): 320 return self._ExprFuncTemplate(self._BitOr, ["AND", "and", "&&"]) 321 322 # A [ | B]* 323 def _BitOr(self): 324 return self._ExprFuncTemplate(self._BitXor, ["|"]) 325 326 # A [ ^ B]* 327 def _BitXor(self): 328 return self._ExprFuncTemplate(self._BitAnd, ["XOR", "xor", "^"]) 329 330 # A [ & B]* 331 def _BitAnd(self): 332 return self._ExprFuncTemplate(self._EqExpr, ["&"]) 333 334 # A [ == B]* 335 def _EqExpr(self): 336 Val = self._RelExpr() 337 while self._IsOperator(["==", "!=", "EQ", "NE", "IN", "in", "!", "NOT", "not"]): 338 Op = self._Token 339 if Op in ["!", "NOT", "not"]: 340 if not self._IsOperator(["IN", "in"]): 341 raise BadExpression(ERR_REL_NOT_IN) 342 Op += ' ' + self._Token 343 try: 344 Val = self.Eval(Op, Val, self._RelExpr()) 345 except WrnExpression, Warn: 346 self._WarnExcept = Warn 347 Val = Warn.result 348 return Val 349 350 # A [ > B]* 351 def _RelExpr(self): 352 return self._ExprFuncTemplate(self._AddExpr, ["<=", ">=", "<", ">", "LE", "GE", "LT", "GT"]) 353 354 # A [ + B]* 355 def _AddExpr(self): 356 return self._ExprFuncTemplate(self._UnaryExpr, ["+", "-"]) 357 358 # [!]*A 359 def _UnaryExpr(self): 360 if self._IsOperator(["!", "NOT", "not"]): 361 Val = self._UnaryExpr() 362 try: 363 return self.Eval('not', Val) 364 except WrnExpression, Warn: 365 self._WarnExcept = Warn 366 return Warn.result 367 return self._IdenExpr() 368 369 # Parse identifier or encapsulated expression 370 def _IdenExpr(self): 371 Tk = self._GetToken() 372 if Tk == '(': 373 Val = self._OrExpr() 374 try: 375 # _GetToken may also raise BadExpression 376 if self._GetToken() != ')': 377 raise BadExpression(ERR_MATCH) 378 except BadExpression: 379 raise BadExpression(ERR_MATCH) 380 return Val 381 return Tk 382 383 # Skip whitespace or tab 384 def __SkipWS(self): 385 for Char in self._Expr[self._Idx:]: 386 if Char not in ' \t': 387 break 388 self._Idx += 1 389 390 # Try to convert string to number 391 def __IsNumberToken(self): 392 Radix = 10 393 if self._Token.lower()[0:2] == '0x' and len(self._Token) > 2: 394 Radix = 16 395 try: 396 self._Token = int(self._Token, Radix) 397 return True 398 except ValueError: 399 return False 400 except TypeError: 401 return False 402 403 # Parse array: {...} 404 def __GetArray(self): 405 Token = '{' 406 self._Idx += 1 407 self.__GetNList(True) 408 Token += self._LiteralToken 409 if self._Idx >= self._Len or self._Expr[self._Idx] != '}': 410 raise BadExpression(ERR_ARRAY_TOKEN % Token) 411 Token += '}' 412 413 # All whitespace and tabs in array are already stripped. 414 IsArray = IsGuid = False 415 if len(Token.split(',')) == 11 and len(Token.split(',{')) == 2 \ 416 and len(Token.split('},')) == 1: 417 HexLen = [11,6,6,5,4,4,4,4,4,4,6] 418 HexList= Token.split(',') 419 if HexList[3].startswith('{') and \ 420 not [Index for Index, Hex in enumerate(HexList) if len(Hex) > HexLen[Index]]: 421 IsGuid = True 422 if Token.lstrip('{').rstrip('}').find('{') == -1: 423 if not [Hex for Hex in Token.lstrip('{').rstrip('}').split(',') if len(Hex) > 4]: 424 IsArray = True 425 if not IsArray and not IsGuid: 426 raise BadExpression(ERR_ARRAY_TOKEN % Token) 427 self._Idx += 1 428 self._Token = self._LiteralToken = Token 429 return self._Token 430 431 # Parse string, the format must be: "..." 432 def __GetString(self): 433 Idx = self._Idx 434 435 # Skip left quote 436 self._Idx += 1 437 438 # Replace escape \\\", \" 439 Expr = self._Expr[self._Idx:].replace('\\\\', '//').replace('\\\"', '\\\'') 440 for Ch in Expr: 441 self._Idx += 1 442 if Ch == '"': 443 break 444 self._Token = self._LiteralToken = self._Expr[Idx:self._Idx] 445 if not self._Token.endswith('"'): 446 raise BadExpression(ERR_STRING_TOKEN % self._Token) 447 self._Token = self._Token[1:-1] 448 return self._Token 449 450 # Get token that is comprised by alphanumeric, underscore or dot(used by PCD) 451 # @param IsAlphaOp: Indicate if parsing general token or script operator(EQ, NE...) 452 def __GetIdToken(self, IsAlphaOp = False): 453 IdToken = '' 454 for Ch in self._Expr[self._Idx:]: 455 if not self.__IsIdChar(Ch): 456 break 457 self._Idx += 1 458 IdToken += Ch 459 460 self._Token = self._LiteralToken = IdToken 461 if not IsAlphaOp: 462 self.__ResolveToken() 463 return self._Token 464 465 # Try to resolve token 466 def __ResolveToken(self): 467 if not self._Token: 468 raise BadExpression(ERR_EMPTY_TOKEN) 469 470 # PCD token 471 if self.PcdPattern.match(self._Token): 472 if self._Token not in self._Symb: 473 Ex = BadExpression(ERR_PCD_RESOLVE % self._Token) 474 Ex.Pcd = self._Token 475 raise Ex 476 self._Token = ValueExpression(self._Symb[self._Token], self._Symb)(True, self._Depth+1) 477 if type(self._Token) != type(''): 478 self._LiteralToken = hex(self._Token) 479 return 480 481 if self._Token.startswith('"'): 482 self._Token = self._Token[1:-1] 483 elif self._Token in ["FALSE", "false", "False"]: 484 self._Token = False 485 elif self._Token in ["TRUE", "true", "True"]: 486 self._Token = True 487 else: 488 self.__IsNumberToken() 489 490 def __GetNList(self, InArray=False): 491 self._GetSingleToken() 492 if not self.__IsHexLiteral(): 493 if InArray: 494 raise BadExpression(ERR_ARRAY_ELE % self._Token) 495 return self._Token 496 497 self.__SkipWS() 498 Expr = self._Expr[self._Idx:] 499 if not Expr.startswith(','): 500 return self._Token 501 502 NList = self._LiteralToken 503 while Expr.startswith(','): 504 NList += ',' 505 self._Idx += 1 506 self.__SkipWS() 507 self._GetSingleToken() 508 if not self.__IsHexLiteral(): 509 raise BadExpression(ERR_ARRAY_ELE % self._Token) 510 NList += self._LiteralToken 511 self.__SkipWS() 512 Expr = self._Expr[self._Idx:] 513 self._Token = self._LiteralToken = NList 514 return self._Token 515 516 def __IsHexLiteral(self): 517 if self._LiteralToken.startswith('{') and \ 518 self._LiteralToken.endswith('}'): 519 return True 520 521 if self.HexPattern.match(self._LiteralToken): 522 Token = self._LiteralToken[2:] 523 Token = Token.lstrip('0') 524 if not Token: 525 self._LiteralToken = '0x0' 526 else: 527 self._LiteralToken = '0x' + Token.lower() 528 return True 529 return False 530 531 def _GetToken(self): 532 return self.__GetNList() 533 534 @staticmethod 535 def __IsIdChar(Ch): 536 return Ch in '._/:' or Ch.isalnum() 537 538 # Parse operand 539 def _GetSingleToken(self): 540 self.__SkipWS() 541 Expr = self._Expr[self._Idx:] 542 if Expr.startswith('L"'): 543 # Skip L 544 self._Idx += 1 545 UStr = self.__GetString() 546 self._Token = 'L"' + UStr + '"' 547 return self._Token 548 549 self._Token = '' 550 if Expr: 551 Ch = Expr[0] 552 Match = self.RegGuidPattern.match(Expr) 553 if Match and not Expr[Match.end():Match.end()+1].isalnum() \ 554 and Expr[Match.end():Match.end()+1] != '_': 555 self._Idx += Match.end() 556 self._Token = ValueExpression(GuidStringToGuidStructureString(Expr[0:Match.end()]))(True, self._Depth+1) 557 return self._Token 558 elif self.__IsIdChar(Ch): 559 return self.__GetIdToken() 560 elif Ch == '"': 561 return self.__GetString() 562 elif Ch == '{': 563 return self.__GetArray() 564 elif Ch == '(' or Ch == ')': 565 self._Idx += 1 566 self._Token = Ch 567 return self._Token 568 569 raise BadExpression(ERR_VALID_TOKEN % Expr) 570 571 # Parse operator 572 def _GetOperator(self): 573 self.__SkipWS() 574 LegalOpLst = ['&&', '||', '!=', '==', '>=', '<='] + self.NonLetterOpLst 575 576 self._Token = '' 577 Expr = self._Expr[self._Idx:] 578 579 # Reach end of expression 580 if not Expr: 581 return '' 582 583 # Script operator: LT, GT, LE, GE, EQ, NE, and, or, xor, not 584 if Expr[0].isalpha(): 585 return self.__GetIdToken(True) 586 587 # Start to get regular operator: +, -, <, > ... 588 if Expr[0] not in self.NonLetterOpLst: 589 return '' 590 591 OpToken = '' 592 for Ch in Expr: 593 if Ch in self.NonLetterOpLst: 594 if '!' == Ch and OpToken: 595 break 596 self._Idx += 1 597 OpToken += Ch 598 else: 599 break 600 601 if OpToken not in LegalOpLst: 602 raise BadExpression(ERR_OPERATOR_UNSUPPORT % OpToken) 603 self._Token = OpToken 604 return OpToken 605 606 # Check if current token matches the operators given from OpList 607 def _IsOperator(self, OpList): 608 Idx = self._Idx 609 self._GetOperator() 610 if self._Token in OpList: 611 if self._Token in self.LogicalOperators: 612 self._Token = self.LogicalOperators[self._Token] 613 return True 614 self._Idx = Idx 615 return False 616 617if __name__ == '__main__': 618 pass 619 while True: 620 input = raw_input('Input expr: ') 621 if input in 'qQ': 622 break 623 try: 624 print ValueExpression(input)(True) 625 print ValueExpression(input)(False) 626 except WrnExpression, Ex: 627 print Ex.result 628 print str(Ex) 629 except Exception, Ex: 630 print str(Ex) 631