python.c - ctags-5.8
Global variables defined
Data types defined
Functions defined
Source code
#include "general.h"
#include <string.h>
#include "entry.h"
#include "options.h"
#include "read.h"
#include "main.h"
#include "vstring.h"
#include "routines.h"
#include "debug.h"
typedef struct NestingLevel NestingLevel;
typedef struct NestingLevels NestingLevels;
struct NestingLevel
{
int indentation;
vString *name;
int type;
};
struct NestingLevels
{
NestingLevel *levels;
int n; int allocated;
};
typedef enum {
K_CLASS, K_FUNCTION, K_MEMBER, K_VARIABLE, K_IMPORT
} pythonKind;
static kindOption PythonKinds[] = {
{TRUE, 'c', "class", "classes"},
{TRUE, 'f', "function", "functions"},
{TRUE, 'm', "member", "class members"},
{TRUE, 'v', "variable", "variables"},
{TRUE, 'i', "namespace", "imports"}
};
static char const * const singletriple = "'''";
static char const * const doubletriple = "\"\"\"";
static NestingLevels *nestingLevelsNew (void)
{
NestingLevels *nls = xCalloc (1, NestingLevels);
return nls;
}
static void nestingLevelsFree (NestingLevels *nls)
{
int i;
for (i = 0; i < nls->allocated; i++)
vStringDelete(nls->levels[i].name);
if (nls->levels) eFree(nls->levels);
eFree(nls);
}
static void nestingLevelsPush (NestingLevels *nls,
const vString *name, int type)
{
NestingLevel *nl = NULL;
if (nls->n >= nls->allocated)
{
nls->allocated++;
nls->levels = xRealloc(nls->levels,
nls->allocated, NestingLevel);
nls->levels[nls->n].name = vStringNew();
}
nl = &nls->levels[nls->n];
nls->n++;
vStringCopy(nl->name, name);
nl->type = type;
}
#if 0
#endif
static boolean isIdentifierFirstCharacter (int c)
{
return (boolean) (isalpha (c) || c == '_');
}
static boolean isIdentifierCharacter (int c)
{
return (boolean) (isalnum (c) || c == '_');
}
static void makeFunctionTag (vString *const function,
vString *const parent, int is_class_parent, const char *arglist __unused__)
{
tagEntryInfo tag;
initTagEntry (&tag, vStringValue (function));
tag.kindName = "function";
tag.kind = 'f';
if (vStringLength (parent) > 0)
{
if (is_class_parent)
{
tag.kindName = "member";
tag.kind = 'm';
tag.extensionFields.scope [0] = "class";
tag.extensionFields.scope [1] = vStringValue (parent);
}
else
{
tag.extensionFields.scope [0] = "function";
tag.extensionFields.scope [1] = vStringValue (parent);
}
}
FIXMETODO if (strncmp (vStringValue (function), "__", 2) == 0 &&
strcmp (vStringValue (function), "__init__") != 0)
{
tag.extensionFields.access = "private";
tag.isFileScope = TRUE;
}
else
{
tag.extensionFields.access = "public";
}
makeTagEntry (&tag);
}
static void makeClassTag (vString *const class, vString *const inheritance,
vString *const parent, int is_class_parent)
{
tagEntryInfo tag;
initTagEntry (&tag, vStringValue (class));
tag.kindName = "class";
tag.kind = 'c';
if (vStringLength (parent) > 0)
{
if (is_class_parent)
{
tag.extensionFields.scope [0] = "class";
tag.extensionFields.scope [1] = vStringValue (parent);
}
else
{
tag.extensionFields.scope [0] = "function";
tag.extensionFields.scope [1] = vStringValue (parent);
}
}
tag.extensionFields.inheritance = vStringValue (inheritance);
makeTagEntry (&tag);
}
static void makeVariableTag (vString *const var, vString *const parent)
{
tagEntryInfo tag;
initTagEntry (&tag, vStringValue (var));
tag.kindName = "variable";
tag.kind = 'v';
if (vStringLength (parent) > 0)
{
tag.extensionFields.scope [0] = "class";
tag.extensionFields.scope [1] = vStringValue (parent);
}
makeTagEntry (&tag);
}
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;
}
static const char *skipEverything (const char *cp)
{
for (; *cp; cp++)
{
if (*cp == '"' || *cp == '\'')
{
cp = skipString(cp);
if (!*cp) break;
}
if (isIdentifierFirstCharacter ((int) *cp))
return cp;
}
return cp;
}
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;
}
static const char *parseIdentifier (const char *cp, vString *const identifier)
{
vStringClear (identifier);
while (isIdentifierCharacter ((int) *cp))
{
vStringPut (identifier, (int) *cp);
++cp;
}
vStringTerminate (identifier);
return cp;
}
static void parseClass (const char *cp, vString *const class,
vString *const parent, int is_class_parent)
{
vString *const inheritance = vStringNew ();
vStringClear (inheritance);
cp = parseIdentifier (cp, class);
cp = skipSpace (cp);
if (*cp == '(')
{
++cp;
while (*cp != ')')
{
if (*cp == '\0')
{
cp = (const char *) fileReadLine ();
if (!cp) break;
vStringPut (inheritance, ' ');
continue;
}
vStringPut (inheritance, *cp);
++cp;
}
vStringTerminate (inheritance);
}
makeClassTag (class, inheritance, parent, is_class_parent);
vStringDelete (inheritance);
}
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;
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);
parseIdentifier (cp, name_next);
if (strcmp (vStringValue (name_next), "as") != 0 &&
strcmp (vStringValue (name), "as") != 0)
{
makeSimpleTag (name, PythonKinds, K_IMPORT);
}
}
vStringDelete (name);
vStringDelete (name_next);
}
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 void parseFunction (const char *cp, vString *const def,
vString *const parent, int is_class_parent)
{
char *arglist;
cp = parseIdentifier (cp, def);
arglist = parseArglist (cp);
makeFunctionTag (def, parent, is_class_parent, arglist);
eFree (arglist);
}
static boolean 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 = nls->levels + i;
if (indent <= nl->indentation)
break;
if (prev)
{
vStringCatS(result, "."); }
vStringCat(result, nl->name);
is_class = (nl->type == K_CLASS);
prev = nl;
}
return is_class;
}
static void checkParent(NestingLevels *nls, int indent, vString *parent)
{
int i;
NestingLevel *n;
for (i = 0; i < nls->n; i++)
{
n = nls->levels + i;
if (strcmp(vStringValue(parent), vStringValue(n->name)) == 0)
{
if (n && indent <= n->indentation)
{
vStringClear(n->name);
}
break;
}
}
}
static void addNestingLevel(NestingLevels *nls, int indentation,
const vString *name, boolean is_class)
{
int i;
NestingLevel *nl = NULL;
for (i = 0; i < nls->n; i++)
{
nl = nls->levels + i;
if (indentation <= nl->indentation) break;
}
if (i == nls->n)
{
nestingLevelsPush(nls, name, 0);
nl = nls->levels + i;
}
else
{ nls->n = i + 1;
vStringCopy(nl->name, name);
}
nl->indentation = indentation;
nl->type = is_class ? K_CLASS : !K_CLASS;
}
static char const *find_triple_start(char const *string, char const **which)
{
char const *cp = string;
for (; *cp; cp++)
{
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;
}
}
return NULL;
}
static void find_triple_end(char const *string, char const **which)
{
char const *s = string;
while (1)
{
s = strstr (s, *which);
if (!s) break;
s += 3;
*which = NULL;
s = find_triple_start(s, which);
if (!s) break;
s += 3;
}
}
static const char *findVariable(const char *line)
{
TODO const char *cp, *sp, *eq, *start;
cp = strstr(line, "=");
if (!cp)
return NULL;
eq = cp + 1;
while (*eq)
{
if (*eq == '=')
return NULL; if (*eq == '(' || *eq == '#')
break; eq++;
}
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) return NULL;
++start;
return start;
}
static const char *skipTypeDecl (const char *cp, boolean *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;
}
while (*ptr && loopCount++ < 2) {
while (*ptr && *ptr != '=' && *ptr != '(' && !isspace(*ptr)) ptr++;
if (!*ptr || *ptr == '=') return NULL;
if (*ptr == '(') {
return lastStart; }
ptr = skipSpace(ptr);
lastStart = ptr;
while (*lastStart == '*') lastStart++; }
return NULL;
}
static void findPythonTags (void)
{
vString *const continuation = vStringNew ();
vString *const name = vStringNew ();
vString *const parent = vStringNew();
NestingLevels *const nesting_levels = nestingLevelsNew();
const char *line;
int line_skip = 0;
char const *longStringLiteral = NULL;
while ((line = (const char *) fileReadLine ()) != NULL)
{
const char *cp = line, *candidate;
char const *longstring;
char const *keyword, *variable;
int indent;
cp = skipSpace (cp);
if (*cp == '\0') continue;
if (*cp == '#' && !longStringLiteral)
continue;
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;
checkParent(nesting_levels, indent, parent);
if (longStringLiteral)
{
find_triple_end(cp, &longStringLiteral);
continue;
}
longstring = find_triple_start(cp, &longStringLiteral);
if (longstring)
{
longstring += 3;
find_triple_end(longstring, &longStringLiteral);
continue;
}
keyword = findDefinitionOrClass (cp);
if (keyword)
{
boolean found = FALSE;
boolean is_class = FALSE;
if (!strncmp (keyword, "def ", 4))
{
cp = skipSpace (keyword + 3);
found = TRUE;
}
else if (!strncmp (keyword, "class ", 6))
{
cp = skipSpace (keyword + 5);
found = TRUE;
is_class = TRUE;
}
else if (!strncmp (keyword, "cdef ", 5))
{
cp = skipSpace(keyword + 4);
candidate = skipTypeDecl (cp, &is_class);
if (candidate)
{
found = TRUE;
cp = candidate;
}
}
else if (!strncmp (keyword, "cpdef ", 6))
{
cp = skipSpace(keyword + 5);
candidate = skipTypeDecl (cp, &is_class);
if (candidate)
{
found = TRUE;
cp = candidate;
}
}
if (found)
{
boolean is_parent_class;
is_parent_class =
constructParentString(nesting_levels, indent, parent);
if (is_class)
parseClass (cp, name, parent, is_parent_class);
else
parseFunction(cp, name, parent, is_parent_class);
addNestingLevel(nesting_levels, indent, name, is_class);
}
}
variable = findVariable(line);
if (variable)
{
const char *start = variable;
boolean parent_is_class;
vStringClear (name);
while (isIdentifierCharacter ((int) *start))
{
vStringPut (name, (int) *start);
++start;
}
vStringTerminate (name);
parent_is_class = constructParentString(nesting_levels, indent, parent);
if (! parent_is_class && vStringLength(parent) > 0)
continue;
makeVariableTag (name, parent);
}
parseImports(line);
}
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->kinds = PythonKinds;
def->kindCount = KIND_COUNT (PythonKinds);
def->extensions = extensions;
def->parser = findPythonTags;
return def;
}