[geany/geany] 378e5e: Merge pull request #3031 from techee/python_sync

Jiří Techet git-noreply at xxxxx
Mon Mar 14 19:39:39 UTC 2022


Branch:      refs/heads/master
Author:      Jiří Techet <techet at gmail.com>
Committer:   GitHub <noreply at github.com>
Date:        Mon, 14 Mar 2022 19:39:39 UTC
Commit:      378e5e8a0db18364cd9ec848aa81f5eded508da5
             https://github.com/geany/geany/commit/378e5e8a0db18364cd9ec848aa81f5eded508da5

Log Message:
-----------
Merge pull request #3031 from techee/python_sync

Use python parser from uctags


Modified Paths:
--------------
    ctags/Makefile.am
    ctags/parsers/geany_python.c
    ctags/parsers/python.c
    src/tagmanager/tm_parser.c
    tests/ctags/cython_sample2.pyx.tags
    tests/ctags/py_constructor_arglist.py.tags
    tests/ctags/simple.py.tags
    tests/ctags/tabindent.py.tags

Modified: ctags/Makefile.am
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -81,7 +81,7 @@ parsers = \
 	parsers/perl.h \
 	parsers/php.c \
 	parsers/powershell.c \
-	parsers/geany_python.c \
+	parsers/python.c \
 	parsers/r.c \
 	parsers/r.h \
 	parsers/rst.c \


Modified: ctags/parsers/geany_python.c
862 lines changed, 0 insertions(+), 862 deletions(-)
===================================================================
@@ -1,862 +0,0 @@
-/*
-*   Copyright (c) 2000-2003, Darren Hiebert
-*
-*   This source code is released for free distribution under the terms of the
-*   GNU General Public License version 2 or (at your option) any later version.
-*
-*   This module contains functions for generating tags for Python language
-*   files.
-*/
-/*
-*   INCLUDE FILES
-*/
-#include "general.h"  /* must always come first */
-
-#include <string.h>
-
-#include "entry.h"
-#include "nestlevel.h"
-#include "options.h"
-#include "read.h"
-#include "parse.h"
-#include "vstring.h"
-#include "routines.h"
-#include "debug.h"
-#include "xtag.h"
-
-/*
-*   DATA DEFINITIONS
-*/
-
-struct corkInfo {
-	int index;
-};
-
-struct nestingLevelUserData {
-	int indentation;
-};
-#define PY_NL_INDENTATION(nl) ((struct nestingLevelUserData *)nestingLevelGetUserData(nl))->indentation
-
-
-typedef enum {
-	K_CLASS, K_FUNCTION, K_METHOD, K_VARIABLE, K_IMPORT
-} pythonKind;
-
-static kindDefinition PythonKinds[] = {
-	{true, 'c', "class",    "classes"},
-	{true, 'f', "function", "functions"},
-	{true, 'm', "member",   "class members"},
-	{true, 'v', "variable", "variables"},
-	{true, 'x', "unknown", "name referring a classe/variable/function/module defined in other module"}
-};
-
-typedef enum {
-	A_PUBLIC, A_PRIVATE, A_PROTECTED
-} pythonAccess;
-
-static const char *const PythonAccesses[] = {
-	"public", "private", "protected"
-};
-
-static char const * const singletriple = "'''";
-static char const * const doubletriple = "\"\"\"";
-
-/*
-*   FUNCTION DEFINITIONS
-*/
-
-static bool isIdentifierFirstCharacter (int c)
-{
-	return (bool) (isalpha (c) || c == '_');
-}
-
-static bool isIdentifierCharacter (int c)
-{
-	return (bool) (isalnum (c) || c == '_');
-}
-
-/* follows PEP-8, and always reports single-underscores as protected
- * See:
- * - http://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables
- * - http://www.python.org/dev/peps/pep-0008/#designing-for-inheritance
- */
-static pythonAccess accessFromIdentifier (const vString *const ident,
-	pythonKind kind, bool has_parent, bool parent_is_class)
-{
-	const char *const p = vStringValue (ident);
-	const size_t len = vStringLength (ident);
-
-	/* inside a function/method, private */
-	if (has_parent && !parent_is_class)
-		return A_PRIVATE;
-	/* not starting with "_", public */
-	else if (len < 1 || p[0] != '_')
-		return A_PUBLIC;
-	/* "__...__": magic methods */
-	else if (kind == K_METHOD && parent_is_class &&
-			 len > 3 && p[1] == '_' && p[len - 2] == '_' && p[len - 1] == '_')
-		return A_PUBLIC;
-	/* "__...": name mangling */
-	else if (parent_is_class && len > 1 && p[1] == '_')
-		return A_PRIVATE;
-	/* "_...": suggested as non-public, but easily accessible */
-	else
-		return A_PROTECTED;
-}
-
-static void addAccessFields (tagEntryInfo *const entry,
-	const vString *const ident, pythonKind kind,
-	bool has_parent, bool parent_is_class)
-{
-	pythonAccess access;
-
-	access = accessFromIdentifier (ident, kind, has_parent, parent_is_class);
-	entry->extensionFields.access = PythonAccesses [access];
-	/* FIXME: should we really set isFileScope in addition to access? */
-	if (access == A_PRIVATE)
-		entry->isFileScope = true;
-}
-
-/* Given a string with the contents of a line directly after the "def" keyword,
- * extract all relevant information and create a tag.
- */
-static struct corkInfo makeFunctionTag (vString *const function,
-	vString *const parent, int is_class_parent, const char *arglist)
-{
-	tagEntryInfo tag;
-	int corkIndex;
-	struct corkInfo info;
-
-	if (vStringLength (parent) > 0)
-	{
-		if (is_class_parent)
-		{
-			initTagEntry (&tag, vStringValue (function), K_METHOD);
-			tag.extensionFields.scopeKindIndex = K_CLASS;
-		}
-		else
-		{
-			initTagEntry (&tag, vStringValue (function), K_FUNCTION);
-			tag.extensionFields.scopeKindIndex = K_FUNCTION;
-		}
-		tag.extensionFields.scopeName = vStringValue (parent);
-	}
-	else
-		initTagEntry (&tag, vStringValue (function), K_FUNCTION);
-
-	tag.extensionFields.signature = arglist;
-
-	addAccessFields (&tag, function, is_class_parent ? K_METHOD : K_FUNCTION,
-		vStringLength (parent) > 0, is_class_parent);
-
-	corkIndex = makeTagEntry (&tag);
-
-	info.index = corkIndex;
-	return info;
-}
-
-/* Given a string with the contents of the line directly after the "class"
- * keyword, extract all necessary information and create a tag.
- */
-static int makeClassTag (vString *const class, vString *const inheritance,
-	vString *const parent, int is_class_parent)
-{
-	tagEntryInfo tag;
-	initTagEntry (&tag, vStringValue (class), K_CLASS);
-	if (vStringLength (parent) > 0)
-	{
-		if (is_class_parent)
-		{
-			tag.extensionFields.scopeKindIndex = K_CLASS;
-			tag.extensionFields.scopeName = vStringValue (parent);
-		}
-		else
-		{
-			tag.extensionFields.scopeKindIndex = K_FUNCTION;
-			tag.extensionFields.scopeName = vStringValue (parent);
-		}
-	}
-	tag.extensionFields.inheritance = vStringValue (inheritance);
-	addAccessFields (&tag, class, K_CLASS, vStringLength (parent) > 0,
-		is_class_parent);
-	return makeTagEntry (&tag);
-}
-
-static void makeVariableTag (vString *const var, vString *const parent,
-	bool is_class_parent)
-{
-	tagEntryInfo tag;
-	initTagEntry (&tag, vStringValue (var), K_VARIABLE);
-	if (vStringLength (parent) > 0)
-	{
-		tag.extensionFields.scopeKindIndex = K_CLASS;
-		tag.extensionFields.scopeName = vStringValue (parent);
-	}
-	addAccessFields (&tag, var, K_VARIABLE, vStringLength (parent) > 0,
-		is_class_parent);
-	makeTagEntry (&tag);
-}
-
-/* Skip a single or double quoted string. */
-static const char *skipString (const char *cp)
-{
-	const char *start = cp;
-	int escaped = 0;
-	for (cp++; *cp; cp++)
-	{
-		if (escaped)
-			escaped--;
-		else if (*cp == '\\')
-			escaped++;
-		else if (*cp == *start)
-			return cp + 1;
-	}
-	return cp;
-}
-
-/* Skip everything up to an identifier start. */
-static const char *skipEverything (const char *cp)
-{
-	int match;
-	for (; *cp; cp++)
-	{
-		if (*cp == '#')
-			return strchr(cp, '\0');
-
-		match = 0;
-		if (*cp == '"' || *cp == '\'')
-			match = 1;
-
-		/* these checks find unicode, binary (Python 3) and raw strings */
-		if (!match)
-		{
-			bool r_first = (*cp == 'r' || *cp == 'R');
-
-			/* "r" | "R" | "u" | "U" | "b" | "B" */
-			if (r_first || *cp == 'u' || *cp == 'U' ||  *cp == 'b' || *cp == 'B')
-			{
-				unsigned int i = 1;
-
-				/*  r_first -> "rb" | "rB" | "Rb" | "RB"
-				   !r_first -> "ur" | "UR" | "Ur" | "uR" | "br" | "Br" | "bR" | "BR" */
-				if (( r_first && (cp[i] == 'b' || cp[i] == 'B')) ||
-					(!r_first && (cp[i] == 'r' || cp[i] == 'R')))
-					i++;
-
-				if (cp[i] == '\'' || cp[i] == '"')
-				{
-					match = 1;
-					cp += i;
-				}
-			}
-		}
-		if (match)
-		{
-			cp = skipString(cp);
-			if (!*cp) break;
-		}
-		if (isIdentifierFirstCharacter ((int) *cp))
-			return cp;
-		if (match)
-			cp--; /* avoid jumping over the character after a skipped string */
-	}
-	return cp;
-}
-
-/* Skip an identifier. */
-static const char *skipIdentifier (const char *cp)
-{
-	while (isIdentifierCharacter ((int) *cp))
-		cp++;
-	return cp;
-}
-
-static const char *findDefinitionOrClass (const char *cp)
-{
-	while (*cp)
-	{
-		cp = skipEverything (cp);
-		if (!strncmp(cp, "def", 3) || !strncmp(cp, "class", 5) ||
-			!strncmp(cp, "cdef", 4) || !strncmp(cp, "cpdef", 5))
-		{
-			return cp;
-		}
-		cp = skipIdentifier (cp);
-	}
-	return NULL;
-}
-
-static const char *skipSpace (const char *cp)
-{
-	while (isspace ((int) *cp))
-		++cp;
-	return cp;
-}
-
-/* Starting at ''cp'', parse an identifier into ''identifier''. */
-static const char *parseIdentifier (const char *cp, vString *const identifier)
-{
-	vStringClear (identifier);
-	while (isIdentifierCharacter ((int) *cp))
-	{
-		vStringPut (identifier, (int) *cp);
-		++cp;
-	}
-	return cp;
-}
-
-static int parseClass (const char *cp, vString *const class,
-	vString *const parent, int is_class_parent)
-{
-	int corkIndex;
-	vString *const inheritance = vStringNew ();
-	vStringClear (inheritance);
-	cp = parseIdentifier (cp, class);
-	cp = skipSpace (cp);
-	if (*cp == '(')
-	{
-		++cp;
-		while (*cp != ')')
-		{
-			if (*cp == '\0')
-			{
-				/* Closing parenthesis can be in follow up line. */
-				cp = (const char *) readLineFromInputFile ();
-				if (!cp) break;
-				vStringPut (inheritance, ' ');
-				continue;
-			}
-			vStringPut (inheritance, *cp);
-			++cp;
-		}
-	}
-	corkIndex = makeClassTag (class, inheritance, parent, is_class_parent);
-	vStringDelete (inheritance);
-	return corkIndex;
-}
-
-static void parseImports (const char *cp)
-{
-	const char *pos;
-	vString *name, *name_next;
-
-	cp = skipEverything (cp);
-
-	if ((pos = strstr (cp, "import")) == NULL)
-		return;
-
-	cp = pos + 6;
-
-	/* continue only if there is some space between the keyword and the identifier */
-	if (! isspace (*cp))
-		return;
-
-	cp++;
-	cp = skipSpace (cp);
-
-	name = vStringNew ();
-	name_next = vStringNew ();
-
-	cp = skipEverything (cp);
-	while (*cp)
-	{
-		cp = parseIdentifier (cp, name);
-
-		cp = skipEverything (cp);
-		/* we parse the next possible import statement as well to be able to ignore 'foo' in
-		 * 'import foo as bar' */
-		parseIdentifier (cp, name_next);
-
-		/* take the current tag only if the next one is not "as" */
-		if (strcmp (vStringValue (name_next), "as") != 0 &&
-			strcmp (vStringValue (name), "as") != 0)
-		{
-			makeSimpleTag (name, K_IMPORT);
-		}
-	}
-	vStringDelete (name);
-	vStringDelete (name_next);
-}
-
-/* modified from lcpp.c getArglistFromStr().
- * warning: terminates rest of string past arglist!
- * note: does not ignore brackets inside strings! */
-static char *parseArglist(const char *buf)
-{
-	char *start, *end;
-	int level;
-	if (NULL == buf)
-		return NULL;
-	if (NULL == (start = strchr(buf, '(')))
-		return NULL;
-	for (level = 1, end = start + 1; level > 0; ++end)
-	{
-		if ('\0' == *end)
-			break;
-		else if ('(' == *end)
-			++ level;
-		else if (')' == *end)
-			-- level;
-	}
-	*end = '\0';
-	return strdup(start);
-}
-
-static struct corkInfo parseFunction (const char *cp, vString *const def,
-	vString *const parent, int is_class_parent)
-{
-	char *arglist;
-	struct corkInfo info;
-
-	cp = parseIdentifier (cp, def);
-	arglist = parseArglist (cp);
-	info = makeFunctionTag (def, parent, is_class_parent, arglist);
-	if (arglist != NULL)
-		eFree (arglist);
-	return info;
-}
-
-/* Get the combined name of a nested symbol. Classes are separated with ".",
- * functions with "/". For example this code:
- * class MyClass:
- *     def myFunction:
- *         def SubFunction:
- *             class SubClass:
- *                 def Method:
- *                     pass
- * Would produce this string:
- * MyClass.MyFunction/SubFunction/SubClass.Method
- */
-static bool constructParentString(NestingLevels *nls, int indent,
-	vString *result)
-{
-	int i;
-	NestingLevel *prev = NULL;
-	int is_class = false;
-	vStringClear (result);
-	for (i = 0; i < nls->n; i++)
-	{
-		NestingLevel *nl = nestingLevelsGetNthFromRoot (nls, i);
-		tagEntryInfo *e;
-
-		if (indent <= PY_NL_INDENTATION(nl))
-			break;
-		if (prev)
-		{
-			vStringCatS(result, ".");	/* make Geany symbol list grouping work properly */
-/*
-			if (prev->kindIndex == K_CLASS)
-				vStringCatS(result, ".");
-			else
-				vStringCatS(result, "/");
-*/
-		}
-
-		e = getEntryOfNestingLevel (nl);
-		if (e)
-		{
-			vStringCatS(result, e->name);
-			is_class = (e->kindIndex == K_CLASS);
-		}
-		else
-			is_class = false;
-
-		prev = nl;
-	}
-	return is_class;
-}
-
-/* Check indentation level and truncate nesting levels accordingly */
-static void checkIndent(NestingLevels *nls, int indent)
-{
-	int i;
-	NestingLevel *n;
-
-	for (i = 0; i < nls->n; i++)
-	{
-		n = nestingLevelsGetNthFromRoot (nls, i);
-		if (n && indent <= PY_NL_INDENTATION(n))
-		{
-			/* truncate levels */
-			nls->n = i;
-			break;
-		}
-	}
-}
-
-static void addNestingLevel(NestingLevels *nls, int indentation, struct corkInfo *info)
-{
-	int i;
-	NestingLevel *nl = NULL;
-
-	for (i = 0; i < nls->n; i++)
-	{
-		nl = nestingLevelsGetNthFromRoot(nls, i);
-		if (indentation <= PY_NL_INDENTATION(nl)) break;
-	}
-	if (i == nls->n)
-		nl = nestingLevelsPush(nls, info->index);
-	else
-		/* reuse existing slot */
-		nl = nestingLevelsTruncate (nls, i + 1, info->index);
-
-	PY_NL_INDENTATION(nl) = indentation;
-}
-
-/* Return a pointer to the start of the next triple string, or NULL. Store
- * the kind of triple string in "which" if the return is not NULL.
- */
-static char const *find_triple_start(char const *string, char const **which)
-{
-	char const *cp = string;
-
-	for (; *cp; cp++)
-	{
-		if (*cp == '#')
-			break;
-		if (*cp == '"' || *cp == '\'')
-		{
-			if (strncmp(cp, doubletriple, 3) == 0)
-			{
-				*which = doubletriple;
-				return cp;
-			}
-			if (strncmp(cp, singletriple, 3) == 0)
-			{
-				*which = singletriple;
-				return cp;
-			}
-			cp = skipString(cp);
-			if (!*cp) break;
-			cp--; /* avoid jumping over the character after a skipped string */
-		}
-	}
-	return NULL;
-}
-
-/* Find the end of a triple string as pointed to by "which", and update "which"
- * with any other triple strings following in the given string.
- */
-static void find_triple_end(char const *string, char const **which)
-{
-	char const *s = string;
-	while (1)
-	{
-		/* Check if the string ends in the same line. */
-		s = strstr (s, *which);
-		if (!s) break;
-		s += 3;
-		*which = NULL;
-		/* If yes, check if another one starts in the same line. */
-		s = find_triple_start(s, which);
-		if (!s) break;
-		s += 3;
-	}
-}
-
-static const char *findVariable(const char *line)
-{
-	/* Parse global and class variable names (C.x) from assignment statements.
-	 * Object attributes (obj.x) are ignored.
-	 * Assignment to a tuple 'x, y = 2, 3' not supported.
-	 * TODO: ignore duplicate tags from reassignment statements. */
-	const char *cp, *sp, *eq, *start;
-
-	cp = strstr(line, "=");
-	if (!cp)
-		return NULL;
-	eq = cp + 1;
-	while (*eq)
-	{
-		if (*eq == '=')
-			return NULL;	/* ignore '==' operator and 'x=5,y=6)' function lines */
-		if (*eq == '(' || *eq == '#')
-			break;	/* allow 'x = func(b=2,y=2,' lines and comments at the end of line */
-		eq++;
-	}
-
-	/* go backwards to the start of the line, checking we have valid chars */
-	start = cp - 1;
-	while (start >= line && isspace ((int) *start))
-		--start;
-	while (start >= line && isIdentifierCharacter ((int) *start))
-		--start;
-	if (!isIdentifierFirstCharacter(*(start + 1)))
-		return NULL;
-	sp = start;
-	while (sp >= line && isspace ((int) *sp))
-		--sp;
-	if ((sp + 1) != line)	/* the line isn't a simple variable assignment */
-		return NULL;
-	/* the line is valid, parse the variable name */
-	++start;
-	return start;
-}
-
-/* Skip type declaration that optionally follows a cdef/cpdef */
-static const char *skipTypeDecl (const char *cp, bool *is_class)
-{
-	const char *lastStart = cp, *ptr = cp;
-	int loopCount = 0;
-	ptr = skipSpace(cp);
-	if (!strncmp("extern", ptr, 6)) {
-		ptr += 6;
-		ptr = skipSpace(ptr);
-		if (!strncmp("from", ptr, 4)) { return NULL; }
-	}
-	if (!strncmp("class", ptr, 5)) {
-		ptr += 5 ;
-		*is_class = true;
-		ptr = skipSpace(ptr);
-		return ptr;
-	}
-	/* limit so that we don't pick off "int item=obj()" */
-	while (*ptr && loopCount++ < 2) {
-		while (*ptr && *ptr != '=' && *ptr != '(' && !isspace(*ptr)) {
-			/* skip over e.g. 'cpdef numpy.ndarray[dtype=double, ndim=1]' */
-			if(*ptr == '[') {
-				while (*ptr && *ptr != ']') ptr++;
-				if (*ptr) ptr++;
-			} else {
-				ptr++;
-			}
-		}
-		if (!*ptr || *ptr == '=') return NULL;
-		if (*ptr == '(') {
-			return lastStart; /* if we stopped on a '(' we are done */
-		}
-		ptr = skipSpace(ptr);
-		lastStart = ptr;
-		while (*lastStart == '*') lastStart++;  /* cdef int *identifier */
-	}
-	return NULL;
-}
-
-/* checks if there is a lambda at position of cp, and return its argument list
- * if so.
- * We don't return the lambda name since it is useless for now since we already
- * know it when we call this function, and it would be a little slower. */
-static bool varIsLambda (const char *cp, char **arglist)
-{
-	bool is_lambda = false;
-
-	cp = skipSpace (cp);
-	cp = skipIdentifier (cp); /* skip the lambda's name */
-	cp = skipSpace (cp);
-	if (*cp == '=')
-	{
-		cp++;
-		cp = skipSpace (cp);
-		if (strncmp (cp, "lambda", 6) == 0)
-		{
-			const char *tmp;
-
-			cp += 6; /* skip the lambda */
-			tmp = skipSpace (cp);
-			/* check if there is a space after lambda to detect assignations
-			 * starting with 'lambdaXXX' */
-			if (tmp != cp)
-			{
-				vString *args = vStringNew ();
-
-				cp = tmp;
-				vStringPut (args, '(');
-				for (; *cp != 0 && *cp != ':'; cp++)
-					vStringPut (args, *cp);
-				vStringPut (args, ')');
-				if (arglist)
-					*arglist = strdup (vStringValue (args));
-				vStringDelete (args);
-				is_lambda = true;
-			}
-		}
-	}
-	return is_lambda;
-}
-
-/* checks if @p cp has keyword @p keyword at the start, and fills @p cp_n with
- * the position of the next non-whitespace after the keyword */
-static bool matchKeyword (const char *keyword, const char *cp, const char **cp_n)
-{
-	size_t kw_len = strlen (keyword);
-	if (strncmp (cp, keyword, kw_len) == 0 && isspace (cp[kw_len]))
-	{
-		*cp_n = skipSpace (&cp[kw_len + 1]);
-		return true;
-	}
-	return false;
-}
-
-static void findPythonTags (void)
-{
-	vString *const continuation = vStringNew ();
-	vString *const name = vStringNew ();
-	vString *const parent = vStringNew();
-
-	NestingLevels *const nesting_levels = nestingLevelsNew(sizeof (struct nestingLevelUserData));
-
-	const char *line;
-	int line_skip = 0;
-	char const *longStringLiteral = NULL;
-
-	while ((line = (const char *) readLineFromInputFile ()) != NULL)
-	{
-		const char *cp = line, *candidate;
-		char const *longstring;
-		char const *keyword, *variable;
-		int indent;
-
-		cp = skipSpace (cp);
-
-		if (*cp == '\0')  /* skip blank line */
-			continue;
-
-		/* Skip comment if we are not inside a multi-line string. */
-		if (*cp == '#' && !longStringLiteral)
-			continue;
-
-		/* Deal with line continuation. */
-		if (!line_skip) vStringClear(continuation);
-		vStringCatS(continuation, line);
-		vStringStripTrailing(continuation);
-		if (vStringLast(continuation) == '\\')
-		{
-			vStringChop(continuation);
-			vStringCatS(continuation, " ");
-			line_skip = 1;
-			continue;
-		}
-		cp = line = vStringValue(continuation);
-		cp = skipSpace (cp);
-		indent = cp - line;
-		line_skip = 0;
-
-		/* Deal with multiline string ending. */
-		if (longStringLiteral)
-		{
-			find_triple_end(cp, &longStringLiteral);
-			continue;
-		}
-
-		checkIndent(nesting_levels, indent);
-
-		/* Find global and class variables */
-		variable = findVariable(line);
-		if (variable)
-		{
-			const char *start = variable;
-			char *arglist;
-			bool parent_is_class;
-
-			vStringClear (name);
-			while (isIdentifierCharacter ((int) *start))
-			{
-				vStringPut (name, (int) *start);
-				++start;
-			}
-
-			parent_is_class = constructParentString(nesting_levels, indent, parent);
-			if (varIsLambda (variable, &arglist))
-			{
-				/* show class members or top-level script lambdas only */
-				if (parent_is_class || vStringLength(parent) == 0)
-					makeFunctionTag (name, parent, parent_is_class, arglist);
-				eFree (arglist);
-			}
-			else
-			{
-				/* skip variables in methods */
-				if (parent_is_class || vStringLength(parent) == 0)
-					makeVariableTag (name, parent, parent_is_class);
-			}
-		}
-
-		/* Deal with multiline string start. */
-		longstring = find_triple_start(cp, &longStringLiteral);
-		if (longstring)
-		{
-			longstring += 3;
-			find_triple_end(longstring, &longStringLiteral);
-			/* We don't parse for any tags in the rest of the line. */
-			continue;
-		}
-
-		/* Deal with def and class keywords. */
-		keyword = findDefinitionOrClass (cp);
-		if (keyword)
-		{
-			bool found = false;
-			bool is_class = false;
-			if (matchKeyword ("def", keyword, &cp))
-			{
-				found = true;
-			}
-			else if (matchKeyword ("class", keyword, &cp))
-			{
-				found = true;
-				is_class = true;
-			}
-			else if (matchKeyword ("cdef", keyword, &cp))
-			{
-				candidate = skipTypeDecl (cp, &is_class);
-				if (candidate)
-				{
-					found = true;
-					cp = candidate;
-				}
-
-			}
-			else if (matchKeyword ("cpdef", keyword, &cp))
-			{
-				candidate = skipTypeDecl (cp, &is_class);
-				if (candidate)
-				{
-					found = true;
-					cp = candidate;
-				}
-			}
-
-			if (found)
-			{
-				bool is_parent_class;
-				struct corkInfo info;
-
-				is_parent_class =
-					constructParentString(nesting_levels, indent, parent);
-
-				if (is_class)
-				{
-					info.index = parseClass (cp, name, parent, is_parent_class);
-				}
-				else
-					info = parseFunction(cp, name, parent, is_parent_class);
-
-				addNestingLevel(nesting_levels, indent, &info);
-			}
-			continue;
-		}
-		/* Find and parse imports */
-		parseImports(line);
-	}
-
-	/* Force popping all nesting levels. */
-	checkIndent(nesting_levels, 0);
-
-	/* Clean up all memory we allocated. */
-	vStringDelete (parent);
-	vStringDelete (name);
-	vStringDelete (continuation);
-	nestingLevelsFree (nesting_levels);
-}
-
-extern parserDefinition *PythonParser (void)
-{
-    static const char *const extensions[] = { "py", "pyx", "pxd", "pxi" ,"scons", NULL };
-	parserDefinition *def = parserNew ("Python");
-	def->kindTable = PythonKinds;
-	def->kindCount = ARRAY_SIZE (PythonKinds);
-	def->extensions = extensions;
-	def->parser = findPythonTags;
-	def->useCork = CORK_QUEUE;
-	return def;
-}


Modified: ctags/parsers/python.c
1597 lines changed, 1597 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,1597 @@
+/*
+*   Copyright (c) 2000-2003, Darren Hiebert
+*   Copyright (c) 2014-2016, Colomban Wendling <ban at herbesfolles.org>
+*
+*   This source code is released for free distribution under the terms of the
+*   GNU General Public License version 2 or (at your option) any later version.
+*
+*   This module contains functions for generating tags for Python language
+*   files.
+*/
+
+#include "general.h"  /* must always come first */
+
+#include <string.h>
+
+#include "entry.h"
+#include "nestlevel.h"
+#include "read.h"
+#include "parse.h"
+#include "vstring.h"
+#include "keyword.h"
+#include "routines.h"
+#include "debug.h"
+#include "xtag.h"
+#include "objpool.h"
+
+#define isIdentifierChar(c) \
+	(isalnum (c) || (c) == '_' || (c) >= 0x80)
+#define newToken() (objPoolGet (TokenPool))
+#define deleteToken(t) (objPoolPut (TokenPool, (t)))
+
+enum {
+	KEYWORD_as,
+	KEYWORD_async,
+	KEYWORD_cdef,
+	KEYWORD_class,
+	KEYWORD_cpdef,
+	KEYWORD_def,
+	KEYWORD_extern,
+	KEYWORD_from,
+	KEYWORD_import,
+	KEYWORD_inline,
+	KEYWORD_lambda,
+	KEYWORD_pass,
+	KEYWORD_return,
+};
+typedef int keywordId; /* to allow KEYWORD_NONE */
+
+typedef enum {
+	ACCESS_PRIVATE,
+	ACCESS_PROTECTED,
+	ACCESS_PUBLIC,
+	COUNT_ACCESS
+} accessType;
+
+static const char *const PythonAccesses[COUNT_ACCESS] = {
+	"private",
+	"protected",
+	"public"
+};
+
+typedef enum {
+	F_DECORATORS,
+	F_NAMEREF,
+	COUNT_FIELD
+} pythonField;
+
+static fieldDefinition PythonFields[COUNT_FIELD] = {
+	{ .name = "decorators",
+	  .description = "decorators on functions and classes",
+	  .enabled = false },
+	{ .name = "nameref",
+	  .description = "the original name for the tag",
+	  .enabled = true },
+};
+
+typedef enum {
+	K_CLASS,
+	K_FUNCTION,
+	K_METHOD,
+	K_VARIABLE,
+	K_NAMESPACE,
+	K_MODULE,
+	K_UNKNOWN,
+	K_PARAMETER,
+	K_LOCAL_VARIABLE,
+	COUNT_KIND
+} pythonKind;
+
+typedef enum {
+	PYTHON_MODULE_IMPORTED,
+	PYTHON_MODULE_NAMESPACE,
+	PYTHON_MODULE_INDIRECTLY_IMPORTED,
+} pythonModuleRole;
+
+typedef enum {
+	PYTHON_UNKNOWN_IMPORTED,
+	PYTHON_UNKNOWN_INDIRECTLY_IMPORTED,
+} pythonUnknownRole;
+
+/* Roles related to `import'
+ * ==========================
+ * import X              X = (kind:module, role:imported)
+ *
+ * import X as Y         X = (kind:module, role:indirectlyImported),
+ *                       Y = (kind:namespace, nameref:module:X)
+ *                       ------------------------------------------------
+ *                       Don't confuse the kind of Y with namespace role of module kind.
+ *
+ * from X import *       X = (kind:module,  role:namespace)
+ *
+ * from X import Y       X = (kind:module,  role:namespace),
+ *                       Y = (kind:unknown, role:imported, scope:module:X)
+ *
+ * from X import Y as Z  X = (kind:module,  role:namespace),
+ *                       Y = (kind:unknown, role:indirectlyImported, scope:module:X)
+ *                       Z = (kind:unknown, nameref:unknown:Y) */
+
+static roleDefinition PythonModuleRoles [] = {
+	{ true, "imported",
+	  "imported modules" },
+	{ true, "namespace",
+	  "namespace from where classes/variables/functions are imported" },
+	{ true, "indirectlyImported",
+	  "module imported in alternative name" },
+};
+
+static roleDefinition PythonUnknownRoles [] = {
+	{ true, "imported",   "imported from the other module" },
+	{ true, "indirectlyImported",
+	  "classes/variables/functions/modules imported in alternative name" },
+};
+
+static kindDefinition PythonKinds[COUNT_KIND] = {
+	{true, 'c', "class",    "classes"},
+	{true, 'f', "function", "functions"},
+	{true, 'm', "member",   "class members"},
+	{true, 'v', "variable", "variables"},
+	{true, 'I', "namespace", "name referring a module defined in other file"},
+	{true, 'i', "module",    "modules",
+	 .referenceOnly = true,  ATTACH_ROLES(PythonModuleRoles)},
+	{true, 'x', "unknown",   "name referring a class/variable/function/module defined in other module",
+	 .referenceOnly = false, ATTACH_ROLES(PythonUnknownRoles)},
+	{false, 'z', "parameter", "function parameters" },
+	{false, 'l', "local",    "local variables" },
+};
+
+static const keywordTable PythonKeywordTable[] = {
+	/* keyword			keyword ID */
+	{ "as",				KEYWORD_as				},
+	{ "async",			KEYWORD_async			},
+	{ "cdef",			KEYWORD_cdef			},
+	{ "cimport",		KEYWORD_import			},
+	{ "class",			KEYWORD_class			},
+	{ "cpdef",			KEYWORD_cpdef			},
+	{ "def",			KEYWORD_def				},
+	{ "extern",			KEYWORD_extern			},
+	{ "from",			KEYWORD_from			},
+	{ "import",			KEYWORD_import			},
+	{ "inline",			KEYWORD_inline			},
+	{ "lambda",			KEYWORD_lambda			},
+	{ "pass",			KEYWORD_pass			},
+	{ "return",			KEYWORD_return			},
+};
+
+typedef enum eTokenType {
+	/* 0..255 are the byte's value */
+	TOKEN_EOF = 256,
+	TOKEN_UNDEFINED,
+	TOKEN_INDENT,
+	TOKEN_KEYWORD,
+	TOKEN_OPERATOR,
+	TOKEN_IDENTIFIER,
+	TOKEN_STRING,
+	TOKEN_ARROW,				/* -> */
+	TOKEN_WHITESPACE,
+} tokenType;
+
+typedef struct {
+	int				type;
+	keywordId		keyword;
+	vString *		string;
+	int				indent;
+	unsigned long 	lineNumber;
+	MIOPos			filePosition;
+} tokenInfo;
+
+struct pythonNestingLevelUserData {
+	int indentation;
+};
+#define PY_NL(nl) ((struct pythonNestingLevelUserData *) nestingLevelGetUserData (nl))
+
+static langType Lang_python;
+static unsigned int TokenContinuationDepth = 0;
+static tokenInfo *NextToken = NULL;
+static NestingLevels *PythonNestingLevels = NULL;
+static objPool *TokenPool = NULL;
+
+
+/* follows PEP-8, and always reports single-underscores as protected
+ * See:
+ * - http://www.python.org/dev/peps/pep-0008/#method-names-and-instance-variables
+ * - http://www.python.org/dev/peps/pep-0008/#designing-for-inheritance
+ */
+static accessType accessFromIdentifier (const vString *const ident,
+                                        pythonKind kind, int parentKind)
+{
+	const char *const p = vStringValue (ident);
+	const size_t len = vStringLength (ident);
+
+	/* inside a function/method, private */
+	if (parentKind != -1 && parentKind != K_CLASS)
+		return ACCESS_PRIVATE;
+	/* not starting with "_", public */
+	else if (len < 1 || p[0] != '_')
+		return ACCESS_PUBLIC;
+	/* "__...__": magic methods */
+	else if (kind == K_FUNCTION && parentKind == K_CLASS &&
+	         len > 3 && p[1] == '_' && p[len - 2] == '_' && p[len - 1] == '_')
+		return ACCESS_PUBLIC;
+	/* "__...": name mangling */
+	else if (parentKind == K_CLASS && len > 1 && p[1] == '_')
+		return ACCESS_PRIVATE;
+	/* "_...": suggested as non-public, but easily accessible */
+	else
+		return ACCESS_PROTECTED;
+}
+
+static void initPythonEntry (tagEntryInfo *const e, const tokenInfo *const token,
+                             const pythonKind kind)
+{
+	accessType access;
+	int parentKind = -1;
+	NestingLevel *nl;
+
+	initTagEntry (e, vStringValue (token->string), kind);
+
+	e->lineNumber	= token->lineNumber;
+	e->filePosition	= token->filePosition;
+
+	nl = nestingLevelsGetCurrent (PythonNestingLevels);
+	if (nl)
+	{
+		tagEntryInfo *nlEntry = getEntryOfNestingLevel (nl);
+
+		e->extensionFields.scopeIndex = nl->corkIndex;
+
+		/* nlEntry can be NULL if a kind was disabled.  But what can we do
+		 * here?  Even disabled kinds should count for the hierarchy I
+		 * guess -- as it'd otherwise be wrong -- but with cork we're
+		 * fucked up as there's nothing to look up.  Damn. */
+		if (nlEntry)
+		{
+			parentKind = nlEntry->kindIndex;
+
+			/* functions directly inside classes are methods, fix it up */
+			if (kind == K_FUNCTION && parentKind == K_CLASS)
+				e->kindIndex = K_METHOD;
+		}
+	}
+
+	access = accessFromIdentifier (token->string, kind, parentKind);
+	e->extensionFields.access = PythonAccesses[access];
+	/* FIXME: should we really set isFileScope in addition to access? */
+	if (access == ACCESS_PRIVATE)
+		e->isFileScope = true;
+}
+
+static int makeClassTag (const tokenInfo *const token,
+                         const vString *const inheritance,
+                         const vString *const decorators)
+{
+	if (PythonKinds[K_CLASS].enabled)
+	{
+		tagEntryInfo e;
+
+		initPythonEntry (&e, token, K_CLASS);
+
+		e.extensionFields.inheritance = inheritance ? vStringValue (inheritance) : "";
+		if (decorators && vStringLength (decorators) > 0)
+		{
+			attachParserField (&e, false, PythonFields[F_DECORATORS].ftype,
+			                   vStringValue (decorators));
+		}
+
+		return makeTagEntry (&e);
+	}
+
+	return CORK_NIL;
+}
+
+static int makeFunctionTag (const tokenInfo *const token,
+                            const vString *const arglist,
+                            const vString *const decorators)
+{
+	if (PythonKinds[K_FUNCTION].enabled)
+	{
+		tagEntryInfo e;
+
+		initPythonEntry (&e, token, K_FUNCTION);
+
+		if (arglist)
+			e.extensionFields.signature = vStringValue (arglist);
+		if (decorators && vStringLength (decorators) > 0)
+		{
+			attachParserField (&e, false, PythonFields[F_DECORATORS].ftype,
+			                   vStringValue (decorators));
+		}
+
+		return makeTagEntry (&e);
+	}
+
+	return CORK_NIL;
+}
+
+static int makeSimplePythonTag (const tokenInfo *const token, pythonKind const kind)
+{
+	if (PythonKinds[kind].enabled)
+	{
+		tagEntryInfo e;
+
+		initPythonEntry (&e, token, kind);
+		return makeTagEntry (&e);
+	}
+
+	return CORK_NIL;
+}
+
+static int makeSimplePythonRefTag (const tokenInfo *const token,
+                                   const vString *const altName,
+                                   pythonKind const kind,
+                                   int roleIndex, xtagType xtag)
+{
+	if (isXtagEnabled (XTAG_REFERENCE_TAGS) &&
+	    PythonKinds[kind].roles[roleIndex].enabled)
+	{
+		tagEntryInfo e;
+
+		initRefTagEntry (&e, vStringValue (altName ? altName : token->string),
+		                 kind, roleIndex);
+
+		e.lineNumber	= token->lineNumber;
+		e.filePosition	= token->filePosition;
+
+		if (xtag != XTAG_UNKNOWN)
+			markTagExtraBit (&e, xtag);
+
+		return makeTagEntry (&e);
+	}
+
+	return CORK_NIL;
+}
+
+static void *newPoolToken (void *createArg CTAGS_ATTR_UNUSED)
+{
+	tokenInfo *token = xMalloc (1, tokenInfo);
+	token->string = vStringNew ();
+	return token;
+}
+
+static void deletePoolToken (void *data)
+{
+	tokenInfo *token = data;
+	vStringDelete (token->string);
+	eFree (token);
+}
+
+static void clearPoolToken (void *data)
+{
+	tokenInfo *token = data;
+
+	token->type			= TOKEN_UNDEFINED;
+	token->keyword		= KEYWORD_NONE;
+	token->indent		= 0;
+	token->lineNumber   = getInputLineNumber ();
+	token->filePosition = getInputFilePosition ();
+	vStringClear (token->string);
+}
+
+static void copyToken (tokenInfo *const dest, const tokenInfo *const src)
+{
+	dest->lineNumber = src->lineNumber;
+	dest->filePosition = src->filePosition;
+	dest->type = src->type;
+	dest->keyword = src->keyword;
+	dest->indent = src->indent;
+	vStringCopy(dest->string, src->string);
+}
+
+/* Skip a single or double quoted string. */
+static void readString (vString *const string, const int delimiter)
+{
+	int escaped = 0;
+	int c;
+
+	while ((c = getcFromInputFile ()) != EOF)
+	{
+		if (escaped)
+		{
+			vStringPut (string, c);
+			escaped--;
+		}
+		else if (c == '\\')
+			escaped++;
+		else if (c == delimiter || c == '\n' || c == '\r')
+		{
+			if (c != delimiter)
+				ungetcToInputFile (c);
+			break;
+		}
+		else
+			vStringPut (string, c);
+	}
+}
+
+/* Skip a single or double triple quoted string. */
+static void readTripleString (vString *const string, const int delimiter)
+{
+	int c;
+	int escaped = 0;
+	int n = 0;
+	while ((c = getcFromInputFile ()) != EOF)
+	{
+		if (c == delimiter && ! escaped)
+		{
+			if (++n >= 3)
+				break;
+		}
+		else
+		{
+			for (; n > 0; n--)
+				vStringPut (string, delimiter);
+			if (c != '\\' || escaped)
+				vStringPut (string, c);
+			n = 0;
+		}
+
+		if (escaped)
+			escaped--;
+		else if (c == '\\')
+			escaped++;
+	}
+}
+
+static void readIdentifier (vString *const string, const int firstChar)
+{
+	int c = firstChar;
+	do
+	{
+		vStringPut (string, (char) c);
+		c = getcFromInputFile ();
+	}
+	while (isIdentifierChar (c));
+	ungetcToInputFile (c);
+}
+
+static void ungetToken (tokenInfo *const token)
+{
+	Assert (NextToken == NULL);
+	NextToken = newToken ();
+	copyToken (NextToken, token);
+}
+
+static void readTokenFull (tokenInfo *const token, bool inclWhitespaces)
+{
+	int c;
+	int n;
+
+	/* if we've got a token held back, emit it */
+	if (NextToken)
+	{
+		copyToken (token, NextToken);
+		deleteToken (NextToken);
+		NextToken = NULL;
+		return;
+	}
+
+	token->type		= TOKEN_UNDEFINED;
+	token->keyword	= KEYWORD_NONE;
+	vStringClear (token->string);
+
+getNextChar:
+
+	n = 0;
+	do
+	{
+		c = getcFromInputFile ();
+		n++;
+	}
+	while (c == ' ' || c == '\t' || c == '\f');
+
+	token->lineNumber   = getInputLineNumber ();
+	token->filePosition = getInputFilePosition ();
+
+	if (inclWhitespaces && n > 1 && c != '\r' && c != '\n')
+	{
+		ungetcToInputFile (c);
+		vStringPut (token->string, ' ');
+		token->type = TOKEN_WHITESPACE;
+		return;
+	}
+
+	switch (c)
+	{
+		case EOF:
+			token->type = TOKEN_EOF;
+			break;
+
+		case '\'':
+		case '"':
+		{
+			int d = getcFromInputFile ();
+			token->type = TOKEN_STRING;
+			vStringPut (token->string, c);
+			if (d != c)
+			{
+				ungetcToInputFile (d);
+				readString (token->string, c);
+			}
+			else if ((d = getcFromInputFile ()) == c)
+				readTripleString (token->string, c);
+			else /* empty string */
+				ungetcToInputFile (d);
+			vStringPut (token->string, c);
+			token->lineNumber = getInputLineNumber ();
+			token->filePosition = getInputFilePosition ();
+			break;
+		}
+
+		case '=':
+		{
+			int d = getcFromInputFile ();
+			vStringPut (token->string, c);
+			if (d == c)
+			{
+				vStringPut (token->string, d);
+				token->type = TOKEN_OPERATOR;
+			}
+			else
+			{
+				ungetcToInputFile (d);
+				token->type = c;
+			}
+			break;
+		}
+
+		case '-':
+		{
+			int d = getcFromInputFile ();
+			if (d == '>')
+			{
+				vStringPut (token->string, c);
+				vStringPut (token->string, d);
+				token->type = TOKEN_ARROW;
+				break;
+			}
+			ungetcToInputFile (d);
+			/* fall through */
+		}
+		case '+':
+		case '*':
+		case '%':
+		case '<':
+		case '>':
+		case '/':
+		{
+			int d = getcFromInputFile ();
+			vStringPut (token->string, c);
+			if (d != '=')
+			{
+				ungetcToInputFile (d);
+				token->type = c;
+			}
+			else
+			{
+				vStringPut (token->string, d);
+				token->type = TOKEN_OPERATOR;
+			}
+			break;
+		}
+
+		/* eats newline to implement line continuation  */
+		case '\\':
+		{
+			int d = getcFromInputFile ();
+			if (d == '\r')
+				d = getcFromInputFile ();
+			if (d != '\n')
+				ungetcToInputFile (d);
+			goto getNextChar;
+		}
+
+		case '#': /* comment */
+		case '\r': /* newlines for indent */
+		case '\n':
+		{
+			int indent = 0;
+			do
+			{
+				if (c == '#')
+				{
+					do
+						c = getcFromInputFile ();
+					while (c != EOF && c != '\r' && c != '\n');
+				}
+				if (c == '\r')
+				{
+					int d = getcFromInputFile ();
+					if (d != '\n')
+						ungetcToInputFile (d);
+				}
+				indent = 0;
+				while ((c = getcFromInputFile ()) == ' ' || c == '\t' || c == '\f')
+				{
+					if (c == '\t')
+						indent += 8 - (indent % 8);
+					else if (c == '\f') /* yeah, it's weird */
+						indent = 0;
+					else
+						indent++;
+				}
+			} /* skip completely empty lines, so retry */
+			while (c == '\r' || c == '\n' || c == '#');
+			ungetcToInputFile (c);
+			if (TokenContinuationDepth > 0)
+			{
+				if (inclWhitespaces)
+				{
+					vStringPut (token->string, ' ');
+					token->type = TOKEN_WHITESPACE;
+				}
+				else
+					goto getNextChar;
+			}
+			else
+			{
+				token->type = TOKEN_INDENT;
+				token->indent = indent;
+			}
+			break;
+		}
+
+		default:
+			if (! isIdentifierChar (c))
+			{
+				vStringPut (token->string, c);
+				token->type = c;
+			}
+			else
+			{
+				/* FIXME: handle U, B, R and F string prefixes? */
+				readIdentifier (token->string, c);
+				token->keyword = lookupKeyword (vStringValue (token->string), Lang_python);
+				if (token->keyword == KEYWORD_NONE)
+					token->type = TOKEN_IDENTIFIER;
+				else
+					token->type = TOKEN_KEYWORD;
+			}
+			break;
+	}
+
+	/* handle implicit continuation lines not to emit INDENT inside brackets
+	 * https://docs.python.org/3.6/reference/lexical_analysis.html#implicit-line-joining */
+	if (token->type == '(' ||
+	    token->type == '{' ||
+	    token->type == '[')
+	{
+		TokenContinuationDepth ++;
+	}
+	else if (TokenContinuationDepth > 0 &&
+	         (token->type == ')' ||
+	          token->type == '}' ||
+	          token->type == ']'))
+	{
+		TokenContinuationDepth --;
+	}
+}
+
+static void readToken (tokenInfo *const token)
+{
+	readTokenFull (token, false);
+}
+
+/*================================= parsing =================================*/
+
+
+static void reprCat (vString *const repr, const tokenInfo *const token)
+{
+	if (token->type != TOKEN_INDENT &&
+	    token->type != TOKEN_WHITESPACE)
+	{
+		vStringCat (repr, token->string);
+	}
+	else if (vStringLength (repr) > 0 && vStringLast (repr) != ' ')
+	{
+		vStringPut (repr, ' ');
+	}
+}
+
+static bool skipOverPair (tokenInfo *const token, int tOpen, int tClose,
+                             vString *const repr, bool reprOuterPair)
+{
+	if (token->type == tOpen)
+	{
+		int depth = 1;
+
+		if (repr && reprOuterPair)
+			reprCat (repr, token);
+		do
+		{
+			readTokenFull (token, true);
+			if (repr && (reprOuterPair || token->type != tClose || depth > 1))
+			{
+				reprCat (repr, token);
+			}
+			if (token->type == tOpen)
+				depth ++;
+			else if (token->type == tClose)
+				depth --;
+		}
+		while (token->type != TOKEN_EOF && depth > 0);
+	}
+
+	return token->type == tClose;
+}
+
+static bool skipLambdaArglist (tokenInfo *const token, vString *const repr)
+{
+	while (token->type != TOKEN_EOF && token->type != ':' &&
+	       /* avoid reading too much, just in case */
+	       token->type != TOKEN_INDENT)
+	{
+		bool readNext = true;
+
+		if (token->type == '(')
+			readNext = skipOverPair (token, '(', ')', repr, true);
+		else if (token->type == '[')
+			readNext = skipOverPair (token, '[', ']', repr, true);
+		else if (token->type == '{')
+			readNext = skipOverPair (token, '{', '}', repr, true);
+		else if (token->keyword == KEYWORD_lambda)
+		{ /* handle lambdas in a default value */
+			if (repr)
+				reprCat (repr, token);
+			readTokenFull (token, true);
+			readNext = skipLambdaArglist (token, repr);
+			if (token->type == ':')
+				readNext = true;
+			if (readNext && repr)
+				reprCat (repr, token);
+		}
+		else if (repr)
+		{
+			reprCat (repr, token);
+		}
+
+		if (readNext)
+			readTokenFull (token, true);
+	}
+	return false;
+}
+
+static void readQualifiedName (tokenInfo *const nameToken)
+{
+	readToken (nameToken);
+
+	if (nameToken->type == TOKEN_IDENTIFIER ||
+	    nameToken->type == '.')
+	{
+		vString *qualifiedName = vStringNew ();
+		tokenInfo *token = newToken ();
+
+		while (nameToken->type == TOKEN_IDENTIFIER ||
+		       nameToken->type == '.')
+		{
+			vStringCat (qualifiedName, nameToken->string);
+			copyToken (token, nameToken);
+
+			readToken (nameToken);
+		}
+		/* put the last, non-matching, token back */
+		ungetToken (nameToken);
+
+		copyToken (nameToken, token);
+		nameToken->type = TOKEN_IDENTIFIER;
+		vStringCopy (nameToken->string, qualifiedName);
+
+		deleteToken (token);
+		vStringDelete (qualifiedName);
+	}
+}
+
+static bool readCDefName (tokenInfo *const token, pythonKind *kind)
+{
+	readToken (token);
+
+	if (token->keyword == KEYWORD_extern ||
+	    token->keyword == KEYWORD_import)
+	{
+		readToken (token);
+		if (token->keyword == KEYWORD_from)
+			return false;
+	}
+
+	if (token->keyword == KEYWORD_class)
+	{
+		*kind = K_CLASS;
+		readToken (token);
+	}
+	else
+	{
+		/* skip the optional type declaration -- everything on the same line
+		 * until an identifier followed by "(". */
+		tokenInfo *candidate = newToken ();
+
+		while (token->type != TOKEN_EOF &&
+		       token->type != TOKEN_INDENT &&
+		       token->type != '=' &&
+		       token->type != ',' &&
+		       token->type != ':')
+		{
+			if (token->type == '[')
+			{
+				if (skipOverPair (token, '[', ']', NULL, false))
+					readToken (token);
+			}
+			else if (token->type == '(')
+			{
+				if (skipOverPair (token, '(', ')', NULL, false))
+					readToken (token);
+			}
+			else if (token->type == TOKEN_IDENTIFIER)
+			{
+				copyToken (candidate, token);
+				readToken (token);
+				if (token->type == '(')
+				{ /* okay, we really found a function, use this */
+					*kind = K_FUNCTION;
+					ungetToken (token);
+					copyToken (token, candidate);
+					break;
+				}
+			}
+			else
+				readToken (token);
+		}
+
+		deleteToken (candidate);
+	}
+
+	return token->type == TOKEN_IDENTIFIER;
+}
+
+static vString *parseParamTypeAnnotation (tokenInfo *const token,
+										  vString *arglist)
+{
+	readToken (token);
+	if (token->type != ':')
+	{
+		ungetToken (token);
+		return NULL;
+	}
+
+	reprCat (arglist, token);
+	int depth = 0;
+	vString *t = vStringNew ();
+	while (true)
+	{
+		readTokenFull (token, true);
+		if (token->type == TOKEN_WHITESPACE)
+		{
+			reprCat (arglist, token);
+			continue;
+		}
+		else if (token->type == TOKEN_EOF)
+			break;
+
+		if (token->type == '(' ||
+			token->type == '[' ||
+			token->type == '{')
+			depth ++;
+		else if (token->type == ')' ||
+				 token->type == ']' ||
+				 token->type == '}')
+			depth --;
+
+		if (depth < 0
+			|| (depth == 0 && (token->type == '='
+							   || token->type == ',')))
+		{
+			ungetToken (token);
+			return t;
+		}
+		reprCat (arglist, token);
+		reprCat (t, token);
+	}
+	vStringDelete (t);
+	return NULL;
+}
+
+static vString *parseReturnTypeAnnotation (tokenInfo *const token)
+{
+	readToken (token);
+	if (token->type != TOKEN_ARROW)
+	{
+		ungetToken (token);
+		return NULL;
+	}
+
+	int depth = 0;
+	vString *t = vStringNew ();
+	while (true)
+	{
+		readToken (token);
+		if (token->type == TOKEN_EOF)
+			break;
+
+		if (token->type == '(' ||
+			token->type == '[' ||
+			token->type == '{')
+			depth ++;
+		else if (token->type == ')' ||
+				 token->type == ']' ||
+				 token->type == '}')
+			depth --;
+		if (depth == 0 && token->type == ':')
+		{
+			ungetToken (token);
+			return t;
+		}
+		else
+			reprCat (t, token);
+	}
+	vStringDelete (t);
+	return NULL;
+}
+
+static bool parseClassOrDef (tokenInfo *const token,
+                                const vString *const decorators,
+                                pythonKind kind, bool isCDef)
+{
+	vString *arglist = NULL;
+	tokenInfo *name = NULL;
+	tokenInfo *parameterTokens[16] = { NULL };
+	vString   *parameterTypes [ARRAY_SIZE(parameterTokens)] = { NULL };
+	unsigned int parameterCount = 0;
+	NestingLevel *lv;
+	int corkIndex;
+
+	if (isCDef)
+	{
+		if (! readCDefName (token, &kind))
+			return false;
+	}
+	else
+	{
+		readToken (token);
+		if (token->type != TOKEN_IDENTIFIER)
+			return false;
+	}
+
+	name = newToken ();
+	copyToken (name, token);
+
+	readToken (token);
+	/* collect parameters or inheritance */
+	if (token->type == '(')
+	{
+		int prevTokenType = token->type;
+		int depth = 1;
+
+		arglist = vStringNew ();
+		if (kind != K_CLASS)
+			reprCat (arglist, token);
+
+		do
+		{
+			if (token->type != TOKEN_WHITESPACE &&
+			    /* for easy `*args` and `**kwargs` support, we also ignore
+			     * `*`, which anyway can't otherwise screw us up */
+			    token->type != '*')
+			{
+				prevTokenType = token->type;
+			}
+
+			readTokenFull (token, true);
+			if (kind != K_CLASS || token->type != ')' || depth > 1)
+				reprCat (arglist, token);
+
+			if (token->type == '(' ||
+			    token->type == '[' ||
+			    token->type == '{')
+				depth ++;
+			else if (token->type == ')' ||
+			         token->type == ']' ||
+			         token->type == '}')
+				depth --;
+			else if (kind != K_CLASS && depth == 1 &&
+			         token->type == TOKEN_IDENTIFIER &&
+			         (prevTokenType == '(' || prevTokenType == ',') &&
+			         parameterCount < ARRAY_SIZE (parameterTokens) &&
+			         PythonKinds[K_PARAMETER].enabled)
+			{
+				tokenInfo *parameterName = newToken ();
+
+				copyToken (parameterName, token);
+				parameterTokens[parameterCount] = parameterName;
+				parameterTypes [parameterCount++] = parseParamTypeAnnotation (token, arglist);
+			}
+		}
+		while (token->type != TOKEN_EOF && depth > 0);
+	}
+
+	if (kind == K_CLASS)
+		corkIndex = makeClassTag (name, arglist, decorators);
+	else
+		corkIndex = makeFunctionTag (name, arglist, decorators);
+
+	lv = nestingLevelsPush (PythonNestingLevels, corkIndex);
+	PY_NL (lv)->indentation = token->indent;
+
+	deleteToken (name);
+	vStringDelete (arglist);
+
+	if (parameterCount > 0)
+	{
+		unsigned int i;
+
+		for (i = 0; i < parameterCount; i++)
+		{
+			int paramCorkIndex = makeSimplePythonTag (parameterTokens[i], K_PARAMETER);
+			deleteToken (parameterTokens[i]);
+			tagEntryInfo *e = getEntryInCorkQueue (paramCorkIndex);
+			if (e && parameterTypes[i])
+			{
+				e->extensionFields.typeRef [0] = eStrdup ("typename");
+				e->extensionFields.typeRef [1] = vStringDeleteUnwrap (parameterTypes[i]);
+				parameterTypes[i] = NULL;
+			}
+			vStringDelete (parameterTypes[i]); /* NULL is acceptable. */
+		}
+	}
+
+	tagEntryInfo *e;
+	vString *t;
+	if (kind != K_CLASS
+		&& (e = getEntryInCorkQueue (corkIndex))
+		&& (t = parseReturnTypeAnnotation (token)))
+	{
+		e->extensionFields.typeRef [0] = eStrdup ("typename");
+		e->extensionFields.typeRef [1] = vStringDeleteUnwrap (t);
+	}
+
+	return true;
+}
+
+static bool parseImport (tokenInfo *const token)
+{
+	tokenInfo *fromModule = NULL;
+
+	if (token->keyword == KEYWORD_from)
+	{
+		readQualifiedName (token);
+		if (token->type == TOKEN_IDENTIFIER)
+		{
+			fromModule = newToken ();
+			copyToken (fromModule, token);
+			readToken (token);
+		}
+	}
+
+	if (token->keyword == KEYWORD_import)
+	{
+		bool parenthesized = false;
+		int moduleIndex;
+
+		if (fromModule)
+		{
+			/* from X import ...
+			 * --------------------
+			 * X = (kind:module, role:namespace) */
+			moduleIndex = makeSimplePythonRefTag (fromModule, NULL, K_MODULE,
+												  PYTHON_MODULE_NAMESPACE,
+												  XTAG_UNKNOWN);
+		}
+
+		do
+		{
+			readQualifiedName (token);
+
+			/* support for `from x import (...)` */
+			if (fromModule && ! parenthesized && token->type == '(')
+			{
+				parenthesized = true;
+				readQualifiedName (token);
+			}
+
+			if (token->type == TOKEN_IDENTIFIER)
+			{
+				tokenInfo *name = newToken ();
+
+				copyToken (name, token);
+				readToken (token);
+				/* if there is an "as", use it as the name */
+				if (token->keyword == KEYWORD_as)
+				{
+					readToken (token);
+					if (token->type == TOKEN_IDENTIFIER)
+					{
+						if (fromModule)
+						{
+							/* from x import Y as Z
+							 * ----------------------------
+							 * x = (kind:module,  role:namespace),
+							 * Y = (kind:unknown, role:indirectlyImported, scope:module:X),
+							 * Z = (kind:unknown, nameref:unknown:Y) */
+							int index;
+
+							/* Y */
+							index = makeSimplePythonRefTag (name, NULL, K_UNKNOWN,
+															PYTHON_UNKNOWN_INDIRECTLY_IMPORTED,
+															XTAG_UNKNOWN);
+							/* fill the scope field for Y */
+							tagEntryInfo *e = getEntryInCorkQueue (index);
+							if (e)
+								e->extensionFields.scopeIndex = moduleIndex;
+
+							/* Z */
+							index = makeSimplePythonTag (token, K_UNKNOWN);
+							/* fill the nameref filed for Y */
+							if (PythonFields[F_NAMEREF].enabled)
+							{
+								vString *nameref = vStringNewInit (PythonKinds [K_UNKNOWN].name);
+								vStringPut (nameref, ':');
+								vStringCat (nameref, name->string);
+								attachParserFieldToCorkEntry (index, PythonFields[F_NAMEREF].ftype,
+															  vStringValue (nameref));
+								vStringDelete (nameref);
+							}
+						}
+						else
+						{
+							/* import x as Y
+							 * ----------------------------
+							 * x = (kind:module, role:indirectlyImported)
+							 * Y = (kind:namespace, nameref:module:x)*/
+							/* x */
+							makeSimplePythonRefTag (name, NULL, K_MODULE,
+							                        PYTHON_MODULE_INDIRECTLY_IMPORTED,
+							                        XTAG_UNKNOWN);
+							/* Y */
+							int index = makeSimplePythonTag (token, K_NAMESPACE);
+							/* fill the nameref filed for Y */
+							if (PythonFields[F_NAMEREF].enabled)
+							{
+								vString *nameref = vStringNewInit (PythonKinds [K_MODULE].name);
+								vStringPut (nameref, ':');
+								vStringCat (nameref, name->string);
+								attachParserFieldToCorkEntry (index, PythonFields[F_NAMEREF].ftype,
+															  vStringValue (nameref));
+								vStringDelete (nameref);
+							}
+						}
+
+						copyToken (name, token);
+						readToken (token);
+					}
+				}
+				else
+				{
+					if (fromModule)
+					{
+						/* from x import Y
+						   --------------
+						   x = (kind:module,  role:namespace),
+						   Y = (kind:unknown, role:imported, scope:module:x) */
+						/* Y */
+						int index = makeSimplePythonRefTag (name, NULL, K_UNKNOWN,
+															PYTHON_UNKNOWN_IMPORTED,
+															XTAG_UNKNOWN);
+						/* fill the scope field for Y */
+						tagEntryInfo *e = getEntryInCorkQueue (index);
+						if (e)
+							e->extensionFields.scopeIndex = moduleIndex;
+					}
+					else
+					{
+						/* import X
+						   --------------
+						   X = (kind:module, role:imported) */
+						makeSimplePythonRefTag (name, NULL, K_MODULE,
+						                        PYTHON_MODULE_IMPORTED,
+						                        XTAG_UNKNOWN);
+					}
+				}
+
+				deleteToken (name);
+			}
+		}
+		while (token->type == ',');
+
+		if (parenthesized && token->type == ')')
+			readToken (token);
+	}
+
+	if (fromModule)
+		deleteToken (fromModule);
+
+	return false;
+}
+
+/* this only handles the most common cases, but an annotation can be any
+ * expression in theory.
+ * this function assumes there must be an annotation, and doesn't do any check
+ * on the token on which it is called: the caller should do that part. */
+static bool skipVariableTypeAnnotation (tokenInfo *const token, vString *const repr)
+{
+	bool readNext = true;
+
+	readToken (token);
+	switch (token->type)
+	{
+		case '[': readNext = skipOverPair (token, '[', ']', repr, true); break;
+		case '(': readNext = skipOverPair (token, '(', ')', repr, true); break;
+		case '{': readNext = skipOverPair (token, '{', '}', repr, true); break;
+		default: reprCat (repr, token);
+	}
+	if (readNext)
+		readToken (token);
+	/* skip subscripts and calls */
+	while (token->type == '[' || token->type == '(' || token->type == '.' || token->type == '|')
+	{
+		switch (token->type)
+		{
+			case '[': readNext = skipOverPair (token, '[', ']', repr, true); break;
+			case '(': readNext = skipOverPair (token, '(', ')', repr, true); break;
+			case '|':
+				reprCat (repr, token);
+				skipVariableTypeAnnotation (token, repr);
+				readNext = false;
+				break;
+			case '.':
+				reprCat (repr, token);
+				readToken (token);
+				readNext = token->type == TOKEN_IDENTIFIER;
+				if (readNext)
+					reprCat (repr, token);
+				break;
+			default:  readNext = false; break;
+		}
+		if (readNext)
+			readToken (token);
+	}
+
+	return false;
+}
+
+static bool parseVariable (tokenInfo *const token, const pythonKind kind)
+{
+	/* In order to support proper tag type for lambdas in multiple
+	 * assignations, we first collect all the names, and then try and map
+	 * an assignation to it */
+	tokenInfo *nameTokens[8] = { NULL };
+	vString   *nameTypes [ARRAY_SIZE (nameTokens)] = { NULL };
+	unsigned int nameCount = 0;
+	vString *type = vStringNew();
+
+	/* first, collect variable name tokens */
+	while (token->type == TOKEN_IDENTIFIER &&
+	       nameCount < ARRAY_SIZE (nameTokens))
+	{
+		unsigned int i;
+		tokenInfo *name = newToken ();
+		copyToken (name, token);
+
+		readToken (token);
+		if (token->type == '.')
+		{
+			/* FIXME: what to do with dotted names?  We currently ignore them
+			 *        as we need to do something not to break the whole
+			 *        declaration, but the expected behavior is questionable */
+			deleteToken (name);
+			name = NULL;
+
+			do
+			{
+				readToken (token);
+			}
+			while (token->type == TOKEN_IDENTIFIER ||
+			       token->type == '.');
+		}
+
+		i = nameCount++;
+		nameTokens[i] = name;
+
+		/* (parse and) skip annotations.  we need not to be too permissive because we
+		 * aren't yet sure we're actually parsing a variable. */
+		if (token->type == ':' && skipVariableTypeAnnotation (token, type))
+			readToken (token);
+
+		if (vStringLength (type) > 0)
+		{
+			nameTypes[i] = type;
+			type = vStringNew ();
+		}
+
+		if (token->type == ',')
+			readToken (token);
+		else
+			break;
+	}
+	vStringDelete (type);
+
+	/* then, if it's a proper assignation, try and map assignations so that
+	 * we catch lambdas and alike */
+	if (token->type == '=')
+	{
+		unsigned int i = 0;
+
+		do
+		{
+			const tokenInfo *const nameToken = nameTokens[i];
+			vString **type = &(nameTypes[i++]);
+
+			readToken (token);
+
+			if (! nameToken)
+				/* nothing */;
+			else if (token->keyword != KEYWORD_lambda)
+			{
+				int index = makeSimplePythonTag (nameToken, kind);
+				tagEntryInfo *e = getEntryInCorkQueue (index);
+				if (e && *type)
+				{
+					e->extensionFields.typeRef [0] = eStrdup ("typename");
+					e->extensionFields.typeRef [1] = vStringDeleteUnwrap (*type);
+					*type = NULL;
+				}
+			}
+			else
+			{
+				tokenInfo *anon  = NULL;
+				vString *arglist = vStringNew ();
+				if (*type)
+				{
+					anon = newToken ();
+					copyToken (anon, token);
+				}
+				readToken (token);
+				vStringPut (arglist, '(');
+				skipLambdaArglist (token, arglist);
+				vStringPut (arglist, ')');
+				if (*type)
+				{
+					/* How to handle lambda assigned to a variable
+					 * --------------------------------------------
+					 *
+					 * input.py:
+					 *
+					 * 	  id = lambda var: var
+					 * 	  id_t: Callable[[int], int] = lambda var: var
+					 *
+					 * `id' is tagged as a function kind object like:
+					 *
+					 *    id	input.py	/^id = lambda var: var$/;"	function
+					 *
+					 * For `id_t' we cannot do the same as `id'.
+					 *
+					 * We should not store `Callable[[int], int]' to typeref
+					 * field of the tag of `id_t' if the tag has "function" as
+					 * its kind because users expect the typeref field of a
+					 * function kind represents a type for the value returned
+					 * from the function (return type).
+					 *
+					 * the unexpected tag:
+					 *
+					 *    id_t	input.py	/^id_t: Callable[[int], int] = lambda var: var$/;"	function \
+					 *                          typeref:typename:Callable[[int], int]
+					 *
+					 * If we make a tag for `id_t' as a function, we should
+					 * attach `typeref:typename:int' and `signature:(int)'. To
+					 * achieve this, we have to make ctags analyze
+					 * `Callable[[int], int]'.  However, we want to avoid the
+					 * level of analyzing.
+					 *
+					 * For recording `Callable[[int], int]', a valuable
+					 * information in the input, we use indirection.
+					 *
+					 *    id_t	input.py	/^id_t: Callable[[int], int] = lambda var: var$/;"	variable \
+					 *                          typeref:typename:Callable[[int], int]	nameref:function:anonFuncNNN
+					 *    anonFuncNNN	input.py	/^id_t: Callable[[int], int] = lambda var: var$/;"	function \
+					 *                          extras:anonymous
+					 */
+					int vindex = makeSimplePythonTag (nameToken, kind);
+					vStringClear (anon->string);
+					anonGenerate (anon->string, "anonFunc", K_FUNCTION);
+					int findex = makeFunctionTag (anon, arglist, NULL);
+					tagEntryInfo *fe = getEntryInCorkQueue (findex);
+					if (fe)
+						markTagExtraBit (fe, XTAG_ANONYMOUS);
+
+					tagEntryInfo *ve = getEntryInCorkQueue (vindex);
+					if (ve)
+					{
+						ve->extensionFields.typeRef [0] = eStrdup ("typename");
+						ve->extensionFields.typeRef [1] = vStringDeleteUnwrap (*type);
+						*type = NULL;
+						vString *nameref = vStringNewInit (PythonKinds [K_FUNCTION].name);
+						vStringPut (nameref, ':');
+						vStringCat (nameref, anon->string);
+						attachParserField (ve, true, PythonFields[F_NAMEREF].ftype,
+										   vStringValue (nameref));
+						vStringDelete (nameref);
+					}
+					if (anon)
+						deleteToken (anon);
+				}
+				else
+					makeFunctionTag (nameToken, arglist, NULL);
+				vStringDelete (arglist);
+			}
+
+			/* skip until next initializer */
+			while ((TokenContinuationDepth > 0 || token->type != ',') &&
+			       token->type != TOKEN_EOF &&
+			       token->type != ';' &&
+			       token->type != TOKEN_INDENT)
+			{
+				readToken (token);
+			}
+		}
+		while (token->type == ',' && i < nameCount);
+
+		/* if we got leftover to initialize, just make variables out of them.
+		 * This handles cases like `a, b, c = (c, d, e)` -- or worse */
+		for (; i < nameCount; i++)
+		{
+			if (nameTokens[i])
+				makeSimplePythonTag (nameTokens[i], kind);
+		}
+	}
+
+	while (nameCount > 0)
+	{
+		if (nameTokens[--nameCount])
+			deleteToken (nameTokens[nameCount]);
+		vStringDelete (nameTypes[nameCount]); /* NULL is acceptable. */
+	}
+
+	return false;
+}
+
+/* pops any level >= to indent */
+static void setIndent (tokenInfo *const token)
+{
+	NestingLevel *lv = nestingLevelsGetCurrent (PythonNestingLevels);
+
+	while (lv && PY_NL (lv)->indentation >= token->indent)
+	{
+		tagEntryInfo *e = getEntryInCorkQueue (lv->corkIndex);
+		if (e)
+			e->extensionFields.endLine = token->lineNumber;
+
+		nestingLevelsPop (PythonNestingLevels);
+		lv = nestingLevelsGetCurrent (PythonNestingLevels);
+	}
+}
+
+static void findPythonTags (void)
+{
+	tokenInfo *const token = newToken ();
+	vString *decorators = vStringNew ();
+	bool atStatementStart = true;
+
+	TokenContinuationDepth = 0;
+	NextToken = NULL;
+	PythonNestingLevels = nestingLevelsNew (sizeof (struct pythonNestingLevelUserData));
+
+	readToken (token);
+	while (token->type != TOKEN_EOF)
+	{
+		tokenType iterationTokenType = token->type;
+		bool readNext = true;
+
+		/* skip async keyword that confuses decorator parsing before a def */
+		if (token->keyword == KEYWORD_async)
+			readToken (token);
+
+		if (token->type == TOKEN_INDENT)
+			setIndent (token);
+		else if (token->keyword == KEYWORD_class ||
+		         token->keyword == KEYWORD_def)
+		{
+			pythonKind kind = token->keyword == KEYWORD_class ? K_CLASS : K_FUNCTION;
+
+			readNext = parseClassOrDef (token, decorators, kind, false);
+		}
+		else if (token->keyword == KEYWORD_cdef ||
+		         token->keyword == KEYWORD_cpdef)
+		{
+			readNext = parseClassOrDef (token, decorators, K_FUNCTION, true);
+		}
+		else if (token->keyword == KEYWORD_from ||
+		         token->keyword == KEYWORD_import)
+		{
+			readNext = parseImport (token);
+		}
+		else if (token->type == '(')
+		{ /* skip parentheses to avoid finding stuff inside them */
+			readNext = skipOverPair (token, '(', ')', NULL, false);
+		}
+		else if (token->type == TOKEN_IDENTIFIER && atStatementStart)
+		{
+			NestingLevel *lv = nestingLevelsGetCurrent (PythonNestingLevels);
+			tagEntryInfo *lvEntry = getEntryOfNestingLevel (lv);
+			pythonKind kind = K_VARIABLE;
+
+			if (lvEntry && lvEntry->kindIndex != K_CLASS)
+				kind = K_LOCAL_VARIABLE;
+
+			readNext = parseVariable (token, kind);
+		}
+		else if (token->type == '@' && atStatementStart &&
+		         PythonFields[F_DECORATORS].enabled)
+		{
+			/* collect decorators */
+			readQualifiedName (token);
+			if (token->type != TOKEN_IDENTIFIER)
+				readNext = false;
+			else
+			{
+				if (vStringLength (decorators) > 0)
+					vStringPut (decorators, ',');
+				vStringCat (decorators, token->string);
+				readToken (token);
+				readNext = skipOverPair (token, '(', ')', decorators, true);
+			}
+		}
+
+		/* clear collected decorators for any non-decorator tokens non-indent
+		 * token.  decorator collection takes care of skipping the possible
+		 * argument list, so we should never hit here parsing a decorator */
+		if (iterationTokenType != TOKEN_INDENT &&
+		    iterationTokenType != '@' &&
+		    PythonFields[F_DECORATORS].enabled)
+		{
+			vStringClear (decorators);
+		}
+
+		atStatementStart = (token->type == TOKEN_INDENT || token->type == ';');
+
+		if (readNext)
+			readToken (token);
+	}
+
+	nestingLevelsFree (PythonNestingLevels);
+	vStringDelete (decorators);
+	deleteToken (token);
+	Assert (NextToken == NULL);
+}
+
+static void initialize (const langType language)
+{
+	Lang_python = language;
+
+	TokenPool = objPoolNew (16, newPoolToken, deletePoolToken, clearPoolToken, NULL);
+}
+
+static void finalize (langType language CTAGS_ATTR_UNUSED, bool initialized)
+{
+	if (!initialized)
+		return;
+
+	objPoolDelete (TokenPool);
+}
+
+extern parserDefinition* PythonParser (void)
+{
+	static const char *const extensions[] = { "py", "pyx", "pxd", "pxi", "scons",
+											  "wsgi", NULL };
+	static const char *const aliases[] = { "python[23]*", "scons", NULL };
+	parserDefinition *def = parserNew ("Python");
+	def->kindTable = PythonKinds;
+	def->kindCount = ARRAY_SIZE (PythonKinds);
+	def->extensions = extensions;
+	def->aliases = aliases;
+	def->parser = findPythonTags;
+	def->initialize = initialize;
+	def->finalize = finalize;
+	def->keywordTable = PythonKeywordTable;
+	def->keywordCount = ARRAY_SIZE (PythonKeywordTable);
+	def->fieldTable = PythonFields;
+	def->fieldCount = ARRAY_SIZE (PythonFields);
+	def->useCork = CORK_QUEUE;
+	def->requestAutomaticFQTag = true;
+	return def;
+}


Modified: src/tagmanager/tm_parser.c
4 lines changed, 4 insertions(+), 0 deletions(-)
===================================================================
@@ -136,9 +136,13 @@ static TMParserMapEntry map_PYTHON[] = {
 	{'f', tm_tag_function_t},
 	{'m', tm_tag_method_t},
 	{'v', tm_tag_variable_t},
+	{'I', tm_tag_externvar_t},
+	{'i', tm_tag_externvar_t},
     /* defined as externvar to get those excluded as forward type in symbols.c:goto_tag()
      * so we can jump to the real implementation (if known) instead of to the import statement */
 	{'x', tm_tag_externvar_t},
+	{'z', tm_tag_undef_t},
+	{'l', tm_tag_undef_t},
 };
 
 /* different parser than tex.c from universal-ctags */


Modified: tests/ctags/cython_sample2.pyx.tags
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -1,3 +1,4 @@
 # format=tagmanager
 my_fun�16�(np.ndarray[dtype=double, ndim=1] x)�0
 np�32768�0
+numpy�32768�0


Modified: tests/ctags/py_constructor_arglist.py.tags
33 lines changed, 21 insertions(+), 12 deletions(-)
===================================================================
@@ -1,16 +1,25 @@
 # format=tagmanager
-HttpResponse�32768�0
-HttpResponseBadRequest�32768�0
-InterfaceDataValidationError�32768�0
-RequestContext�32768�0
+HttpResponse�32768�django.http�0
+HttpResponseBadRequest�32768�django.http�0
+InterfaceDataValidationError�32768�vnstat.api.error�0
+RequestContext�32768�django.template.context�0
 SomeClass�1�0
 __init__�128�(self, filename, pathsep='', treegap=64)�SomeClass�0
-btopen�32768�0
-csrf_exempt�32768�0
-csrf_exempt2�32768�0
-login_required�32768�0
-permission_required�32768�0
-render_to_response�32768�0
-require_POST�32768�0
-simplejson�32768�0
+bsddb�32768�0
+btopen�32768�bsddb�0
+csrf_exempt�32768�django.views.decorators.csrf�0
+csrf_exempt2�32768�django.views.decorators.csrf�0
+django.contrib.auth.decorators�32768�0
+django.http�32768�0
+django.shortcuts�32768�0
+django.template.context�32768�0
+django.utils�32768�0
+django.views.decorators.csrf�32768�0
+django.views.decorators.http�32768�0
+login_required�32768�django.contrib.auth.decorators�0
+permission_required�32768�django.contrib.auth.decorators�0
+render_to_response�32768�django.shortcuts�0
+require_POST�32768�django.views.decorators.http�0
+simplejson�32768�django.utils�0
 sys�32768�0
+vnstat.api.error�32768�0


Modified: tests/ctags/simple.py.tags
6 lines changed, 4 insertions(+), 2 deletions(-)
===================================================================
@@ -7,11 +7,13 @@ TREEID
 VERSION�16384�0
 __init__�128�(self, filename, pathsep='', treegap=64)�one�0
 __private_function__�128�(self, key, data)�one�0
+_pack�128�(self, i, s)�one�0
 _test�16�(test, code, outcome, exception)�0
-btopen�32768�0
+bsddb�32768�0
+btopen�32768�bsddb�0
 deeply_nested�1�_test.ignored_function.more_nesting�0
 even_more�128�()�_test.ignored_function.more_nesting.deeply_nested�0
-foo�16�(�0
+foo�16�( x , y, z)�0
 ignored_function�16�()�_test�0
 more_nesting�16�()�_test.ignored_function�0
 one�1�0


Modified: tests/ctags/tabindent.py.tags
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -1,2 +1,2 @@
 # format=tagmanager
-find_heading�16�(self, position=0, direction=Direction.FORWARD,  		heading=Heading, connect_with_document=True)�0
+find_heading�16�(self, position=0, direction=Direction.FORWARD, heading=Heading, connect_with_document=True)�0



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Commits mailing list