Bram Moolenaar | bd5e15f | 2010-07-17 21:19:38 +0200 | [diff] [blame] | 1 | "python3complete.vim - Omni Completion for python |
| 2 | " Maintainer: Aaron Griffin <aaronmgriffin@gmail.com> |
| 3 | " Version: 0.9 |
Bram Moolenaar | ca63501 | 2015-09-25 20:34:21 +0200 | [diff] [blame] | 4 | " Last Updated: 18 Jun 2009 (small fix 2015 Sep 14 from Debian) |
Bram Moolenaar | bd5e15f | 2010-07-17 21:19:38 +0200 | [diff] [blame] | 5 | " |
| 6 | " Roland Puntaier: this file contains adaptations for python3 and is parallel to pythoncomplete.vim |
| 7 | " |
| 8 | " Changes |
| 9 | " TODO: |
| 10 | " 'info' item output can use some formatting work |
| 11 | " Add an "unsafe eval" mode, to allow for return type evaluation |
| 12 | " Complete basic syntax along with import statements |
| 13 | " i.e. "import url<c-x,c-o>" |
| 14 | " Continue parsing on invalid line?? |
| 15 | " |
| 16 | " v 0.9 |
| 17 | " * Fixed docstring parsing for classes and functions |
| 18 | " * Fixed parsing of *args and **kwargs type arguments |
| 19 | " * Better function param parsing to handle things like tuples and |
| 20 | " lambda defaults args |
| 21 | " |
| 22 | " v 0.8 |
| 23 | " * Fixed an issue where the FIRST assignment was always used instead of |
| 24 | " using a subsequent assignment for a variable |
| 25 | " * Fixed a scoping issue when working inside a parameterless function |
| 26 | " |
| 27 | " |
| 28 | " v 0.7 |
| 29 | " * Fixed function list sorting (_ and __ at the bottom) |
| 30 | " * Removed newline removal from docs. It appears vim handles these better in |
| 31 | " recent patches |
| 32 | " |
| 33 | " v 0.6: |
| 34 | " * Fixed argument completion |
| 35 | " * Removed the 'kind' completions, as they are better indicated |
| 36 | " with real syntax |
| 37 | " * Added tuple assignment parsing (whoops, that was forgotten) |
| 38 | " * Fixed import handling when flattening scope |
| 39 | " |
| 40 | " v 0.5: |
| 41 | " Yeah, I skipped a version number - 0.4 was never public. |
| 42 | " It was a bugfix version on top of 0.3. This is a complete |
| 43 | " rewrite. |
| 44 | " |
| 45 | |
| 46 | if !has('python3') |
| 47 | echo "Error: Required vim compiled with +python3" |
| 48 | finish |
| 49 | endif |
| 50 | |
| 51 | function! python3complete#Complete(findstart, base) |
| 52 | "findstart = 1 when we need to get the text length |
| 53 | if a:findstart == 1 |
| 54 | let line = getline('.') |
| 55 | let idx = col('.') |
| 56 | while idx > 0 |
| 57 | let idx -= 1 |
| 58 | let c = line[idx] |
| 59 | if c =~ '\w' |
| 60 | continue |
| 61 | elseif ! c =~ '\.' |
| 62 | let idx = -1 |
| 63 | break |
| 64 | else |
| 65 | break |
| 66 | endif |
| 67 | endwhile |
| 68 | |
| 69 | return idx |
| 70 | "findstart = 0 when we need to return the list of completions |
| 71 | else |
| 72 | "vim no longer moves the cursor upon completion... fix that |
| 73 | let line = getline('.') |
| 74 | let idx = col('.') |
| 75 | let cword = '' |
| 76 | while idx > 0 |
| 77 | let idx -= 1 |
| 78 | let c = line[idx] |
| 79 | if c =~ '\w' || c =~ '\.' |
| 80 | let cword = c . cword |
| 81 | continue |
| 82 | elseif strlen(cword) > 0 || idx == 0 |
| 83 | break |
| 84 | endif |
| 85 | endwhile |
| 86 | execute "py3 vimpy3complete('" . cword . "', '" . a:base . "')" |
| 87 | return g:python3complete_completions |
| 88 | endif |
| 89 | endfunction |
| 90 | |
| 91 | function! s:DefPython() |
| 92 | py3 << PYTHONEOF |
| 93 | import sys, tokenize, io, types |
| 94 | from token import NAME, DEDENT, NEWLINE, STRING |
| 95 | |
| 96 | debugstmts=[] |
| 97 | def dbg(s): debugstmts.append(s) |
| 98 | def showdbg(): |
| 99 | for d in debugstmts: print("DBG: %s " % d) |
| 100 | |
| 101 | def vimpy3complete(context,match): |
| 102 | global debugstmts |
| 103 | debugstmts = [] |
| 104 | try: |
| 105 | import vim |
| 106 | cmpl = Completer() |
| 107 | cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')")) |
| 108 | all = cmpl.get_completions(context,match) |
| 109 | all.sort(key=lambda x:x['abbr'].replace('_','z')) |
| 110 | dictstr = '[' |
| 111 | # have to do this for double quoting |
| 112 | for cmpl in all: |
| 113 | dictstr += '{' |
| 114 | for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x]) |
| 115 | dictstr += '"icase":0},' |
| 116 | if dictstr[-1] == ',': dictstr = dictstr[:-1] |
| 117 | dictstr += ']' |
| 118 | #dbg("dict: %s" % dictstr) |
| 119 | vim.command("silent let g:python3complete_completions = %s" % dictstr) |
| 120 | #dbg("Completion dict:\n%s" % all) |
| 121 | except vim.error: |
| 122 | dbg("VIM Error: %s" % vim.error) |
| 123 | |
| 124 | class Completer(object): |
| 125 | def __init__(self): |
| 126 | self.compldict = {} |
| 127 | self.parser = PyParser() |
| 128 | |
| 129 | def evalsource(self,text,line=0): |
| 130 | sc = self.parser.parse(text,line) |
| 131 | src = sc.get_code() |
| 132 | dbg("source: %s" % src) |
| 133 | try: exec(src,self.compldict) |
| 134 | except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1])) |
| 135 | for l in sc.locals: |
| 136 | try: exec(l,self.compldict) |
| 137 | except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l)) |
| 138 | |
| 139 | def _cleanstr(self,doc): |
| 140 | return doc.replace('"',' ').replace("'",' ') |
| 141 | |
| 142 | def get_arguments(self,func_obj): |
| 143 | def _ctor(class_ob): |
| 144 | try: return class_ob.__init__ |
| 145 | except AttributeError: |
| 146 | for base in class_ob.__bases__: |
| 147 | rc = _ctor(base) |
| 148 | if rc is not None: return rc |
| 149 | return None |
| 150 | |
| 151 | arg_offset = 1 |
| 152 | if type(func_obj) == type: func_obj = _ctor(func_obj) |
| 153 | elif type(func_obj) == types.MethodType: arg_offset = 1 |
| 154 | else: arg_offset = 0 |
| 155 | |
| 156 | arg_text='' |
| 157 | if type(func_obj) in [types.FunctionType, types.LambdaType,types.MethodType]: |
| 158 | try: |
| 159 | cd = func_obj.__code__ |
| 160 | real_args = cd.co_varnames[arg_offset:cd.co_argcount] |
| 161 | defaults = func_obj.__defaults__ or [] |
| 162 | defaults = ["=%s" % name for name in defaults] |
| 163 | defaults = [""] * (len(real_args)-len(defaults)) + defaults |
| 164 | items = [a+d for a,d in zip(real_args,defaults)] |
| 165 | if func_obj.__code__.co_flags & 0x4: |
| 166 | items.append("...") |
| 167 | if func_obj.__code__.co_flags & 0x8: |
| 168 | items.append("***") |
| 169 | arg_text = (','.join(items)) + ')' |
| 170 | except: |
| 171 | dbg("arg completion: %s: %s" % (sys.exc_info()[0],sys.exc_info()[1])) |
| 172 | pass |
| 173 | if len(arg_text) == 0: |
| 174 | # The doc string sometimes contains the function signature |
| 175 | # this works for alot of C modules that are part of the |
| 176 | # standard library |
| 177 | doc = func_obj.__doc__ |
| 178 | if doc: |
| 179 | doc = doc.lstrip() |
| 180 | pos = doc.find('\n') |
| 181 | if pos > 0: |
| 182 | sigline = doc[:pos] |
| 183 | lidx = sigline.find('(') |
| 184 | ridx = sigline.find(')') |
| 185 | if lidx > 0 and ridx > 0: |
| 186 | arg_text = sigline[lidx+1:ridx] + ')' |
| 187 | if len(arg_text) == 0: arg_text = ')' |
| 188 | return arg_text |
| 189 | |
| 190 | def get_completions(self,context,match): |
| 191 | #dbg("get_completions('%s','%s')" % (context,match)) |
| 192 | stmt = '' |
| 193 | if context: stmt += str(context) |
| 194 | if match: stmt += str(match) |
| 195 | try: |
| 196 | result = None |
| 197 | all = {} |
| 198 | ridx = stmt.rfind('.') |
| 199 | if len(stmt) > 0 and stmt[-1] == '(': |
| 200 | result = eval(_sanitize(stmt[:-1]), self.compldict) |
| 201 | doc = result.__doc__ |
| 202 | if doc is None: doc = '' |
| 203 | args = self.get_arguments(result) |
| 204 | return [{'word':self._cleanstr(args),'info':self._cleanstr(doc)}] |
| 205 | elif ridx == -1: |
| 206 | match = stmt |
| 207 | all = self.compldict |
| 208 | else: |
| 209 | match = stmt[ridx+1:] |
| 210 | stmt = _sanitize(stmt[:ridx]) |
| 211 | result = eval(stmt, self.compldict) |
| 212 | all = dir(result) |
| 213 | |
| 214 | dbg("completing: stmt:%s" % stmt) |
| 215 | completions = [] |
| 216 | |
| 217 | try: maindoc = result.__doc__ |
| 218 | except: maindoc = ' ' |
| 219 | if maindoc is None: maindoc = ' ' |
| 220 | for m in all: |
| 221 | if m == "_PyCmplNoType": continue #this is internal |
| 222 | try: |
| 223 | dbg('possible completion: %s' % m) |
| 224 | if m.find(match) == 0: |
| 225 | if result is None: inst = all[m] |
| 226 | else: inst = getattr(result,m) |
| 227 | try: doc = inst.__doc__ |
| 228 | except: doc = maindoc |
| 229 | typestr = str(inst) |
| 230 | if doc is None or doc == '': doc = maindoc |
| 231 | |
| 232 | wrd = m[len(match):] |
| 233 | c = {'word':wrd, 'abbr':m, 'info':self._cleanstr(doc)} |
| 234 | if "function" in typestr: |
| 235 | c['word'] += '(' |
| 236 | c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst)) |
| 237 | elif "method" in typestr: |
| 238 | c['word'] += '(' |
| 239 | c['abbr'] += '(' + self._cleanstr(self.get_arguments(inst)) |
| 240 | elif "module" in typestr: |
| 241 | c['word'] += '.' |
| 242 | elif "type" in typestr: |
| 243 | c['word'] += '(' |
| 244 | c['abbr'] += '(' |
| 245 | completions.append(c) |
| 246 | except: |
| 247 | i = sys.exc_info() |
| 248 | dbg("inner completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt)) |
| 249 | return completions |
| 250 | except: |
| 251 | i = sys.exc_info() |
| 252 | dbg("completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt)) |
| 253 | return [] |
| 254 | |
| 255 | class Scope(object): |
| 256 | def __init__(self,name,indent,docstr=''): |
| 257 | self.subscopes = [] |
| 258 | self.docstr = docstr |
| 259 | self.locals = [] |
| 260 | self.parent = None |
| 261 | self.name = name |
| 262 | self.indent = indent |
| 263 | |
| 264 | def add(self,sub): |
| 265 | #print('push scope: [%s@%s]' % (sub.name,sub.indent)) |
| 266 | sub.parent = self |
| 267 | self.subscopes.append(sub) |
| 268 | return sub |
| 269 | |
| 270 | def doc(self,str): |
| 271 | """ Clean up a docstring """ |
| 272 | d = str.replace('\n',' ') |
| 273 | d = d.replace('\t',' ') |
| 274 | while d.find(' ') > -1: d = d.replace(' ',' ') |
| 275 | while d[0] in '"\'\t ': d = d[1:] |
| 276 | while d[-1] in '"\'\t ': d = d[:-1] |
| 277 | dbg("Scope(%s)::docstr = %s" % (self,d)) |
| 278 | self.docstr = d |
| 279 | |
| 280 | def local(self,loc): |
| 281 | self._checkexisting(loc) |
| 282 | self.locals.append(loc) |
| 283 | |
| 284 | def copy_decl(self,indent=0): |
| 285 | """ Copy a scope's declaration only, at the specified indent level - not local variables """ |
| 286 | return Scope(self.name,indent,self.docstr) |
| 287 | |
| 288 | def _checkexisting(self,test): |
| 289 | "Convienance function... keep out duplicates" |
| 290 | if test.find('=') > -1: |
| 291 | var = test.split('=')[0].strip() |
| 292 | for l in self.locals: |
| 293 | if l.find('=') > -1 and var == l.split('=')[0].strip(): |
| 294 | self.locals.remove(l) |
| 295 | |
| 296 | def get_code(self): |
| 297 | str = "" |
| 298 | if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n' |
| 299 | for l in self.locals: |
| 300 | if l.startswith('import'): str += l+'\n' |
| 301 | str += 'class _PyCmplNoType:\n def __getattr__(self,name):\n return None\n' |
| 302 | for sub in self.subscopes: |
| 303 | str += sub.get_code() |
| 304 | for l in self.locals: |
| 305 | if not l.startswith('import'): str += l+'\n' |
| 306 | |
| 307 | return str |
| 308 | |
| 309 | def pop(self,indent): |
| 310 | #print('pop scope: [%s] to [%s]' % (self.indent,indent)) |
| 311 | outer = self |
| 312 | while outer.parent != None and outer.indent >= indent: |
| 313 | outer = outer.parent |
| 314 | return outer |
| 315 | |
| 316 | def currentindent(self): |
| 317 | #print('parse current indent: %s' % self.indent) |
| 318 | return ' '*self.indent |
| 319 | |
| 320 | def childindent(self): |
| 321 | #print('parse child indent: [%s]' % (self.indent+1)) |
| 322 | return ' '*(self.indent+1) |
| 323 | |
| 324 | class Class(Scope): |
| 325 | def __init__(self, name, supers, indent, docstr=''): |
| 326 | Scope.__init__(self,name,indent, docstr) |
| 327 | self.supers = supers |
| 328 | def copy_decl(self,indent=0): |
| 329 | c = Class(self.name,self.supers,indent, self.docstr) |
| 330 | for s in self.subscopes: |
| 331 | c.add(s.copy_decl(indent+1)) |
| 332 | return c |
| 333 | def get_code(self): |
| 334 | str = '%sclass %s' % (self.currentindent(),self.name) |
| 335 | if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers) |
| 336 | str += ':\n' |
| 337 | if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' |
| 338 | if len(self.subscopes) > 0: |
| 339 | for s in self.subscopes: str += s.get_code() |
| 340 | else: |
| 341 | str += '%spass\n' % self.childindent() |
| 342 | return str |
| 343 | |
| 344 | |
| 345 | class Function(Scope): |
| 346 | def __init__(self, name, params, indent, docstr=''): |
| 347 | Scope.__init__(self,name,indent, docstr) |
| 348 | self.params = params |
| 349 | def copy_decl(self,indent=0): |
| 350 | return Function(self.name,self.params,indent, self.docstr) |
| 351 | def get_code(self): |
| 352 | str = "%sdef %s(%s):\n" % \ |
| 353 | (self.currentindent(),self.name,','.join(self.params)) |
| 354 | if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' |
| 355 | str += "%spass\n" % self.childindent() |
| 356 | return str |
| 357 | |
| 358 | class PyParser: |
| 359 | def __init__(self): |
| 360 | self.top = Scope('global',0) |
| 361 | self.scope = self.top |
Bram Moolenaar | ca63501 | 2015-09-25 20:34:21 +0200 | [diff] [blame] | 362 | self.parserline = 0 |
Bram Moolenaar | bd5e15f | 2010-07-17 21:19:38 +0200 | [diff] [blame] | 363 | |
| 364 | def _parsedotname(self,pre=None): |
| 365 | #returns (dottedname, nexttoken) |
| 366 | name = [] |
| 367 | if pre is None: |
| 368 | tokentype, token, indent = self.donext() |
| 369 | if tokentype != NAME and token != '*': |
| 370 | return ('', token) |
| 371 | else: token = pre |
| 372 | name.append(token) |
| 373 | while True: |
| 374 | tokentype, token, indent = self.donext() |
| 375 | if token != '.': break |
| 376 | tokentype, token, indent = self.donext() |
| 377 | if tokentype != NAME: break |
| 378 | name.append(token) |
| 379 | return (".".join(name), token) |
| 380 | |
| 381 | def _parseimportlist(self): |
| 382 | imports = [] |
| 383 | while True: |
| 384 | name, token = self._parsedotname() |
| 385 | if not name: break |
| 386 | name2 = '' |
| 387 | if token == 'as': name2, token = self._parsedotname() |
| 388 | imports.append((name, name2)) |
| 389 | while token != "," and "\n" not in token: |
| 390 | tokentype, token, indent = self.donext() |
| 391 | if token != ",": break |
| 392 | return imports |
| 393 | |
| 394 | def _parenparse(self): |
| 395 | name = '' |
| 396 | names = [] |
| 397 | level = 1 |
| 398 | while True: |
| 399 | tokentype, token, indent = self.donext() |
| 400 | if token in (')', ',') and level == 1: |
| 401 | if '=' not in name: name = name.replace(' ', '') |
| 402 | names.append(name.strip()) |
| 403 | name = '' |
| 404 | if token == '(': |
| 405 | level += 1 |
| 406 | name += "(" |
| 407 | elif token == ')': |
| 408 | level -= 1 |
| 409 | if level == 0: break |
| 410 | else: name += ")" |
| 411 | elif token == ',' and level == 1: |
| 412 | pass |
| 413 | else: |
| 414 | name += "%s " % str(token) |
| 415 | return names |
| 416 | |
| 417 | def _parsefunction(self,indent): |
| 418 | self.scope=self.scope.pop(indent) |
| 419 | tokentype, fname, ind = self.donext() |
| 420 | if tokentype != NAME: return None |
| 421 | |
| 422 | tokentype, open, ind = self.donext() |
| 423 | if open != '(': return None |
| 424 | params=self._parenparse() |
| 425 | |
| 426 | tokentype, colon, ind = self.donext() |
| 427 | if colon != ':': return None |
| 428 | |
| 429 | return Function(fname,params,indent) |
| 430 | |
| 431 | def _parseclass(self,indent): |
| 432 | self.scope=self.scope.pop(indent) |
| 433 | tokentype, cname, ind = self.donext() |
| 434 | if tokentype != NAME: return None |
| 435 | |
| 436 | super = [] |
| 437 | tokentype, thenext, ind = self.donext() |
| 438 | if thenext == '(': |
| 439 | super=self._parenparse() |
| 440 | elif thenext != ':': return None |
| 441 | |
| 442 | return Class(cname,super,indent) |
| 443 | |
| 444 | def _parseassignment(self): |
| 445 | assign='' |
| 446 | tokentype, token, indent = self.donext() |
| 447 | if tokentype == tokenize.STRING or token == 'str': |
| 448 | return '""' |
| 449 | elif token == '(' or token == 'tuple': |
| 450 | return '()' |
| 451 | elif token == '[' or token == 'list': |
| 452 | return '[]' |
| 453 | elif token == '{' or token == 'dict': |
| 454 | return '{}' |
| 455 | elif tokentype == tokenize.NUMBER: |
| 456 | return '0' |
| 457 | elif token == 'open' or token == 'file': |
| 458 | return 'file' |
| 459 | elif token == 'None': |
| 460 | return '_PyCmplNoType()' |
| 461 | elif token == 'type': |
| 462 | return 'type(_PyCmplNoType)' #only for method resolution |
| 463 | else: |
| 464 | assign += token |
| 465 | level = 0 |
| 466 | while True: |
| 467 | tokentype, token, indent = self.donext() |
| 468 | if token in ('(','{','['): |
| 469 | level += 1 |
| 470 | elif token in (']','}',')'): |
| 471 | level -= 1 |
| 472 | if level == 0: break |
| 473 | elif level == 0: |
| 474 | if token in (';','\n'): break |
| 475 | assign += token |
| 476 | return "%s" % assign |
| 477 | |
| 478 | def donext(self): |
| 479 | type, token, (lineno, indent), end, self.parserline = next(self.gen) |
| 480 | if lineno == self.curline: |
| 481 | #print('line found [%s] scope=%s' % (line.replace('\n',''),self.scope.name)) |
| 482 | self.currentscope = self.scope |
| 483 | return (type, token, indent) |
| 484 | |
| 485 | def _adjustvisibility(self): |
| 486 | newscope = Scope('result',0) |
| 487 | scp = self.currentscope |
| 488 | while scp != None: |
| 489 | if type(scp) == Function: |
| 490 | slice = 0 |
| 491 | #Handle 'self' params |
| 492 | if scp.parent != None and type(scp.parent) == Class: |
| 493 | slice = 1 |
| 494 | newscope.local('%s = %s' % (scp.params[0],scp.parent.name)) |
| 495 | for p in scp.params[slice:]: |
| 496 | i = p.find('=') |
| 497 | if len(p) == 0: continue |
| 498 | pvar = '' |
| 499 | ptype = '' |
| 500 | if i == -1: |
| 501 | pvar = p |
| 502 | ptype = '_PyCmplNoType()' |
| 503 | else: |
| 504 | pvar = p[:i] |
| 505 | ptype = _sanitize(p[i+1:]) |
| 506 | if pvar.startswith('**'): |
| 507 | pvar = pvar[2:] |
| 508 | ptype = '{}' |
| 509 | elif pvar.startswith('*'): |
| 510 | pvar = pvar[1:] |
| 511 | ptype = '[]' |
| 512 | |
| 513 | newscope.local('%s = %s' % (pvar,ptype)) |
| 514 | |
| 515 | for s in scp.subscopes: |
| 516 | ns = s.copy_decl(0) |
| 517 | newscope.add(ns) |
| 518 | for l in scp.locals: newscope.local(l) |
| 519 | scp = scp.parent |
| 520 | |
| 521 | self.currentscope = newscope |
| 522 | return self.currentscope |
| 523 | |
| 524 | #p.parse(vim.current.buffer[:],vim.eval("line('.')")) |
| 525 | def parse(self,text,curline=0): |
| 526 | self.curline = int(curline) |
| 527 | buf = io.StringIO(''.join(text) + '\n') |
| 528 | self.gen = tokenize.generate_tokens(buf.readline) |
| 529 | self.currentscope = self.scope |
| 530 | |
| 531 | try: |
| 532 | freshscope=True |
| 533 | while True: |
| 534 | tokentype, token, indent = self.donext() |
| 535 | #dbg( 'main: token=[%s] indent=[%s]' % (token,indent)) |
| 536 | |
| 537 | if tokentype == DEDENT or token == "pass": |
| 538 | self.scope = self.scope.pop(indent) |
| 539 | elif token == 'def': |
| 540 | func = self._parsefunction(indent) |
| 541 | if func is None: |
| 542 | print("function: syntax error...") |
| 543 | continue |
| 544 | dbg("new scope: function") |
| 545 | freshscope = True |
| 546 | self.scope = self.scope.add(func) |
| 547 | elif token == 'class': |
| 548 | cls = self._parseclass(indent) |
| 549 | if cls is None: |
| 550 | print("class: syntax error...") |
| 551 | continue |
| 552 | freshscope = True |
| 553 | dbg("new scope: class") |
| 554 | self.scope = self.scope.add(cls) |
| 555 | |
| 556 | elif token == 'import': |
| 557 | imports = self._parseimportlist() |
| 558 | for mod, alias in imports: |
| 559 | loc = "import %s" % mod |
| 560 | if len(alias) > 0: loc += " as %s" % alias |
| 561 | self.scope.local(loc) |
| 562 | freshscope = False |
| 563 | elif token == 'from': |
| 564 | mod, token = self._parsedotname() |
| 565 | if not mod or token != "import": |
| 566 | print("from: syntax error...") |
| 567 | continue |
| 568 | names = self._parseimportlist() |
| 569 | for name, alias in names: |
| 570 | loc = "from %s import %s" % (mod,name) |
| 571 | if len(alias) > 0: loc += " as %s" % alias |
| 572 | self.scope.local(loc) |
| 573 | freshscope = False |
| 574 | elif tokentype == STRING: |
| 575 | if freshscope: self.scope.doc(token) |
| 576 | elif tokentype == NAME: |
| 577 | name,token = self._parsedotname(token) |
| 578 | if token == '=': |
| 579 | stmt = self._parseassignment() |
| 580 | dbg("parseassignment: %s = %s" % (name, stmt)) |
| 581 | if stmt != None: |
| 582 | self.scope.local("%s = %s" % (name,stmt)) |
| 583 | freshscope = False |
| 584 | except StopIteration: #thrown on EOF |
| 585 | pass |
| 586 | except: |
| 587 | dbg("parse error: %s, %s @ %s" % |
| 588 | (sys.exc_info()[0], sys.exc_info()[1], self.parserline)) |
| 589 | return self._adjustvisibility() |
| 590 | |
| 591 | def _sanitize(str): |
| 592 | val = '' |
| 593 | level = 0 |
| 594 | for c in str: |
| 595 | if c in ('(','{','['): |
| 596 | level += 1 |
| 597 | elif c in (']','}',')'): |
| 598 | level -= 1 |
| 599 | elif level == 0: |
| 600 | val += c |
| 601 | return val |
| 602 | |
| 603 | sys.path.extend(['.','..']) |
| 604 | PYTHONEOF |
| 605 | endfunction |
| 606 | |
| 607 | call s:DefPython() |