Return a boolean expression parsed from `expr` either a unicode string
        or tokens iterable.

        Optionally simplify the expression if `simplify` is True.

        Raise ParseError on errors.

        If `expr` is a string, the standard `tokenizer` is used for tokenization
        and the algebra configured Symbol type is used to create Symbol
        instances from Symbol tokens.

        If `expr` is an iterable, it should contain 3-tuples of: (token_type,
        token_string, token_position). In this case, the `token_type` can be
        a Symbol instance or one of the TOKEN_* constant types.
        See the `tokenize()` method for detailed specification.

def parse(self, expr, simplify=False):
        """
        Return a boolean expression parsed from `expr` either a unicode string
        or tokens iterable.

        Optionally simplify the expression if `simplify` is True.

        Raise ParseError on errors.

        If `expr` is a string, the standard `tokenizer` is used for tokenization
        and the algebra configured Symbol type is used to create Symbol
        instances from Symbol tokens.

        If `expr` is an iterable, it should contain 3-tuples of: (token_type,
        token_string, token_position). In this case, the `token_type` can be
        a Symbol instance or one of the TOKEN_* constant types.
        See the `tokenize()` method for detailed specification.
        """

        precedence = {self.NOT: 5, self.AND: 10, self.OR: 15, TOKEN_LPAR: 20}

        if isinstance(expr, basestring):
            tokenized = self.tokenize(expr)
        else:
            tokenized = iter(expr)

        if TRACE_PARSE:
            tokenized = list(tokenized)
            print('tokens:')
            map(print, tokenized)
            tokenized = iter(tokenized)

        # the abstract syntax tree for this expression that will be build as we
        # process tokens
        # the first two items are None
        # symbol items are appended to this structure
        ast = [None, None]

        def is_sym(_t):
            return isinstance(_t, Symbol) or _t in (TOKEN_TRUE, TOKEN_FALSE, TOKEN_SYMBOL)

        def is_operator(_t):
            return _t in (TOKEN_AND, TOKEN_OR)

        prev_token = None
        for token_type, token_string, token_position in tokenized:
            if TRACE_PARSE:
                print('\nprocessing token_type:', repr(token_type), 'token_string:', repr(token_string), 'token_position:', repr(token_position))

            if prev_token:
                prev_token_type, _prev_token_string, _prev_token_position = prev_token
                if TRACE_PARSE:
                    print('  prev_token:', repr(prev_token))

                if is_sym(prev_token_type) and (is_sym(token_type)):  # or token_type == TOKEN_LPAR) :
                    raise ParseError(token_type, token_string, token_position, PARSE_INVALID_SYMBOL_SEQUENCE)

                if is_operator(prev_token_type) and (is_operator(token_type) or token_type == TOKEN_RPAR):
                    raise ParseError(token_type, token_string, token_position, PARSE_INVALID_OPERATOR_SEQUENCE)

            else:
                if is_operator(token_type):
                    raise ParseError(token_type, token_string, token_position, PARSE_INVALID_OPERATOR_SEQUENCE)

            if token_type == TOKEN_SYMBOL:
                ast.append(self.Symbol(token_string))
                if TRACE_PARSE:
                    print(' ast: token_type is TOKEN_SYMBOL: append new symbol', repr(ast))

            elif isinstance(token_type, Symbol):
                ast.append(token_type)
                if TRACE_PARSE:
                    print(' ast: token_type is Symbol): append existing symbol', repr(ast))

            elif token_type == TOKEN_TRUE:
                ast.append(self.TRUE)
                if TRACE_PARSE: print(' ast: token_type is TOKEN_TRUE:', repr(ast))

            elif token_type == TOKEN_FALSE:
                ast.append(self.FALSE)
                if TRACE_PARSE: print(' ast: token_type is TOKEN_FALSE:', repr(ast))

            elif token_type == TOKEN_NOT:
                ast = [ast, self.NOT]
                if TRACE_PARSE: print(' ast: token_type is TOKEN_NOT:', repr(ast))

            elif token_type == TOKEN_AND:
#                if not prev_token or not is_sym(prev_token_type):
#                    raise ParseError(token_type, token_string, token_position, PARSE_INVALID_OPERATOR_SEQUENCE)

                ast = self._start_operation(ast, self.AND, precedence)
                if TRACE_PARSE:
                    print('  ast:token_type is TOKEN_AND: start_operation', ast)

            elif token_type == TOKEN_OR:
#                if not prev_token or not is_sym(prev_token_type):
#                    raise ParseError(token_type, token_string, token_position, PARSE_INVALID_OPERATOR_SEQUENCE)

                ast = self._start_operation(ast, self.OR, precedence)
                if TRACE_PARSE:
                    print('  ast:token_type is TOKEN_OR: start_operation', ast)

            elif token_type == TOKEN_LPAR:
                if prev_token:
                    # Check that an opening parens is preceded by a function
                    # or an opening parens
                    if prev_token_type not in (TOKEN_NOT, TOKEN_AND, TOKEN_OR, TOKEN_LPAR):
                        raise ParseError(token_type, token_string, token_position, PARSE_INVALID_NESTING)
                ast = [ast, TOKEN_LPAR]

            elif token_type == TOKEN_RPAR:
                while True:
                    if ast[0] is None:
                        raise ParseError(token_type, token_string, token_position, PARSE_UNBALANCED_CLOSING_PARENS)

                    if ast[1] is TOKEN_LPAR:
                        ast[0].append(ast[2])
                        if TRACE_PARSE: print('ast9:', repr(ast))
                        ast = ast[0]
                        if TRACE_PARSE: print('ast10:', repr(ast))
                        break

                    if isinstance(ast[1], int):
                        raise ParseError(token_type, token_string, token_position, PARSE_UNBALANCED_CLOSING_PARENS)

                    # the parens are properly nested
                    # the top ast node should be a function subclass
                    if not (inspect.isclass(ast[1]) and issubclass(ast[1], Function)):
                        raise ParseError(token_type, token_string, token_position, PARSE_INVALID_NESTING)

                    subex = ast[1](*ast[2:])
                    ast[0].append(subex)
                    if TRACE_PARSE: print('ast11:', repr(ast))
                    ast = ast[0]
                    if TRACE_PARSE: print('ast12:', repr(ast))
            else:
                raise ParseError(token_type, token_string, token_position, PARSE_UNKNOWN_TOKEN)

            prev_token = (token_type, token_string, token_position)

        try:
            while True:
                if ast[0] is None:
                    if TRACE_PARSE: print('ast[0] is None:', repr(ast))
                    if ast[1] is None:
                        if TRACE_PARSE: print('  ast[1] is None:', repr(ast))
                        if len(ast) != 3:
                            raise ParseError(error_code=PARSE_INVALID_EXPRESSION)
                        parsed = ast[2]
                        if TRACE_PARSE: print('    parsed = ast[2]:', repr(parsed))

                    else:
                        # call the function in ast[1] with the rest of the ast as args
                        parsed = ast[1](*ast[2:])
                        if TRACE_PARSE: print('  parsed = ast[1](*ast[2:]):', repr(parsed))
                    break
                else:
                    if TRACE_PARSE: print('subex = ast[1](*ast[2:]):', repr(ast))
                    subex = ast[1](*ast[2:])
                    ast[0].append(subex)
                    if TRACE_PARSE: print('  ast[0].append(subex):', repr(ast))
                    ast = ast[0]
                    if TRACE_PARSE: print('    ast = ast[0]:', repr(ast))
        except TypeError:
            raise ParseError(error_code=PARSE_INVALID_EXPRESSION)

        if simplify:
            return parsed.simplify()

        if TRACE_PARSE: print('final parsed:', repr(parsed))
        return parsed