cosmopolitan/third_party/ctags/go.c

597 lines
15 KiB
C

#include "third_party/ctags/general.h"
/* must always come first */
#include "libc/calls/calls.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "third_party/ctags/debug.h"
#include "third_party/ctags/entry.h"
#include "third_party/ctags/keyword.h"
#include "third_party/ctags/main.h"
#include "third_party/ctags/options.h"
#include "third_party/ctags/read.h"
#include "third_party/ctags/routines.h"
#include "third_party/ctags/vstring.h"
/*
* MACROS
*/
#define isType(token, t) (boolean)((token)->type == (t))
#define isKeyword(token, k) (boolean)((token)->keyword == (k))
/*
* DATA DECLARATIONS
*/
typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
typedef enum eKeywordId {
KEYWORD_NONE = -1,
KEYWORD_package,
KEYWORD_import,
KEYWORD_const,
KEYWORD_type,
KEYWORD_var,
KEYWORD_func,
KEYWORD_struct,
KEYWORD_interface,
KEYWORD_map,
KEYWORD_chan
} keywordId;
/* Used to determine whether keyword is valid for the current language and
* what its ID is.
*/
typedef struct sKeywordDesc {
const char *name;
keywordId id;
} keywordDesc;
typedef enum eTokenType {
TOKEN_NONE = -1,
TOKEN_CHARACTER,
// Don't need TOKEN_FORWARD_SLASH
TOKEN_FORWARD_SLASH,
TOKEN_KEYWORD,
TOKEN_IDENTIFIER,
TOKEN_STRING,
TOKEN_OPEN_PAREN,
TOKEN_CLOSE_PAREN,
TOKEN_OPEN_CURLY,
TOKEN_CLOSE_CURLY,
TOKEN_OPEN_SQUARE,
TOKEN_CLOSE_SQUARE,
TOKEN_SEMICOLON,
TOKEN_STAR,
TOKEN_LEFT_ARROW,
TOKEN_DOT,
TOKEN_COMMA
} tokenType;
typedef struct sTokenInfo {
tokenType type;
keywordId keyword;
vString *string; /* the name of the token */
unsigned long lineNumber; /* line number of tag */
fpos_t filePosition; /* file position of line containing name */
} tokenInfo;
/*
* DATA DEFINITIONS
*/
static int Lang_go;
static jmp_buf Exception;
static vString *scope;
typedef enum {
GOTAG_UNDEFINED = -1,
GOTAG_PACKAGE,
GOTAG_FUNCTION,
GOTAG_CONST,
GOTAG_TYPE,
GOTAG_VAR,
} goKind;
static kindOption GoKinds[] = {{TRUE, 'p', "package", "packages"},
{TRUE, 'f', "func", "functions"},
{TRUE, 'c', "const", "constants"},
{TRUE, 't', "type", "types"},
{TRUE, 'v', "var", "variables"}};
static keywordDesc GoKeywordTable[] = {
{"package", KEYWORD_package}, {"import", KEYWORD_import},
{"const", KEYWORD_const}, {"type", KEYWORD_type},
{"var", KEYWORD_var}, {"func", KEYWORD_func},
{"struct", KEYWORD_struct}, {"interface", KEYWORD_interface},
{"map", KEYWORD_map}, {"chan", KEYWORD_chan}};
/*
* FUNCTION DEFINITIONS
*/
// XXX UTF-8
static boolean isIdentChar(const int c) {
return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' ||
c == '_' || c == '#' || c > 128);
}
static void initialize(const langType language) {
size_t i;
const size_t count = sizeof(GoKeywordTable) / sizeof(GoKeywordTable[0]);
Lang_go = language;
for (i = 0; i < count; ++i) {
const keywordDesc *const p = &GoKeywordTable[i];
addKeyword(p->name, language, (int)p->id);
}
}
static tokenInfo *newToken(void) {
tokenInfo *const token = xMalloc(1, tokenInfo);
token->type = TOKEN_NONE;
token->keyword = KEYWORD_NONE;
token->string = vStringNew();
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
return token;
}
static void deleteToken(tokenInfo *const token) {
if (token != NULL) {
vStringDelete(token->string);
eFree(token);
}
}
/*
* Parsing functions
*/
static void parseString(vString *const string, const int delimiter) {
boolean end = FALSE;
while (!end) {
int c = fileGetc();
if (c == EOF)
end = TRUE;
else if (c == '\\' && delimiter != '`') {
c = fileGetc(); /* This maybe a ' or ". */
vStringPut(string, c);
} else if (c == delimiter)
end = TRUE;
else
vStringPut(string, c);
}
vStringTerminate(string);
}
static void parseIdentifier(vString *const string, const int firstChar) {
int c = firstChar;
// Assert (isIdentChar (c));
do {
vStringPut(string, c);
c = fileGetc();
} while (isIdentChar(c));
vStringTerminate(string);
fileUngetc(c); /* always unget, LF might add a semicolon */
}
static void readToken(tokenInfo *const token) {
int c;
static tokenType lastTokenType = TOKEN_NONE;
token->type = TOKEN_NONE;
token->keyword = KEYWORD_NONE;
vStringClear(token->string);
getNextChar:
do {
c = fileGetc();
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
if (c == '\n' &&
(lastTokenType == TOKEN_IDENTIFIER || lastTokenType == TOKEN_STRING ||
lastTokenType == TOKEN_CLOSE_PAREN ||
lastTokenType == TOKEN_CLOSE_CURLY ||
lastTokenType == TOKEN_CLOSE_SQUARE)) {
token->type = TOKEN_SEMICOLON;
goto done;
}
} while (c == '\t' || c == ' ' || c == '\r' || c == '\n');
switch (c) {
case EOF:
longjmp(Exception, (int)ExceptionEOF);
break;
case '/': {
boolean hasNewline = FALSE;
int d = fileGetc();
switch (d) {
case '/':
fileSkipToCharacter('\n');
/* Line comments start with the
* character sequence // and
* continue through the next
* newline. A line comment acts
* like a newline. */
fileUngetc('\n');
goto getNextChar;
case '*':
do {
int d;
do {
d = fileGetc();
if (d == '\n') {
hasNewline = TRUE;
}
} while (d != EOF && d != '*');
c = fileGetc();
if (c == '/')
break;
else
fileUngetc(c);
} while (c != EOF && c != '\0');
fileUngetc(hasNewline ? '\n' : ' ');
goto getNextChar;
default:
token->type = TOKEN_FORWARD_SLASH;
fileUngetc(d);
break;
}
} break;
case '"':
case '\'':
case '`':
token->type = TOKEN_STRING;
parseString(token->string, c);
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
break;
case '<': {
int d = fileGetc();
if (d == '-') {
token->type = TOKEN_LEFT_ARROW;
break;
} else
goto getNextChar;
}
case '(':
token->type = TOKEN_OPEN_PAREN;
break;
case ')':
token->type = TOKEN_CLOSE_PAREN;
break;
case '{':
token->type = TOKEN_OPEN_CURLY;
break;
case '}':
token->type = TOKEN_CLOSE_CURLY;
break;
case '[':
token->type = TOKEN_OPEN_SQUARE;
break;
case ']':
token->type = TOKEN_CLOSE_SQUARE;
break;
case '*':
token->type = TOKEN_STAR;
break;
case '.':
token->type = TOKEN_DOT;
break;
case ',':
token->type = TOKEN_COMMA;
break;
default:
parseIdentifier(token->string, c);
token->lineNumber = getSourceLineNumber();
token->filePosition = getInputFilePosition();
token->keyword = lookupKeyword(vStringValue(token->string), Lang_go);
if (isKeyword(token, KEYWORD_NONE))
token->type = TOKEN_IDENTIFIER;
else
token->type = TOKEN_KEYWORD;
break;
}
done:
lastTokenType = token->type;
}
static void skipToMatched(tokenInfo *const token) {
int nest_level = 0;
tokenType open_token;
tokenType close_token;
switch (token->type) {
case TOKEN_OPEN_PAREN:
open_token = TOKEN_OPEN_PAREN;
close_token = TOKEN_CLOSE_PAREN;
break;
case TOKEN_OPEN_CURLY:
open_token = TOKEN_OPEN_CURLY;
close_token = TOKEN_CLOSE_CURLY;
break;
case TOKEN_OPEN_SQUARE:
open_token = TOKEN_OPEN_SQUARE;
close_token = TOKEN_CLOSE_SQUARE;
break;
default:
return;
}
/*
* This routine will skip to a matching closing token.
* It will also handle nested tokens like the (, ) below.
* ( name varchar(30), text binary(10) )
*/
if (isType(token, open_token)) {
nest_level++;
while (!(isType(token, close_token) && (nest_level == 0))) {
readToken(token);
if (isType(token, open_token)) {
nest_level++;
}
if (isType(token, close_token)) {
if (nest_level > 0) {
nest_level--;
}
}
}
readToken(token);
}
}
static void skipType(tokenInfo *const token) {
again:
// Type = TypeName | TypeLit | "(" Type ")" .
if (isType(token, TOKEN_OPEN_PAREN)) {
skipToMatched(token);
return;
}
// TypeName = QualifiedIdent.
// QualifiedIdent = [ PackageName "." ] identifier .
// PackageName = identifier .
if (isType(token, TOKEN_IDENTIFIER)) {
readToken(token);
if (isType(token, TOKEN_DOT)) {
readToken(token);
Assert(isType(token, TOKEN_IDENTIFIER));
readToken(token);
}
return;
}
// StructType = "struct" "{" { FieldDecl ";" } "}"
// InterfaceType = "interface" "{" { MethodSpec ";" } "}" .
if (isKeyword(token, KEYWORD_struct) || isKeyword(token, KEYWORD_interface)) {
readToken(token);
Assert(isType(token, TOKEN_OPEN_CURLY));
skipToMatched(token);
return;
}
// ArrayType = "[" ArrayLength "]" ElementType .
// SliceType = "[" "]" ElementType .
// ElementType = Type .
if (isType(token, TOKEN_OPEN_SQUARE)) {
skipToMatched(token);
goto again;
}
// PointerType = "*" BaseType .
// BaseType = Type .
// ChannelType = ( "chan" [ "<-" ] | "<-" "chan" ) ElementType .
if (isType(token, TOKEN_STAR) || isKeyword(token, KEYWORD_chan) ||
isType(token, TOKEN_LEFT_ARROW)) {
readToken(token);
goto again;
}
// MapType = "map" "[" KeyType "]" ElementType .
// KeyType = Type .
if (isKeyword(token, KEYWORD_map)) {
readToken(token);
Assert(isType(token, TOKEN_OPEN_SQUARE));
skipToMatched(token);
goto again;
}
// FunctionType = "func" Signature .
// Signature = Parameters [ Result ] .
// Result = Parameters | Type .
// Parameters = "(" [ ParameterList [ "," ] ] ")" .
if (isKeyword(token, KEYWORD_func)) {
readToken(token);
Assert(isType(token, TOKEN_OPEN_PAREN));
// Parameters
skipToMatched(token);
// Result is parameters or type or nothing. skipType treats anything
// surrounded by parentheses as a type, and does nothing if what
// follows is not a type.
goto again;
}
}
// Skip to the next semicolon, skipping over matching brackets.
static void skipToTopLevelSemicolon(tokenInfo *const token) {
while (!isType(token, TOKEN_SEMICOLON)) {
readToken(token);
skipToMatched(token);
}
}
static void makeTag(tokenInfo *const token, const goKind kind) {
const char *const name = vStringValue(token->string);
tagEntryInfo e;
initTagEntry(&e, name);
if (!GoKinds[kind].enabled) return;
e.lineNumber = token->lineNumber;
e.filePosition = token->filePosition;
e.kindName = GoKinds[kind].name;
e.kind = GoKinds[kind].letter;
makeTagEntry(&e);
if (scope && Option.include.qualifiedTags) {
vString *qualifiedName = vStringNew();
vStringCopy(qualifiedName, scope);
vStringCatS(qualifiedName, ".");
vStringCat(qualifiedName, token->string);
e.name = vStringValue(qualifiedName);
makeTagEntry(&e);
vStringDelete(qualifiedName);
}
}
static void parsePackage(tokenInfo *const token) {
tokenInfo *const name = newToken();
readToken(name);
Assert(isType(name, TOKEN_IDENTIFIER));
makeTag(name, GOTAG_PACKAGE);
if (!scope && Option.include.qualifiedTags) {
scope = vStringNew();
vStringCopy(scope, name->string);
}
deleteToken(name);
}
static void parseFunctionOrMethod(tokenInfo *const token) {
// FunctionDecl = "func" identifier Signature [ Body ] .
// Body = Block.
//
// MethodDecl = "func" Receiver MethodName Signature [ Body ] .
// Receiver = "(" [ identifier ] [ "*" ] BaseTypeName ")" .
// BaseTypeName = identifier .
tokenInfo *const name = newToken();
// Skip over receiver.
readToken(name);
if (isType(name, TOKEN_OPEN_PAREN)) skipToMatched(name);
Assert(isType(name, TOKEN_IDENTIFIER));
// Skip over parameters.
readToken(token);
skipToMatched(token);
// Skip over result.
skipType(token);
// Skip over function body.
if (isType(token, TOKEN_OPEN_CURLY)) skipToMatched(token);
makeTag(name, GOTAG_FUNCTION);
deleteToken(name);
}
static void parseConstTypeVar(tokenInfo *const token, goKind kind) {
// ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
// ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
// IdentifierList = identifier { "," identifier } .
// ExpressionList = Expression { "," Expression } .
// TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
// TypeSpec = identifier Type .
// VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
// VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "="
// ExpressionList ) .
tokenInfo *const name = newToken();
boolean usesParens = FALSE;
readToken(name);
if (isType(name, TOKEN_OPEN_PAREN)) {
usesParens = TRUE;
readToken(name);
}
again:
while (1) {
makeTag(name, kind);
readToken(token);
if (!isType(token, TOKEN_COMMA) && !isType(token, TOKEN_CLOSE_PAREN)) break;
readToken(name);
}
skipType(token);
skipToTopLevelSemicolon(token);
if (usesParens) {
readToken(name);
if (!isType(name, TOKEN_CLOSE_PAREN)) goto again;
}
deleteToken(name);
}
static void parseGoFile(tokenInfo *const token) {
do {
readToken(token);
if (isType(token, TOKEN_KEYWORD)) {
switch (token->keyword) {
case KEYWORD_package:
parsePackage(token);
break;
case KEYWORD_func:
parseFunctionOrMethod(token);
break;
case KEYWORD_const:
parseConstTypeVar(token, GOTAG_CONST);
break;
case KEYWORD_type:
parseConstTypeVar(token, GOTAG_TYPE);
break;
case KEYWORD_var:
parseConstTypeVar(token, GOTAG_VAR);
break;
default:
break;
}
}
} while (TRUE);
}
static void findGoTags(void) {
tokenInfo *const token = newToken();
exception_t exception;
exception = (exception_t)(setjmp(Exception));
while (exception == ExceptionNone) parseGoFile(token);
deleteToken(token);
vStringDelete(scope);
scope = NULL;
}
extern parserDefinition *GoParser(void) {
static const char *const extensions[] = {"go", NULL};
parserDefinition *def = parserNew("Go");
def->kinds = GoKinds;
def->kindCount = KIND_COUNT(GoKinds);
def->extensions = extensions;
def->parser = findGoTags;
def->initialize = initialize;
return def;
}