diff --git a/_compatibility.py b/_compatibility.py index 67b5be8d..91b8f424 100644 --- a/_compatibility.py +++ b/_compatibility.py @@ -3,5 +3,14 @@ try: next = next except NameError: - def next(obj): - return obj.next() + _raiseStopIteration = object() + def next(iterator, default=_raiseStopIteration): + if not hasattr(iterator, 'next'): + raise TypeError("not an iterator") + try: + return iterator.next() + except StopIteration: + if default is _raiseStopIteration: + raise + else: + return default diff --git a/evaluate.py b/evaluate.py index 4725fe3c..611c03ba 100644 --- a/evaluate.py +++ b/evaluate.py @@ -25,12 +25,16 @@ import builtin memoize_caches = [] +class MultiLevelStopIteration(Exception): + pass + + def clear_caches(): for m in memoize_caches: m.clear() -def memoize(default=None): +def memoize_default(default=None): """ This is a typical memoization decorator, BUT there is one difference: To prevent recursion it sets defaults. @@ -78,7 +82,7 @@ class Executable(object): pass raise IndexError('No params available') - @memoize(default=[]) + @memoize_default(default=[]) def get_params(self): """ This returns the params for an Execution/Instance and is injected as a @@ -86,6 +90,21 @@ class Executable(object): This needs to be here, because Instance can have __init__ functions, which act the same way as normal functions """ + def gen_param_name_copy(param, keys=[], values=[], array_type=None): + calls = parsing.Array(parsing.Array.NOARRAY, + self.var_args.parent_stmt) + calls.values = values + calls.keys = keys + print 'arr_t', array_type + calls.type = array_type + new_param = copy.copy(param) + new_param._assignment_calls_calculated = True + new_param._assignment_calls = calls + name = copy.copy(param.get_name()) + name.parent = new_param + #print 'insert', i, name, calls.values, value, self.func.params + return name + result = [] start_offset = 0 #print '\n\nfunc_params', self.func, self.func.parent, self.func @@ -104,54 +123,56 @@ class Executable(object): # There may be calls, which don't fit all the params, this just ignores # it. var_arg_iterator = self.get_var_args_iterator() - for i, (key, value) in enumerate(var_arg_iterator, start_offset): - param = None - try: - param = param_dict[key] - except: + + non_matching_keys = [] + for param in self.func.params[start_offset:]: + # The value and key can both be null. There, the defaults apply. + # args / kwargs will just be empty arrays / dicts, respectively. + key, value = next(var_arg_iterator, (None, None)) + print '\n\nlala', key, value + while key: try: - param = self.func.params[i] + key_param = param_dict[key] except IndexError: - debug.warning('Too many arguments given.', value) + non_matching_keys.append((key, value)) + else: + result.append(gen_param_name_copy(key_param, + values=[value])) + key, value = next(var_arg_iterator, (None, None)) - if param: - calls = parsing.Array(parsing.Array.NOARRAY, - self.var_args.parent_stmt) + #debug.warning('Too many arguments given.', value) - assignment = param.get_assignment_calls().values[0] - if assignment[0] == '*': - # *args param - print '\n\n3*', value, assignment - calls.type = parsing.Array.TUPLE + assignment = param.get_assignment_calls().values[0] + keys = [] + values = [] + array_type = None + if assignment[0] == '*': + # *args param + print '\n\n3*', value, assignment + + array_type = parsing.Array.TUPLE + if value: + values.append(value) + for key, value in var_arg_iterator: + # iterate until a key argument is found if key: var_arg_iterator.push_back(key, value) - else: - calls.values = [value] - for key, value in var_arg_iterator: - if key: - var_arg_iterator.push_back(key, value) - break - calls.values.append(value) - elif assignment[0] == '**': - # **kwargs param - calls.type = parsing.Array.DICT - calls.values = [value] - calls.keys = [key] - for value in var_arg_iterator: - calls.values.append(value) - calls.keys.append(key) - # it is a **args param - print '\n\n**', key, value, assignment - else: - # normal param - calls.values = [value] + break + values.append(value) + elif assignment[0] == '**': + # **kwargs param + array_type = parsing.Array.DICT + if non_matching_keys: + keys, values = zip(*non_matching_keys) + print '\n\n**', keys, values, assignment + else: + # normal param + print 'normal', value + if value: + values = [value] - new_param = copy.copy(param) - new_param._assignment_calls = calls - name = copy.copy(param.get_name()) - name.parent = new_param - #print 'insert', i, name, calls.values, value, self.func.params - result.append(name) + result.append(gen_param_name_copy(param, keys=keys, values=values, + array_type=array_type)) return result def get_var_args_iterator(self): @@ -345,7 +366,7 @@ class Execution(Executable): """ cache = {} - @memoize(default=[]) + @memoize_default(default=[]) def get_return_types(self): """ Get the return vars of a function. @@ -465,7 +486,10 @@ def get_names_for_scope(scope, position=None, star_search=True): # class variables/functions are only available if (not isinstance(scope, Class) or scope == start_scope) \ and not isinstance(scope, parsing.Flow): - yield scope, get_defined_names_for_position(scope, position) + try: + yield scope, get_defined_names_for_position(scope, position) + except StopIteration: + raise MultiLevelStopIteration('StopIteration raised somewhere') scope = scope.parent # add star imports @@ -638,7 +662,7 @@ def assign_tuples(tup, results, seek_name): return result -@memoize(default=[]) +@memoize_default(default=[]) def follow_statement(stmt, scope=None, seek_name=None): """ :param stmt: contains a statement @@ -673,9 +697,12 @@ def follow_call_list(scope, call_list): It is used to evaluate a two dimensional object, that has calls, arrays and operators in it. """ - if parsing.Array.is_type(call_list, parsing.Array.TUPLE): + print 'inpu', scope, call_list + if parsing.Array.is_type(call_list, parsing.Array.TUPLE, + parsing.Array.DICT): # Tuples can stand just alone without any braces. These would be # recognized as separate calls, but actually are a tuple. + print 'inpu', scope, call_list result = follow_call(scope, call_list) else: result = [] @@ -729,16 +756,19 @@ def follow_call(scope, call): def follow_paths(path, results, position=None): results_new = [] - try: - if results: - if len(results) > 1: - iter_paths = itertools.tee(path, len(results)) + if results: + if len(results) > 1: + iter_paths = itertools.tee(path, len(results)) + else: + iter_paths = [path] + + for i, r in enumerate(results): + fp = follow_path(iter_paths[i], r, position=position) + if fp is not None: + results_new += fp else: - iter_paths = [path] - for i, r in enumerate(results): - results_new += follow_path(iter_paths[i], r, position=position) - except StopIteration: - return results + # this means stop iteration + return results return results_new @@ -747,7 +777,10 @@ def follow_path(path, scope, position=None): Takes a generator and tries to complete the path. """ # current is either an Array or a Scope - current = next(path) + try: + current = next(path) + except StopIteration: + return None debug.dbg('follow', current, scope) result = [] diff --git a/functions.py b/functions.py index cd8da39d..69f55cf9 100644 --- a/functions.py +++ b/functions.py @@ -205,6 +205,7 @@ def complete(source, row, column, source_path): for s in scopes: # TODO is this reall the right way? just ignore the functions? \ # do the magic functions first? and then recheck here? + print 'scope, ' if not isinstance(s, parsing.Function): completions += s.get_defined_names() diff --git a/parsetest.py b/parsetest.py index 9ef824d6..d9d3bfc4 100644 --- a/parsetest.py +++ b/parsetest.py @@ -160,10 +160,6 @@ a. - - - - class C(object): def c_a(self): self.c_b = 1 @@ -171,10 +167,14 @@ class C(object): test = [1,2] -def args_func(arg1, *args, **kwargs): - return (arg1, args) +def args_func(arg1, arg2=1, *args, **kwargs): + return arg1, arg2, args, kwargs -exe = args_func(list,"", 3) -args_func(1,"", a=list)[0]. +exe = args_func(1,"") +args_func() #1,"", a=list)[0]. args_func(arg1=0, *test + [3], *[4,5], **{'a': 'b'}). -exe[1]. + + + + +exe[3]. diff --git a/parsing.py b/parsing.py index 4b9ede00..ca0f1330 100644 --- a/parsing.py +++ b/parsing.py @@ -325,7 +325,7 @@ class Function(Scope): def get_set_vars(self): n = super(Function, self).get_set_vars() - if self.param_cb or False: + if self.param_cb: # this is the really ugly part, where the functional style of this # get methods is broken, it executes a callback. # This is important, because something has to inject the params @@ -533,6 +533,8 @@ class Statement(Simple): # cache self._assignment_calls = None self._assignment_details = None + # this is important for other scripts + self._assignment_calls_calculated = False def get_code(self, new_line=True): if new_line: @@ -566,7 +568,7 @@ class Statement(Simple): This is not really nice written, sorry for that. If you plan to replace it and make it nicer, that would be cool :-) """ - if self._assignment_calls: + if self._assignment_calls_calculated: return self._assignment_calls self._assignment_details = [] result = Array(Array.NOARRAY, self) @@ -827,13 +829,13 @@ class Array(Call): return self.values[0][0] @staticmethod - def is_type(instance, typ): + def is_type(instance, *types): """ This is not only used for calls on the actual object, but for ducktyping, to invoke this function with anything as `self`. """ if isinstance(instance, Array): - if instance.type == typ: + if instance.type in types: return True return False diff --git a/test/completion/functions.py b/test/completion/functions.py index b92c1e95..1bb440c2 100644 --- a/test/completion/functions.py +++ b/test/completion/functions.py @@ -109,7 +109,21 @@ exe[1][1].append # ----------------- # ** kwargs # ----------------- +def kwargs_func(**kwargs): + return kwargs + +exe = kwargs_func(a=3,b=4) +#? [] +exe. # ----------------- # *args / ** kwargs # ----------------- + +def fu(a=1, b="", *args, **kwargs): + return a,b,args,kwargs + +exe = fu(list, 1, "", c=set) + +##? ['append'] +exe[0].