def eval_statement(self, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `pr.ExprStmt`. """ debug.dbg('eval_statement %s (%s)', stmt, seek_name) types = self.eval_element(stmt.get_rhs()) if seek_name: types = finder.check_tuple_assignments(types, seek_name) first_operation = stmt.first_operation() if first_operation not in ('=', None) and not isinstance(stmt, er.InstanceElement): # TODO don't check for this. # `=` is always the last character in aug assignments -> -1 operator = copy.copy(first_operation) operator.value = operator.value[:-1] name = str(stmt.get_defined_names()[0]) parent = er.wrap(self, stmt.get_parent_scope()) left = self.find_types(parent, name, stmt.start_pos, search_global=True) if isinstance(stmt.get_parent_until(pr.ForStmt), pr.ForStmt): # Iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. for r in types: left = precedence.calculate(self, left, operator, [r]) types = left else: types = precedence.calculate(self, left, operator, types) debug.dbg('eval_statement result %s', types) return types
def eval_statement(self, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `tree.ExprStmt`. """ debug.dbg('eval_statement %s (%s)', stmt, seek_name) rhs = stmt.get_rhs() types = self.eval_element(rhs) if seek_name: types = finder.check_tuple_assignments(self, types, seek_name) first_operation = stmt.first_operation() if first_operation not in ('=', None) and not isinstance( stmt, er.InstanceElement ) and first_operation.type == 'operator': # TODO don't check for this. id:458 gh:459 # `=` is always the last character in aug assignments -> -1 operator = copy.copy(first_operation) operator.value = operator.value[:-1] name = str(stmt.get_defined_names()[0]) parent = self.wrap(stmt.get_parent_scope()) left = self.find_types(parent, name, stmt.start_pos, search_global=True) for_stmt = stmt.get_parent_until(tree.ForStmt) if isinstance(for_stmt, tree.ForStmt) and types \ and for_stmt.defines_one_name(): # Iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. Also only do it, if the variable is not a tuple. node = for_stmt.get_input_node() for_iterables = self.eval_element(node) ordered = list(iterable.py__iter__(self, for_iterables, node)) for index_types in ordered: dct = {str(for_stmt.children[1]): index_types} self.predefined_if_name_dict_dict[for_stmt] = dct t = self.eval_element(rhs) left = precedence.calculate(self, left, operator, t) types = left if ordered: # If there are no for entries, we cannot iterate and the # types are defined by += entries. Therefore the for loop # is never called. del self.predefined_if_name_dict_dict[for_stmt] else: types = precedence.calculate(self, left, operator, types) debug.dbg('eval_statement result %s', types) return types
def eval_statement(self, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `tree.ExprStmt`. """ debug.dbg("eval_statement %s (%s)", stmt, seek_name) rhs = stmt.get_rhs() types = self.eval_element(rhs) if seek_name: types = finder.check_tuple_assignments(self, types, seek_name) first_operation = stmt.first_operation() if first_operation not in ("=", None) and not isinstance( stmt, er.InstanceElement ): # TODO don't check for this. # `=` is always the last character in aug assignments -> -1 operator = copy.copy(first_operation) operator.value = operator.value[:-1] name = str(stmt.get_defined_names()[0]) parent = self.wrap(stmt.get_parent_scope()) left = self.find_types(parent, name, stmt.start_pos, search_global=True) for_stmt = stmt.get_parent_until(tree.ForStmt) if isinstance(for_stmt, tree.ForStmt) and types and for_stmt.defines_one_name(): # Iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. Also only do it, if the variable is not a tuple. node = for_stmt.get_input_node() for_iterables = self.eval_element(node) ordered = list(iterable.py__iter__(self, for_iterables, node)) for index_types in ordered: dct = {str(for_stmt.children[1]): index_types} self.predefined_if_name_dict_dict[for_stmt] = dct t = self.eval_element(rhs) left = precedence.calculate(self, left, operator, t) types = left if ordered: # If there are no for entries, we cannot iterate and the # types are defined by += entries. Therefore the for loop # is never called. del self.predefined_if_name_dict_dict[for_stmt] else: types = precedence.calculate(self, left, operator, types) debug.dbg("eval_statement result %s", types) return types
def _eval_stmt(self, context, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `tree.ExprStmt`. """ debug.dbg('eval_statement %s (%s)', stmt, seek_name) rhs = stmt.get_rhs() types = self.eval_element(context, rhs) if seek_name: c_node = ContextualizedName(context, seek_name) types = finder.check_tuple_assignments(self, c_node, types) first_operation = stmt.first_operation() if first_operation not in ( '=', None) and first_operation.type == 'operator': # `=` is always the last character in aug assignments -> -1 operator = copy.copy(first_operation) operator.value = operator.value[:-1] name = str(stmt.get_defined_names()[0]) left = context.py__getattribute__(name, position=stmt.start_pos, search_global=True) for_stmt = tree.search_ancestor(stmt, 'for_stmt') if for_stmt is not None and for_stmt.type == 'for_stmt' and types \ and for_stmt.defines_one_name(): # Iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. Also only do it, if the variable is not a tuple. node = for_stmt.get_input_node() cn = ContextualizedNode(context, node) ordered = list(iterable.py__iter__(self, cn.infer(), cn)) for lazy_context in ordered: dct = {str(for_stmt.children[1]): lazy_context.infer()} with helpers.predefine_names(context, for_stmt, dct): t = self.eval_element(context, rhs) left = precedence.calculate(self, context, left, operator, t) types = left else: types = precedence.calculate(self, context, left, operator, types) debug.dbg('eval_statement result %s', types) return types
def calculate_children(evaluator, children): """ Calculate a list of children with operators. """ iterator = iter(children) types = evaluator.eval_element(next(iterator)) for operator in iterator: try:# PATCH: Catches StopIteration error right = next(iterator) if tree.is_node(operator, 'comp_op'): # not in / is not operator = ' '.join(str(c.value) for c in operator.children) # handle lazy evaluation of and/or here. if operator in ('and', 'or'): left_bools = set([left.py__bool__() for left in types]) if left_bools == set([True]): if operator == 'and': types = evaluator.eval_element(right) elif left_bools == set([False]): if operator != 'and': types = evaluator.eval_element(right) # Otherwise continue, because of uncertainty. else: types = calculate(evaluator, types, operator, evaluator.eval_element(right)) except StopIteration: debug.warning('calculate_children StopIteration %s', types) debug.dbg('calculate_children types %s', types) return types
def _eval_element_not_cached(self, element): debug.dbg('eval_element %s@%s', element, element.start_pos) types = set() if isinstance(element, (tree.Name, tree.Literal)) or tree.is_node( element, 'atom'): types = self._eval_atom(element) elif isinstance(element, tree.Keyword): # For False/True/None if element.value in ('False', 'True', 'None'): types.add(compiled.builtin_from_name(self, element.value)) # else: print e.g. could be evaluated like this in Python 2.7 elif element.isinstance(tree.Lambda): types = set([er.LambdaWrapper(self, element)]) elif element.isinstance(er.LambdaWrapper): types = set([element ]) # TODO this is no real evaluation. id:121 gh:122 elif element.type == 'expr_stmt': types = self.eval_statement(element) elif element.type in ('power', 'atom_expr'): types = self._eval_atom(element.children[0]) for trailer in element.children[1:]: if trailer == '**': # has a power operation. right = self.eval_element(element.children[2]) types = set( precedence.calculate(self, types, trailer, right)) break types = self.eval_trailer(types, trailer) elif element.type in ( 'testlist_star_expr', 'testlist', ): # The implicit tuple in statements. types = set([iterable.ImplicitTuple(self, element)]) elif element.type in ('not_test', 'factor'): types = self.eval_element(element.children[-1]) for operator in element.children[:-1]: types = set(precedence.factor_calculate(self, types, operator)) elif element.type == 'test': # `x if foo else y` case. types = (self.eval_element(element.children[0]) | self.eval_element(element.children[-1])) elif element.type == 'operator': # Must be an ellipsis, other operators are not evaluated. assert element.value == '...' types = set([compiled.create(self, Ellipsis)]) elif element.type == 'dotted_name': types = self._eval_atom(element.children[0]) for next_name in element.children[2::2]: types = set( chain.from_iterable( self.find_types(typ, next_name) for typ in types)) types = types elif element.type == 'eval_input': types = self._eval_element_not_cached(element.children[0]) elif element.type == 'annassign': types = self.eval_element(element.children[1]) else: types = precedence.calculate_children(self, element.children) debug.dbg('eval_element result %s', types) return types
def calculate_children(evaluator, children): """ Calculate a list of children with operators. """ iterator = iter(children) types = evaluator.eval_element(next(iterator)) for operator in iterator: try: # PATCH: Catches StopIteration error right = next(iterator) if tree.is_node(operator, 'comp_op'): # not in / is not operator = ' '.join(str(c.value) for c in operator.children) # handle lazy evaluation of and/or here. if operator in ('and', 'or'): left_bools = set([left.py__bool__() for left in types]) if left_bools == set([True]): if operator == 'and': types = evaluator.eval_element(right) elif left_bools == set([False]): if operator != 'and': types = evaluator.eval_element(right) # Otherwise continue, because of uncertainty. else: types = calculate(evaluator, types, operator, evaluator.eval_element(right)) except StopIteration: debug.warning('calculate_children StopIteration %s', types) debug.dbg('calculate_children types %s', types) return types
def _eval_element_not_cached(self, context, element): debug.dbg('eval_element %s@%s', element, element.start_pos) types = set() typ = element.type if typ in ('name', 'number', 'string', 'atom'): types = self.eval_atom(context, element) elif typ == 'keyword': # For False/True/None if element.value in ('False', 'True', 'None'): types.add(compiled.builtin_from_name(self, element.value)) # else: print e.g. could be evaluated like this in Python 2.7 elif typ == 'lambdef': types = set([er.FunctionContext(self, context, element)]) elif typ == 'expr_stmt': types = self.eval_statement(context, element) elif typ in ('power', 'atom_expr'): first_child = element.children[0] if not (first_child.type == 'keyword' and first_child.value == 'await'): types = self.eval_atom(context, first_child) for trailer in element.children[1:]: if trailer == '**': # has a power operation. right = self.eval_element(context, element.children[2]) types = set(precedence.calculate(self, context, types, trailer, right)) break types = self.eval_trailer(context, types, trailer) elif typ in ('testlist_star_expr', 'testlist',): # The implicit tuple in statements. types = set([iterable.SequenceLiteralContext(self, context, element)]) elif typ in ('not_test', 'factor'): types = self.eval_element(context, element.children[-1]) for operator in element.children[:-1]: types = set(precedence.factor_calculate(self, types, operator)) elif typ == 'test': # `x if foo else y` case. types = (self.eval_element(context, element.children[0]) | self.eval_element(context, element.children[-1])) elif typ == 'operator': # Must be an ellipsis, other operators are not evaluated. # In Python 2 ellipsis is coded as three single dot tokens, not # as one token 3 dot token. assert element.value in ('.', '...') types = set([compiled.create(self, Ellipsis)]) elif typ == 'dotted_name': types = self.eval_atom(context, element.children[0]) for next_name in element.children[2::2]: # TODO add search_global=True? types = unite( typ.py__getattribute__(next_name, name_context=context) for typ in types ) types = types elif typ == 'eval_input': types = self._eval_element_not_cached(context, element.children[0]) elif typ == 'annassign': types = pep0484._evaluate_for_annotation(context, element.children[1]) else: types = precedence.calculate_children(self, context, element.children) debug.dbg('eval_element result %s', types) return types
def _eval_atom(self, atom): """ Basically to process ``atom`` nodes. The parser sometimes doesn't generate the node (because it has just one child). In that case an atom might be a name or a literal as well. """ if isinstance(atom, tree.Name): # This is the first global lookup. stmt = atom.get_definition() scope = stmt.get_parent_until(tree.IsScope, include_current=True) if isinstance(scope, (tree.Function, er.FunctionExecution)): # Adjust scope: If the name is not in the suite, it's a param # default or annotation and will be resolved as part of the # parent scope. colon = scope.children.index(':') if atom.start_pos < scope.children[colon + 1].start_pos: scope = scope.get_parent_scope() if isinstance(stmt, tree.CompFor): stmt = stmt.get_parent_until((tree.ClassOrFunc, tree.ExprStmt)) if stmt.type != 'expr_stmt': # We only need to adjust the start_pos for statements, because # there the name cannot be used. stmt = atom return self.find_types(scope, atom, stmt.start_pos, search_global=True) elif isinstance(atom, tree.Literal): return set([compiled.create(self, atom.eval())]) else: c = atom.children if c[0].type == 'string': # Will be one string. types = self._eval_atom(c[0]) for string in c[1:]: right = self._eval_atom(string) types = precedence.calculate(self, types, '+', right) return types # Parentheses without commas are not tuples. elif c[0] == '(' and not len(c) == 2 \ and not(tree.is_node(c[1], 'testlist_comp') and len(c[1].children) > 1): return self.eval_element(c[1]) try: comp_for = c[1].children[1] except (IndexError, AttributeError): pass else: if comp_for == ':': # Dict comprehensions have a colon at the 3rd index. try: comp_for = c[1].children[3] except IndexError: pass if comp_for.type == 'comp_for': return set([iterable.Comprehension.from_atom(self, atom)]) return set([iterable.Array(self, atom)])
def _eval_stmt(self, context, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `tree.ExprStmt`. """ debug.dbg('eval_statement %s (%s)', stmt, seek_name) rhs = stmt.get_rhs() types = self.eval_element(context, rhs) if seek_name: c_node = ContextualizedName(context, seek_name) types = finder.check_tuple_assignments(self, c_node, types) first_operation = stmt.first_operation() if first_operation not in ('=', None) and first_operation.type == 'operator': # `=` is always the last character in aug assignments -> -1 operator = copy.copy(first_operation) operator.value = operator.value[:-1] name = str(stmt.get_defined_names()[0]) left = context.py__getattribute__( name, position=stmt.start_pos, search_global=True) for_stmt = tree.search_ancestor(stmt, 'for_stmt') if for_stmt is not None and for_stmt.type == 'for_stmt' and types \ and for_stmt.defines_one_name(): # Iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. Also only do it, if the variable is not a tuple. node = for_stmt.get_input_node() cn = ContextualizedNode(context, node) ordered = list(iterable.py__iter__(self, cn.infer(), cn)) for lazy_context in ordered: dct = {str(for_stmt.children[1]): lazy_context.infer()} with helpers.predefine_names(context, for_stmt, dct): t = self.eval_element(context, rhs) left = precedence.calculate(self, context, left, operator, t) types = left else: types = precedence.calculate(self, context, left, operator, types) debug.dbg('eval_statement result %s', types) return types
def eval_statement(self, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `pr.Statement`. """ debug.dbg('eval_statement %s (%s)', stmt, seek_name) expression_list = stmt.expression_list() if isinstance(stmt, FakeStatement): return expression_list # Already contains the results. result = self.eval_expression_list(expression_list) ass_details = stmt.assignment_details if ass_details and ass_details[0][1] != '=' and not isinstance( stmt, er.InstanceElement): # TODO don't check for this. expr_list, operator = ass_details[0] # `=` is always the last character in aug assignments -> -1 operator = operator[:-1] name = str(expr_list[0].name) parent = stmt.parent if isinstance(parent, (pr.SubModule, fast.Module)): parent = er.ModuleWrapper(self, parent) left = self.find_types(parent, name, stmt.start_pos) if isinstance(stmt.parent, pr.ForFlow): # iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. for r in result: left = precedence.calculate(self, left, operator, [r]) result = left else: result = precedence.calculate(self, left, operator, result) elif len(stmt.get_defined_names()) > 1 and seek_name and ass_details: # Assignment checking is only important if the statement defines # multiple variables. new_result = [] for ass_expression_list, op in ass_details: new_result += finder.find_assignments(ass_expression_list[0], result, seek_name) result = new_result return result
def eval_statement(self, stmt, seek_name=None): """ The starting point of the completion. A statement always owns a call list, which are the calls, that a statement does. In case multiple names are defined in the statement, `seek_name` returns the result for this name. :param stmt: A `pr.ExprStmt`. """ debug.dbg('eval_statement %s (%s)', stmt, seek_name) expression_list = stmt.expression_list() if isinstance(stmt, FakeStatement): return expression_list # Already contains the results. result = self.eval_expression_list(expression_list) ass_details = stmt.assignment_details if ass_details and ass_details[0][1] != '=' and not isinstance(stmt, er.InstanceElement): # TODO don't check for this. expr_list, _operator = ass_details[0] # `=` is always the last character in aug assignments -> -1 operator = copy.copy(_operator) operator.string = operator.string[:-1] name = str(expr_list[0].name) parent = stmt.parent.get_parent_until(pr.Flow, reverse=True) if isinstance(parent, (pr.SubModule, fast.Module)): parent = er.ModuleWrapper(self, parent) left = self.find_types(parent, name, stmt.start_pos) if isinstance(stmt.parent, pr.ForFlow): # iterate through result and add the values, that's possible # only in for loops without clutter, because they are # predictable. for r in result: left = precedence.calculate(self, left, operator, [r]) result = left else: result = precedence.calculate(self, left, operator, result) elif len(stmt.get_defined_names()) > 1 and seek_name and ass_details: # Assignment checking is only important if the statement defines # multiple variables. new_result = [] for ass_expression_list, op in ass_details: new_result += finder.find_assignments(ass_expression_list[0], result, seek_name) result = new_result return result
def _eval_element_not_cached(self, element): debug.dbg('eval_element %s@%s', element, element.start_pos) types = set() if isinstance(element, (tree.Name, tree.Literal)) or tree.is_node(element, 'atom'): types = self._eval_atom(element) elif isinstance(element, tree.Keyword): # For False/True/None if element.value in ('False', 'True', 'None'): types.add(compiled.builtin_from_name(self, element.value)) # else: print e.g. could be evaluated like this in Python 2.7 elif element.isinstance(tree.Lambda): types = set([er.LambdaWrapper(self, element)]) elif element.isinstance(er.LambdaWrapper): types = set([element]) # TODO this is no real evaluation. elif element.type == 'expr_stmt': types = self.eval_statement(element) elif element.type in ('power', 'atom_expr'): types = self._eval_atom(element.children[0]) for trailer in element.children[1:]: if trailer == '**': # has a power operation. right = self.eval_element(element.children[2]) types = set(precedence.calculate(self, types, trailer, right)) break types = self.eval_trailer(types, trailer) elif element.type in ('testlist_star_expr', 'testlist',): # The implicit tuple in statements. types = set([iterable.ImplicitTuple(self, element)]) elif element.type in ('not_test', 'factor'): types = self.eval_element(element.children[-1]) for operator in element.children[:-1]: types = set(precedence.factor_calculate(self, types, operator)) elif element.type == 'test': # `x if foo else y` case. types = (self.eval_element(element.children[0]) | self.eval_element(element.children[-1])) elif element.type == 'operator': # Must be an ellipsis, other operators are not evaluated. assert element.value == '...' types = set([compiled.create(self, Ellipsis)]) elif element.type == 'dotted_name': types = self._eval_atom(element.children[0]) for next_name in element.children[2::2]: types = set(chain.from_iterable(self.find_types(typ, next_name) for typ in types)) types = types elif element.type == 'eval_input': types = self._eval_element_not_cached(element.children[0]) elif element.type == 'annassign': types = self.eval_element(element.children[1]) else: types = precedence.calculate_children(self, element.children) debug.dbg('eval_element result %s', types) return types
def _eval_precedence(self, _precedence): left = self._process_precedence_element(_precedence.left) right = self._process_precedence_element(_precedence.right) return precedence.calculate(left, _precedence.operator, right)
def _eval_precedence(self, _precedence): left = self.process_precedence_element(_precedence.left) right = self.process_precedence_element(_precedence.right) return precedence.calculate(self, left, _precedence.operator, right)
def eval_atom(self, context, atom): """ Basically to process ``atom`` nodes. The parser sometimes doesn't generate the node (because it has just one child). In that case an atom might be a name or a literal as well. """ if atom.type == 'name': # This is the first global lookup. stmt = atom.get_definition() if stmt.type == 'comp_for': stmt = tree.search_ancestor( stmt, ('expr_stmt', 'lambda', 'funcdef', 'classdef')) if stmt is None or stmt.type != 'expr_stmt': # We only need to adjust the start_pos for statements, because # there the name cannot be used. stmt = atom return context.py__getattribute__(name_or_str=atom, position=stmt.start_pos, search_global=True) elif isinstance(atom, tree.Literal): return set([compiled.create(self, atom.eval())]) else: c = atom.children if c[0].type == 'string': # Will be one string. types = self.eval_atom(context, c[0]) for string in c[1:]: right = self.eval_atom(context, string) types = precedence.calculate(self, context, types, '+', right) return types # Parentheses without commas are not tuples. elif c[0] == '(' and not len(c) == 2 \ and not(c[1].type == 'testlist_comp' and len(c[1].children) > 1): return self.eval_element(context, c[1]) try: comp_for = c[1].children[1] except (IndexError, AttributeError): pass else: if comp_for == ':': # Dict comprehensions have a colon at the 3rd index. try: comp_for = c[1].children[3] except IndexError: pass if comp_for.type == 'comp_for': return set([ iterable.Comprehension.from_atom(self, context, atom) ]) # It's a dict/list/tuple literal. array_node = c[1] try: array_node_c = array_node.children except AttributeError: array_node_c = [] if c[0] == '{' and (array_node == '}' or ':' in array_node_c): context = iterable.DictLiteralContext(self, context, atom) else: context = iterable.SequenceLiteralContext(self, context, atom) return set([context])
def eval_atom(self, context, atom): """ Basically to process ``atom`` nodes. The parser sometimes doesn't generate the node (because it has just one child). In that case an atom might be a name or a literal as well. """ if atom.type == 'name': # This is the first global lookup. stmt = atom.get_definition() if stmt.type == 'comp_for': stmt = tree.search_ancestor(stmt, ('expr_stmt', 'lambda', 'funcdef', 'classdef')) if stmt is None or stmt.type != 'expr_stmt': # We only need to adjust the start_pos for statements, because # there the name cannot be used. stmt = atom return context.py__getattribute__( name_or_str=atom, position=stmt.start_pos, search_global=True ) elif isinstance(atom, tree.Literal): return set([compiled.create(self, atom.eval())]) else: c = atom.children if c[0].type == 'string': # Will be one string. types = self.eval_atom(context, c[0]) for string in c[1:]: right = self.eval_atom(context, string) types = precedence.calculate(self, context, types, '+', right) return types # Parentheses without commas are not tuples. elif c[0] == '(' and not len(c) == 2 \ and not(c[1].type == 'testlist_comp' and len(c[1].children) > 1): return self.eval_element(context, c[1]) try: comp_for = c[1].children[1] except (IndexError, AttributeError): pass else: if comp_for == ':': # Dict comprehensions have a colon at the 3rd index. try: comp_for = c[1].children[3] except IndexError: pass if comp_for.type == 'comp_for': return set([iterable.Comprehension.from_atom(self, context, atom)]) # It's a dict/list/tuple literal. array_node = c[1] try: array_node_c = array_node.children except AttributeError: array_node_c = [] if c[0] == '{' and (array_node == '}' or ':' in array_node_c): context = iterable.DictLiteralContext(self, context, atom) else: context = iterable.SequenceLiteralContext(self, context, atom) return set([context])