diff --git a/evaluate.py b/evaluate.py index b03e38b3..bed30c3c 100644 --- a/evaluate.py +++ b/evaluate.py @@ -1,12 +1,14 @@ import parsing -import __builtin__ import itertools - -class Instance(object): +class Exec(object): + def __init__(self, base): + self.base = base + def get_parent_until(self, *args): + return self.base.get_parent_until(*args) + +class Instance(Exec): """ This class is used to evaluate instances. """ - def __init__(self, cl): - self.cl = cl def get_instance_vars(self): """ @@ -14,7 +16,7 @@ class Instance(object): classes """ n = [] - for s in self.cl.subscopes: + for s in self.base.subscopes: try: # get the self name, if there's one self_name = s.params[0].used_vars[0].names[0] @@ -27,23 +29,46 @@ class Instance(object): # because otherwise, they are just something else if n2.names[0] == self_name and len(n2.names) == 2: n.append(n2) - n += self.cl.get_set_vars() + n += self.base.get_set_vars() return n + def __repr__(self): + return "<%s of %s>" % \ + (self.__class__.__name__, self.base) -class Execution(object): - """ This class is used to evaluate functions and their returns. """ - def __init__(self, function): - self.cl = function - def get_return_vars(self): +class Execution(Exec): + """ + This class is used to evaluate functions and their returns. + """ + + def get_return_types(self): """ - Get the instance vars of a class. This includes the vars of all - classes + Get the return vars of a function. """ - n = [] - n += self.function.get_set_vars() - return n + result = [] + if isinstance(self.base, Execution): + stmts = self.base.get_return_types() + else: + stmts = self.base.returns + + #n += self.function.get_set_vars() + # these are the statements of the return functions + for stmt in stmts: + if isinstance(stmt, parsing.Class): + # it might happen, that a function returns a Class and this + # gets executed, therefore get the instance here. + result.append(Instance(stmt)) + else: + print 'addstmt', stmt + result += follow_statement(stmt) + + print 'ret', stmt + return result + + def __repr__(self): + return "<%s of %s>" % \ + (self.__class__.__name__, self.base) def get_names_for_scope(scope): @@ -55,47 +80,67 @@ def get_names_for_scope(scope): if not isinstance(scope, parsing.Class) or scope == start_scope: compl += scope.get_set_vars() scope = scope.parent + print 'get_names_for_scope', scope, len(compl) return compl -def get_scopes_for_name(scope, name, search_global=False): +def get_scopes_for_name(scope, name, search_global=False, search_func=None): """ :return: List of Names. Their parents are the scopes, they are defined in. :rtype: list """ - if search_global: + def remove_statements(result): + """ + This is the part where statements are being stripped. + + Due to lazy evaluation, statements like a = func; b = a; b() have to be + evaluated. + """ + res_new = [] + for r in result: + if isinstance(r, parsing.Statement): + scopes = follow_statement(r) + res_new += remove_statements(scopes) + else: + res_new.append(r) + return res_new + + if search_func: + names = search_func() + elif search_global: names = get_names_for_scope(scope) else: names = scope.get_set_vars() result = [c.parent for c in names if [name] == list(c.names)] + return remove_statements(result) + + +def follow_statement(stmt, scope=None): + """ + :param stmt: contains a statement + :param scope: contains a scope. If not given, takes the parent of stmt. + """ + if scope is None: + scope = stmt.get_parent_until(parsing.Function) + result = [] + calls = stmt.get_assignment_calls() + print 'calls', calls, calls.values + for tokens in calls: + for tok in tokens: + print 'tok', tok, type(tok), isinstance(tok,str) + if not isinstance(tok, str): + # the string tokens are just operations (+, -, etc.) + result += follow_call(scope, tok) return result - -# default: name in scope -# point: chaining -# execution: -> eval returns default & ? -def follow_statement(scope, stmt): - arr = stmt.get_assignment_calls().values[0][0] - print arr - - path = arr.generate_call_list() - if debug_function: - path, path_print = itertools.tee(path) - dbg('') - dbg('') - dbg('calls:') - for c in path_print: - dbg(c) - - dbg('') - dbg('') - dbg('follow:') +def follow_call(scope, call): + path = call.generate_call_list() current = next(path) result = [] if isinstance(current, parsing.Array): - if current.arr_type == parsing.Array.EMPTY: + """if current.arr_type == parsing.Array.EMPTY: # the normal case - no array type print 'length', len(current) elif current.arr_type == parsing.Array.LIST: @@ -106,14 +151,15 @@ def follow_statement(scope, stmt): result.append(__builtin__.tuple()) elif current.arr_type == parsing.Array.DICT: result.append(__builtin__.dict()) + """ + result.append(current) else: result = get_scopes_for_name(scope, current, search_global=True) pass - print result + print 'before', result result = follow_paths(path, result) - print result - exit() + print 'after result', result return result @@ -125,43 +171,56 @@ def follow_paths(path, results): iter_paths = itertools.tee(path, len(results)) else: iter_paths = [path] - for i, r in enumerate(results): - results_new += follow_path(iter_paths[i], r) + print 'enter', results, len(results) + if len(results): + for i, r in enumerate(results): + print 1 + results_new += follow_path(iter_paths[i], r) except StopIteration: return results return results_new def follow_path(path, input): - current = next(path) - print 'follow', input, current - result = [] - - if isinstance(current, parsing.Array): - # this must be an execution, either () or [] - if current.arr_type == parsing.Array.LIST: - print 'dini mami' - result = [] # TODO eval lists - else: - # input must be a class or func -> make an instance or execution - if isinstance(input, parsing.Class): - result.append(Instance(input)) + """ takes a generator and tries to complete the path """ + def add_result(current, input): + result = [] + if isinstance(current, parsing.Array): + # this must be an execution, either () or [] + if current.arr_type == parsing.Array.LIST: + result = [] # TODO eval lists else: - result.append(Execution(input)) - else: - if isinstance(input, parsing.Function): - # TODO check default function methods and return them - result = [] + # input must be a class or func - make an instance or execution + if isinstance(input, parsing.Class): + result.append(Instance(input)) + else: + result.append(Execution(input)) else: - # TODO check default class methods and return them also - if isinstance(input, Instance): - result = input.get_instance_vars() + if isinstance(input, parsing.Function): + # TODO check default function methods and return them + result = [] + elif isinstance(input, Instance): + result = get_scopes_for_name(input, current, + search_func=input.get_instance_vars) elif isinstance(input, Execution): - result = input.get_return_vars() + #try: + stmts = input.get_return_types() + print 'exec', stmts + for s in stmts: + result += add_result(current, s) + #except AttributeError: + # dbg('cannot execute:', input) + elif isinstance(input, parsing.Import): + print 'dini mueter, steile griech!' else: + # TODO check default class methods and return them also result = get_scopes_for_name(input, current) + return result - return follow_paths(path, result) + cur = next(path) + print 'follow', input, cur + + return follow_paths(path, add_result(cur, input)) def dbg(*args): diff --git a/functions.py b/functions.py index 0b202e58..92666f47 100644 --- a/functions.py +++ b/functions.py @@ -177,7 +177,7 @@ def complete(source, row, column, file_callback=None): column = 17 row = 140 - row = 144 + row = 148 column = 200 f = File(source=source, row=row) scope = f.parser.user_scope @@ -197,11 +197,11 @@ def complete(source, row, column, file_callback=None): dbg(e) result = [] - if path: + if path and path[0]: # just parse one statement r = parsing.PyFuzzyParser(".".join(path)) print 'p', r.top.get_code().replace('\n', r'\n'), r.top.statements[0] - evaluate.follow_statement(scope, r.top.statements[0]) + evaluate.follow_statement(r.top.statements[0], scope) exit() name = path.pop() @@ -237,7 +237,7 @@ def set_debug_function(func_cb): """ global debug_function debug_function = func_cb - parsing.debug_function = func_cb + #parsing.debug_function = func_cb evaluate.debug_function = func_cb diff --git a/parsing.py b/parsing.py index 510a7860..44b44ad1 100644 --- a/parsing.py +++ b/parsing.py @@ -64,6 +64,13 @@ class Simple(object): self.line_end = line_end self.parent = None + def get_parent_until(self, *classes): + """ Takes always the parent, until one class """ + scope = self + while not (scope.parent is None or scope.__class__ in classes): + scope = scope.parent + return scope + def __repr__(self): code = self.get_code().replace('\n', ' ') return "<%s: %s@%s>" % \ @@ -179,7 +186,10 @@ class Scope(Simple): """ n = [] for stmt in self.statements: - n += stmt.get_set_vars() + try: + n += stmt.get_set_vars(True) + except TypeError: + n += stmt.get_set_vars() # function and class names n += [s.name for s in self.subscopes] @@ -281,6 +291,8 @@ class Function(Scope): for p in params: p.parent = self self.decorators = [] + self.returns = [] + is_generator = False def get_code(self, first_indent=False, indention=" "): str = "\n".join('@' + stmt.get_code() for stmt in self.decorators) @@ -355,18 +367,25 @@ class Flow(Scope): str += self.next.get_code() return str - def get_set_vars(self): + def get_set_vars(self, is_internal_call=False): """ Get the names for the flow. This includes also a call to the super class. + :param is_internal_call: defines an option for internal files to crawl\ + through this class. Normally it will just call its superiors, to\ + generate the output. """ - n = self.set_vars - if self.statement: - n += self.statement.set_vars - if self.next: - n += self.next.get_set_vars() - n += super(Flow, self).get_set_vars() - return n + if is_internal_call: + n = [] + n += self.set_vars + if self.statement: + n += self.statement.set_vars + if self.next: + n += self.next.get_set_vars(is_internal_call) + n += super(Flow, self).get_set_vars() + return n + else: + return self.get_parent_until(Class, Function).get_set_vars() def set_next(self, next): """ Set the next element in the flow, those are else, except, etc. """ @@ -464,6 +483,9 @@ class Statement(Simple): for s in set_vars + used_funcs + used_vars: s.parent = self + # cache + self.assignment_calls = None + def get_code(self, new_line=True): if new_line: return self.code + '\n' @@ -480,24 +502,27 @@ class Statement(Simple): most of the statements won't need this data anyway. This is something 'like' a lazy execution. """ + if self.assignment_calls: + return self.assignment_calls result = Array(Array.EMPTY) top = result level = 0 is_chain = False close_brackets = False - print 'tok_list', self.token_list + dbg('tok_list', self.token_list) for i, tok_temp in enumerate(self.token_list): #print 'tok', tok_temp, result try: token_type, tok, indent = tok_temp - if level == 0 and \ + if tok in ['return', 'yield'] or level == 0 and \ '=' in tok and not tok in ['>=', '<=', '==', '!=']: # This means, there is an assignment here. # TODO there may be multiple assignments: a = b = 1 # initialize the first item result = Array(Array.EMPTY) + top = result continue except TypeError: # the token is a Name, which has already been parsed @@ -506,7 +531,7 @@ class Statement(Simple): brackets = {'(': Array.EMPTY, '[': Array.LIST, '{': Array.SET} is_call = lambda: result.__class__ == Call is_call_or_close = lambda: is_call() or close_brackets - if isinstance(tok, Name): + if isinstance(tok, Name): # names if is_chain: call = Call(tok, result) result = result.set_next_chain_call(call) @@ -519,7 +544,7 @@ class Statement(Simple): call = Call(tok, result) result.add_to_current_field(call) result = call - elif tok in brackets.keys(): + elif tok in brackets.keys(): # brackets level += 1 if is_call_or_close(): result = Array(brackets[tok], result) @@ -557,19 +582,31 @@ class Statement(Simple): level -= 1 #result = result.parent close_brackets = True - else: + elif tok in [tokenize.STRING, tokenize.NUMBER]: # TODO catch numbers and strings -> token_type and make # calls out of them + if is_call_or_close(): + result = result.parent + close_brackets = False + + call = Call(tok, result) + result.add_to_current_field(call) + result = call + result.add_to_current_field(tok) + pass + else: if is_call_or_close(): result = result.parent close_brackets = False result.add_to_current_field(tok) - print 'tok_end', tok_temp, result, close_brackets + #print 'tok_end', tok_temp, result, close_brackets if level != 0: raise ParserError("Brackets don't match: %s. This is not normal " "behaviour. Please submit a bug" % level) + + self.assignment_calls = top return top @@ -943,6 +980,7 @@ class PyFuzzyParser(object): used_funcs = [] used_vars = [] level = 0 # The level of parentheses + is_return = None if pre_used_token: token_type, tok, indent = pre_used_token @@ -981,6 +1019,8 @@ class PyFuzzyParser(object): #print 'is_name', tok if tok in ['return', 'yield', 'del', 'raise', 'assert']: set_string = tok + ' ' + if tok in ['return', 'yield']: + is_return = tok elif tok in ['print', 'exec']: # delete those statements, just let the rest stand there set_string = '' @@ -1021,6 +1061,13 @@ class PyFuzzyParser(object): #print 'new_stat', string, set_vars, used_funcs, used_vars stmt = Statement(string, set_vars, used_funcs, used_vars,\ tok_list, indent, line_start, self.line_nr) + if is_return: + # add returns to the scope + func = self.scope.get_parent_until(Function) + func.returns.append(stmt) + if is_return == 'yield': + func.is_generator = True + return stmt, tok def next(self): diff --git a/test.py b/test.py index 78b53367..d11ed14e 100644 --- a/test.py +++ b/test.py @@ -44,16 +44,16 @@ class Supi(A, datetime.datetime): class A(): class B(): def test(self): - return A() + return a.test() class init: def __init__(self, a, b=3, c = A): self.a = a -a = A() +aclass = A() -b = a.test() -c = a or b -c = a or c +b = aclass.test() +c = aclass or b +c = aclass or c class Empty(): pass @@ -131,9 +131,9 @@ class c1(): c2,c5 = c1(), c1().c3() def c3(self): import time as c4 - c5 = 3 - return 'asdf' - + c5 = c4 + c5 = 1 + return c5+1+'asdf' (c1().c2.\ @@ -141,4 +141,8 @@ c, 1, c3()) [0].pop() c = "a".join([1,2]) -c = c1().c3()[0] +c = c1().c3().sleep() + +asdf = c1; asdf2 = asdf +b= asdf2 +c = b().test()