From 8942bc810bc2091581027ea578717cf1d8342af1 Mon Sep 17 00:00:00 2001 From: Colomban Wendling Date: Sun, 13 Nov 2011 00:45:12 +0100 Subject: [PATCH] Add Objective-C support Based on a patch from Elias Pschernig, thanks. Parser was taken from upstream CTags. Closes patch#3325139. --- data/filetype_extensions.conf | 1 + data/filetypes.objectivec | 48 ++ src/document.c | 1 + src/filetypes.c | 10 + src/filetypes.h | 1 + src/highlighting.c | 2 + src/highlightingmappings.h | 14 + tagmanager/Makefile.am | 1 + tagmanager/makefile.win32 | 2 +- tagmanager/objc.c | 1142 +++++++++++++++++++++++++++++++++++++++++ tagmanager/parsers.h | 4 +- wscript | 2 +- 12 files changed, 1225 insertions(+), 3 deletions(-) create mode 100644 data/filetypes.objectivec create mode 100644 tagmanager/objc.c diff --git a/data/filetype_extensions.conf b/data/filetype_extensions.conf index 56fdbdbb2..196fe8eaa 100644 --- a/data/filetype_extensions.conf +++ b/data/filetype_extensions.conf @@ -38,6 +38,7 @@ Make=*.mak;*.mk;GNUmakefile;makefile;Makefile;makefile.*;Makefile.*; Markdown=*.mdml;*.markdown;*.md;*.mkd; Matlab/Octave=*.m; NSIS=*.nsi;*.nsh; +Objective-C=*.m;*.h; Pascal=*.pas;*.pp;*.inc;*.dpr;*.dpk; Perl=*.pl;*.perl;*.pm;*.agi;*.pod; PHP=*.php;*.php3;*.php4;*.php5;*.phtml; diff --git a/data/filetypes.objectivec b/data/filetypes.objectivec new file mode 100644 index 000000000..8cc04d4d4 --- /dev/null +++ b/data/filetypes.objectivec @@ -0,0 +1,48 @@ +# For complete documentation of this file, please see Geany's main documentation +[styling=C] + +[keywords] +# all items must be in one line +primary=asm auto break case char const continue default do double else enum extern float for goto if inline int long register restrict return short signed sizeof static struct switch typedef union unsigned void volatile while FALSE NULL TRUE +secondary=@class @defs @dynamic @encode @end @implementation @interface @optional @package @public @private @property @protocol @protected @required @selector @synthesize @synchronized +# these are some doxygen keywords (incomplete) +docComment=attention author brief bug class code date def enum example exception file fn namespace note param remarks return see since struct throw todo typedef var version warning union + +[lexer_properties=C] + +[settings] +# default extension used when saving files +extension=m + +# the following characters are these which a "word" can contains, see documentation +#wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 + +# single comments, like # in this file +comment_single=// +# multiline comments +comment_open=/* +comment_close=*/ + +# set to false if a comment character/string should start at column 0 of a line, true uses any +# indentation of the line, e.g. setting to true causes the following on pressing CTRL+d + #command_example(); +# setting to false would generate this +# command_example(); +# This setting works only for single line comments +comment_use_indent=true + +# context action command (please see Geany's main documentation for details) +context_action_cmd= + +[indentation] +#width=4 +# 0 is spaces, 1 is tabs, 2 is tab & spaces +#type=1 + +[build_settings] +# %f will be replaced by the complete filename +# %e will be replaced by the filename without extension +# (use only one of it at one time) +compiler=gcc -Wall -c "%f" +linker=gcc -Wall -o "%e" "%f" -lobjc +run_cmd="./%e" diff --git a/src/document.c b/src/document.c index f97960331..d891f2525 100644 --- a/src/document.c +++ b/src/document.c @@ -2279,6 +2279,7 @@ void document_update_tags(GeanyDocument *doc) case GEANY_FILETYPES_CS: case GEANY_FILETYPES_D: case GEANY_FILETYPES_JAVA: + case GEANY_FILETYPES_OBJECTIVEC: case GEANY_FILETYPES_VALA: { diff --git a/src/filetypes.c b/src/filetypes.c index f27707c10..e55b496b6 100644 --- a/src/filetypes.c +++ b/src/filetypes.c @@ -118,6 +118,14 @@ static void init_builtin_filetypes(void) ft->mime_type = g_strdup("text/x-c++src"); ft->group = GEANY_FILETYPE_GROUP_COMPILED; +#define OBJECTIVEC + ft = filetypes[GEANY_FILETYPES_OBJECTIVEC]; + ft->lang = 42; + ft->name = g_strdup("Objective-C"); + filetype_make_title(ft, TITLE_SOURCE_FILE); + ft->mime_type = g_strdup("text/x-objc"); + ft->group = GEANY_FILETYPE_GROUP_COMPILED; + #define CS ft = filetypes[GEANY_FILETYPES_CS]; ft->lang = 25; @@ -1430,6 +1438,8 @@ static gchar *filetypes_get_conf_extension(const GeanyFiletype *ft) case GEANY_FILETYPES_NONE: result = g_strdup("common"); break; /* name is Matlab/Octave */ case GEANY_FILETYPES_MATLAB: result = g_strdup("matlab"); break; + /* name is Objective-C, and we don't want the hyphen */ + case GEANY_FILETYPES_OBJECTIVEC: result = g_strdup("objectivec"); break; default: result = g_ascii_strdown(ft->name, -1); break; diff --git a/src/filetypes.h b/src/filetypes.h index 4e3cbeba8..eb690d4cb 100644 --- a/src/filetypes.h +++ b/src/filetypes.h @@ -87,6 +87,7 @@ typedef enum GEANY_FILETYPES_LISP, GEANY_FILETYPES_ERLANG, GEANY_FILETYPES_COBOL, + GEANY_FILETYPES_OBJECTIVEC, /* ^ append items here */ GEANY_MAX_BUILT_IN_FILETYPES /* Don't use this, use filetypes_array->len instead */ } diff --git a/src/highlighting.c b/src/highlighting.c index 93462b10d..1f98ae21f 100644 --- a/src/highlighting.c +++ b/src/highlighting.c @@ -977,6 +977,7 @@ void highlighting_init_styles(guint filetype_idx, GKeyFile *config, GKeyFile *co init_styleset_case(MATLAB); init_styleset_case(MARKDOWN); init_styleset_case(NSIS); + init_styleset_case(OBJECTIVEC); init_styleset_case(PASCAL); init_styleset_case(PERL); init_styleset_case(PHP); @@ -1055,6 +1056,7 @@ void highlighting_set_styles(ScintillaObject *sci, GeanyFiletype *ft) styleset_case(MARKDOWN); styleset_case(MATLAB); styleset_case(NSIS); + styleset_case(OBJECTIVEC); styleset_case(PASCAL); styleset_case(PERL); styleset_case(PHP); diff --git a/src/highlightingmappings.h b/src/highlightingmappings.h index 4f11bc3ca..cad6eb942 100644 --- a/src/highlightingmappings.h +++ b/src/highlightingmappings.h @@ -989,6 +989,20 @@ static const HLKeyword highlighting_keywords_NSIS[] = #define highlighting_properties_NSIS EMPTY_PROPERTIES +/* Objective-C */ +#define highlighting_lexer_OBJECTIVEC highlighting_lexer_C +#define highlighting_styles_OBJECTIVEC highlighting_styles_C +static const HLKeyword highlighting_keywords_OBJECTIVEC[] = +{ + { 0, "primary" }, + /* SCI_SETKEYWORDS = 1 - secondary + global tags file types, see below */ + { 1, "secondary", TRUE }, + { 2, "docComment" } + /* SCI_SETKEYWORDS = 3 is for current session types - see editor_lexer_get_type_keyword_idx() */ +}; +#define highlighting_properties_OBJECTIVEC highlighting_properties_C + + /* Pascal */ #define highlighting_lexer_PASCAL SCLEX_PASCAL static const HLStyle highlighting_styles_PASCAL[] = diff --git a/tagmanager/Makefile.am b/tagmanager/Makefile.am index 8e8079986..7542517e9 100644 --- a/tagmanager/Makefile.am +++ b/tagmanager/Makefile.am @@ -53,6 +53,7 @@ libtagmanager_a_SOURCES =\ lregex.c\ matlab.c\ markdown.c\ + objc.c\ pascal.c\ perl.c\ rest.c\ diff --git a/tagmanager/makefile.win32 b/tagmanager/makefile.win32 index 1fd12b27e..f3f504615 100644 --- a/tagmanager/makefile.win32 +++ b/tagmanager/makefile.win32 @@ -40,7 +40,7 @@ clean: -$(RM) deps.mak *.o $(COMPLIB) $(COMPLIB): abc.o args.o c.o cobol.o fortran.o make.o conf.o pascal.o perl.o php.o diff.o vhdl.o verilog.o lua.o js.o \ -actionscript.o nsis.o \ +actionscript.o nsis.o objc.o \ haskell.o haxe.o html.o python.o lregex.o rest.o sh.o ctags.o entry.o get.o keyword.o nestlevel.o \ options.o \ parse.o basic.o read.o sort.o strlist.o latex.o markdown.o matlab.o docbook.o tcl.o ruby.o asm.o sql.o txt2tags.o css.o \ diff --git a/tagmanager/objc.c b/tagmanager/objc.c new file mode 100644 index 000000000..d72c02084 --- /dev/null +++ b/tagmanager/objc.c @@ -0,0 +1,1142 @@ + +/* +* Copyright (c) 2010, Vincent Berthoux +* +* This source code is released for free distribution under the terms of the +* GNU General Public License. +* +* This module contains functions for generating tags for Objective C +* language files. +*/ +/* +* INCLUDE FILES +*/ +#include "general.h" /* must always come first */ + +#include + +#include "keyword.h" +#include "entry.h" +#include "options.h" +#include "read.h" +#include "vstring.h" + +/* To get rid of unused parameter warning in + * -Wextra */ +#ifdef UNUSED +#elif defined(__GNUC__) +# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) +#elif defined(__LCLINT__) +# define UNUSED(x) /*@unused@*/ x +#else +# define UNUSED(x) x +#endif + +typedef enum { + K_INTERFACE, + K_IMPLEMENTATION, + K_PROTOCOL, + K_METHOD, + K_CLASSMETHOD, + K_VAR, + K_FIELD, + K_FUNCTION, + K_PROPERTY, + K_TYPEDEF, + K_STRUCT, + K_ENUM, + K_MACRO +} objcKind; + +static kindOption ObjcKinds[] = { + {TRUE, 'i', "interface", "class interface"}, + {TRUE, 'I', "implementation", "class implementation"}, + {TRUE, 'p', "protocol", "Protocol"}, + {TRUE, 'm', "method", "Object's method"}, + {TRUE, 'c', "class", "Class' method"}, + {TRUE, 'v', "var", "Global variable"}, + {TRUE, 'F', "field", "Object field"}, + {TRUE, 'f', "function", "A function"}, + {TRUE, 'p', "property", "A property"}, + {TRUE, 't', "typedef", "A type alias"}, + {TRUE, 's', "struct", "A type structure"}, + {TRUE, 'e', "enum", "An enumeration"}, + {TRUE, 'M', "macro", "A preprocessor macro"}, +}; + +typedef enum { + ObjcTYPEDEF, + ObjcSTRUCT, + ObjcENUM, + ObjcIMPLEMENTATION, + ObjcINTERFACE, + ObjcPROTOCOL, + ObjcENCODE, + ObjcSYNCHRONIZED, + ObjcSELECTOR, + ObjcPROPERTY, + ObjcEND, + ObjcDEFS, + ObjcCLASS, + ObjcPRIVATE, + ObjcPACKAGE, + ObjcPUBLIC, + ObjcPROTECTED, + ObjcSYNTHESIZE, + ObjcDYNAMIC, + ObjcOPTIONAL, + ObjcREQUIRED, + ObjcSTRING, + ObjcIDENTIFIER, + + Tok_COMA, /* ',' */ + Tok_PLUS, /* '+' */ + Tok_MINUS, /* '-' */ + Tok_PARL, /* '(' */ + Tok_PARR, /* ')' */ + Tok_CurlL, /* '{' */ + Tok_CurlR, /* '}' */ + Tok_SQUAREL, /* '[' */ + Tok_SQUARER, /* ']' */ + Tok_semi, /* ';' */ + Tok_dpoint, /* ':' */ + Tok_Sharp, /* '#' */ + Tok_Backslash, /* '\\' */ + Tok_EOL, /* '\r''\n' */ + Tok_any, + + Tok_EOF /* END of file */ +} objcKeyword; + +typedef objcKeyword objcToken; + +typedef struct sOBjcKeywordDesc { + const char *name; + objcKeyword id; +} objcKeywordDesc; + + +static const objcKeywordDesc objcKeywordTable[] = { + {"typedef", ObjcTYPEDEF}, + {"struct", ObjcSTRUCT}, + {"enum", ObjcENUM}, + {"@implementation", ObjcIMPLEMENTATION}, + {"@interface", ObjcINTERFACE}, + {"@protocol", ObjcPROTOCOL}, + {"@encode", ObjcENCODE}, + {"@property", ObjcPROPERTY}, + {"@synchronized", ObjcSYNCHRONIZED}, + {"@selector", ObjcSELECTOR}, + {"@end", ObjcEND}, + {"@defs", ObjcDEFS}, + {"@class", ObjcCLASS}, + {"@private", ObjcPRIVATE}, + {"@package", ObjcPACKAGE}, + {"@public", ObjcPUBLIC}, + {"@protected", ObjcPROTECTED}, + {"@synthesize", ObjcSYNTHESIZE}, + {"@dynamic", ObjcDYNAMIC}, + {"@optional", ObjcOPTIONAL}, + {"@required", ObjcREQUIRED}, +}; + +static langType Lang_ObjectiveC; + +/*////////////////////////////////////////////////////////////////// +//// lexingInit */ +typedef struct _lexingState { + vString *name; /* current parsed identifier/operator */ + const unsigned char *cp; /* position in stream */ +} lexingState; + +static void initKeywordHash (void) +{ + const size_t count = sizeof (objcKeywordTable) / sizeof (objcKeywordDesc); + size_t i; + + for (i = 0; i < count; ++i) + { + addKeyword (objcKeywordTable[i].name, Lang_ObjectiveC, + (int) objcKeywordTable[i].id); + } +} + +/*////////////////////////////////////////////////////////////////////// +//// Lexing */ +static boolean isNum (char c) +{ + return c >= '0' && c <= '9'; +} + +static boolean isLowerAlpha (char c) +{ + return c >= 'a' && c <= 'z'; +} + +static boolean isUpperAlpha (char c) +{ + return c >= 'A' && c <= 'Z'; +} + +static boolean isAlpha (char c) +{ + return isLowerAlpha (c) || isUpperAlpha (c); +} + +static boolean isIdent (char c) +{ + return isNum (c) || isAlpha (c) || c == '_'; +} + +static boolean isSpace (char c) +{ + return c == ' ' || c == '\t'; +} + +/* return true if it end with an end of line */ +static void eatWhiteSpace (lexingState * st) +{ + const unsigned char *cp = st->cp; + while (isSpace (*cp)) + cp++; + + st->cp = cp; +} + +static void eatString (lexingState * st) +{ + boolean lastIsBackSlash = FALSE; + boolean unfinished = TRUE; + const unsigned char *c = st->cp + 1; + + while (unfinished) + { + /* end of line should never happen. + * we tolerate it */ + if (c == NULL || c[0] == '\0') + break; + else if (*c == '"' && !lastIsBackSlash) + unfinished = FALSE; + else + lastIsBackSlash = *c == '\\'; + + c++; + } + + st->cp = c; +} + +static void eatComment (lexingState * st) +{ + boolean unfinished = TRUE; + boolean lastIsStar = FALSE; + const unsigned char *c = st->cp + 2; + + while (unfinished) + { + /* we've reached the end of the line.. + * so we have to reload a line... */ + if (c == NULL || *c == '\0') + { + st->cp = fileReadLine (); + /* WOOPS... no more input... + * we return, next lexing read + * will be null and ok */ + if (st->cp == NULL) + return; + c = st->cp; + } + /* we've reached the end of the comment */ + else if (*c == '/' && lastIsStar) + unfinished = FALSE; + else + { + lastIsStar = '*' == *c; + c++; + } + } + + st->cp = c; +} + +static void readIdentifier (lexingState * st) +{ + const unsigned char *p; + vStringClear (st->name); + + /* first char is a simple letter */ + if (isAlpha (*st->cp) || *st->cp == '_') + vStringPut (st->name, (int) *st->cp); + + /* Go till you get identifier chars */ + for (p = st->cp + 1; isIdent (*p); p++) + vStringPut (st->name, (int) *p); + + st->cp = p; + + vStringTerminate (st->name); +} + +/* read the @something directives */ +static void readIdentifierObjcDirective (lexingState * st) +{ + const unsigned char *p; + vStringClear (st->name); + + /* first char is a simple letter */ + if (*st->cp == '@') + vStringPut (st->name, (int) *st->cp); + + /* Go till you get identifier chars */ + for (p = st->cp + 1; isIdent (*p); p++) + vStringPut (st->name, (int) *p); + + st->cp = p; + + vStringTerminate (st->name); +} + +/* The lexer is in charge of reading the file. + * Some of sub-lexer (like eatComment) also read file. + * lexing is finished when the lexer return Tok_EOF */ +static objcKeyword lex (lexingState * st) +{ + int retType; + + /* handling data input here */ + while (st->cp == NULL || st->cp[0] == '\0') + { + st->cp = fileReadLine (); + if (st->cp == NULL) + return Tok_EOF; + + return Tok_EOL; + } + + if (isAlpha (*st->cp)) + { + readIdentifier (st); + retType = lookupKeyword (vStringValue (st->name), Lang_ObjectiveC); + + if (retType == -1) /* If it's not a keyword */ + { + return ObjcIDENTIFIER; + } + else + { + return retType; + } + } + else if (*st->cp == '@') + { + readIdentifierObjcDirective (st); + retType = lookupKeyword (vStringValue (st->name), Lang_ObjectiveC); + + if (retType == -1) /* If it's not a keyword */ + { + return Tok_any; + } + else + { + return retType; + } + } + else if (isSpace (*st->cp)) + { + eatWhiteSpace (st); + return lex (st); + } + else + switch (*st->cp) + { + case '(': + st->cp++; + return Tok_PARL; + + case '\\': + st->cp++; + return Tok_Backslash; + + case '#': + st->cp++; + return Tok_Sharp; + + case '/': + if (st->cp[1] == '*') /* ergl, a comment */ + { + eatComment (st); + return lex (st); + } + else if (st->cp[1] == '/') + { + st->cp = NULL; + return lex (st); + } + else + { + st->cp++; + return Tok_any; + } + break; + + case ')': + st->cp++; + return Tok_PARR; + case '{': + st->cp++; + return Tok_CurlL; + case '}': + st->cp++; + return Tok_CurlR; + case '[': + st->cp++; + return Tok_SQUAREL; + case ']': + st->cp++; + return Tok_SQUARER; + case ',': + st->cp++; + return Tok_COMA; + case ';': + st->cp++; + return Tok_semi; + case ':': + st->cp++; + return Tok_dpoint; + case '"': + eatString (st); + return Tok_any; + case '+': + st->cp++; + return Tok_PLUS; + case '-': + st->cp++; + return Tok_MINUS; + + default: + st->cp++; + break; + } + + /* default return if nothing is recognized, + * shouldn't happen, but at least, it will + * be handled without destroying the parsing. */ + return Tok_any; +} + +/*////////////////////////////////////////////////////////////////////// +//// Parsing */ +typedef void (*parseNext) (vString * const ident, objcToken what); + +/********** Helpers */ +/* This variable hold the 'parser' which is going to + * handle the next token */ +parseNext toDoNext; + +/* Special variable used by parser eater to + * determine which action to put after their + * job is finished. */ +parseNext comeAfter; + +/* Used by some parsers detecting certain token + * to revert to previous parser. */ +parseNext fallback; + + +/********** Grammar */ +static void globalScope (vString * const ident, objcToken what); +static void parseMethods (vString * const ident, objcToken what); +static void parseImplemMethods (vString * const ident, objcToken what); +static vString *tempName = NULL; +static vString *parentName = NULL; +static objcKind parentType = K_INTERFACE; + +/* used to prepare tag for OCaml, just in case their is a need to + * add additional information to the tag. */ +static void prepareTag (tagEntryInfo * tag, vString const *name, objcKind kind) +{ + initTagEntry (tag, vStringValue (name)); + tag->kindName = ObjcKinds[kind].name; + tag->kind = ObjcKinds[kind].letter; + + if (parentName != NULL) + { + tag->extensionFields.scope[0] = ObjcKinds[parentType].name; + tag->extensionFields.scope[1] = vStringValue (parentName); + } +} + +void pushEnclosingContext (const vString * parent, objcKind type) +{ + vStringCopy (parentName, parent); + parentType = type; +} + +void popEnclosingContext (void) +{ + vStringClear (parentName); +} + +/* Used to centralise tag creation, and be able to add + * more information to it in the future */ +static void addTag (vString * const ident, int kind) +{ + tagEntryInfo toCreate; + prepareTag (&toCreate, ident, kind); + makeTagEntry (&toCreate); +} + +objcToken waitedToken, fallBackToken; + +/* Ignore everything till waitedToken and jump to comeAfter. + * If the "end" keyword is encountered break, doesn't remember + * why though. */ +static void tillToken (vString * const UNUSED (ident), objcToken what) +{ + if (what == waitedToken) + toDoNext = comeAfter; +} + +static void tillTokenOrFallBack (vString * const UNUSED (ident), objcToken what) +{ + if (what == waitedToken) + toDoNext = comeAfter; + else if (what == fallBackToken) + { + toDoNext = fallback; + } +} + +static void ignoreBalanced (vString * const UNUSED (ident), objcToken what) +{ + static int count = 0; + + switch (what) + { + case Tok_PARL: + case Tok_CurlL: + case Tok_SQUAREL: + count++; + break; + + case Tok_PARR: + case Tok_CurlR: + case Tok_SQUARER: + count--; + break; + + default: + /* don't care */ + break; + } + + if (count == 0) + toDoNext = comeAfter; +} + +static void parseFields (vString * const ident, objcToken what) +{ + switch (what) + { + case Tok_CurlR: + toDoNext = &parseMethods; + break; + + case Tok_SQUAREL: + case Tok_PARL: + toDoNext = &ignoreBalanced; + comeAfter = &parseFields; + break; + + /* we got an identifier, keep track of it */ + case ObjcIDENTIFIER: + vStringCopy (tempName, ident); + break; + + /* our last kept identifier must be our variable name =) */ + case Tok_semi: + addTag (tempName, K_FIELD); + vStringClear (tempName); + break; + + default: + /* NOTHING */ + break; + } +} + +objcKind methodKind; + + +static vString *fullMethodName; +static vString *prevIdent; + +static void parseMethodsName (vString * const ident, objcToken what) +{ + switch (what) + { + case Tok_PARL: + toDoNext = &tillToken; + comeAfter = &parseMethodsName; + waitedToken = Tok_PARR; + break; + + case Tok_dpoint: + vStringCat (fullMethodName, prevIdent); + vStringCatS (fullMethodName, ":"); + vStringClear (prevIdent); + break; + + case ObjcIDENTIFIER: + vStringCopy (prevIdent, ident); + break; + + case Tok_CurlL: + case Tok_semi: + /* method name is not simple */ + if (vStringLength (fullMethodName) != '\0') + { + addTag (fullMethodName, methodKind); + vStringClear (fullMethodName); + } + else + addTag (prevIdent, methodKind); + + toDoNext = &parseMethods; + parseImplemMethods (ident, what); + vStringClear (prevIdent); + break; + + default: + break; + } +} + +static void parseMethodsImplemName (vString * const ident, objcToken what) +{ + switch (what) + { + case Tok_PARL: + toDoNext = &tillToken; + comeAfter = &parseMethodsImplemName; + waitedToken = Tok_PARR; + break; + + case Tok_dpoint: + vStringCat (fullMethodName, prevIdent); + vStringCatS (fullMethodName, ":"); + vStringClear (prevIdent); + break; + + case ObjcIDENTIFIER: + vStringCopy (prevIdent, ident); + break; + + case Tok_CurlL: + case Tok_semi: + /* method name is not simple */ + if (vStringLength (fullMethodName) != '\0') + { + addTag (fullMethodName, methodKind); + vStringClear (fullMethodName); + } + else + addTag (prevIdent, methodKind); + + toDoNext = &parseImplemMethods; + parseImplemMethods (ident, what); + vStringClear (prevIdent); + break; + + default: + break; + } +} + +static void parseImplemMethods (vString * const ident, objcToken what) +{ + switch (what) + { + case Tok_PLUS: /* + */ + toDoNext = &parseMethodsImplemName; + methodKind = K_CLASSMETHOD; + break; + + case Tok_MINUS: /* - */ + toDoNext = &parseMethodsImplemName; + methodKind = K_METHOD; + break; + + case ObjcEND: /* @end */ + popEnclosingContext (); + toDoNext = &globalScope; + break; + + case Tok_CurlL: /* { */ + toDoNext = &ignoreBalanced; + ignoreBalanced (ident, what); + comeAfter = &parseImplemMethods; + break; + + default: + break; + } +} + +static void parseProperty (vString * const ident, objcToken what) +{ + switch (what) + { + case Tok_PARL: + toDoNext = &tillToken; + comeAfter = &parseProperty; + waitedToken = Tok_PARR; + break; + + /* we got an identifier, keep track of it */ + case ObjcIDENTIFIER: + vStringCopy (tempName, ident); + break; + + /* our last kept identifier must be our variable name =) */ + case Tok_semi: + addTag (tempName, K_PROPERTY); + vStringClear (tempName); + break; + + default: + break; + } +} + +static void parseMethods (vString * const UNUSED (ident), objcToken what) +{ + switch (what) + { + case Tok_PLUS: /* + */ + toDoNext = &parseMethodsName; + methodKind = K_CLASSMETHOD; + break; + + case Tok_MINUS: /* - */ + toDoNext = &parseMethodsName; + methodKind = K_METHOD; + break; + + case ObjcPROPERTY: + toDoNext = &parseProperty; + break; + + case ObjcEND: /* @end */ + popEnclosingContext (); + toDoNext = &globalScope; + break; + + case Tok_CurlL: /* { */ + toDoNext = &parseFields; + break; + + default: + break; + } +} + + +static void parseProtocol (vString * const ident, objcToken what) +{ + if (what == ObjcIDENTIFIER) + { + pushEnclosingContext (ident, K_PROTOCOL); + addTag (ident, K_PROTOCOL); + } + toDoNext = &parseMethods; +} + +static void parseImplementation (vString * const ident, objcToken what) +{ + if (what == ObjcIDENTIFIER) + { + addTag (ident, K_IMPLEMENTATION); + pushEnclosingContext (ident, K_IMPLEMENTATION); + } + toDoNext = &parseImplemMethods; +} + +static void parseInterface (vString * const ident, objcToken what) +{ + if (what == ObjcIDENTIFIER) + { + addTag (ident, K_INTERFACE); + pushEnclosingContext (ident, K_INTERFACE); + } + + toDoNext = &parseMethods; +} + +static void parseStructMembers (vString * const ident, objcToken what) +{ + static parseNext prev = NULL; + + if (prev != NULL) + { + comeAfter = prev; + prev = NULL; + } + + switch (what) + { + case ObjcIDENTIFIER: + vStringCopy (tempName, ident); + break; + + case Tok_semi: /* ';' */ + addTag (tempName, K_FIELD); + vStringClear (tempName); + break; + + /* some types are complex, the only one + * we will loose is the function type. */ + case Tok_CurlL: /* '{' */ + case Tok_PARL: /* '(' */ + case Tok_SQUAREL: /* '[' */ + toDoNext = &ignoreBalanced; + prev = comeAfter; + comeAfter = &parseStructMembers; + ignoreBalanced (ident, what); + break; + + case Tok_CurlR: + toDoNext = comeAfter; + break; + + default: + /* don't care */ + break; + } +} + +/* Called just after the struct keyword */ +static void parseStruct (vString * const ident, objcToken what) +{ + static boolean gotName = FALSE; + + switch (what) + { + case ObjcIDENTIFIER: + if (!gotName) + { + addTag (ident, K_STRUCT); + pushEnclosingContext (ident, K_STRUCT); + gotName = TRUE; + } + else + { + gotName = FALSE; + popEnclosingContext (); + toDoNext = comeAfter; + comeAfter (ident, what); + } + break; + + case Tok_CurlL: + toDoNext = &parseStructMembers; + break; + + /* maybe it was just a forward declaration + * in which case, we pop the context */ + case Tok_semi: + if (gotName) + popEnclosingContext (); + + toDoNext = comeAfter; + comeAfter (ident, what); + break; + + default: + /* we don't care */ + break; + } +} + +/* Parse enumeration members, ignoring potential initialization */ +static void parseEnumFields (vString * const ident, objcToken what) +{ + static parseNext prev = NULL; + + if (prev != NULL) + { + comeAfter = prev; + prev = NULL; + } + + switch (what) + { + case ObjcIDENTIFIER: + addTag (ident, K_ENUM); + prev = comeAfter; + waitedToken = Tok_COMA; + /* last item might not have a coma */ + fallBackToken = Tok_CurlR; + fallback = comeAfter; + comeAfter = parseEnumFields; + toDoNext = &tillTokenOrFallBack; + break; + + case Tok_CurlR: + toDoNext = comeAfter; + popEnclosingContext (); + break; + + default: + /* don't care */ + break; + } +} + +/* parse enum ... { ... */ +static void parseEnum (vString * const ident, objcToken what) +{ + static boolean named = FALSE; + + switch (what) + { + case ObjcIDENTIFIER: + if (!named) + { + addTag (ident, K_ENUM); + pushEnclosingContext (ident, K_ENUM); + named = TRUE; + } + else + { + named = FALSE; + popEnclosingContext (); + toDoNext = comeAfter; + comeAfter (ident, what); + } + break; + + case Tok_CurlL: /* '{' */ + toDoNext = &parseEnumFields; + named = FALSE; + break; + + case Tok_semi: /* ';' */ + if (named) + popEnclosingContext (); + toDoNext = comeAfter; + comeAfter (ident, what); + break; + + default: + /* don't care */ + break; + } +} + +/* Parse something like + * typedef .... ident ; + * ignoring the defined type but in the case of struct, + * in which case struct are parsed. + */ +static void parseTypedef (vString * const ident, objcToken what) +{ + switch (what) + { + case ObjcSTRUCT: + toDoNext = &parseStruct; + comeAfter = &parseTypedef; + break; + + case ObjcENUM: + toDoNext = &parseEnum; + comeAfter = &parseTypedef; + break; + + case ObjcIDENTIFIER: + vStringCopy (tempName, ident); + break; + + case Tok_semi: /* ';' */ + addTag (tempName, K_TYPEDEF); + vStringClear (tempName); + toDoNext = &globalScope; + break; + + default: + /* we don't care */ + break; + } +} + +static void ignorePreprocStuff (vString * const UNUSED (ident), objcToken what) +{ + static boolean escaped = FALSE; + + switch (what) + { + case Tok_Backslash: + escaped = TRUE; + break; + + case Tok_EOL: + if (escaped) + { + escaped = FALSE; + } + else + { + toDoNext = &globalScope; + } + break; + + default: + escaped = FALSE; + break; + } +} + +static void parseMacroName (vString * const ident, objcToken what) +{ + if (what == ObjcIDENTIFIER) + addTag (ident, K_MACRO); + + toDoNext = &ignorePreprocStuff; +} + +static void parsePreproc (vString * const ident, objcToken what) +{ + switch (what) + { + case ObjcIDENTIFIER: + if (strcmp (vStringValue (ident), "define") == 0) + toDoNext = &parseMacroName; + else + toDoNext = &ignorePreprocStuff; + break; + + default: + toDoNext = &ignorePreprocStuff; + break; + } +} + +/* Handle the "strong" top levels, all 'big' declarations + * happen here */ +static void globalScope (vString * const ident, objcToken what) +{ + switch (what) + { + case Tok_Sharp: + toDoNext = &parsePreproc; + break; + + case ObjcSTRUCT: + toDoNext = &parseStruct; + comeAfter = &globalScope; + break; + + case ObjcIDENTIFIER: + /* we keep track of the identifier if we + * come across a function. */ + vStringCopy (tempName, ident); + break; + + case Tok_PARL: + /* if we find an opening parenthesis it means we + * found a function (or a macro...) */ + addTag (tempName, K_FUNCTION); + vStringClear (tempName); + comeAfter = &globalScope; + toDoNext = &ignoreBalanced; + ignoreBalanced (ident, what); + break; + + case ObjcINTERFACE: + toDoNext = &parseInterface; + break; + + case ObjcIMPLEMENTATION: + toDoNext = &parseImplementation; + break; + + case ObjcPROTOCOL: + toDoNext = &parseProtocol; + break; + + case ObjcTYPEDEF: + toDoNext = parseTypedef; + comeAfter = &globalScope; + break; + + case Tok_CurlL: + comeAfter = &globalScope; + toDoNext = &ignoreBalanced; + ignoreBalanced (ident, what); + break; + + case ObjcEND: + case ObjcPUBLIC: + case ObjcPROTECTED: + case ObjcPRIVATE: + + default: + /* we don't care */ + break; + } +} + +/*//////////////////////////////////////////////////////////////// +//// Deal with the system */ + +static void findObjcTags (void) +{ + vString *name = vStringNew (); + lexingState st; + objcToken tok; + + parentName = vStringNew (); + tempName = vStringNew (); + fullMethodName = vStringNew (); + prevIdent = vStringNew (); + + st.name = vStringNew (); + st.cp = fileReadLine (); + toDoNext = &globalScope; + tok = lex (&st); + while (tok != Tok_EOF) + { + (*toDoNext) (st.name, tok); + tok = lex (&st); + } + + vStringDelete (name); + vStringDelete (parentName); + vStringDelete (tempName); + vStringDelete (fullMethodName); + vStringDelete (prevIdent); + parentName = NULL; + tempName = NULL; + prevIdent = NULL; + fullMethodName = NULL; +} + +static void objcInitialize (const langType language) +{ + Lang_ObjectiveC = language; + + initKeywordHash (); +} + +extern parserDefinition *ObjcParser (void) +{ + static const char *const extensions[] = { "m", "h", NULL }; + parserDefinition *def = parserNew ("ObjectiveC"); + def->kinds = ObjcKinds; + def->kindCount = KIND_COUNT (ObjcKinds); + def->extensions = extensions; + def->parser = findObjcTags; + def->initialize = objcInitialize; + + return def; +} diff --git a/tagmanager/parsers.h b/tagmanager/parsers.h index d802f5a0c..1773d302c 100644 --- a/tagmanager/parsers.h +++ b/tagmanager/parsers.h @@ -56,7 +56,8 @@ AbcParser, \ VerilogParser, \ RParser, \ - CobolParser + CobolParser, \ + ObjcParser /* langType of each parser 0 CParser @@ -101,6 +102,7 @@ langType of each parser 39 Verilog 40 RParser 41 CobolParser +42 ObjcParser */ #endif /* _PARSERS_H */ diff --git a/wscript b/wscript index bcc23a8ca..d5149f5ac 100644 --- a/wscript +++ b/wscript @@ -66,7 +66,7 @@ tagmanager_sources = set([ 'tagmanager/haskell.c', 'tagmanager/haxe.c', 'tagmanager/html.c', 'tagmanager/js.c', 'tagmanager/keyword.c', 'tagmanager/latex.c', 'tagmanager/lregex.c', 'tagmanager/lua.c', 'tagmanager/make.c', 'tagmanager/markdown.c', 'tagmanager/matlab.c', 'tagmanager/nsis.c', - 'tagmanager/nestlevel.c', 'tagmanager/options.c', + 'tagmanager/nestlevel.c', 'tagmanager/objc.c', 'tagmanager/options.c', 'tagmanager/parse.c', 'tagmanager/pascal.c', 'tagmanager/r.c', 'tagmanager/perl.c', 'tagmanager/php.c', 'tagmanager/python.c', 'tagmanager/read.c', 'tagmanager/rest.c', 'tagmanager/ruby.c', 'tagmanager/sh.c', 'tagmanager/sort.c', -- 2.11.4.GIT