class ComputedHashKeyValueConstructor:

    def __init__(self, key):
        self.key = key
        self.value_fn = None


    def set_value(self, fn):
        self.value_fn = fn


    def evaluate(self):
        verbose_print('Evaluating value expression for constructed hash key "{0}"'.format(self.key), indent_after=True)

        value = self.value_fn()

        msg = u'Finished evaluating; value of constructed hash key "{0}" is {1}'
        verbose_print(lambda: msg.format(self.key, debug_dump_anything(value)), outdent_before=True)

        return HashKeyValue(self.key, value) def equals(first, second):
    first_type = object_type(first)
    second_type = object_type(second)
    try:
        reverse = first_type > second_type
        op = equality_ops_table[first_type if not reverse else second_type][second_type if not reverse else first_type]
        return boolean(op(first if not reverse else second, second if not reverse else first))
    except TypeError:
        msg = 'type mismatch comparing {0} and {1} for equality'
        raise HqueryEvaluationError(msg.format(object_type_name(first_type), object_type_name(second_type))) def not_equals(first, second):
    return boolean(not bool(equals(first, second))) class Flwor:

    def __init__(self):
        self.global_variables = []
        self.per_iteration_variables = []
        self.return_expression = None
        self.sequence_expression = None
        self.sequence_variable = None


    def __str__(self):
        return '{0}{1}return <expr>'.format(
            '' if self.sequence_expression is None else 'for ${0}:=<expr> '.format(self.sequence_variable),
            (' '.join('let ${0} := <expr>'.format(v[0]) for v in self.per_iteration_variables) + ' ') if len(self.per_iteration_variables) else ''
        )


    def append_let(self, variable_name, expression_fn):
        var_tuple = (variable_name, expression_fn)
        if self.sequence_expression is None:
            self.global_variables.append(var_tuple)
        else:
            self.per_iteration_variables.append(var_tuple)


    def debug_dump(self):
        return debug_dump_long_string(str(self))


    def evaluate(self):
        verbose_print('Evaluating FLWOR {0}'.format(self), indent_after=True)

        if self.sequence_expression is not None:
            result = self._evaluate_iteration()
        else:
            result = self._evaluate_without_iteration()

        verbose_print(lambda: 'FLWOR evaluation completed; returning {0}'.format(debug_dump_anything(result)),
                      outdent_before=True)
        return result


    def set_iteration_expression(self, variable_name, expression_fn):
        if self.sequence_expression is not None:
            raise HquerySyntaxError('More than one "for" clause found in FLWOR "{0}"'.format(self.debug_dump()))
        self.sequence_variable = variable_name
        self.sequence_expression = expression_fn


    def set_return_expression(self, expression_fn):
        if self.return_expression is not None:
            raise HquerySyntaxError('More than one return clause found for FLWOR {0}'.format(self.debug_dump()))
        self.return_expression = expression_fn


    def _evaluate_iteration(self):
        with variable_scope():
            self._push_global_variables()

            sequence = make_sequence(self.sequence_expression())
            verbose_print('Iterating over sequence containing {0} items'.format(len(sequence)))
            result = []

            for item in sequence:
                verbose_print(lambda: u'Visiting item {0}'.format(debug_dump_anything(item)), indent_after=True)

                with variable_scope():
                    push_variable(self.sequence_variable, make_sequence(item))
                    self._push_iteration_variables()
                    this_result = make_sequence(self.return_expression())
                    verbose_print('Return clause yielded {0} results for this visit'.format(len(this_result)))
                    result = sequence_concat(result, this_result)

                verbose_print('Visit finished', outdent_before=True)

        return result


    def _evaluate_without_iteration(self):
        with variable_scope():
            self._push_global_variables()
            verbose_print('Evaluating return expression.', indent_after=True)
            result = self.return_expression()
            verbose_print('Return expression produced {0}'.format(str(result)), outdent_before=True)
        return result


    def _push_global_variables(self):
        for let in self.global_variables:
            verbose_print('Evaluating let {0} := <expr>'.format(let[0]))
            push_variable(let[0], let[1]())


    def _push_iteration_variables(self):
        for let in self.per_iteration_variables:
            verbose_print('Evaluating let {0} := <expr>'.format(let[0]))
            push_variable(let[0], let[1]()) class FunctionSupport:
    all_functions = None


    def call_function(self, name, *args):
        self._load_all_functions()

        py_name = name.replace('-', '_')

        try:
            fn = self.all_functions[py_name]
        except KeyError:
            raise HqueryEvaluationError('Unknown function name "{0}"'.format(name))

        try:
            return fn(*args)
        except TypeError as err:
            if re.search(r'\d+ (?:.+ )?argument', err.args[0]):
                raise HqueryEvaluationError(err.args[0])
            else:
                raise


    def _load_all_functions(self):
        if self.all_functions is None:
            self.all_functions = dict()
            my_package = '{0}/__init__.py'.format(os.path.dirname(__file__))
            verbose_print('FunctionSupport loading all function modules near {0}.'.format(my_package),
                          indent_after=True)
            for importer, modname, ispkg in walk_packages(my_package):
                if not ispkg and modname.startswith('hq.hquery.functions.'):
                    verbose_print('Found candidate module {0} -- loading.'.format(modname))
                    module = importer.find_module(modname).load_module(modname)
                    if hasattr(module, 'exports'):
                        exports = {name.rstrip('_'): getattr(module, name) for name in getattr(module, 'exports')}
                        verbose_print('Module {0} exports are: {1}'.format(modname, exports.keys()))
                        if any(not (isclass(obj) or isfunction(obj)) for obj in exports.values()):
                            raise RuntimeError('Non-class/function export(s) loaded from module {0}'.format(modname))
                        self.all_functions.update(exports)
                    else:
                        verbose_print('Module {0} defined no exports.'.format(modname))
            verbose_print('Finished loading function modules.', outdent_before=True) class boolean:

    def __init__(self, obj):
        if is_node_set(obj):
            self.value = len(obj) > 0
        elif is_number(obj):
            f = float(obj)
            self.value = bool(f) and not isnan(f)
        else:
            self.value = bool(obj)

    def __bool__(self):
        return self.value

    def __nonzero__(self):
        return self.__bool__()

    def __str__(self):
        return str(self.value).lower()

    def __eq__(self, other):
        return is_boolean(other) and self.value == other.value

    def __repr__(self):
        return 'boolean({0})'.format(self.value) class number:

    def __init__(self, obj):
        if isinstance(obj, number):
            self.value = obj.value
        elif is_boolean(obj):
            self.value = 1 if obj else 0
        elif is_node_set(obj) or is_any_node(obj):
            self.value = self._int_or_float(float(string_value(obj)))
        else:
            try:
                self.value = self._int_or_float(float(obj))
            except ValueError:
                self.value = float('nan')

    def __float__(self):
        return float(self.value)

    def __int__(self):
        return int(self.value)

    def __str__(self):
        result = str(self.value)
        if result == 'nan':
            result = 'NaN'
        return result

    def __hash__(self):
        return self.value.__hash__()

    def __add__(self, other):
        return number(self.value + self._value_of_other_operand(other))

    def __sub__(self, other):
        return number(self.value - self._value_of_other_operand(other))

    def __neg__(self):
        return number(-self.value)

    def __mul__(self, other):
        return number(self.value * self._value_of_other_operand(other))

    def __div__(self, other):
        return self.__truediv__(other)

    def __truediv__(self, other):
        other = self._value_of_other_operand(other)
        if other == 0:
            return number(float('nan'))
        else:
            return number(self.value / other)

    def __mod__(self, other):
        return number(self.value % self._value_of_other_operand(other))

    def __eq__(self, other):
        return self.value == self._value_of_other_operand(other)

    def __ge__(self, other):
        return self.value >= self._value_of_other_operand(other)

    def __gt__(self, other):
        return self.value > self._value_of_other_operand(other)

    def __le__(self, other):
        return self.value <= self._value_of_other_operand(other)

    def __lt__(self, other):
        return self.value < self._value_of_other_operand(other)

    def __repr__(self):
        return 'number({0})'.format(str(self.value))

    @staticmethod
    def _int_or_float(numeric_value):
        if isinstance(numeric_value, int) or numeric_value % 1 != 0:
            return numeric_value
        else:
            return int(numeric_value)

    @staticmethod
    def _value_of_other_operand(other):
        return other.value if is_number(other) else other class NodeTest:

    def __init__(self, value, name_test=False):
        value = value.lower()
        self.repr = value
        self.is_name_test = name_test

        if name_test:
            self.accept_fn = _make_name_accept_fn(value)
        elif value == '*':
            self.accept_fn = _accept_principal_node_type
        elif value == 'node':
            self.accept_fn = _make_axis_agnostic_accept_fn(is_any_node)
        elif value == 'text':
            self.accept_fn = _make_axis_agnostic_accept_fn(is_text_node)
        elif value == 'comment':
            self.accept_fn = _make_axis_agnostic_accept_fn(is_comment_node)

        self.repr = '{0}{1}'.format(self.repr, '' if name_test or value == '*' else '()')


    def __repr__(self):
        return self.repr


    def apply(self, axis, node):
        nodes = getattr(self, 'gather_{0}'.format(axis.name))(node)
        return [node for node in nodes if self.accept_fn(node, axis=axis)]


    def gather_ancestor(self, node):
        if hasattr(node, 'parents'):
            return list(node.parents)
        else:
            return []


    def gather_ancestor_or_self(self, node):
        result = self.gather_self(node)
        result.extend(self.gather_ancestor(node))
        return result


    def gather_attribute(self, node):
        return list(AttributeNode.enumerate(node))


    def gather_child(self, node):
        if is_root_node(node):
            return [root_tag_from_soup(node)]
        elif is_tag_node(node):
            return node.contents
        else:
            return []


    def gather_css_class(self, node):
        return self.gather_child(node)


    def gather_descendant(self, node):
        if hasattr(node, 'descendants'):
            return list(node.descendants)
        else:
            return []


    def gather_descendant_or_self(self, node):
        result = self.gather_self(node)
        result.extend(self.gather_descendant(node))
        return result


    def gather_following(self, node):
        result = []
        while is_tag_node(node):
            for sibling in node.next_siblings:
                result.append(sibling)
                result.extend(self.gather_descendant(sibling))
            node = node.parent
        return result


    def gather_following_sibling(self, node):
        if hasattr(node, 'next_siblings'):
            return list(node.next_siblings)
        else:
            return []


    def gather_parent(self, node):
        if hasattr(node, 'parent') and node.parent is not None:
            return [node.parent]
        else:
            return []


    def gather_preceding(self, node):
        result = []
        while is_tag_node(node):
            for sibling in node.previous_siblings:
                result.append(sibling)
                result.extend(self.gather_descendant(sibling))
            node = node.parent
        return result


    def gather_preceding_sibling(self, node):
        if hasattr(node, 'previous_siblings'):
            return list(node.previous_siblings)
        else:
            return []


    def gather_self(self, node):
        return [node] def object_type_name(obj):
    result = 'NULL OR UNKNOWN TYPE'

    if obj is not None:
        if isinstance(obj, int):
            index = obj
        else:
            index = object_type(obj)
        result = TYPE_NAMES[index]

    return result def debug_dump_anything(obj):
    if is_any_node(obj):
        result = debug_dump_node(obj)
    elif is_boolean(obj) or is_number(obj) or is_hash(obj) or is_array(obj):
        result = repr(obj)
    elif is_string(obj):
        result = u'string("{0}")'.format(obj)
    elif is_node_set(obj):
        result = u'node-set({0})'.format(', '.join(truncate_string(debug_dump_node(node), 20) for node in obj))
    elif is_sequence(obj):
        result = u'sequence({0})'.format(', '.join(truncate_string(debug_dump_anything(item), 20) for item in obj))
    else:
        raise RuntimeError("debug_dump_anything doesn't know how to handle {0}".format(obj.__class__.__name__))
    return debug_dump_long_string(result) def make_node_set(node_set, reverse=False):
    ids = set()

    def is_unique_id(node):
        node_id = id(node)
        if node_id in ids:
            return False
        else:
            ids.add(node_id)
            return True

    if not isinstance(node_set, list):
        node_set = [node_set]

    non_node_member = next(filterfalse(is_any_node, node_set), False)
    if non_node_member:
        format_str = 'Constructed node set that includes {0} object "{1}"'
        raise HqueryEvaluationError(format_str.format(object_type_name(non_node_member), non_node_member))

    node_set = list(sorted(filter(is_unique_id, node_set), key=lambda n: n.hq_doc_index, reverse=reverse))

    return node_set def sequence_concat(first, second):
    first = make_sequence(first)
    first.extend(make_sequence(second))
    return first class RelationalOperator:

    def __init__(self, op):
        if op == '>':
            self.base_op = gt
        elif op == '>=':
            self.base_op = ge
        elif op == '<':
            self.base_op = lt
        elif op == '<=':
            self.base_op = le
        else:
            raise HquerySyntaxError('unexpected relational operator "{0}"'.format(op))


    def evaluate(self, first, second):
        first_type = object_type(first)
        second_type = object_type(second)
        cmp = comparison_method_table[first_type][second_type]
        return boolean(cmp(self.base_op, first, second))


    @property
    def name(self):
        return self.base_op.__name__ def parse_interpolated_string(source, parse_interface):
    verbose_print(u'Parsing interpolated string contents `{0}`'.format(source), indent_after=True)

    expressions = []
    for embedded_expr, embedded_var, literal in clauses_pattern.findall(source):
        if embedded_expr:
            verbose_print(u'Adding embedded expression: {0}'.format(embedded_expr))
            expressions.append(reduce_filters_and_expression(embedded_expr[2:-1], parse_interface))
        elif embedded_var:
            verbose_print('Adding embedded variable reference: {0}'.format(embedded_var))
            expressions.append(parse_interface.parse_in_new_processor(embedded_var))
        else:
            verbose_print(u'Adding literal string contents `{0}`'.format(literal))
            expressions.append(_make_literal_identity_closure(literal))

    def evaluate():
        chunks = [string_value(exp()) for exp in expressions]
        verbose_print(u'Interpolated string evaluation assembling {0} chunks{1}.'.format(
            len(chunks),
            '' if len(chunks) == 0 else u' ("{0}")'.format(u'", "'.join(chunks)))
        )
        return ''.join(chunks)

    verbose_print(
        u'Finished parsing interpolated string `{0}` ({1} chunk(s) found)'.format(debug_dump_long_string(source),
                                                                                  len(expressions)),
        outdent_before=True
    )
    return evaluate class HquerySyntaxError(ValueError):
    pass class UnionDecomposition:

    def __init__(self):
        self.mapping_generators = None
        self.union_expression = None


    def __str__(self):
        union_str = ' | '.join('<expr>' * len(self.mapping_generators))
        return '{0} => {0}'.format(union_str)


    def evaluate(self):
        verbose_print('Evaluating union decomposition ({} clauses)'.format(len(self.mapping_generators)),
                      indent_after=True)

        sequence = make_sequence(self.union_expression())
        result = []

        for item in sequence:
            verbose_print(lambda: u'Visiting item {0}'.format(debug_dump_anything(item)), indent_after=True)

            with variable_scope():
                push_variable('_', make_sequence(item))
                if not hasattr(item, 'union_index'):
                    raise HqueryEvaluationError(
                        "Union decomposition applied to something that wasn't produced by a union"
                    )
                if item.union_index >= len(self.mapping_generators):
                    raise HqueryEvaluationError("Decomposed union had more clauses than its mapping")
                this_result = make_sequence(self.mapping_generators[item.union_index]())
                verbose_print(
                    'Mapping yielded {0} results for this visit'.format(
                        len(this_result)))
                result = sequence_concat(result, this_result)

            verbose_print('Visit finished', outdent_before=True)

        verbose_print('Union decomposition completed', outdent_before=True)
        return result


    def set_mapping_generators(self, mgs):
        self.mapping_generators = mgs


    def set_union_expression(self, ug):
        self.union_expression = ug def value_of_variable(name):
    if len(variable_stack) > 0:
        for index in range(len(variable_stack) - 1, -1, -1):
            if variable_stack[index][NAME] == name:
                reverse_index = len(variable_stack) - (index + 1)
                verbose_print('Variable "${0}" found on stack (position {1}).'.format(name, reverse_index))
                return variable_stack[index][VALUE]

    verbose_print('Variable "${0}" NOT FOUND on variable stack.'.format(name))
    return None def soup_from_any_tag(obj):
    while obj.parent is not None:
        obj = obj.parent
    return obj def html_entity_decode(s):
    result = re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: str(unichr(name2codepoint[m.group(1)])), s)
    result = re.sub(r'&#(\d{2,3});', lambda m: chr(int(m.group(1))), result)
    return result def verbose_print(text, indent_after=False, outdent_before=False):
    if settings.VERBOSE:
        if outdent_before:
            pop_indent()
        if not is_a_string(text):
            text = text()
        print(u'{0}{1}'.format(' ' * indent_level, text), file=sys.stderr)
        if indent_after:
            push_indent() class Axis(Enum):
    # standard
    ancestor            = 1
    ancestor_or_self    = 2
    attribute           = 3
    child               = 4
    descendant          = 5
    descendant_or_self  = 6
    following           = 7
    following_sibling   = 8
    parent              = 9
    preceding           = 10
    preceding_sibling   = 11
    self                = 12
    # extended
    css_class           = 13


    def is_reverse_order(self):
        return self in reverse_order_axes

    def token(self):
        return self.name.replace('_', '-')

    @classmethod
    def abbreviations(self):
        return _abbreviations.keys()

    @classmethod
    def canonicalize(cls, name):
        if name in _abbreviations.keys():
            result = _abbreviations[name]
        else:
            result = name.replace('-', '_')
        return result def get_context_node():
    return peek_context().node
from hq.hquery.computed_constructors.hash_key_value import ComputedHashKeyValueConstructor
from hq.hquery.equality_operators import equals, not_equals
from hq.hquery.flwor import Flwor
from hq.hquery.function_support import FunctionSupport
from hq.hquery.functions.core_boolean import boolean
from hq.hquery.functions.core_number import number
from hq.hquery.node_test import NodeTest
from hq.hquery.object_type import object_type_name, debug_dump_anything
from hq.hquery.sequences import make_node_set, sequence_concat
from hq.hquery.relational_operators import RelationalOperator
from hq.hquery.string_interpolation import parse_interpolated_string
from hq.hquery.syntax_error import HquerySyntaxError
from hq.hquery.union_decomposition import UnionDecomposition
from hq.hquery.variables import value_of_variable
from hq.soup_util import soup_from_any_tag
from hq.string_util import html_entity_decode
from hq.verbosity import verbose_print
from .axis import Axis
from .expression_context import get_context_node

        while (not isinstance(self.parse_interface.peek(), CloseParenthesisToken)):
            arg_generators.append(self.parse_interface.expression(LBP.sequence))
            self.parse_interface.advance_if(CommaToken)

        self.parse_interface.advance(CloseParenthesisToken)

        def evaluate():
            self._gab('evaluating argument list for function "{0}."'.format(self.value))
            arguments = [gen() for gen in arg_generators]
            arg_types = ','.join(object_type_name(arg) for arg in arguments)
            self._gab('calling {0}({1}).'.format(self.value, arg_types))
            return function_support.call_function(self.value, *arguments)

        return evaluate



class HashKeyToken(Token):
    lpb = LBP.nothing

    def __str__(self):
        return '(hash-key "{0}")'.format(self.value)

    def nud(self):
        constructor = ComputedHashKeyValueConstructor(self.value)
        constructor.set_value(self.parse_interface.expression(LBP.sequence))
        return constructor.evaluate



class IfElseToken(Token):
    lbp = LBP.nothing

    def __str__(self):
        return '(if-reserved-word)'

    def nud(self):
        return self.parse_interface.if_then_else()



class InterpolatedStringToken(Token):
    lbp = LBP.nothing

    def __init__(self, parse_interface, value, **kwargs):
        super(InterpolatedStringToken, self).__init__(parse_interface, value[1:-1], **kwargs)

    def __str__(self):
        return u'(interpolated-string `{0}`)'.format(self.value)

    def nud(self):
        return parse_interpolated_string(self.value, self.parse_interface)



class LiteralNumberToken(Token):
    lbp = LBP.nothing

    def __str__(self):
        return '(literal-number {0})'.format(self.value)

    def nud(self):
        return lambda: number(self.value)



class LiteralStringToken(Token):
    lbp = LBP.nothing

    def __init__(self, parse_interface, value, **kwargs):
        super(LiteralStringToken, self).__init__(parse_interface, html_entity_decode(value[1:-1]), **kwargs)

    def __str__(self):
        return u'(literal-string "{0}")'.format(self.value)

    def nud(self):
        return lambda: self.value



class MultiplyOperatorToken(Token):
    lbp = LBP.mult_or_div

    def __str__(self):
        return '(times)'

    def led(self, left):
        right = self.parse_interface.expression(self.lbp)

        def evaluate():
            left_value, right_value = self._evaluate_binary_operands(left, right, constructor=number, type_name='number')
            result = left_value * right_value
            self._gab('{0} returning {1}'.format(self, result))
            return result

        return evaluate



class NameTestToken(Token):
    lbp = LBP.node_test

    def __init__(self, *args, **kwargs):
        super(NameTestToken, self).__init__(*args, **kwargs)
        self.node_test = NodeTest(self.value, name_test=True)

    def __str__(self):
        return '(name-test "{0}")'.format(self.value)

    def nud(self):
        return self.parse_interface.location_path(self).evaluate



class NodeTestToken(Token):
    lbp = LBP.node_test

    def __init__(self, *args, **kwargs):
        super(NodeTestToken, self).__init__(*args, **kwargs)

        self.node_test = NodeTest(self.value)