Hello Bram!
Hallo Aaron!
E836: This Vim cannot execute :python after using :py3
This happened to me several times already when changing to a buffer with
python code, e.g. to my .vimrc file. The reason was that omnifunc was
set to pythoncomplete#Complete.
Python 2.6 and python 2.7 have had changes to make it more compatible
with python3. It is now quite easy to write code that works for both,
python2.x and python3.x.
With the error message triggering and the latter thought I tried to go
for the following: forward to the other python command.
- The first time vim realizes that it cannot run python (py3) because of
the RTLD problem this error message is issued:
E836: This Vim cannot execute :python after using :py3. Will
forward future calls to :py3.
or
E837: This Vim cannot execute :py3 after using :python. Will
forward future calls to python.
(Of course, I've not changed the translations of the these messages.)
- The second time the python command is forwarded to the other python
command
- If the system/python version allows both, python 2.x and python 3.x,
there is no forwarding.
- This applies only if vim is configure for both python versions
If somebody has a script, which uses either :py3 or :python, the
following sequence in the .vimrc as first python interaction will
redirect all calls to :py3.
"redirect :python to :py3
py3 3
python 2
"argument has no meaning
If the order is changed :py3 calls will be redirected to :python.
@Bram: If you agree, please merge it to the main vim line.
@Aaron: Since the changes in python3complete.vim do work for python 2.6
and python 2.7 (I've test it), I have moved it to pythoncomplete.vim.
The patch contains this move. The original pythoncomplete.vim I've moved
to python25complete.vim with according internal renaming. It is attached.
Do you agree with this proceeding?
I'm moving to ArchLinux and in the vim package there pythoncomplete.vim
is separate. The same version is already included in the vim sources.
- Roland
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
diff -r 3edc4535acfa runtime/autoload/pythoncomplete.vim
--- a/runtime/autoload/pythoncomplete.vim Wed Aug 10 18:36:54 2011 +0200
+++ b/runtime/autoload/pythoncomplete.vim Wed Aug 17 05:35:24 2011 +0200
@@ -3,6 +3,9 @@
" Version: 0.9
" Last Updated: 18 Jun 2009
"
+" Roland Puntaier: this file contains adaptations for python3, which also work
for python2.6 onward.
+" The original pythoncomplete.vim has been moved to python25complete.vim with
according internal renamings
+"
" Changes
" TODO:
" 'info' item output can use some formatting work
@@ -41,8 +44,8 @@
" rewrite.
"
-if !has('python')
- echo "Error: Required vim compiled with +python"
+if !has('python3') && !has('python')
+ echo "Error: Required vim compiled with +python or +python3"
finish
endif
@@ -88,42 +91,23 @@
function! s:DefPython()
python << PYTHONEOF
-import sys, tokenize, cStringIO, types
+import sys, tokenize, io, types
from token import NAME, DEDENT, NEWLINE, STRING
debugstmts=[]
def dbg(s): debugstmts.append(s)
def showdbg():
- for d in debugstmts: print "DBG: %s " % d
+ for d in debugstmts: print("DBG: %s " % d)
def vimcomplete(context,match):
global debugstmts
debugstmts = []
try:
import vim
- def complsort(x,y):
- try:
- xa = x['abbr']
- ya = y['abbr']
- if xa[0] == '_':
- if xa[1] == '_' and ya[0:2] == '__':
- return xa > ya
- elif ya[0:2] == '__':
- return -1
- elif y[0] == '_':
- return xa > ya
- else:
- return 1
- elif ya[0] == '_':
- return -1
- else:
- return xa > ya
- except:
- return 0
cmpl = Completer()
cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
all = cmpl.get_completions(context,match)
- all.sort(complsort)
+ all.sort(key=lambda x:x['abbr'].replace('_','z'))
dictstr = '['
# have to do this for double quoting
for cmpl in all:
@@ -147,44 +131,43 @@
sc = self.parser.parse(text,line)
src = sc.get_code()
dbg("source: %s" % src)
- try: exec(src) in self.compldict
+ try: exec(src,self.compldict)
except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1]))
for l in sc.locals:
- try: exec(l) in self.compldict
+ try: exec(l,self.compldict)
except: dbg("locals: %s, %s [%s]" %
(sys.exc_info()[0],sys.exc_info()[1],l))
def _cleanstr(self,doc):
return doc.replace('"',' ').replace("'",' ')
def get_arguments(self,func_obj):
- def _ctor(obj):
- try: return class_ob.__init__.im_func
+ def _ctor(class_ob):
+ try: return class_ob.__init__
except AttributeError:
for base in class_ob.__bases__:
- rc = _find_constructor(base)
+ rc = _ctor(base)
if rc is not None: return rc
return None
arg_offset = 1
- if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj)
- elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func
+ if type(func_obj) == type: func_obj = _ctor(func_obj)
+ elif type(func_obj) == types.MethodType: arg_offset = 1
else: arg_offset = 0
-
+
arg_text=''
- if type(func_obj) in [types.FunctionType, types.LambdaType]:
+ if type(func_obj) in [types.FunctionType,
types.LambdaType,types.MethodType]:
try:
- cd = func_obj.func_code
+ cd = func_obj.__code__
real_args = cd.co_varnames[arg_offset:cd.co_argcount]
- defaults = func_obj.func_defaults or ''
- defaults = map(lambda name: "=%s" % name, defaults)
+ defaults = func_obj.__defaults__ or []
+ defaults = ["=%s" % name for name in defaults]
defaults = [""] * (len(real_args)-len(defaults)) + defaults
- items = map(lambda a,d: a+d, real_args, defaults)
- if func_obj.func_code.co_flags & 0x4:
+ items = [a+d for a,d in zip(real_args,defaults)]
+ if func_obj.__code__.co_flags & 0x4:
items.append("...")
- if func_obj.func_code.co_flags & 0x8:
+ if func_obj.__code__.co_flags & 0x8:
items.append("***")
arg_text = (','.join(items)) + ')'
-
except:
dbg("arg completion: %s: %s" %
(sys.exc_info()[0],sys.exc_info()[1]))
pass
@@ -206,7 +189,7 @@
return arg_text
def get_completions(self,context,match):
- dbg("get_completions('%s','%s')" % (context,match))
+ #dbg("get_completions('%s','%s')" % (context,match))
stmt = ''
if context: stmt += str(context)
if match: stmt += str(match)
@@ -257,7 +240,7 @@
c['abbr'] += '(' +
self._cleanstr(self.get_arguments(inst))
elif "module" in typestr:
c['word'] += '.'
- elif "class" in typestr:
+ elif "type" in typestr:
c['word'] += '('
c['abbr'] += '('
completions.append(c)
@@ -280,7 +263,7 @@
self.indent = indent
def add(self,sub):
- #print 'push scope: [%s@%s]' % (sub.name,sub.indent)
+ #print('push scope: [%s@%s]' % (sub.name,sub.indent))
sub.parent = self
self.subscopes.append(sub)
return sub
@@ -325,18 +308,18 @@
return str
def pop(self,indent):
- #print 'pop scope: [%s] to [%s]' % (self.indent,indent)
+ #print('pop scope: [%s] to [%s]' % (self.indent,indent))
outer = self
while outer.parent != None and outer.indent >= indent:
outer = outer.parent
return outer
def currentindent(self):
- #print 'parse current indent: %s' % self.indent
+ #print('parse current indent: %s' % self.indent)
return ' '*self.indent
def childindent(self):
- #print 'parse child indent: [%s]' % (self.indent+1)
+ #print('parse child indent: [%s]' % (self.indent+1))
return ' '*(self.indent+1)
class Class(Scope):
@@ -382,15 +365,15 @@
#returns (dottedname, nexttoken)
name = []
if pre is None:
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if tokentype != NAME and token != '*':
return ('', token)
else: token = pre
name.append(token)
while True:
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if token != '.': break
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if tokentype != NAME: break
name.append(token)
return (".".join(name), token)
@@ -404,7 +387,7 @@
if token == 'as': name2, token = self._parsedotname()
imports.append((name, name2))
while token != "," and "\n" not in token:
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if token != ",": break
return imports
@@ -413,7 +396,7 @@
names = []
level = 1
while True:
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if token in (')', ',') and level == 1:
if '=' not in name: name = name.replace(' ', '')
names.append(name.strip())
@@ -433,34 +416,34 @@
def _parsefunction(self,indent):
self.scope=self.scope.pop(indent)
- tokentype, fname, ind = self.next()
+ tokentype, fname, ind = self.donext()
if tokentype != NAME: return None
- tokentype, open, ind = self.next()
+ tokentype, open, ind = self.donext()
if open != '(': return None
params=self._parenparse()
- tokentype, colon, ind = self.next()
+ tokentype, colon, ind = self.donext()
if colon != ':': return None
return Function(fname,params,indent)
def _parseclass(self,indent):
self.scope=self.scope.pop(indent)
- tokentype, cname, ind = self.next()
+ tokentype, cname, ind = self.donext()
if tokentype != NAME: return None
super = []
- tokentype, next, ind = self.next()
- if next == '(':
+ tokentype, thenext, ind = self.donext()
+ if thenext == '(':
super=self._parenparse()
- elif next != ':': return None
+ elif thenext != ':': return None
return Class(cname,super,indent)
def _parseassignment(self):
assign=''
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if tokentype == tokenize.STRING or token == 'str':
return '""'
elif token == '(' or token == 'tuple':
@@ -481,7 +464,7 @@
assign += token
level = 0
while True:
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
if token in ('(','{','['):
level += 1
elif token in (']','}',')'):
@@ -492,10 +475,10 @@
assign += token
return "%s" % assign
- def next(self):
- type, token, (lineno, indent), end, self.parserline = self.gen.next()
+ def donext(self):
+ type, token, (lineno, indent), end, self.parserline = next(self.gen)
if lineno == self.curline:
- #print 'line found [%s] scope=%s' %
(line.replace('\n',''),self.scope.name)
+ #print('line found [%s] scope=%s' %
(line.replace('\n',''),self.scope.name))
self.currentscope = self.scope
return (type, token, indent)
@@ -541,14 +524,14 @@
#p.parse(vim.current.buffer[:],vim.eval("line('.')"))
def parse(self,text,curline=0):
self.curline = int(curline)
- buf = cStringIO.StringIO(''.join(text) + '\n')
+ buf = io.StringIO(''.join(text) + '\n')
self.gen = tokenize.generate_tokens(buf.readline)
self.currentscope = self.scope
try:
freshscope=True
while True:
- tokentype, token, indent = self.next()
+ tokentype, token, indent = self.donext()
#dbg( 'main: token=[%s] indent=[%s]' % (token,indent))
if tokentype == DEDENT or token == "pass":
@@ -556,7 +539,7 @@
elif token == 'def':
func = self._parsefunction(indent)
if func is None:
- print "function: syntax error..."
+ print("function: syntax error...")
continue
dbg("new scope: function")
freshscope = True
@@ -564,7 +547,7 @@
elif token == 'class':
cls = self._parseclass(indent)
if cls is None:
- print "class: syntax error..."
+ print("class: syntax error...")
continue
freshscope = True
dbg("new scope: class")
@@ -580,7 +563,7 @@
elif token == 'from':
mod, token = self._parsedotname()
if not mod or token != "import":
- print "from: syntax error..."
+ print("from: syntax error...")
continue
names = self._parseimportlist()
for name, alias in names:
@@ -622,4 +605,3 @@
endfunction
call s:DefPython()
-" vim: set et ts=4:
diff -r 3edc4535acfa src/if_python.c
--- a/src/if_python.c Wed Aug 10 18:36:54 2011 +0200
+++ b/src/if_python.c Wed Aug 17 05:35:24 2011 +0200
@@ -352,6 +352,8 @@
}
}
+static int forward_to_py3 = 0;
+
/*
* Load library and get all pointers.
* Parameter 'libname' provides name of DLL.
@@ -366,10 +368,12 @@
/* Can't have Python and Python3 loaded at the same time.
* It cause a crash, because RTLD_GLOBAL is needed for
* standard C extension libraries of one or both python versions. */
+ if (forward_to_py3) return OK;
if (python3_loaded())
{
- EMSG(_("E836: This Vim cannot execute :python after using :py3"));
- return FAIL;
+ EMSG(_("E836: This Vim cannot execute :python after using :py3. Will
forward future calls to :py3."));
+ forward_to_py3 = 1;
+ return OK;
}
#endif
@@ -515,6 +519,9 @@
if (recurse != 0)
return;
+ if (forward_to_py3)
+ return;
+
++recurse;
#ifdef DYNAMIC_PYTHON
@@ -554,6 +561,8 @@
EMSG(_("E263: Sorry, this command is disabled, the Python library
could not be loaded."));
goto fail;
}
+ if (forward_to_py3)
+ return -1;
#endif
#ifdef PYTHON_HOME
@@ -690,6 +699,8 @@
{
char_u *script;
+ if (forward_to_py3) ex_py3(eap);
+
script = script_get(eap, eap->arg);
if (!eap->skip)
{
@@ -713,6 +724,8 @@
const char *file = (char *)eap->arg;
char *p;
+ if (forward_to_py3) ex_py3file(eap);
+
/* Have to do it like this. PyRun_SimpleFile requires you to pass a
* stdio file pointer, but Vim and the Python DLL are compiled with
* different options under Windows, meaning that stdio pointers aren't
diff -r 3edc4535acfa src/if_python3.c
--- a/src/if_python3.c Wed Aug 10 18:36:54 2011 +0200
+++ b/src/if_python3.c Wed Aug 17 05:35:24 2011 +0200
@@ -350,6 +350,8 @@
}
}
+static int forward_to_python = 0;
+
/*
* Load library and get all pointers.
* Parameter 'libname' provides name of DLL.
@@ -365,10 +367,12 @@
/* Can't have Python and Python3 loaded at the same time.
* It cause a crash, because RTLD_GLOBAL is needed for
* standard C extension libraries of one or both python versions. */
+ if (forward_to_python) return OK;
if (python_loaded())
{
- EMSG(_("E837: This Vim cannot execute :py3 after using :python"));
- return FAIL;
+ EMSG(_("E837: This Vim cannot execute :py3 after using :python. Will
forward future calls to python."));
+ forward_to_python = 1;
+ return OK;
}
# endif
@@ -521,6 +525,9 @@
{
static int recurse = 0;
+ if (forward_to_python)
+ return;
+
/* If a crash occurs while doing this, don't try again. */
if (recurse != 0)
return;
@@ -565,11 +572,12 @@
EMSG(_("E263: Sorry, this command is disabled, the Python library
could not be loaded."));
goto fail;
}
+ if (forward_to_python)
+ return -1;
#endif
init_structs();
-
#ifdef PYTHON3_HOME
Py_SetPythonHome(PYTHON3_HOME);
#endif
@@ -703,6 +711,8 @@
{
char_u *script;
+ if (forward_to_python) ex_python(eap);
+
script = script_get(eap, eap->arg);
if (!eap->skip)
{
@@ -727,6 +737,8 @@
char *p;
int i;
+ if (forward_to_python) ex_pyfile(eap);
+
/* Have to do it like this. PyRun_SimpleFile requires you to pass a
* stdio file pointer, but Vim and the Python DLL are compiled with
* different options under Windows, meaning that stdio pointers aren't
"python25complete.vim - Omni Completion for python 2.5 or earlier
" Maintainer: Aaron Griffin <[email protected]>
" Version: 0.9
" Last Updated: 18 Jun 2009
"
" Roland Puntaier 2011_08_17: I've renamed to *25* in this file, nothing else.
"
" Changes
" TODO:
" 'info' item output can use some formatting work
" Add an "unsafe eval" mode, to allow for return type evaluation
" Complete basic syntax along with import statements
" i.e. "import url<c-x,c-o>"
" Continue parsing on invalid line??
"
" v 0.9
" * Fixed docstring parsing for classes and functions
" * Fixed parsing of *args and **kwargs type arguments
" * Better function param parsing to handle things like tuples and
" lambda defaults args
"
" v 0.8
" * Fixed an issue where the FIRST assignment was always used instead of
" using a subsequent assignment for a variable
" * Fixed a scoping issue when working inside a parameterless function
"
"
" v 0.7
" * Fixed function list sorting (_ and __ at the bottom)
" * Removed newline removal from docs. It appears vim handles these better in
" recent patches
"
" v 0.6:
" * Fixed argument completion
" * Removed the 'kind' completions, as they are better indicated
" with real syntax
" * Added tuple assignment parsing (whoops, that was forgotten)
" * Fixed import handling when flattening scope
"
" v 0.5:
" Yeah, I skipped a version number - 0.4 was never public.
" It was a bugfix version on top of 0.3. This is a complete
" rewrite.
"
if !has('python')
echo "Error: Required vim compiled with +python"
finish
endif
function! python25complete#Complete(findstart, base)
"findstart = 1 when we need to get the text length
if a:findstart == 1
let line = getline('.')
let idx = col('.')
while idx > 0
let idx -= 1
let c = line[idx]
if c =~ '\w'
continue
elseif ! c =~ '\.'
let idx = -1
break
else
break
endif
endwhile
return idx
"findstart = 0 when we need to return the list of completions
else
"vim no longer moves the cursor upon completion... fix that
let line = getline('.')
let idx = col('.')
let cword = ''
while idx > 0
let idx -= 1
let c = line[idx]
if c =~ '\w' || c =~ '\.'
let cword = c . cword
continue
elseif strlen(cword) > 0 || idx == 0
break
endif
endwhile
execute "python vimcomplete('" . cword . "', '" . a:base . "')"
return g:python25complete_completions
endif
endfunction
function! s:DefPython()
python << PYTHONEOF
import sys, tokenize, cStringIO, types
from token import NAME, DEDENT, NEWLINE, STRING
debugstmts=[]
def dbg(s): debugstmts.append(s)
def showdbg():
for d in debugstmts: print "DBG: %s " % d
def vimcomplete(context,match):
global debugstmts
debugstmts = []
try:
import vim
def complsort(x,y):
try:
xa = x['abbr']
ya = y['abbr']
if xa[0] == '_':
if xa[1] == '_' and ya[0:2] == '__':
return xa > ya
elif ya[0:2] == '__':
return -1
elif y[0] == '_':
return xa > ya
else:
return 1
elif ya[0] == '_':
return -1
else:
return xa > ya
except:
return 0
cmpl = Completer()
cmpl.evalsource('\n'.join(vim.current.buffer),vim.eval("line('.')"))
all = cmpl.get_completions(context,match)
all.sort(complsort)
dictstr = '['
# have to do this for double quoting
for cmpl in all:
dictstr += '{'
for x in cmpl: dictstr += '"%s":"%s",' % (x,cmpl[x])
dictstr += '"icase":0},'
if dictstr[-1] == ',': dictstr = dictstr[:-1]
dictstr += ']'
#dbg("dict: %s" % dictstr)
vim.command("silent let g:python25complete_completions = %s" % dictstr)
#dbg("Completion dict:\n%s" % all)
except vim.error:
dbg("VIM Error: %s" % vim.error)
class Completer(object):
def __init__(self):
self.compldict = {}
self.parser = PyParser()
def evalsource(self,text,line=0):
sc = self.parser.parse(text,line)
src = sc.get_code()
dbg("source: %s" % src)
try: exec(src) in self.compldict
except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1]))
for l in sc.locals:
try: exec(l) in self.compldict
except: dbg("locals: %s, %s [%s]" %
(sys.exc_info()[0],sys.exc_info()[1],l))
def _cleanstr(self,doc):
return doc.replace('"',' ').replace("'",' ')
def get_arguments(self,func_obj):
def _ctor(obj):
try: return class_ob.__init__.im_func
except AttributeError:
for base in class_ob.__bases__:
rc = _find_constructor(base)
if rc is not None: return rc
return None
arg_offset = 1
if type(func_obj) == types.ClassType: func_obj = _ctor(func_obj)
elif type(func_obj) == types.MethodType: func_obj = func_obj.im_func
else: arg_offset = 0
arg_text=''
if type(func_obj) in [types.FunctionType, types.LambdaType]:
try:
cd = func_obj.func_code
real_args = cd.co_varnames[arg_offset:cd.co_argcount]
defaults = func_obj.func_defaults or ''
defaults = map(lambda name: "=%s" % name, defaults)
defaults = [""] * (len(real_args)-len(defaults)) + defaults
items = map(lambda a,d: a+d, real_args, defaults)
if func_obj.func_code.co_flags & 0x4:
items.append("...")
if func_obj.func_code.co_flags & 0x8:
items.append("***")
arg_text = (','.join(items)) + ')'
except:
dbg("arg completion: %s: %s" %
(sys.exc_info()[0],sys.exc_info()[1]))
pass
if len(arg_text) == 0:
# The doc string sometimes contains the function signature
# this works for alot of C modules that are part of the
# standard library
doc = func_obj.__doc__
if doc:
doc = doc.lstrip()
pos = doc.find('\n')
if pos > 0:
sigline = doc[:pos]
lidx = sigline.find('(')
ridx = sigline.find(')')
if lidx > 0 and ridx > 0:
arg_text = sigline[lidx+1:ridx] + ')'
if len(arg_text) == 0: arg_text = ')'
return arg_text
def get_completions(self,context,match):
dbg("get_completions('%s','%s')" % (context,match))
stmt = ''
if context: stmt += str(context)
if match: stmt += str(match)
try:
result = None
all = {}
ridx = stmt.rfind('.')
if len(stmt) > 0 and stmt[-1] == '(':
result = eval(_sanitize(stmt[:-1]), self.compldict)
doc = result.__doc__
if doc is None: doc = ''
args = self.get_arguments(result)
return
[{'word':self._cleanstr(args),'info':self._cleanstr(doc)}]
elif ridx == -1:
match = stmt
all = self.compldict
else:
match = stmt[ridx+1:]
stmt = _sanitize(stmt[:ridx])
result = eval(stmt, self.compldict)
all = dir(result)
dbg("completing: stmt:%s" % stmt)
completions = []
try: maindoc = result.__doc__
except: maindoc = ' '
if maindoc is None: maindoc = ' '
for m in all:
if m == "_PyCmplNoType": continue #this is internal
try:
dbg('possible completion: %s' % m)
if m.find(match) == 0:
if result is None: inst = all[m]
else: inst = getattr(result,m)
try: doc = inst.__doc__
except: doc = maindoc
typestr = str(inst)
if doc is None or doc == '': doc = maindoc
wrd = m[len(match):]
c = {'word':wrd, 'abbr':m, 'info':self._cleanstr(doc)}
if "function" in typestr:
c['word'] += '('
c['abbr'] += '(' +
self._cleanstr(self.get_arguments(inst))
elif "method" in typestr:
c['word'] += '('
c['abbr'] += '(' +
self._cleanstr(self.get_arguments(inst))
elif "module" in typestr:
c['word'] += '.'
elif "class" in typestr:
c['word'] += '('
c['abbr'] += '('
completions.append(c)
except:
i = sys.exc_info()
dbg("inner completion: %s,%s [stmt='%s']" %
(i[0],i[1],stmt))
return completions
except:
i = sys.exc_info()
dbg("completion: %s,%s [stmt='%s']" % (i[0],i[1],stmt))
return []
class Scope(object):
def __init__(self,name,indent,docstr=''):
self.subscopes = []
self.docstr = docstr
self.locals = []
self.parent = None
self.name = name
self.indent = indent
def add(self,sub):
#print 'push scope: [%s@%s]' % (sub.name,sub.indent)
sub.parent = self
self.subscopes.append(sub)
return sub
def doc(self,str):
""" Clean up a docstring """
d = str.replace('\n',' ')
d = d.replace('\t',' ')
while d.find(' ') > -1: d = d.replace(' ',' ')
while d[0] in '"\'\t ': d = d[1:]
while d[-1] in '"\'\t ': d = d[:-1]
dbg("Scope(%s)::docstr = %s" % (self,d))
self.docstr = d
def local(self,loc):
self._checkexisting(loc)
self.locals.append(loc)
def copy_decl(self,indent=0):
""" Copy a scope's declaration only, at the specified indent level -
not local variables """
return Scope(self.name,indent,self.docstr)
def _checkexisting(self,test):
"Convienance function... keep out duplicates"
if test.find('=') > -1:
var = test.split('=')[0].strip()
for l in self.locals:
if l.find('=') > -1 and var == l.split('=')[0].strip():
self.locals.remove(l)
def get_code(self):
str = ""
if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n'
for l in self.locals:
if l.startswith('import'): str += l+'\n'
str += 'class _PyCmplNoType:\n def __getattr__(self,name):\n
return None\n'
for sub in self.subscopes:
str += sub.get_code()
for l in self.locals:
if not l.startswith('import'): str += l+'\n'
return str
def pop(self,indent):
#print 'pop scope: [%s] to [%s]' % (self.indent,indent)
outer = self
while outer.parent != None and outer.indent >= indent:
outer = outer.parent
return outer
def currentindent(self):
#print 'parse current indent: %s' % self.indent
return ' '*self.indent
def childindent(self):
#print 'parse child indent: [%s]' % (self.indent+1)
return ' '*(self.indent+1)
class Class(Scope):
def __init__(self, name, supers, indent, docstr=''):
Scope.__init__(self,name,indent, docstr)
self.supers = supers
def copy_decl(self,indent=0):
c = Class(self.name,self.supers,indent, self.docstr)
for s in self.subscopes:
c.add(s.copy_decl(indent+1))
return c
def get_code(self):
str = '%sclass %s' % (self.currentindent(),self.name)
if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers)
str += ':\n'
if len(self.docstr) > 0: str +=
self.childindent()+'"""'+self.docstr+'"""\n'
if len(self.subscopes) > 0:
for s in self.subscopes: str += s.get_code()
else:
str += '%spass\n' % self.childindent()
return str
class Function(Scope):
def __init__(self, name, params, indent, docstr=''):
Scope.__init__(self,name,indent, docstr)
self.params = params
def copy_decl(self,indent=0):
return Function(self.name,self.params,indent, self.docstr)
def get_code(self):
str = "%sdef %s(%s):\n" % \
(self.currentindent(),self.name,','.join(self.params))
if len(self.docstr) > 0: str +=
self.childindent()+'"""'+self.docstr+'"""\n'
str += "%spass\n" % self.childindent()
return str
class PyParser:
def __init__(self):
self.top = Scope('global',0)
self.scope = self.top
def _parsedotname(self,pre=None):
#returns (dottedname, nexttoken)
name = []
if pre is None:
tokentype, token, indent = self.next()
if tokentype != NAME and token != '*':
return ('', token)
else: token = pre
name.append(token)
while True:
tokentype, token, indent = self.next()
if token != '.': break
tokentype, token, indent = self.next()
if tokentype != NAME: break
name.append(token)
return (".".join(name), token)
def _parseimportlist(self):
imports = []
while True:
name, token = self._parsedotname()
if not name: break
name2 = ''
if token == 'as': name2, token = self._parsedotname()
imports.append((name, name2))
while token != "," and "\n" not in token:
tokentype, token, indent = self.next()
if token != ",": break
return imports
def _parenparse(self):
name = ''
names = []
level = 1
while True:
tokentype, token, indent = self.next()
if token in (')', ',') and level == 1:
if '=' not in name: name = name.replace(' ', '')
names.append(name.strip())
name = ''
if token == '(':
level += 1
name += "("
elif token == ')':
level -= 1
if level == 0: break
else: name += ")"
elif token == ',' and level == 1:
pass
else:
name += "%s " % str(token)
return names
def _parsefunction(self,indent):
self.scope=self.scope.pop(indent)
tokentype, fname, ind = self.next()
if tokentype != NAME: return None
tokentype, open, ind = self.next()
if open != '(': return None
params=self._parenparse()
tokentype, colon, ind = self.next()
if colon != ':': return None
return Function(fname,params,indent)
def _parseclass(self,indent):
self.scope=self.scope.pop(indent)
tokentype, cname, ind = self.next()
if tokentype != NAME: return None
super = []
tokentype, next, ind = self.next()
if next == '(':
super=self._parenparse()
elif next != ':': return None
return Class(cname,super,indent)
def _parseassignment(self):
assign=''
tokentype, token, indent = self.next()
if tokentype == tokenize.STRING or token == 'str':
return '""'
elif token == '(' or token == 'tuple':
return '()'
elif token == '[' or token == 'list':
return '[]'
elif token == '{' or token == 'dict':
return '{}'
elif tokentype == tokenize.NUMBER:
return '0'
elif token == 'open' or token == 'file':
return 'file'
elif token == 'None':
return '_PyCmplNoType()'
elif token == 'type':
return 'type(_PyCmplNoType)' #only for method resolution
else:
assign += token
level = 0
while True:
tokentype, token, indent = self.next()
if token in ('(','{','['):
level += 1
elif token in (']','}',')'):
level -= 1
if level == 0: break
elif level == 0:
if token in (';','\n'): break
assign += token
return "%s" % assign
def next(self):
type, token, (lineno, indent), end, self.parserline = self.gen.next()
if lineno == self.curline:
#print 'line found [%s] scope=%s' %
(line.replace('\n',''),self.scope.name)
self.currentscope = self.scope
return (type, token, indent)
def _adjustvisibility(self):
newscope = Scope('result',0)
scp = self.currentscope
while scp != None:
if type(scp) == Function:
slice = 0
#Handle 'self' params
if scp.parent != None and type(scp.parent) == Class:
slice = 1
newscope.local('%s = %s' % (scp.params[0],scp.parent.name))
for p in scp.params[slice:]:
i = p.find('=')
if len(p) == 0: continue
pvar = ''
ptype = ''
if i == -1:
pvar = p
ptype = '_PyCmplNoType()'
else:
pvar = p[:i]
ptype = _sanitize(p[i+1:])
if pvar.startswith('**'):
pvar = pvar[2:]
ptype = '{}'
elif pvar.startswith('*'):
pvar = pvar[1:]
ptype = '[]'
newscope.local('%s = %s' % (pvar,ptype))
for s in scp.subscopes:
ns = s.copy_decl(0)
newscope.add(ns)
for l in scp.locals: newscope.local(l)
scp = scp.parent
self.currentscope = newscope
return self.currentscope
#p.parse(vim.current.buffer[:],vim.eval("line('.')"))
def parse(self,text,curline=0):
self.curline = int(curline)
buf = cStringIO.StringIO(''.join(text) + '\n')
self.gen = tokenize.generate_tokens(buf.readline)
self.currentscope = self.scope
try:
freshscope=True
while True:
tokentype, token, indent = self.next()
#dbg( 'main: token=[%s] indent=[%s]' % (token,indent))
if tokentype == DEDENT or token == "pass":
self.scope = self.scope.pop(indent)
elif token == 'def':
func = self._parsefunction(indent)
if func is None:
print "function: syntax error..."
continue
dbg("new scope: function")
freshscope = True
self.scope = self.scope.add(func)
elif token == 'class':
cls = self._parseclass(indent)
if cls is None:
print "class: syntax error..."
continue
freshscope = True
dbg("new scope: class")
self.scope = self.scope.add(cls)
elif token == 'import':
imports = self._parseimportlist()
for mod, alias in imports:
loc = "import %s" % mod
if len(alias) > 0: loc += " as %s" % alias
self.scope.local(loc)
freshscope = False
elif token == 'from':
mod, token = self._parsedotname()
if not mod or token != "import":
print "from: syntax error..."
continue
names = self._parseimportlist()
for name, alias in names:
loc = "from %s import %s" % (mod,name)
if len(alias) > 0: loc += " as %s" % alias
self.scope.local(loc)
freshscope = False
elif tokentype == STRING:
if freshscope: self.scope.doc(token)
elif tokentype == NAME:
name,token = self._parsedotname(token)
if token == '=':
stmt = self._parseassignment()
dbg("parseassignment: %s = %s" % (name, stmt))
if stmt != None:
self.scope.local("%s = %s" % (name,stmt))
freshscope = False
except StopIteration: #thrown on EOF
pass
except:
dbg("parse error: %s, %s @ %s" %
(sys.exc_info()[0], sys.exc_info()[1], self.parserline))
return self._adjustvisibility()
def _sanitize(str):
val = ''
level = 0
for c in str:
if c in ('(','{','['):
level += 1
elif c in (']','}',')'):
level -= 1
elif level == 0:
val += c
return val
sys.path.extend(['.','..'])
PYTHONEOF
endfunction
call s:DefPython()
" vim: set et ts=4: