pcpp.evaluator module

Source code
#!/usr/bin/python
# Python C99 conforming preprocessor expression evaluator
# (C) 2019-2020 Niall Douglas http://www.nedproductions.biz/
# Started: Apr 2019

from __future__ import generators, print_function, absolute_import, division

import sys, os, re, codecs, copy
if __name__ == '__main__' and __package__ is None:
    sys.path.append( os.path.dirname( os.path.dirname( os.path.abspath(__file__) ) ) )
from pcpp.parser import STRING_TYPES, yacc, default_lexer, in_production

# The width of signed integer which this evaluator will use
INTMAXBITS = 64

# Some Python 3 compatibility shims
if sys.version_info.major < 3:
    INTBASETYPE = long
else:
    INTBASETYPE = int

# Precompile the regular expression for correctly expanding unicode escape
# sequences in Python 2 and 3. See https://stackoverflow.com/questions/4020539/process-escape-sequences-in-a-string-in-python
# for more information.
_expand_escape_sequences_pat = re.compile(r'''
    ( \\U........      # 8-digit hex escapes
    | \\u....          # 4-digit hex escapes
    | \\x..            # 2-digit hex escapes
    | \\[0-7]{1,3}     # Octal escapes
    | \\N\{[^}]+\}     # Unicode characters by name
    | \\[\\'"abfnrtv]  # Single-character escapes
)''', re.UNICODE | re.VERBOSE)

class Value(INTBASETYPE):
    """A signed or unsigned integer within a preprocessor expression, bounded
    to within INT_MIN and INT_MAX, or 0 and UINT_MAX. Signed overflow is handled
    like a two's complement CPU, despite being UB, as that's what GCC and clang do.
    
    >>> Value(5)
    Value(5)
    >>> Value('5L')
    Value(5)
    >>> Value('5U')
    Value(5U)
    >>> Value('0')
    Value(0)
    >>> Value('0U')
    Value(0U)
    >>> Value('-1U')
    Value(18446744073709551615U)
    >>> Value(5) * Value(2)
    Value(10)
    >>> Value(5) + Value('2u')
    Value(7U)
    >>> Value(5) * 2
    Value(10)
    >>> Value(5) / 2   # Must return integer
    Value(2)
    >>> Value(50) % 8
    Value(2)
    >>> -Value(5)
    Value(-5)
    >>> +Value(-5)
    Value(-5)
    >>> ~Value(5)
    Value(-6)
    >>> Value(6) & 2
    Value(2)
    >>> Value(4) | 2
    Value(6)
    >>> Value(6) ^ 2
    Value(4)
    >>> Value(2) << 2
    Value(8)
    >>> Value(8) >> 2
    Value(2)
    >>> Value(9223372036854775808)
    Value(-9223372036854775808)
    >>> Value(-9223372036854775809)
    Value(9223372036854775807)
    >>> Value(18446744073709551615)
    Value(-1)
    >>> Value(False)
    Value(0)
    >>> Value(True)
    Value(1)
    >>> Value(5) == Value(6)
    Value(0)
    >>> Value(5) == Value(5)
    Value(1)
    >>> not Value(2)
    Traceback (most recent call last):
    ...
    AssertionError
    >>> Value(4) and Value(2)
    Traceback (most recent call last):
    ...
    AssertionError
    >>> Value(5) and not Value(6)
    Traceback (most recent call last):
    ...
    AssertionError
    >>> Value('0x3f')
    Value(63)
    >>> Value('077')
    Value(63)
    >>> Value("'N'")
    Value(78)
    >>> Value("L'N'")
    Value(78)
    >>> Value("'\\n'")
    Value(10)
    >>> Value("'\\\\n'")
    Value(10)
    >>> Value("'\\\\'")
    Value(92)
    >>> Value("'\\'")
    Traceback (most recent call last):
    ...
    SyntaxError: Empty character escape sequence
    """
    INT_MIN = -(1 << (INTMAXBITS - 1))
    INT_MAX = (1 << (INTMAXBITS - 1)) - 1
    INT_MASK = (1 << INTMAXBITS) - 1
    UINT_MIN = 0
    UINT_MAX = (1 << INTMAXBITS) - 1
    @classmethod
    def __sclamp(cls, value):
        value = INTBASETYPE(value)
        return ((value - cls.INT_MIN) & cls.INT_MASK) + cls.INT_MIN
    @classmethod
    def __uclamp(cls, value):
        value = INTBASETYPE(value)
        return value & cls.UINT_MAX
    def __new__(cls, value, unsigned = False, exception = None):
        if isinstance(value, Value):
            unsigned = value.unsigned
            exception = value.exception
        elif isinstance(value, INTBASETYPE) or isinstance(value, int) or isinstance(value, float):
            value = cls.__uclamp(value) if unsigned else cls.__sclamp(value)
        elif isinstance(value, STRING_TYPES):
            if (value.startswith("L'") or value[0] == "'") and value[-1] == "'":
                startidx = 2 if value.startswith("L'") else 1
                #print("1. ***", value, file = sys.stderr)
                value = value[startidx:-1]
                if len(value) == 0:
                    raise SyntaxError('Empty character escape sequence')
                #print("2. ***", value, file = sys.stderr)
                value = _expand_escape_sequences_pat.sub(lambda x: codecs.decode(x.group(0), 'unicode-escape'), value)
                #print("3. ***", value, file = sys.stderr)
                x = INTBASETYPE(ord(value))
                #print("4. ***", x, file = sys.stderr)
            elif value.startswith('0x') or value.startswith('0X'):
                # Strip any terminators
                while not ((value[-1] >= '0' and value[-1] <= '9') or (value[-1] >= 'a' and value[-1] <= 'f') or (value[-1] >= 'A' and value[-1] <= 'F')):
                    if value[-1] == 'u' or value[-1] == 'U':
                        unsigned = True
                    value = value[:-1]
                x = INTBASETYPE(value, base = 16)
            elif value.startswith('0'):
                # Strip any terminators
                while not (value[-1] >= '0' and value[-1] <= '7'):
                    if value[-1] == 'u' or value[-1] == 'U':
                        unsigned = True
                    value = value[:-1]
                x = INTBASETYPE(value, base = 8)
            else:
                # Strip any terminators
                while not (value[-1] >= '0' and value[-1] <= '9'):
                    if value[-1] == 'u' or value[-1] == 'U':
                        unsigned = True
                    value = value[:-1]
                x = INTBASETYPE(value)
            value = cls.__uclamp(x) if unsigned else cls.__sclamp(x)
            #assert x == value
        else:
            print('Unknown value type: %s' % repr(type(value)), file = sys.stderr)
            assert False  # Input is an unrecognised type
        inst = super(Value, cls).__new__(cls, value)
        inst.unsigned = unsigned
        inst.exception = exception
        return inst
    def value(self):
        if self.exception is not None:
            raise self.exception
        return INTBASETYPE(self)
    def __add__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) + self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__add__(other))
    def __sub__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) - self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__sub__(other))
    def __mul__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) * self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__mul__(other))
    def __div__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) / self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__div__(other))
    def __truediv__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) / self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__truediv__(other))
    def __mod__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) % self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__mod__(other))
    def __neg__(self):
        if self.exception is not None:
            return self
        return Value(super(Value, self).__neg__(), self.unsigned)
    def __invert__(self):
        if self.exception is not None:
            return self
        return Value(super(Value, self).__invert__(), self.unsigned)
    def __and__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) & self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__and__(other))
    def __or__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) | self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__or__(other))
    def __pos__(self):
        if self.exception is not None:
            return self
        return Value(super(Value, self).__pos__())
    def __pow__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) ** self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__pow__(other))
    def __lshift__(self, other):
        if self.exception is not None:
            return self
        # Ignore other signedness
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) << self.__uclamp(other), True) if (self.unsigned) else Value(super(Value, self).__lshift__(other))
    def __rshift__(self, other):
        if self.exception is not None:
            return self
        # Ignore other signedness
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) >> self.__uclamp(other), True) if (self.unsigned) else Value(super(Value, self).__rshift__(other))
    def __xor__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) ^ self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__xor__(other))
    def __repr__(self):
        if self.exception is not None:
            return "Exception(%s)" % repr(self.exception)
        elif self.unsigned:
            return "Value(%dU)" % INTBASETYPE(self)
        else:
            return "Value(%d)" % INTBASETYPE(self)
    def __bool__(self):
        assert False  # Do not use Python logical operations
    def __nonzero__(self):
        assert False  # Do not use Python logical operations
    def __cmp__(self, other):
        assert False
    def __lt__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) < self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) < self.__sclamp(other), False)
    def __le__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) <= self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) <= self.__sclamp(other), False)
    def __eq__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) == self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) == self.__sclamp(other), False)
    def __ne__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) != self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) != self.__sclamp(other), False)
    def __ge__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) >= self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) >= self.__sclamp(other), False)
    def __gt__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) > self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) > self.__sclamp(other), False)

        
# PLY yacc specification
# Valid C preprocessor expression items:
#   - Integer constants
#   - Character constants
#   - Addition, subtraction, multiplication, division, bitwise and-or-xor, shifts,
#     comparisons, logical and-or-not
#   - defined()
#
# The C preprocessor does not support:
#   - assignment
#   - increment and decrement
#   - array indexing, indirection
#   - casting
#   - sizeof, alignof

# The subset of tokens from Preprocessor used in preprocessor expressions
tokens = (
   'CPP_ID', 'CPP_INTEGER', 'CPP_CHAR', 'CPP_STRING',
   'CPP_PLUS', 'CPP_MINUS', 'CPP_STAR', 'CPP_FSLASH', 'CPP_PERCENT', 'CPP_BAR',
   'CPP_AMPERSAND', 'CPP_TILDE', 'CPP_HAT', 'CPP_LESS', 'CPP_GREATER', 'CPP_EXCLAMATION',
   'CPP_QUESTION', 'CPP_LPAREN', 'CPP_RPAREN',
   'CPP_COMMA', 'CPP_COLON',

   'CPP_LSHIFT', 'CPP_LESSEQUAL', 'CPP_RSHIFT',
   'CPP_GREATEREQUAL', 'CPP_LOGICALOR', 'CPP_LOGICALAND', 'CPP_EQUALITY',
   'CPP_INEQUALITY'
)
# 'CPP_WS', 'CPP_EQUAL',  'CPP_BSLASH', 'CPP_SQUOTE',

precedence = (
    ('left', 'CPP_COMMA'),                                                     # 15
                                                                               # 14 (assignments, unused)
    ('left', 'CPP_QUESTION', 'CPP_COLON'),                                     # 13
    ('left', 'CPP_LOGICALOR'),                                                 # 12
    ('left', 'CPP_LOGICALAND'),                                                # 11
    ('left', 'CPP_BAR'),                                                       # 10
    ('left', 'CPP_HAT'),                                                       # 9
    ('left', 'CPP_AMPERSAND'),                                                 # 8
    ('left', 'CPP_EQUALITY', 'CPP_INEQUALITY'),                                # 7
    ('left', 'CPP_LESS', 'CPP_LESSEQUAL', 'CPP_GREATER', 'CPP_GREATEREQUAL'),  # 6
    ('left', 'CPP_LSHIFT', 'CPP_RSHIFT'),                                      # 5
    ('left', 'CPP_PLUS', 'CPP_MINUS'),                                         # 4
    ('left', 'CPP_STAR', 'CPP_FSLASH', 'CPP_PERCENT'),                         # 3
    ('right', 'UPLUS', 'UMINUS', 'CPP_EXCLAMATION', 'CPP_TILDE'),              # 2
                                                                               # 1 (unused in the C preprocessor)
)

def p_error(p):
    if p:
        raise SyntaxError("around token '%s' type %s" % (p.value, p.type))
    else:
        raise SyntaxError("at EOF")

def p_expression_number(p):
    'expression : CPP_INTEGER'
    p[0] = Value(p[1])

def p_expression_character(p):
    'expression : CPP_CHAR'
    p[0] = Value(p[1])

def p_expression_string(p):
    """
    expression : CPP_STRING
              | CPP_LESS expression CPP_GREATER
    """
    p[0] = p[1]

def p_expression_group(t):
    'expression : CPP_LPAREN expression CPP_RPAREN'
    t[0] = t[2]

def p_expression_uplus(p):
    'expression : CPP_PLUS expression %prec UPLUS'
    p[0] = +Value(p[2])

def p_expression_uminus(p):
    'expression : CPP_MINUS expression %prec UMINUS'
    p[0] = -Value(p[2])

def p_expression_unop(p):
    """
    expression : CPP_EXCLAMATION expression
              | CPP_TILDE expression
    """
    try:
        if p[1] == '!':
            p[0] = Value(0) if (Value(p[2]).value()!=0) else Value(1)
        elif p[1] == '~':
            p[0] = ~Value(p[2])
    except Exception as e:
        p[0] = Value(0, exception = e)

def p_expression_binop(p):
    """
    expression : expression CPP_STAR expression
              | expression CPP_FSLASH expression
              | expression CPP_PERCENT expression
              | expression CPP_PLUS expression
              | expression CPP_MINUS expression
              | expression CPP_LSHIFT expression
              | expression CPP_RSHIFT expression
              | expression CPP_LESS expression
              | expression CPP_LESSEQUAL expression
              | expression CPP_GREATER expression
              | expression CPP_GREATEREQUAL expression
              | expression CPP_EQUALITY expression
              | expression CPP_INEQUALITY expression
              | expression CPP_AMPERSAND expression
              | expression CPP_HAT expression
              | expression CPP_BAR expression
              | expression CPP_LOGICALAND expression
              | expression CPP_LOGICALOR expression
              | expression CPP_COMMA expression
    """
    # print [repr(p[i]) for i in range(0,4)]
    try:
        if p[2] == '*':
            p[0] = Value(p[1]) * Value(p[3])
        elif p[2] == '/':
            p[0] = Value(p[1]) / Value(p[3])
        elif p[2] == '%':
            p[0] = Value(p[1]) % Value(p[3])
        elif p[2] == '+':
            p[0] = Value(p[1]) + Value(p[3])
        elif p[2] == '-':
            p[0] = Value(p[1]) - Value(p[3])
        elif p[2] == '<<':
            p[0] = Value(p[1]) << Value(p[3])
        elif p[2] == '>>':
            p[0] = Value(p[1]) >> Value(p[3])
        elif p[2] == '<':
            p[0] = Value(p[1]) < Value(p[3])
        elif p[2] == '<=':
            p[0] = Value(p[1]) <= Value(p[3])
        elif p[2] == '>':
            p[0] = Value(p[1]) > Value(p[3])
        elif p[2] == '>=':
            p[0] = Value(p[1]) >= Value(p[3])
        elif p[2] == '==':
            p[0] = Value(p[1]) == Value(p[3])
        elif p[2] == '!=':
            p[0] = Value(p[1]) != Value(p[3])
        elif p[2] == '&':
            p[0] = Value(p[1]) & Value(p[3])
        elif p[2] == '^':
            p[0] = Value(p[1]) ^ Value(p[3])
        elif p[2] == '|':
            p[0] = Value(p[1]) | Value(p[3])
        elif p[2] == '&&':
            p[0] = Value(1) if (Value(p[1]).value()!=0 and Value(p[3]).value()!=0) else Value(0)
        elif p[2] == '||':
            p[0] = Value(1) if (Value(p[1]).value()!=0 or Value(p[3]).value()!=0) else Value(0)
        elif p[2] == ',':
            p[0] = Value(p[3])
    except Exception as e:
        p[0] = Value(0, exception = e)

def p_expression_conditional(p):
    'expression : expression CPP_QUESTION expression CPP_COLON expression'
    try:
        # Output type must cast up to unsigned if either input is unsigned
        p[0] = Value(p[3]) if (Value(p[1]).value()!=0) else Value(p[5])
        try:
            p[0] = Value(p[0].value(), unsigned = Value(p[3]).unsigned or Value(p[5]).unsigned)
        except:
            pass
    except Exception as e:
        p[0] = Value(0, exception = e)

def p_expression_function_call(p):
    "expression : CPP_ID CPP_LPAREN expression CPP_RPAREN"
    try:
        p.lexer.on_function_call(p)
    except Exception as e:
        p[0] = Value(0, exception = e)

def p_expression_identifier(p):
    "expression : CPP_ID"
    try:
        p.lexer.on_identifier(p)
    except Exception as e:
        p[0] = Value(0, exception = e)


class Evaluator(object):
    """Evaluator of #if C preprocessor expressions.
    
    >>> e = Evaluator()
    >>> e('5')
    Value(5)
    >>> e('5+6')
    Value(11)
    >>> e('5+6*2')
    Value(17)
    >>> e('5/2+6*2')
    Value(14)
    >>> e('5 < 6 <= 7')
    Value(1)
    >>> e('5 < 6 && 8 > 7')
    Value(1)
    >>> e('18446744073709551615 == -1')
    Value(1)
    >>> e('-9223372036854775809 == 9223372036854775807')
    Value(1)
    >>> e('-1 < 0U')
    Value(0U)
    >>> e('(( 0L && 0) || (!0L && !0 ))')
    Value(1)
    >>> e('(1)?2:3')
    Value(2)
    >>> e('(1 ? -1 : 0) <= 0')
    Value(1)
    >>> e('(1 ? -1 : 0U)')       # Output type of ? must be common between both choices
    Value(18446744073709551615U)
    >>> e('(1 ? -1 : 0U) <= 0')
    Value(0U)
    >>> e('1 && 10 / 0')         # doctest: +ELLIPSIS
    Exception(ZeroDivisionError('division by zero'...
    >>> e('0 && 10 / 0')         # && must shortcut
    Value(0)
    >>> e('1 ? 10 / 0 : 0')      # doctest: +ELLIPSIS
    Exception(ZeroDivisionError('division by zero'...
    >>> e('0 ? 10 / 0 : 0')      # ? must shortcut
    Value(0)
    >>> e('(3 ^ 5) != 6 || (3 | 5) != 7 || (3 & 5) != 1')
    Value(0)
    >>> e('1 << 2 != 4 || 8 >> 1 != 4')
    Value(0)
    >>> e('(2 || 3) != 1 || (2 && 3) != 1 || (0 || 4) != 1 || (0 && 5) != 0')
    Value(0)
    >>> e('-1 << 3U > 0')
    Value(0)
    >>> e("'N' == 78")
    Value(1)
    >>> e('0x3f == 63')
    Value(1)
    >>> e("'\\\\n'")
    Value(10)
    >>> e("'\\\\\\\\'")
    Value(92)
    >>> e("'\\\\n' == 0xA")
    Value(1)
    >>> e("'\\\\\\\\' == 0x5c")
    Value(1)
    >>> e("L'\\\\0' == 0")
    Value(1)
    >>> e('12 == 12')
    Value(1)
    >>> e('12L == 12')
    Value(1)
    >>> e('-1 >= 0U')
    Value(1U)
    >>> e('(1<<2) == 4')
    Value(1)
    >>> e('(-!+!9) == -1')
    Value(1)
    >>> e('(2 || 3) == 1')
    Value(1)
    >>> e('1L * 3 != 3')
    Value(0)
    >>> e('(!1L != 0) || (-1L != -1)')
    Value(0)
    >>> e('0177777 == 65535')
    Value(1)
    >>> e('0Xffff != 65535 || 0XFfFf == 65535')
    Value(1)
    >>> e('0L != 0 || 0l != 0')
    Value(0)
    >>> e('1U != 1 || 1u == 1')
    Value(1)
    >>> e('0 <= -1')
    Value(0)
    >>> e('1 << 2 != 4 || 8 >> 1 == 4')
    Value(1)
    >>> e('(3 ^ 5) == 6')
    Value(1)
    >>> e('(3 | 5) == 7')
    Value(1)
    >>> e('(3 & 5) == 1')
    Value(1)
    >>> e('(3 ^ 5) != 6 || (3 | 5) != 7 || (3 & 5) != 1')
    Value(0)
    >>> e('(0 ? 1 : 2) != 2')
    Value(0)
    >>> e('-1 << 3U > 0')
    Value(0)
    >>> e('0 && 10 / 0')
    Value(0)
    >>> e('not_defined && 10 / not_defined')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown identifier not_defined'...
    >>> e('0 && 10 / 0 > 1')
    Value(0)
    >>> e('(0) ? 10 / 0 : 0')
    Value(0)
    >>> e('0 == 0 || 10 / 0 > 1')
    Value(1)
    >>> e('(15 >> 2 >> 1 != 1) || (3 << 2 << 1 != 24)')
    Value(0)
    >>> e('(1 | 2) == 3 && 4 != 5 || 0')
    Value(1)
    >>> e('1  >  0')
    Value(1)
    >>> e("'\123' != 83")
    Value(0)
    >>> e("'\x1b' != '\033'")
    Value(0)
    >>> e('0 + (1 - (2 + (3 - (4 + (5 - (6 + (7 - (8 + (9 - (10 + (11 - (12 +          (13 - (14 + (15 - (16 + (17 - (18 + (19 - (20 + (21 - (22 + (23 -           (24 + (25 - (26 + (27 - (28 + (29 - (30 + (31 - (32 + 0))))))))))           )))))))))))))))))))))) == 0')
    Value(1)
    >>> e('test_function(X)', functions={'test_function':lambda x: 55})
    Value(55)
    >>> e('test_identifier', identifiers={'test_identifier':11})
    Value(11)
    >>> e('defined(X)', functions={'defined':lambda x: 55})
    Value(55)
    >>> e('defined(X)')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown function defined'...
    >>> e('__has_include("variant")')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown function __has_include'...
    >>> e('__has_include(<variant>)')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown function __has_include'...
    >>> e('5  // comment')
    Value(5)
    >>> e('5  /* comment */')
    Value(5)
    >>> e('5  /* comment // more */')
    Value(5)
    >>> e('5  // /* comment */')
    Value(5)
    """
#    >>> e('defined X', functions={'defined':lambda x: 55})
#    Value(55)

    def __init__(self, lexer = None):
        self.lexer = lexer if lexer is not None else default_lexer()
        self.parser = yacc.yacc(optimize=in_production,debug=not in_production,write_tables=not in_production)

    class __lexer(object):

        def __init__(self, functions, identifiers):
            self.__toks = []
            self.__functions = functions
            self.__identifiers = identifiers

        def input(self, toks):
            self.__toks = [tok for tok in toks if tok.type != 'CPP_WS' and tok.type != 'CPP_LINECONT' and tok.type != 'CPP_COMMENT1' and tok.type != 'CPP_COMMENT2']
            self.__idx = 0

        def token(self):
            if self.__idx >= len(self.__toks):
                return None
            self.__idx = self.__idx + 1
            return self.__toks[self.__idx - 1]

        def on_function_call(self, p):
            if p[1] not in self.__functions:
                raise SyntaxError('Unknown function %s' % p[1])
            p[0] = Value(self.__functions[p[1]](p[3]))

        def on_identifier(self, p):
            if p[1] not in self.__identifiers:
                raise SyntaxError('Unknown identifier %s' % p[1])
            p[0] = Value(self.__identifiers[p[1]])
            
    def __call__(self, input, functions = {}, identifiers = {}):
        """Execute a fully macro expanded set of tokens representing an expression,
        returning the result of the evaluation.
        """
        if not isinstance(input,list):
            self.lexer.input(input)
            input = []
            while True:
                tok = self.lexer.token()
                if not tok:
                    break
                input.append(tok)
        return self.parser.parse(input, lexer = self.__lexer(functions, identifiers))


if __name__ == "__main__":
    import doctest
    doctest.testmod()

Functions

def p_error(p)
Source code
def p_error(p):
    if p:
        raise SyntaxError("around token '%s' type %s" % (p.value, p.type))
    else:
        raise SyntaxError("at EOF")
def p_expression_binop(p)
expression : expression CPP_STAR expression
| expression CPP_FSLASH expression | expression CPP_PERCENT expression | expression CPP_PLUS expression | expression CPP_MINUS expression | expression CPP_LSHIFT expression | expression CPP_RSHIFT expression | expression CPP_LESS expression | expression CPP_LESSEQUAL expression | expression CPP_GREATER expression | expression CPP_GREATEREQUAL expression | expression CPP_EQUALITY expression | expression CPP_INEQUALITY expression | expression CPP_AMPERSAND expression | expression CPP_HAT expression | expression CPP_BAR expression | expression CPP_LOGICALAND expression | expression CPP_LOGICALOR expression | expression CPP_COMMA expression
Source code
def p_expression_binop(p):
    """
    expression : expression CPP_STAR expression
              | expression CPP_FSLASH expression
              | expression CPP_PERCENT expression
              | expression CPP_PLUS expression
              | expression CPP_MINUS expression
              | expression CPP_LSHIFT expression
              | expression CPP_RSHIFT expression
              | expression CPP_LESS expression
              | expression CPP_LESSEQUAL expression
              | expression CPP_GREATER expression
              | expression CPP_GREATEREQUAL expression
              | expression CPP_EQUALITY expression
              | expression CPP_INEQUALITY expression
              | expression CPP_AMPERSAND expression
              | expression CPP_HAT expression
              | expression CPP_BAR expression
              | expression CPP_LOGICALAND expression
              | expression CPP_LOGICALOR expression
              | expression CPP_COMMA expression
    """
    # print [repr(p[i]) for i in range(0,4)]
    try:
        if p[2] == '*':
            p[0] = Value(p[1]) * Value(p[3])
        elif p[2] == '/':
            p[0] = Value(p[1]) / Value(p[3])
        elif p[2] == '%':
            p[0] = Value(p[1]) % Value(p[3])
        elif p[2] == '+':
            p[0] = Value(p[1]) + Value(p[3])
        elif p[2] == '-':
            p[0] = Value(p[1]) - Value(p[3])
        elif p[2] == '<<':
            p[0] = Value(p[1]) << Value(p[3])
        elif p[2] == '>>':
            p[0] = Value(p[1]) >> Value(p[3])
        elif p[2] == '<':
            p[0] = Value(p[1]) < Value(p[3])
        elif p[2] == '<=':
            p[0] = Value(p[1]) <= Value(p[3])
        elif p[2] == '>':
            p[0] = Value(p[1]) > Value(p[3])
        elif p[2] == '>=':
            p[0] = Value(p[1]) >= Value(p[3])
        elif p[2] == '==':
            p[0] = Value(p[1]) == Value(p[3])
        elif p[2] == '!=':
            p[0] = Value(p[1]) != Value(p[3])
        elif p[2] == '&':
            p[0] = Value(p[1]) & Value(p[3])
        elif p[2] == '^':
            p[0] = Value(p[1]) ^ Value(p[3])
        elif p[2] == '|':
            p[0] = Value(p[1]) | Value(p[3])
        elif p[2] == '&&':
            p[0] = Value(1) if (Value(p[1]).value()!=0 and Value(p[3]).value()!=0) else Value(0)
        elif p[2] == '||':
            p[0] = Value(1) if (Value(p[1]).value()!=0 or Value(p[3]).value()!=0) else Value(0)
        elif p[2] == ',':
            p[0] = Value(p[3])
    except Exception as e:
        p[0] = Value(0, exception = e)
def p_expression_character(p)
expression : CPP_CHAR
 
Source code
def p_expression_character(p):
    'expression : CPP_CHAR'
    p[0] = Value(p[1])
def p_expression_conditional(p)
expression : expression CPP_QUESTION expression CPP_COLON expression
 
Source code
def p_expression_conditional(p):
    'expression : expression CPP_QUESTION expression CPP_COLON expression'
    try:
        # Output type must cast up to unsigned if either input is unsigned
        p[0] = Value(p[3]) if (Value(p[1]).value()!=0) else Value(p[5])
        try:
            p[0] = Value(p[0].value(), unsigned = Value(p[3]).unsigned or Value(p[5]).unsigned)
        except:
            pass
    except Exception as e:
        p[0] = Value(0, exception = e)
def p_expression_function_call(p)
expression : CPP_ID CPP_LPAREN expression CPP_RPAREN
 
Source code
def p_expression_function_call(p):
    "expression : CPP_ID CPP_LPAREN expression CPP_RPAREN"
    try:
        p.lexer.on_function_call(p)
    except Exception as e:
        p[0] = Value(0, exception = e)
def p_expression_group(t)
expression : CPP_LPAREN expression CPP_RPAREN
 
Source code
def p_expression_group(t):
    'expression : CPP_LPAREN expression CPP_RPAREN'
    t[0] = t[2]
def p_expression_identifier(p)
expression : CPP_ID
 
Source code
def p_expression_identifier(p):
    "expression : CPP_ID"
    try:
        p.lexer.on_identifier(p)
    except Exception as e:
        p[0] = Value(0, exception = e)
def p_expression_number(p)
expression : CPP_INTEGER
 
Source code
def p_expression_number(p):
    'expression : CPP_INTEGER'
    p[0] = Value(p[1])
def p_expression_string(p)
expression : CPP_STRING
| CPP_LESS expression CPP_GREATER
Source code
def p_expression_string(p):
    """
    expression : CPP_STRING
              | CPP_LESS expression CPP_GREATER
    """
    p[0] = p[1]
def p_expression_uminus(p)
expression : CPP_MINUS expression %prec UMINUS
 
Source code
def p_expression_uminus(p):
    'expression : CPP_MINUS expression %prec UMINUS'
    p[0] = -Value(p[2])
def p_expression_unop(p)
expression : CPP_EXCLAMATION expression
| CPP_TILDE expression
Source code
def p_expression_unop(p):
    """
    expression : CPP_EXCLAMATION expression
              | CPP_TILDE expression
    """
    try:
        if p[1] == '!':
            p[0] = Value(0) if (Value(p[2]).value()!=0) else Value(1)
        elif p[1] == '~':
            p[0] = ~Value(p[2])
    except Exception as e:
        p[0] = Value(0, exception = e)
def p_expression_uplus(p)
expression : CPP_PLUS expression %prec UPLUS
 
Source code
def p_expression_uplus(p):
    'expression : CPP_PLUS expression %prec UPLUS'
    p[0] = +Value(p[2])

Classes

class Evaluator

Evaluator of #if C preprocessor expressions.

>>> e = Evaluator()
>>> e('5')
Value(5)
>>> e('5+6')
Value(11)
>>> e('5+6*2')
Value(17)
>>> e('5/2+6*2')
Value(14)
>>> e('5 < 6 <= 7')
Value(1)
>>> e('5 < 6 && 8 > 7')
Value(1)
>>> e('18446744073709551615 == -1')
Value(1)
>>> e('-9223372036854775809 == 9223372036854775807')
Value(1)
>>> e('-1 < 0U')
Value(0U)
>>> e('(( 0L && 0) || (!0L && !0 ))')
Value(1)
>>> e('(1)?2:3')
Value(2)
>>> e('(1 ? -1 : 0) <= 0')
Value(1)
>>> e('(1 ? -1 : 0U)')       # Output type of ? must be common between both choices
Value(18446744073709551615U)
>>> e('(1 ? -1 : 0U) <= 0')
Value(0U)
>>> e('1 && 10 / 0')         # doctest: +ELLIPSIS
Exception(ZeroDivisionError('division by zero'...
>>> e('0 && 10 / 0')         # && must shortcut
Value(0)
>>> e('1 ? 10 / 0 : 0')      # doctest: +ELLIPSIS
Exception(ZeroDivisionError('division by zero'...
>>> e('0 ? 10 / 0 : 0')      # ? must shortcut
Value(0)
>>> e('(3 ^ 5) != 6 || (3 | 5) != 7 || (3 & 5) != 1')
Value(0)
>>> e('1 << 2 != 4 || 8 >> 1 != 4')
Value(0)
>>> e('(2 || 3) != 1 || (2 && 3) != 1 || (0 || 4) != 1 || (0 && 5) != 0')
Value(0)
>>> e('-1 << 3U > 0')
Value(0)
>>> e("'N' == 78")
Value(1)
>>> e('0x3f == 63')
Value(1)
>>> e("'\\n'")
Value(10)
>>> e("'\\\\'")
Value(92)
>>> e("'\\n' == 0xA")
Value(1)
>>> e("'\\\\' == 0x5c")
Value(1)
>>> e("L'\\0' == 0")
Value(1)
>>> e('12 == 12')
Value(1)
>>> e('12L == 12')
Value(1)
>>> e('-1 >= 0U')
Value(1U)
>>> e('(1<<2) == 4')
Value(1)
>>> e('(-!+!9) == -1')
Value(1)
>>> e('(2 || 3) == 1')
Value(1)
>>> e('1L * 3 != 3')
Value(0)
>>> e('(!1L != 0) || (-1L != -1)')
Value(0)
>>> e('0177777 == 65535')
Value(1)
>>> e('0Xffff != 65535 || 0XFfFf == 65535')
Value(1)
>>> e('0L != 0 || 0l != 0')
Value(0)
>>> e('1U != 1 || 1u == 1')
Value(1)
>>> e('0 <= -1')
Value(0)
>>> e('1 << 2 != 4 || 8 >> 1 == 4')
Value(1)
>>> e('(3 ^ 5) == 6')
Value(1)
>>> e('(3 | 5) == 7')
Value(1)
>>> e('(3 & 5) == 1')
Value(1)
>>> e('(3 ^ 5) != 6 || (3 | 5) != 7 || (3 & 5) != 1')
Value(0)
>>> e('(0 ? 1 : 2) != 2')
Value(0)
>>> e('-1 << 3U > 0')
Value(0)
>>> e('0 && 10 / 0')
Value(0)
>>> e('not_defined && 10 / not_defined')  # doctest: +ELLIPSIS
Exception(SyntaxError('Unknown identifier not_defined'...
>>> e('0 && 10 / 0 > 1')
Value(0)
>>> e('(0) ? 10 / 0 : 0')
Value(0)
>>> e('0 == 0 || 10 / 0 > 1')
Value(1)
>>> e('(15 >> 2 >> 1 != 1) || (3 << 2 << 1 != 24)')
Value(0)
>>> e('(1 | 2) == 3 && 4 != 5 || 0')
Value(1)
>>> e('1  >  0')
Value(1)
>>> e("'S' != 83")
Value(0)
>>> e("'' != ''")
Value(0)
>>> e('0 + (1 - (2 + (3 - (4 + (5 - (6 + (7 - (8 + (9 - (10 + (11 - (12 +          (13 - (14 + (15 - (16 + (17 - (18 + (19 - (20 + (21 - (22 + (23 -           (24 + (25 - (26 + (27 - (28 + (29 - (30 + (31 - (32 + 0))))))))))           )))))))))))))))))))))) == 0')
Value(1)
>>> e('test_function(X)', functions={'test_function':lambda x: 55})
Value(55)
>>> e('test_identifier', identifiers={'test_identifier':11})
Value(11)
>>> e('defined(X)', functions={'defined':lambda x: 55})
Value(55)
>>> e('defined(X)')  # doctest: +ELLIPSIS
Exception(SyntaxError('Unknown function defined'...
>>> e('__has_include("variant")')  # doctest: +ELLIPSIS
Exception(SyntaxError('Unknown function __has_include'...
>>> e('__has_include(<variant>)')  # doctest: +ELLIPSIS
Exception(SyntaxError('Unknown function __has_include'...
>>> e('5  // comment')
Value(5)
>>> e('5  /* comment */')
Value(5)
>>> e('5  /* comment // more */')
Value(5)
>>> e('5  // /* comment */')

Value(5)

Source code
class Evaluator(object):
    """Evaluator of #if C preprocessor expressions.
    
    >>> e = Evaluator()
    >>> e('5')
    Value(5)
    >>> e('5+6')
    Value(11)
    >>> e('5+6*2')
    Value(17)
    >>> e('5/2+6*2')
    Value(14)
    >>> e('5 < 6 <= 7')
    Value(1)
    >>> e('5 < 6 && 8 > 7')
    Value(1)
    >>> e('18446744073709551615 == -1')
    Value(1)
    >>> e('-9223372036854775809 == 9223372036854775807')
    Value(1)
    >>> e('-1 < 0U')
    Value(0U)
    >>> e('(( 0L && 0) || (!0L && !0 ))')
    Value(1)
    >>> e('(1)?2:3')
    Value(2)
    >>> e('(1 ? -1 : 0) <= 0')
    Value(1)
    >>> e('(1 ? -1 : 0U)')       # Output type of ? must be common between both choices
    Value(18446744073709551615U)
    >>> e('(1 ? -1 : 0U) <= 0')
    Value(0U)
    >>> e('1 && 10 / 0')         # doctest: +ELLIPSIS
    Exception(ZeroDivisionError('division by zero'...
    >>> e('0 && 10 / 0')         # && must shortcut
    Value(0)
    >>> e('1 ? 10 / 0 : 0')      # doctest: +ELLIPSIS
    Exception(ZeroDivisionError('division by zero'...
    >>> e('0 ? 10 / 0 : 0')      # ? must shortcut
    Value(0)
    >>> e('(3 ^ 5) != 6 || (3 | 5) != 7 || (3 & 5) != 1')
    Value(0)
    >>> e('1 << 2 != 4 || 8 >> 1 != 4')
    Value(0)
    >>> e('(2 || 3) != 1 || (2 && 3) != 1 || (0 || 4) != 1 || (0 && 5) != 0')
    Value(0)
    >>> e('-1 << 3U > 0')
    Value(0)
    >>> e("'N' == 78")
    Value(1)
    >>> e('0x3f == 63')
    Value(1)
    >>> e("'\\\\n'")
    Value(10)
    >>> e("'\\\\\\\\'")
    Value(92)
    >>> e("'\\\\n' == 0xA")
    Value(1)
    >>> e("'\\\\\\\\' == 0x5c")
    Value(1)
    >>> e("L'\\\\0' == 0")
    Value(1)
    >>> e('12 == 12')
    Value(1)
    >>> e('12L == 12')
    Value(1)
    >>> e('-1 >= 0U')
    Value(1U)
    >>> e('(1<<2) == 4')
    Value(1)
    >>> e('(-!+!9) == -1')
    Value(1)
    >>> e('(2 || 3) == 1')
    Value(1)
    >>> e('1L * 3 != 3')
    Value(0)
    >>> e('(!1L != 0) || (-1L != -1)')
    Value(0)
    >>> e('0177777 == 65535')
    Value(1)
    >>> e('0Xffff != 65535 || 0XFfFf == 65535')
    Value(1)
    >>> e('0L != 0 || 0l != 0')
    Value(0)
    >>> e('1U != 1 || 1u == 1')
    Value(1)
    >>> e('0 <= -1')
    Value(0)
    >>> e('1 << 2 != 4 || 8 >> 1 == 4')
    Value(1)
    >>> e('(3 ^ 5) == 6')
    Value(1)
    >>> e('(3 | 5) == 7')
    Value(1)
    >>> e('(3 & 5) == 1')
    Value(1)
    >>> e('(3 ^ 5) != 6 || (3 | 5) != 7 || (3 & 5) != 1')
    Value(0)
    >>> e('(0 ? 1 : 2) != 2')
    Value(0)
    >>> e('-1 << 3U > 0')
    Value(0)
    >>> e('0 && 10 / 0')
    Value(0)
    >>> e('not_defined && 10 / not_defined')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown identifier not_defined'...
    >>> e('0 && 10 / 0 > 1')
    Value(0)
    >>> e('(0) ? 10 / 0 : 0')
    Value(0)
    >>> e('0 == 0 || 10 / 0 > 1')
    Value(1)
    >>> e('(15 >> 2 >> 1 != 1) || (3 << 2 << 1 != 24)')
    Value(0)
    >>> e('(1 | 2) == 3 && 4 != 5 || 0')
    Value(1)
    >>> e('1  >  0')
    Value(1)
    >>> e("'\123' != 83")
    Value(0)
    >>> e("'\x1b' != '\033'")
    Value(0)
    >>> e('0 + (1 - (2 + (3 - (4 + (5 - (6 + (7 - (8 + (9 - (10 + (11 - (12 +          (13 - (14 + (15 - (16 + (17 - (18 + (19 - (20 + (21 - (22 + (23 -           (24 + (25 - (26 + (27 - (28 + (29 - (30 + (31 - (32 + 0))))))))))           )))))))))))))))))))))) == 0')
    Value(1)
    >>> e('test_function(X)', functions={'test_function':lambda x: 55})
    Value(55)
    >>> e('test_identifier', identifiers={'test_identifier':11})
    Value(11)
    >>> e('defined(X)', functions={'defined':lambda x: 55})
    Value(55)
    >>> e('defined(X)')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown function defined'...
    >>> e('__has_include("variant")')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown function __has_include'...
    >>> e('__has_include(<variant>)')  # doctest: +ELLIPSIS
    Exception(SyntaxError('Unknown function __has_include'...
    >>> e('5  // comment')
    Value(5)
    >>> e('5  /* comment */')
    Value(5)
    >>> e('5  /* comment // more */')
    Value(5)
    >>> e('5  // /* comment */')
    Value(5)
    """
#    >>> e('defined X', functions={'defined':lambda x: 55})
#    Value(55)

    def __init__(self, lexer = None):
        self.lexer = lexer if lexer is not None else default_lexer()
        self.parser = yacc.yacc(optimize=in_production,debug=not in_production,write_tables=not in_production)

    class __lexer(object):

        def __init__(self, functions, identifiers):
            self.__toks = []
            self.__functions = functions
            self.__identifiers = identifiers

        def input(self, toks):
            self.__toks = [tok for tok in toks if tok.type != 'CPP_WS' and tok.type != 'CPP_LINECONT' and tok.type != 'CPP_COMMENT1' and tok.type != 'CPP_COMMENT2']
            self.__idx = 0

        def token(self):
            if self.__idx >= len(self.__toks):
                return None
            self.__idx = self.__idx + 1
            return self.__toks[self.__idx - 1]

        def on_function_call(self, p):
            if p[1] not in self.__functions:
                raise SyntaxError('Unknown function %s' % p[1])
            p[0] = Value(self.__functions[p[1]](p[3]))

        def on_identifier(self, p):
            if p[1] not in self.__identifiers:
                raise SyntaxError('Unknown identifier %s' % p[1])
            p[0] = Value(self.__identifiers[p[1]])
            
    def __call__(self, input, functions = {}, identifiers = {}):
        """Execute a fully macro expanded set of tokens representing an expression,
        returning the result of the evaluation.
        """
        if not isinstance(input,list):
            self.lexer.input(input)
            input = []
            while True:
                tok = self.lexer.token()
                if not tok:
                    break
                input.append(tok)
        return self.parser.parse(input, lexer = self.__lexer(functions, identifiers))

Methods

def __init__(self, lexer=None)

Initialize self. See help(type(self)) for accurate signature.

Source code
def __init__(self, lexer = None):
    self.lexer = lexer if lexer is not None else default_lexer()
    self.parser = yacc.yacc(optimize=in_production,debug=not in_production,write_tables=not in_production)
class Value (ancestors: builtins.int)

A signed or unsigned integer within a preprocessor expression, bounded to within INT_MIN and INT_MAX, or 0 and UINT_MAX. Signed overflow is handled like a two's complement CPU, despite being UB, as that's what GCC and clang do.

>>> Value(5)
Value(5)
>>> Value('5L')
Value(5)
>>> Value('5U')
Value(5U)
>>> Value('0')
Value(0)
>>> Value('0U')
Value(0U)
>>> Value('-1U')
Value(18446744073709551615U)
>>> Value(5) * Value(2)
Value(10)
>>> Value(5) + Value('2u')
Value(7U)
>>> Value(5) * 2
Value(10)
>>> Value(5) / 2   # Must return integer
Value(2)
>>> Value(50) % 8
Value(2)
>>> -Value(5)
Value(-5)
>>> +Value(-5)
Value(-5)
>>> ~Value(5)
Value(-6)
>>> Value(6) & 2
Value(2)
>>> Value(4) | 2
Value(6)
>>> Value(6) ^ 2
Value(4)
>>> Value(2) << 2
Value(8)
>>> Value(8) >> 2
Value(2)
>>> Value(9223372036854775808)
Value(-9223372036854775808)
>>> Value(-9223372036854775809)
Value(9223372036854775807)
>>> Value(18446744073709551615)
Value(-1)
>>> Value(False)
Value(0)
>>> Value(True)
Value(1)
>>> Value(5) == Value(6)
Value(0)
>>> Value(5) == Value(5)
Value(1)
>>> not Value(2)
Traceback (most recent call last):

… AssertionError

>>> Value(4) and Value(2)
Traceback (most recent call last):

… AssertionError

>>> Value(5) and not Value(6)
Traceback (most recent call last):

… AssertionError

>>> Value('0x3f')
Value(63)
>>> Value('077')
Value(63)
>>> Value("'N'")
Value(78)
>>> Value("L'N'")
Value(78)
>>> Value("'\n'")
Value(10)
>>> Value("'\\n'")
Value(10)
>>> Value("'\\'")
Value(92)
>>> Value("'\'")
Traceback (most recent call last):
SyntaxError : Empty character escape sequence
 
Source code
class Value(INTBASETYPE):
    """A signed or unsigned integer within a preprocessor expression, bounded
    to within INT_MIN and INT_MAX, or 0 and UINT_MAX. Signed overflow is handled
    like a two's complement CPU, despite being UB, as that's what GCC and clang do.
    
    >>> Value(5)
    Value(5)
    >>> Value('5L')
    Value(5)
    >>> Value('5U')
    Value(5U)
    >>> Value('0')
    Value(0)
    >>> Value('0U')
    Value(0U)
    >>> Value('-1U')
    Value(18446744073709551615U)
    >>> Value(5) * Value(2)
    Value(10)
    >>> Value(5) + Value('2u')
    Value(7U)
    >>> Value(5) * 2
    Value(10)
    >>> Value(5) / 2   # Must return integer
    Value(2)
    >>> Value(50) % 8
    Value(2)
    >>> -Value(5)
    Value(-5)
    >>> +Value(-5)
    Value(-5)
    >>> ~Value(5)
    Value(-6)
    >>> Value(6) & 2
    Value(2)
    >>> Value(4) | 2
    Value(6)
    >>> Value(6) ^ 2
    Value(4)
    >>> Value(2) << 2
    Value(8)
    >>> Value(8) >> 2
    Value(2)
    >>> Value(9223372036854775808)
    Value(-9223372036854775808)
    >>> Value(-9223372036854775809)
    Value(9223372036854775807)
    >>> Value(18446744073709551615)
    Value(-1)
    >>> Value(False)
    Value(0)
    >>> Value(True)
    Value(1)
    >>> Value(5) == Value(6)
    Value(0)
    >>> Value(5) == Value(5)
    Value(1)
    >>> not Value(2)
    Traceback (most recent call last):
    ...
    AssertionError
    >>> Value(4) and Value(2)
    Traceback (most recent call last):
    ...
    AssertionError
    >>> Value(5) and not Value(6)
    Traceback (most recent call last):
    ...
    AssertionError
    >>> Value('0x3f')
    Value(63)
    >>> Value('077')
    Value(63)
    >>> Value("'N'")
    Value(78)
    >>> Value("L'N'")
    Value(78)
    >>> Value("'\\n'")
    Value(10)
    >>> Value("'\\\\n'")
    Value(10)
    >>> Value("'\\\\'")
    Value(92)
    >>> Value("'\\'")
    Traceback (most recent call last):
    ...
    SyntaxError: Empty character escape sequence
    """
    INT_MIN = -(1 << (INTMAXBITS - 1))
    INT_MAX = (1 << (INTMAXBITS - 1)) - 1
    INT_MASK = (1 << INTMAXBITS) - 1
    UINT_MIN = 0
    UINT_MAX = (1 << INTMAXBITS) - 1
    @classmethod
    def __sclamp(cls, value):
        value = INTBASETYPE(value)
        return ((value - cls.INT_MIN) & cls.INT_MASK) + cls.INT_MIN
    @classmethod
    def __uclamp(cls, value):
        value = INTBASETYPE(value)
        return value & cls.UINT_MAX
    def __new__(cls, value, unsigned = False, exception = None):
        if isinstance(value, Value):
            unsigned = value.unsigned
            exception = value.exception
        elif isinstance(value, INTBASETYPE) or isinstance(value, int) or isinstance(value, float):
            value = cls.__uclamp(value) if unsigned else cls.__sclamp(value)
        elif isinstance(value, STRING_TYPES):
            if (value.startswith("L'") or value[0] == "'") and value[-1] == "'":
                startidx = 2 if value.startswith("L'") else 1
                #print("1. ***", value, file = sys.stderr)
                value = value[startidx:-1]
                if len(value) == 0:
                    raise SyntaxError('Empty character escape sequence')
                #print("2. ***", value, file = sys.stderr)
                value = _expand_escape_sequences_pat.sub(lambda x: codecs.decode(x.group(0), 'unicode-escape'), value)
                #print("3. ***", value, file = sys.stderr)
                x = INTBASETYPE(ord(value))
                #print("4. ***", x, file = sys.stderr)
            elif value.startswith('0x') or value.startswith('0X'):
                # Strip any terminators
                while not ((value[-1] >= '0' and value[-1] <= '9') or (value[-1] >= 'a' and value[-1] <= 'f') or (value[-1] >= 'A' and value[-1] <= 'F')):
                    if value[-1] == 'u' or value[-1] == 'U':
                        unsigned = True
                    value = value[:-1]
                x = INTBASETYPE(value, base = 16)
            elif value.startswith('0'):
                # Strip any terminators
                while not (value[-1] >= '0' and value[-1] <= '7'):
                    if value[-1] == 'u' or value[-1] == 'U':
                        unsigned = True
                    value = value[:-1]
                x = INTBASETYPE(value, base = 8)
            else:
                # Strip any terminators
                while not (value[-1] >= '0' and value[-1] <= '9'):
                    if value[-1] == 'u' or value[-1] == 'U':
                        unsigned = True
                    value = value[:-1]
                x = INTBASETYPE(value)
            value = cls.__uclamp(x) if unsigned else cls.__sclamp(x)
            #assert x == value
        else:
            print('Unknown value type: %s' % repr(type(value)), file = sys.stderr)
            assert False  # Input is an unrecognised type
        inst = super(Value, cls).__new__(cls, value)
        inst.unsigned = unsigned
        inst.exception = exception
        return inst
    def value(self):
        if self.exception is not None:
            raise self.exception
        return INTBASETYPE(self)
    def __add__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) + self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__add__(other))
    def __sub__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) - self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__sub__(other))
    def __mul__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) * self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__mul__(other))
    def __div__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) / self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__div__(other))
    def __truediv__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) / self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__truediv__(other))
    def __mod__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) % self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__mod__(other))
    def __neg__(self):
        if self.exception is not None:
            return self
        return Value(super(Value, self).__neg__(), self.unsigned)
    def __invert__(self):
        if self.exception is not None:
            return self
        return Value(super(Value, self).__invert__(), self.unsigned)
    def __and__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) & self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__and__(other))
    def __or__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) | self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__or__(other))
    def __pos__(self):
        if self.exception is not None:
            return self
        return Value(super(Value, self).__pos__())
    def __pow__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) ** self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__pow__(other))
    def __lshift__(self, other):
        if self.exception is not None:
            return self
        # Ignore other signedness
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) << self.__uclamp(other), True) if (self.unsigned) else Value(super(Value, self).__lshift__(other))
    def __rshift__(self, other):
        if self.exception is not None:
            return self
        # Ignore other signedness
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) >> self.__uclamp(other), True) if (self.unsigned) else Value(super(Value, self).__rshift__(other))
    def __xor__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) ^ self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(super(Value, self).__xor__(other))
    def __repr__(self):
        if self.exception is not None:
            return "Exception(%s)" % repr(self.exception)
        elif self.unsigned:
            return "Value(%dU)" % INTBASETYPE(self)
        else:
            return "Value(%d)" % INTBASETYPE(self)
    def __bool__(self):
        assert False  # Do not use Python logical operations
    def __nonzero__(self):
        assert False  # Do not use Python logical operations
    def __cmp__(self, other):
        assert False
    def __lt__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) < self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) < self.__sclamp(other), False)
    def __le__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) <= self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) <= self.__sclamp(other), False)
    def __eq__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) == self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) == self.__sclamp(other), False)
    def __ne__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) != self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) != self.__sclamp(other), False)
    def __ge__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) >= self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) >= self.__sclamp(other), False)
    def __gt__(self, other):
        if self.exception is not None:
            return self
        other = Value(other)
        if other.exception is not None:
            return other
        return Value(self.__uclamp(self) > self.__uclamp(other), True) if (self.unsigned or other.unsigned) else Value(self.__sclamp(self) > self.__sclamp(other), False)

Class variables

var INT_MASK
var INT_MAX
var INT_MIN
var UINT_MAX
var UINT_MIN

Methods

def value(self)
Source code
def value(self):
    if self.exception is not None:
        raise self.exception
    return INTBASETYPE(self)