1## @file
2# This file is used to check PCD logical expression
3#
4# Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
5#
6# SPDX-License-Identifier: BSD-2-Clause-Patent
7
8'''
9ExpressionValidate
10'''
11from __future__ import print_function
12
13##
14# Import Modules
15#
16import re
17from Logger import StringTable as ST
18
19## IsValidBareCString
20#
21# Check if String is comprised by whitespace(0x20), !(0x21), 0x23 - 0x7E
22# or '\n', '\t', '\f', '\r', '\b', '\0', '\\'
23#
24# @param String: string to be checked
25#
26def IsValidBareCString(String):
27    EscapeList = ['n', 't', 'f', 'r', 'b', '0', '\\', '"']
28    PreChar = ''
29    LastChar = ''
30    for Char in String:
31        LastChar = Char
32        if PreChar == '\\':
33            if Char not in EscapeList:
34                return False
35            if Char == '\\':
36                PreChar = ''
37                continue
38        else:
39            IntChar = ord(Char)
40            if IntChar != 0x20 and IntChar != 0x09 and IntChar != 0x21 \
41                and (IntChar < 0x23 or IntChar > 0x7e):
42                return False
43        PreChar = Char
44
45    # Last char cannot be \ if PreChar is not \
46    if LastChar == '\\' and PreChar == LastChar:
47        return False
48    return True
49
50def _ValidateToken(Token):
51    Token = Token.strip()
52    Index = Token.find("\"")
53    if Index != -1:
54        return IsValidBareCString(Token[Index+1:-1])
55    return True
56
57## _ExprError
58#
59# @param      Exception:    Exception
60#
61class _ExprError(Exception):
62    def __init__(self, Error = ''):
63        Exception.__init__(self)
64        self.Error = Error
65
66## _ExprBase
67#
68class _ExprBase:
69    HEX_PATTERN = '[\t\s]*0[xX][a-fA-F0-9]+'
70    INT_PATTERN = '[\t\s]*[0-9]+'
71    MACRO_PATTERN = '[\t\s]*\$\(([A-Z][_A-Z0-9]*)\)'
72    PCD_PATTERN = \
73    '[\t\s]*[_a-zA-Z][a-zA-Z0-9_]*[\t\s]*\.[\t\s]*[_a-zA-Z][a-zA-Z0-9_]*'
74    QUOTED_PATTERN = '[\t\s]*L?"[^"]*"'
75    BOOL_PATTERN = '[\t\s]*(true|True|TRUE|false|False|FALSE)'
76    def __init__(self, Token):
77        self.Token = Token
78        self.Index = 0
79        self.Len = len(Token)
80
81    ## SkipWhitespace
82    #
83    def SkipWhitespace(self):
84        for Char in self.Token[self.Index:]:
85            if Char not in ' \t':
86                break
87            self.Index += 1
88
89    ## IsCurrentOp
90    #
91    # @param      OpList:   option list
92    #
93    def IsCurrentOp(self, OpList):
94        self.SkipWhitespace()
95        LetterOp = ["EQ", "NE", "GE", "LE", "GT", "LT", "NOT", "and", "AND",
96                    "or", "OR", "XOR"]
97        OpMap = {
98            '|' : '|',
99            '&' : '&',
100            '!' : '=',
101            '>' : '=',
102            '<' : '='
103        }
104
105        for Operator in OpList:
106            if not self.Token[self.Index:].startswith(Operator):
107                continue
108
109            self.Index += len(Operator)
110            Char = self.Token[self.Index : self.Index + 1]
111
112            if (Operator in LetterOp and (Char == '_' or Char.isalnum())) \
113                or (Operator in OpMap and OpMap[Operator] == Char):
114                self.Index -= len(Operator)
115                break
116
117            return True
118
119        return False
120
121## _LogicalExpressionParser
122#
123# @param      _ExprBase:   _ExprBase object
124#
125class _LogicalExpressionParser(_ExprBase):
126    #
127    # STRINGITEM can only be logical field according to spec
128    #
129    STRINGITEM = -1
130
131    #
132    # Evaluate to True or False
133    #
134    LOGICAL = 0
135    REALLOGICAL = 2
136
137    #
138    # Just arithmetic expression
139    #
140    ARITH = 1
141
142    def __init__(self, Token):
143        _ExprBase.__init__(self, Token)
144        self.Parens = 0
145
146    def _CheckToken(self, MatchList):
147        for Match in MatchList:
148            if Match and Match.start() == 0:
149                if not _ValidateToken(
150                            self.Token[self.Index:self.Index+Match.end()]
151                        ):
152                    return False
153
154                self.Index += Match.end()
155                if self.Token[self.Index - 1] == '"':
156                    return True
157                if self.Token[self.Index:self.Index+1] == '_' or \
158                    self.Token[self.Index:self.Index+1].isalnum():
159                    self.Index -= Match.end()
160                    return False
161
162                Token = self.Token[self.Index - Match.end():self.Index]
163                if Token.strip() in ["EQ", "NE", "GE", "LE", "GT", "LT",
164                    "NOT", "and", "AND", "or", "OR", "XOR"]:
165                    self.Index -= Match.end()
166                    return False
167
168                return True
169
170        return False
171
172    def IsAtomicNumVal(self):
173        #
174        # Hex number
175        #
176        Match1 = re.compile(self.HEX_PATTERN).match(self.Token[self.Index:])
177
178        #
179        # Number
180        #
181        Match2 = re.compile(self.INT_PATTERN).match(self.Token[self.Index:])
182
183        #
184        # Macro
185        #
186        Match3 = re.compile(self.MACRO_PATTERN).match(self.Token[self.Index:])
187
188        #
189        # PcdName
190        #
191        Match4 = re.compile(self.PCD_PATTERN).match(self.Token[self.Index:])
192
193        return self._CheckToken([Match1, Match2, Match3, Match4])
194
195
196    def IsAtomicItem(self):
197        #
198        # Macro
199        #
200        Match1 = re.compile(self.MACRO_PATTERN).match(self.Token[self.Index:])
201
202        #
203        # PcdName
204        #
205        Match2 = re.compile(self.PCD_PATTERN).match(self.Token[self.Index:])
206
207        #
208        # Quoted string
209        #
210        Match3 = re.compile(self.QUOTED_PATTERN).\
211            match(self.Token[self.Index:].replace('\\\\', '//').\
212                  replace('\\\"', '\\\''))
213
214        return self._CheckToken([Match1, Match2, Match3])
215
216    ## A || B
217    #
218    def LogicalExpression(self):
219        Ret = self.SpecNot()
220        while self.IsCurrentOp(['||', 'OR', 'or', '&&', 'AND', 'and', 'XOR', 'xor', '^']):
221            if self.Token[self.Index-1] == '|' and self.Parens <= 0:
222                raise  _ExprError(ST.ERR_EXPR_OR % self.Token)
223            if Ret not in [self.ARITH, self.LOGICAL, self.REALLOGICAL, self.STRINGITEM]:
224                raise _ExprError(ST.ERR_EXPR_LOGICAL % self.Token)
225            Ret = self.SpecNot()
226            if Ret not in [self.ARITH, self.LOGICAL, self.REALLOGICAL, self.STRINGITEM]:
227                raise _ExprError(ST.ERR_EXPR_LOGICAL % self.Token)
228            Ret = self.REALLOGICAL
229        return Ret
230
231    def SpecNot(self):
232        if self.IsCurrentOp(["NOT", "!", "not"]):
233            return self.SpecNot()
234        return self.Rel()
235
236    ## A < B, A > B, A <= B, A >= B
237    #
238    def Rel(self):
239        Ret = self.Expr()
240        if self.IsCurrentOp(["<=", ">=", ">", "<", "GT", "LT", "GE", "LE",
241                             "==", "EQ", "!=", "NE"]):
242            if Ret == self.STRINGITEM:
243                raise _ExprError(ST.ERR_EXPR_LOGICAL % self.Token)
244            Ret = self.Expr()
245            if Ret == self.REALLOGICAL:
246                raise _ExprError(ST.ERR_EXPR_LOGICAL % self.Token)
247            Ret = self.REALLOGICAL
248        return Ret
249
250    ## A + B, A - B
251    #
252    def Expr(self):
253        Ret = self.Factor()
254        while self.IsCurrentOp(["+", "-", "&", "|", "^", "XOR", "xor"]):
255            if self.Token[self.Index-1] == '|' and self.Parens <= 0:
256                raise  _ExprError(ST.ERR_EXPR_OR)
257            if Ret == self.STRINGITEM or Ret == self.REALLOGICAL:
258                raise _ExprError(ST.ERR_EXPR_LOGICAL % self.Token)
259            Ret = self.Factor()
260            if Ret == self.STRINGITEM or Ret == self.REALLOGICAL:
261                raise _ExprError(ST.ERR_EXPR_LOGICAL % self.Token)
262            Ret = self.ARITH
263        return Ret
264
265    ## Factor
266    #
267    def Factor(self):
268        if self.IsCurrentOp(["("]):
269            self.Parens += 1
270            Ret = self.LogicalExpression()
271            if not self.IsCurrentOp([")"]):
272                raise _ExprError(ST.ERR_EXPR_RIGHT_PAREN % \
273                                 (self.Token, self.Token[self.Index:]))
274            self.Parens -= 1
275            return Ret
276
277        if self.IsAtomicItem():
278            if self.Token[self.Index - 1] == '"':
279                return self.STRINGITEM
280            return self.LOGICAL
281        elif self.IsAtomicNumVal():
282            return self.ARITH
283        else:
284            raise _ExprError(ST.ERR_EXPR_FACTOR % \
285                             (self.Token[self.Index:], self.Token))
286
287    ## IsValidLogicalExpression
288    #
289    def IsValidLogicalExpression(self):
290        if self.Len == 0:
291            return False, ST.ERR_EXPRESS_EMPTY
292        try:
293            if self.LogicalExpression() not in [self.ARITH, self.LOGICAL, self.REALLOGICAL, self.STRINGITEM]:
294                return False, ST.ERR_EXPR_LOGICAL % self.Token
295        except _ExprError as XExcept:
296            return False, XExcept.Error
297        self.SkipWhitespace()
298        if self.Index != self.Len:
299            return False, (ST.ERR_EXPR_BOOLEAN % \
300                           (self.Token[self.Index:], self.Token))
301        return True, ''
302
303## _ValidRangeExpressionParser
304#
305class _ValidRangeExpressionParser(_ExprBase):
306    INT_RANGE_PATTERN = '[\t\s]*[0-9]+[\t\s]*-[\t\s]*[0-9]+'
307    HEX_RANGE_PATTERN = \
308        '[\t\s]*0[xX][a-fA-F0-9]+[\t\s]*-[\t\s]*0[xX][a-fA-F0-9]+'
309    def __init__(self, Token):
310        _ExprBase.__init__(self, Token)
311        self.Parens = 0
312        self.HEX = 1
313        self.INT = 2
314        self.IsParenHappen = False
315        self.IsLogicalOpHappen = False
316
317    ## IsValidRangeExpression
318    #
319    def IsValidRangeExpression(self):
320        if self.Len == 0:
321            return False, ST.ERR_EXPR_RANGE_EMPTY
322        try:
323            if self.RangeExpression() not in [self.HEX, self.INT]:
324                return False, ST.ERR_EXPR_RANGE % self.Token
325        except _ExprError as XExcept:
326            return False, XExcept.Error
327
328        self.SkipWhitespace()
329        if self.Index != self.Len:
330            return False, (ST.ERR_EXPR_RANGE % self.Token)
331        return True, ''
332
333    ## RangeExpression
334    #
335    def RangeExpression(self):
336        Ret = self.Unary()
337        while self.IsCurrentOp(['OR', 'AND', 'and', 'or']):
338            self.IsLogicalOpHappen = True
339            if not self.IsParenHappen:
340                raise _ExprError(ST.ERR_PAREN_NOT_USED % self.Token)
341            self.IsParenHappen = False
342            Ret = self.Unary()
343
344        if self.IsCurrentOp(['XOR']):
345            Ret = self.Unary()
346
347        return Ret
348
349    ## Unary
350    #
351    def Unary(self):
352        if self.IsCurrentOp(["NOT"]):
353            return self.Unary()
354
355        return self.ValidRange()
356
357    ## ValidRange
358    #
359    def ValidRange(self):
360        Ret = -1
361        if self.IsCurrentOp(["("]):
362            self.IsLogicalOpHappen = False
363            self.IsParenHappen = True
364            self.Parens += 1
365            if self.Parens > 1:
366                raise _ExprError(ST.ERR_EXPR_RANGE_DOUBLE_PAREN_NESTED % self.Token)
367            Ret = self.RangeExpression()
368            if not self.IsCurrentOp([")"]):
369                raise _ExprError(ST.ERR_EXPR_RIGHT_PAREN % self.Token)
370            self.Parens -= 1
371            return Ret
372
373        if self.IsLogicalOpHappen:
374            raise _ExprError(ST.ERR_PAREN_NOT_USED % self.Token)
375
376        if self.IsCurrentOp(["LT", "GT", "LE", "GE", "EQ", "XOR"]):
377            IntMatch = \
378                re.compile(self.INT_PATTERN).match(self.Token[self.Index:])
379            HexMatch = \
380                re.compile(self.HEX_PATTERN).match(self.Token[self.Index:])
381            if HexMatch and HexMatch.start() == 0:
382                self.Index += HexMatch.end()
383                Ret = self.HEX
384            elif IntMatch and IntMatch.start() == 0:
385                self.Index += IntMatch.end()
386                Ret = self.INT
387            else:
388                raise _ExprError(ST.ERR_EXPR_RANGE_FACTOR % (self.Token[self.Index:], self.Token))
389        else:
390            IntRangeMatch = re.compile(
391                self.INT_RANGE_PATTERN).match(self.Token[self.Index:]
392            )
393            HexRangeMatch = re.compile(
394                self.HEX_RANGE_PATTERN).match(self.Token[self.Index:]
395            )
396            if HexRangeMatch and HexRangeMatch.start() == 0:
397                self.Index += HexRangeMatch.end()
398                Ret = self.HEX
399            elif IntRangeMatch and IntRangeMatch.start() == 0:
400                self.Index += IntRangeMatch.end()
401                Ret = self.INT
402            else:
403                raise _ExprError(ST.ERR_EXPR_RANGE % self.Token)
404
405        return Ret
406
407## _ValidListExpressionParser
408#
409class _ValidListExpressionParser(_ExprBase):
410    VALID_LIST_PATTERN = '(0[xX][0-9a-fA-F]+|[0-9]+)([\t\s]*,[\t\s]*(0[xX][0-9a-fA-F]+|[0-9]+))*'
411    def __init__(self, Token):
412        _ExprBase.__init__(self, Token)
413        self.NUM = 1
414
415    def IsValidListExpression(self):
416        if self.Len == 0:
417            return False, ST.ERR_EXPR_LIST_EMPTY
418        try:
419            if self.ListExpression() not in [self.NUM]:
420                return False, ST.ERR_EXPR_LIST % self.Token
421        except _ExprError as XExcept:
422            return False, XExcept.Error
423
424        self.SkipWhitespace()
425        if self.Index != self.Len:
426            return False, (ST.ERR_EXPR_LIST % self.Token)
427
428        return True, ''
429
430    def ListExpression(self):
431        Ret = -1
432        self.SkipWhitespace()
433        ListMatch = re.compile(self.VALID_LIST_PATTERN).match(self.Token[self.Index:])
434        if ListMatch and ListMatch.start() == 0:
435            self.Index += ListMatch.end()
436            Ret = self.NUM
437        else:
438            raise _ExprError(ST.ERR_EXPR_LIST % self.Token)
439
440        return Ret
441
442## _StringTestParser
443#
444class _StringTestParser(_ExprBase):
445    def __init__(self, Token):
446        _ExprBase.__init__(self, Token)
447
448    ## IsValidStringTest
449    #
450    def IsValidStringTest(self):
451        if self.Len == 0:
452            return False, ST.ERR_EXPR_EMPTY
453        try:
454            self.StringTest()
455        except _ExprError as XExcept:
456            return False, XExcept.Error
457        return True, ''
458
459    ## StringItem
460    #
461    def StringItem(self):
462        Match1 = re.compile(self.QUOTED_PATTERN)\
463            .match(self.Token[self.Index:].replace('\\\\', '//')\
464                   .replace('\\\"', '\\\''))
465        Match2 = re.compile(self.MACRO_PATTERN).match(self.Token[self.Index:])
466        Match3 = re.compile(self.PCD_PATTERN).match(self.Token[self.Index:])
467        MatchList = [Match1, Match2, Match3]
468        for Match in MatchList:
469            if Match and Match.start() == 0:
470                if not _ValidateToken(
471                            self.Token[self.Index:self.Index+Match.end()]
472                        ):
473                    raise _ExprError(ST.ERR_EXPR_STRING_ITEM % \
474                                     (self.Token, self.Token[self.Index:]))
475                self.Index += Match.end()
476                Token = self.Token[self.Index - Match.end():self.Index]
477                if Token.strip() in ["EQ", "NE"]:
478                    raise _ExprError(ST.ERR_EXPR_STRING_ITEM % \
479                             (self.Token, self.Token[self.Index:]))
480                return
481        else:
482            raise _ExprError(ST.ERR_EXPR_STRING_ITEM % \
483                             (self.Token, self.Token[self.Index:]))
484
485    ## StringTest
486    #
487    def StringTest(self):
488        self.StringItem()
489        if not self.IsCurrentOp(["==", "EQ", "!=", "NE"]):
490            raise _ExprError(ST.ERR_EXPR_EQUALITY % \
491                             (self.Token[self.Index:], self.Token))
492        self.StringItem()
493        if self.Index != self.Len:
494            raise _ExprError(ST.ERR_EXPR_BOOLEAN % \
495                             (self.Token[self.Index:], self.Token))
496
497##
498# Check syntax of string test
499#
500# @param Token: string test token
501#
502def IsValidStringTest(Token, Flag=False):
503    #
504    # Not do the check right now, keep the implementation for future enhancement.
505    #
506    if not Flag:
507        return True, ""
508    return _StringTestParser(Token).IsValidStringTest()
509
510
511##
512# Check syntax of logical expression
513#
514# @param Token: expression token
515#
516def IsValidLogicalExpr(Token, Flag=False):
517    #
518    # Not do the check right now, keep the implementation for future enhancement.
519    #
520    if not Flag:
521        return True, ""
522    return _LogicalExpressionParser(Token).IsValidLogicalExpression()
523
524##
525# Check syntax of range expression
526#
527# @param Token: range expression token
528#
529def IsValidRangeExpr(Token):
530    return _ValidRangeExpressionParser(Token).IsValidRangeExpression()
531
532##
533# Check syntax of value list expression token
534#
535# @param Token: value list expression token
536#
537def IsValidListExpr(Token):
538    return _ValidListExpressionParser(Token).IsValidListExpression()
539
540##
541# Check whether the feature flag expression is valid or not
542#
543# @param Token: feature flag expression
544#
545def IsValidFeatureFlagExp(Token, Flag=False):
546    #
547    # Not do the check right now, keep the implementation for future enhancement.
548    #
549    if not Flag:
550        return True, "", Token
551    else:
552        if Token in ['TRUE', 'FALSE', 'true', 'false', 'True', 'False',
553                     '0x1', '0x01', '0x0', '0x00']:
554            return True, ""
555        Valid, Cause = IsValidStringTest(Token, Flag)
556        if not Valid:
557            Valid, Cause = IsValidLogicalExpr(Token, Flag)
558        if not Valid:
559            return False, Cause
560        return True, ""
561
562if __name__ == '__main__':
563#    print IsValidRangeExpr('LT 9')
564    print(_LogicalExpressionParser('gCrownBayTokenSpaceGuid.PcdPciDevice1BridgeAddressLE0').IsValidLogicalExpression())
565
566
567
568