Beispiel #1
0
    def test_underscores(self):
        """Test identifiers with underscores."""
        self.assertFalse(is_valid_identifier('_var'))
        self.assertFalse(is_valid_identifier('___var'))

        self.assertTrue(is_valid_identifier('va_r'))
        self.assertTrue(is_valid_identifier('var_'))
Beispiel #2
0
 def test_keywords(self):
     """Test that some Python keywords not valid identifiers."""
     self.assertFalse(is_valid_identifier('True'))
     self.assertFalse(is_valid_identifier('False'))
     self.assertFalse(is_valid_identifier('is'))
     self.assertFalse(is_valid_identifier('not'))
     self.assertFalse(is_valid_identifier('while'))
Beispiel #3
0
 def test_invalid_symbols(self):
     """Test invalid identifier containing bad symbols."""
     self.assertFalse(is_valid_identifier('#'))
     self.assertFalse(is_valid_identifier('!v!a!r!'))
     self.assertFalse(is_valid_identifier('var@'))
     self.assertFalse(is_valid_identifier('v(a)r'))
     self.assertFalse(is_valid_identifier('v``ar'))
Beispiel #4
0
    def test_numbers(self):
        """Test invalid identifier with leading number."""
        self.assertFalse(is_valid_identifier('9var'))
        self.assertFalse(is_valid_identifier('10var'))

        self.assertTrue(is_valid_identifier('v9ar'))
        self.assertTrue(is_valid_identifier('var12345'))
Beispiel #5
0
    def _init_from_values(self, from_values, ordering):
        if isinstance(from_values, str):
            valid = {'0', '1', DONT_CARE_VALUE}
            if not from_values:
                raise InvalidArgumentValueError(
                    'Cannot specify an empty string')
            elif not all(value in valid for value in from_values):
                raise InvalidBooleanValueError(
                    'Invalid Boolean/don\'t care value specified')
        else:
            raise InvalidArgumentTypeError(
                '`from_values` must either be a string or list of strings')

        num_values = len(from_values)
        if (num_values & (num_values - 1)) != 0:
            # assert that number of input values is a power of 2
            raise InvalidArgumentValueError(
                'Must specify a number of input values that is a power of 2')

        user_gave_symbols = ordering is not None
        if not user_gave_symbols:
            # user left it up to us to generate symbols
            num_required_symbols = int(log(num_values, 2))
            self._ordering = TruthTable.generate_symbols(num_required_symbols)
        elif not isinstance(ordering, list):
            raise InvalidArgumentTypeError('`ordering` must be a list')
        elif not all(isinstance(elt, str) for elt in ordering):
            raise InvalidArgumentTypeError(
                '`ordering` must only contain strings')
        else:
            # validate user-provided ordering/symbols
            num_symbols = len(ordering)
            if not num_symbols:
                raise InvalidArgumentValueError(
                    'If specifying `ordering`, it must be non-empty')

            num_expected_values = 2**num_symbols
            if num_values < num_expected_values:
                raise ExtraSymbolError(
                    'Too many symbols provided for the specified values')
            elif num_values > num_expected_values:
                raise MissingSymbolError(
                    'Too few symbols provided for the specified values')

            # verify all symbols are valid identifiers
            for symbol_name in ordering:
                if not is_valid_identifier(symbol_name):
                    raise InvalidIdentifierError(
                        '"{}" in ordering is not a valid symbol name'.format(
                            symbol_name),
                        None, None)

            self._ordering = ordering

        self._expr = None

        bool_dict = {'0': False, '1': True, DONT_CARE_VALUE: DONT_CARE_VALUE}
        self._results = [bool_dict[v] for v in from_values]
        self._num_filled_slots = len(self._results)
Beispiel #6
0
    def _tokenize(self):
        """Make the first pass through the expression, tokenizing it.

        This method is a helper for initializing an expression object from a
        string and will populate the ``_symbols``, ``_symbol_set``, and
        ``_tokens`` attributes of this object.

        :raises GrammarError: If a malformed expression is received.

        """
        operator_strs = [k for k in OPERATOR_MAPPING.keys()]
        is_symbolic = {op: not op[0].isalpha() for op in operator_strs}
        operator_search_list = sorted(operator_strs, key=len, reverse=True)
        delimiters = DELIMITERS | set(k[0]
                                      for k, v in is_symbolic.items() if v)
        EXPECTING_OPERAND = 1
        EXPECTING_OPERATOR = 2
        grammar_state = EXPECTING_OPERAND

        idx = 0
        open_paren_count = 0
        num_chars = len(self._raw_expr)

        while idx < num_chars:
            c = self._raw_expr[idx].strip()

            if not c:
                # do nothing
                idx += 1
            elif c == '(':
                if grammar_state != EXPECTING_OPERAND:
                    raise BadParenPositionError('Unexpected parenthesis',
                                                self._raw_expr, idx)

                open_paren_count += 1
                self._tokens.append(c)
                idx += 1
            elif c == ')':
                if grammar_state != EXPECTING_OPERATOR:
                    raise BadParenPositionError('Unexpected parenthesis',
                                                self._raw_expr, idx)
                elif not open_paren_count:
                    raise UnbalancedParenError('Unbalanced parenthesis',
                                               self._raw_expr, idx)

                open_paren_count -= 1
                self._tokens.append(c)
                idx += 1
            else:
                is_operator = False
                num_chars_remaining = num_chars - idx

                matching_operators = [
                    operator for operator in operator_search_list
                    if len(operator) <= num_chars_remaining
                    and self._raw_expr[idx:(idx + len(operator))] == operator
                ]

                if matching_operators:
                    match = matching_operators[0]
                    match_length = len(match)
                    next_c_pos = idx + match_length
                    next_c = (None if next_c_pos >= num_chars else
                              self._raw_expr[idx + match_length])

                    if next_c is None:
                        # trailing operator
                        raise ExpressionOrderError(
                            'Unexpected operator "{}"'.format(match),
                            self._raw_expr, idx)

                    if next_c in delimiters or is_symbolic[match]:
                        if OPERATOR_MAPPING[match] == TT_NOT_OP:
                            if grammar_state != EXPECTING_OPERAND:
                                raise ExpressionOrderError(
                                    'Unexpected unary operator "{}"'.format(
                                        match), self._raw_expr, idx)
                        else:
                            if grammar_state != EXPECTING_OPERATOR:
                                raise ExpressionOrderError(
                                    'Unexpected binary operator "{}"'.format(
                                        match), self._raw_expr, idx)
                            grammar_state = EXPECTING_OPERAND

                        is_operator = True
                        self._tokens.append(match)
                        idx += match_length

                if not is_operator:
                    if grammar_state != EXPECTING_OPERAND:
                        raise ExpressionOrderError('Unexpected operand',
                                                   self._raw_expr, idx)

                    operand_end_idx = idx + 1
                    while (operand_end_idx < num_chars and
                           self._raw_expr[operand_end_idx] not in delimiters):
                        operand_end_idx += 1

                    operand = self._raw_expr[idx:operand_end_idx]
                    if (operand not in CONSTANT_VALUES
                            and not is_valid_identifier(operand)):
                        raise InvalidIdentifierError(
                            'Invalid operand name "{}"'.format(operand),
                            self._raw_expr, idx)

                    self._tokens.append(operand)
                    if operand not in self._symbol_set:
                        self._symbols.append(operand)
                        self._symbol_set.add(operand)

                    idx = operand_end_idx
                    grammar_state = EXPECTING_OPERATOR

        if open_paren_count:
            left_paren_positions = [
                m.start() for m in re.finditer(r'\(', self._raw_expr)
            ]
            raise UnbalancedParenError(
                'Unbalanced left parenthesis', self._raw_expr,
                left_paren_positions[open_paren_count - 1])

        if not self._tokens:
            raise EmptyExpressionError('Empty expression is invalid')
Beispiel #7
0
 def test_non_str(self):
     """Test that a non-string argument raises an error."""
     with self.assertRaises(InvalidArgumentTypeError):
         is_valid_identifier(None)
Beispiel #8
0
 def test_empty_str(self):
     """Test that an empty string raises an error."""
     with self.assertRaises(InvalidArgumentValueError):
         is_valid_identifier('')
Beispiel #9
0
 def test_valid_identifiers(self):
     """Test a few valid identifiers."""
     self.assertTrue(is_valid_identifier('var11'))
     self.assertTrue(is_valid_identifier('var_20'))
     self.assertTrue(is_valid_identifier('variable'))