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