# Revision 816 Index: sql.c =================================================================== --- sql.c (.../tags/ctags-5.8) +++ sql.c (.../trunk) @@ -65,9 +65,14 @@ KEYWORD_end, KEYWORD_function, KEYWORD_if, + KEYWORD_else, + KEYWORD_elseif, + KEYWORD_endif, KEYWORD_loop, + KEYWORD_while, KEYWORD_case, KEYWORD_for, + KEYWORD_do, KEYWORD_call, KEYWORD_package, KEYWORD_pragma, @@ -114,6 +119,7 @@ KEYWORD_ml_conn_dnet, KEYWORD_ml_conn_java, KEYWORD_ml_conn_chk, + KEYWORD_ml_prop, KEYWORD_local, KEYWORD_temporary, KEYWORD_drop, @@ -140,6 +146,7 @@ TOKEN_BLOCK_LABEL_END, TOKEN_CHARACTER, TOKEN_CLOSE_PAREN, + TOKEN_COLON, TOKEN_SEMICOLON, TOKEN_COMMA, TOKEN_IDENTIFIER, @@ -154,7 +161,8 @@ TOKEN_OPEN_SQUARE, TOKEN_CLOSE_SQUARE, TOKEN_TILDE, - TOKEN_FORWARD_SLASH + TOKEN_FORWARD_SLASH, + TOKEN_EQUAL } tokenType; typedef struct sTokenInfoSQL { @@ -198,6 +206,7 @@ SQLTAG_SYNONYM, SQLTAG_MLTABLE, SQLTAG_MLCONN, + SQLTAG_MLPROP, SQLTAG_COUNT } sqlKind; @@ -223,7 +232,8 @@ { TRUE, 'V', "view", "views" }, { TRUE, 'n', "synonym", "synonyms" }, { TRUE, 'x', "mltable", "MobiLink Table Scripts" }, - { TRUE, 'y', "mlconn", "MobiLink Conn Scripts" } + { TRUE, 'y', "mlconn", "MobiLink Conn Scripts" }, + { TRUE, 'z', "mlprop", "MobiLink Properties " } }; static const keywordDesc SqlKeywordTable [] = { @@ -237,9 +247,14 @@ { "end", KEYWORD_end }, { "function", KEYWORD_function }, { "if", KEYWORD_if }, + { "else", KEYWORD_else }, + { "elseif", KEYWORD_elseif }, + { "endif", KEYWORD_endif }, { "loop", KEYWORD_loop }, + { "while", KEYWORD_while }, { "case", KEYWORD_case }, { "for", KEYWORD_for }, + { "do", KEYWORD_do }, { "call", KEYWORD_call }, { "package", KEYWORD_package }, { "pragma", KEYWORD_pragma }, @@ -286,6 +301,7 @@ { "ml_add_dnet_connection_script", KEYWORD_ml_conn_dnet }, { "ml_add_java_connection_script", KEYWORD_ml_conn_java }, { "ml_add_lang_conn_script_chk", KEYWORD_ml_conn_chk }, + { "ml_add_property", KEYWORD_ml_prop }, { "local", KEYWORD_local }, { "temporary", KEYWORD_temporary }, { "drop", KEYWORD_drop }, @@ -303,6 +319,7 @@ /* Recursive calls */ static void parseBlock (tokenInfo *const token, const boolean local); +static void parseDeclare (tokenInfo *const token, const boolean local); static void parseKeywords (tokenInfo *const token); static void parseSqlFile (tokenInfo *const token); @@ -541,6 +558,7 @@ case EOF: longjmp (Exception, (int)ExceptionEOF); break; case '(': token->type = TOKEN_OPEN_PAREN; break; case ')': token->type = TOKEN_CLOSE_PAREN; break; + case ':': token->type = TOKEN_COLON; break; case ';': token->type = TOKEN_SEMICOLON; break; case '.': token->type = TOKEN_PERIOD; break; case ',': token->type = TOKEN_COMMA; break; @@ -549,6 +567,7 @@ case '~': token->type = TOKEN_TILDE; break; case '[': token->type = TOKEN_OPEN_SQUARE; break; case ']': token->type = TOKEN_CLOSE_SQUARE; break; + case '=': token->type = TOKEN_EQUAL; break; case '\'': case '"': @@ -764,6 +783,16 @@ } } +static void copyToken (tokenInfo *const dest, tokenInfo *const src) +{ + dest->lineNumber = src->lineNumber; + dest->filePosition = src->filePosition; + dest->type = src->type; + dest->keyword = src->keyword; + vStringCopy(dest->string, src->string); + vStringCopy(dest->scope, src->scope); +} + static void skipArgumentList (tokenInfo *const token) { /* @@ -782,6 +811,7 @@ static void parseSubProgram (tokenInfo *const token) { tokenInfo *const name = newToken (); + vString * saveScope = vStringNew (); /* * This must handle both prototypes and the body of @@ -821,17 +851,45 @@ * * RETURN @name; * END; + * + * Note, a Package adds scope to the items within. + * create or replace package demo_pkg is + * test_var number; + * function test_func return varchar2; + * function more.test_func2 return varchar2; + * end demo_pkg; + * So the tags generated here, contain the package name: + * demo_pkg.test_var + * demo_pkg.test_func + * demo_pkg.more.test_func2 */ const sqlKind kind = isKeyword (token, KEYWORD_function) ? SQLTAG_FUNCTION : SQLTAG_PROCEDURE; Assert (isKeyword (token, KEYWORD_function) || isKeyword (token, KEYWORD_procedure)); - readToken (name); + + vStringCopy(saveScope, token->scope); readToken (token); + copyToken (name, token); + readToken (token); + if (isType (token, TOKEN_PERIOD)) { - readToken (name); + /* + * If this is an Oracle package, then the token->scope should + * already be set. If this is the case, also add this value to the + * scope. + * If this is not an Oracle package, chances are the scope should be + * blank and the value just read is the OWNER or CREATOR of the + * function and should not be considered part of the scope. + */ + if ( vStringLength(saveScope) > 0 ) + { + addToScope(token, name->string); + } readToken (token); + copyToken (name, token); + readToken (token); } if (isType (token, TOKEN_OPEN_PAREN)) { @@ -870,6 +928,7 @@ isKeyword (token, KEYWORD_internal) || isKeyword (token, KEYWORD_external) || isKeyword (token, KEYWORD_url) || + isType (token, TOKEN_EQUAL) || isCmdTerm (token) ) ) @@ -900,6 +959,12 @@ vStringClear (token->scope); } + if ( isType (token, TOKEN_EQUAL) ) + readToken (token); + + if ( isKeyword (token, KEYWORD_declare) ) + parseDeclare (token, FALSE); + if (isKeyword (token, KEYWORD_is) || isKeyword (token, KEYWORD_begin) ) { @@ -914,7 +979,9 @@ vStringClear (token->scope); } } + vStringCopy(token->scope, saveScope); deleteToken (name); + vStringDelete(saveScope); } static void parseRecord (tokenInfo *const token) @@ -1066,18 +1133,18 @@ case KEYWORD_type: parseType (token); break; default: - if (isType (token, TOKEN_IDENTIFIER)) - { - if (local) - { - makeSqlTag (token, SQLTAG_LOCAL_VARIABLE); - } - else - { - makeSqlTag (token, SQLTAG_VARIABLE); - } - } - break; + if (isType (token, TOKEN_IDENTIFIER)) + { + if (local) + { + makeSqlTag (token, SQLTAG_LOCAL_VARIABLE); + } + else + { + makeSqlTag (token, SQLTAG_VARIABLE); + } + } + break; } findToken (token, TOKEN_SEMICOLON); readToken (token); @@ -1164,12 +1231,13 @@ } } -static void parseStatements (tokenInfo *const token) +static void parseStatements (tokenInfo *const token, const boolean exit_on_endif ) { - boolean isAnsi = TRUE; + /* boolean isAnsi = TRUE; */ boolean stmtTerm = FALSE; do { + if (isType (token, TOKEN_BLOCK_LABEL_BEGIN)) parseLabel (token); else @@ -1210,6 +1278,7 @@ */ while (! isKeyword (token, KEYWORD_then)) readToken (token); + readToken (token); continue; @@ -1220,6 +1289,15 @@ * IF...THEN * END IF; * + * IF...THEN + * ELSE + * END IF; + * + * IF...THEN + * ELSEIF...THEN + * ELSE + * END IF; + * * or non-ANSI * IF ... * BEGIN @@ -1233,7 +1311,7 @@ if( isKeyword (token, KEYWORD_begin ) ) { - isAnsi = FALSE; + /* isAnsi = FALSE; */ parseBlock(token, FALSE); /* @@ -1248,7 +1326,22 @@ else { readToken (token); - parseStatements (token); + + while( ! (isKeyword (token, KEYWORD_end ) || + isKeyword (token, KEYWORD_endif ) ) + ) + { + if ( isKeyword (token, KEYWORD_else) || + isKeyword (token, KEYWORD_elseif) ) + readToken (token); + + parseStatements (token, TRUE); + + if ( isCmdTerm(token) ) + readToken (token); + + } + /* * parseStatements returns when it finds an END, an IF * should follow the END for ANSI anyway. @@ -1258,8 +1351,14 @@ if( isKeyword (token, KEYWORD_end ) ) readToken (token); - if( ! isKeyword (token, KEYWORD_if ) ) + if( isKeyword (token, KEYWORD_if ) || isKeyword (token, KEYWORD_endif ) ) { + readToken (token); + if ( isCmdTerm(token) ) + stmtTerm = TRUE; + } + else + { /* * Well we need to do something here. * There are lots of different END statements @@ -1284,14 +1383,64 @@ * END CASE; * * FOR loop_name AS cursor_name CURSOR FOR ... + * DO * END FOR; */ + if( isKeyword (token, KEYWORD_for ) ) + { + /* loop name */ + readToken (token); + /* AS */ + readToken (token); + + while ( ! isKeyword (token, KEYWORD_is) ) + { + /* + * If this is not an AS keyword this is + * not a proper FOR statement and should + * simply be ignored + */ + return; + } + + while ( ! isKeyword (token, KEYWORD_do) ) + readToken (token); + } + + readToken (token); - parseStatements (token); + while( ! isKeyword (token, KEYWORD_end ) ) + { + /* + if ( isKeyword (token, KEYWORD_else) || + isKeyword (token, KEYWORD_elseif) ) + readToken (token); + */ + parseStatements (token, FALSE); + + if ( isCmdTerm(token) ) + readToken (token); + } + + if( isKeyword (token, KEYWORD_end ) ) readToken (token); + /* + * Typically ended with + * END LOOP [loop name]; + * END CASE + * END FOR [loop name]; + */ + if ( isKeyword (token, KEYWORD_loop) || + isKeyword (token, KEYWORD_case) || + isKeyword (token, KEYWORD_for) ) + readToken (token); + + if ( isCmdTerm(token) ) + stmtTerm = TRUE; + break; case KEYWORD_create: @@ -1324,11 +1473,36 @@ * * So we must read to the first semi-colon or an END block */ - while ( ! stmtTerm && - ! ( isKeyword (token, KEYWORD_end) || - (isCmdTerm(token)) ) + while ( ! stmtTerm && + ! ( isKeyword (token, KEYWORD_end) || + (isCmdTerm(token)) ) ) { + if ( isKeyword (token, KEYWORD_endif) && + exit_on_endif ) + return; + + if (isType (token, TOKEN_COLON) ) + { + /* + * A : can signal a loop name + * myloop: + * LOOP + * LEAVE myloop; + * END LOOP; + * Unfortunately, labels do not have a + * cmd terminator, therefore we have to check + * if the next token is a keyword and process + * it accordingly. + */ + readToken (token); + if ( isKeyword (token, KEYWORD_loop) || + isKeyword (token, KEYWORD_while) || + isKeyword (token, KEYWORD_for) ) + /* parseStatements (token); */ + return; + } + readToken (token); if (isType (token, TOKEN_OPEN_PAREN) || @@ -1336,6 +1510,20 @@ isType (token, TOKEN_OPEN_SQUARE) ) skipToMatched (token); + /* + * Since we know how to parse various statements + * if we detect them, parse them to completion + */ + if (isType (token, TOKEN_BLOCK_LABEL_BEGIN) || + isKeyword (token, KEYWORD_exception) || + isKeyword (token, KEYWORD_loop) || + isKeyword (token, KEYWORD_case) || + isKeyword (token, KEYWORD_for) || + isKeyword (token, KEYWORD_begin) ) + parseStatements (token, FALSE); + else if (isKeyword (token, KEYWORD_if)) + parseStatements (token, TRUE); + } } /* @@ -1343,11 +1531,12 @@ * See comment above, now, only read if the current token * is not a command terminator. */ - if ( isCmdTerm(token) ) - { - readToken (token); - } - } while (! isKeyword (token, KEYWORD_end) && ! stmtTerm ); + if ( isCmdTerm(token) && ! stmtTerm ) + stmtTerm = TRUE; + + } while (! isKeyword (token, KEYWORD_end) && + ! (exit_on_endif && isKeyword (token, KEYWORD_endif) ) && + ! stmtTerm ); } static void parseBlock (tokenInfo *const token, const boolean local) @@ -1378,7 +1567,10 @@ token->begin_end_nest_lvl++; while (! isKeyword (token, KEYWORD_end)) { - parseStatements (token); + parseStatements (token, FALSE); + + if ( isCmdTerm(token) ) + readToken (token); } token->begin_end_nest_lvl--; @@ -1415,7 +1607,7 @@ * or by specifying a package body * CREATE OR REPLACE PACKAGE BODY pkg_name AS * CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS - */ + */ tokenInfo *const name = newToken (); readToken (name); if (isKeyword (name, KEYWORD_body)) @@ -1440,7 +1632,9 @@ if (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING)) makeSqlTag (name, SQLTAG_PACKAGE); + addToScope (token, name->string); parseBlock (token, FALSE); + vStringClear (token->scope); } findCmdTerm (token, FALSE); deleteToken (name); @@ -1994,6 +2188,70 @@ deleteToken (event); } +static void parseMLProp (tokenInfo *const token) +{ + tokenInfo *const component = newToken (); + tokenInfo *const prop_set_name = newToken (); + tokenInfo *const prop_name = newToken (); + + /* + * This deals with these formats + * ml_add_property ( + * 'comp_name', + * 'prop_set_name', + * 'prop_name', + * 'prop_value' + * ) + */ + + readToken (token); + if ( isType (token, TOKEN_OPEN_PAREN) ) + { + readToken (component); + readToken (token); + while (!(isType (token, TOKEN_COMMA) || + isType (token, TOKEN_CLOSE_PAREN) + )) + { + readToken (token); + } + + if (isType (token, TOKEN_COMMA)) + { + readToken (prop_set_name); + readToken (token); + while (!(isType (token, TOKEN_COMMA) || + isType (token, TOKEN_CLOSE_PAREN) + )) + { + readToken (token); + } + + if (isType (token, TOKEN_COMMA)) + { + readToken (prop_name); + + if (isType (component, TOKEN_STRING) && + isType (prop_set_name, TOKEN_STRING) && + isType (prop_name, TOKEN_STRING) ) + { + addToScope(component, prop_set_name->string); + addToScope(component, prop_name->string); + makeSqlTag (component, SQLTAG_MLPROP); + } + } + if( !isType (token, TOKEN_CLOSE_PAREN) ) + findToken (token, TOKEN_CLOSE_PAREN); + } + } + + findCmdTerm (token, TRUE); + + deleteToken (component); + deleteToken (prop_set_name); + deleteToken (prop_name); +} + static void parseComment (tokenInfo *const token) { /* @@ -2039,7 +2297,7 @@ case KEYWORD_drop: parseDrop (token); break; case KEYWORD_event: parseEvent (token); break; case KEYWORD_function: parseSubProgram (token); break; - case KEYWORD_if: parseStatements (token); break; + case KEYWORD_if: parseStatements (token, FALSE); break; case KEYWORD_index: parseIndex (token); break; case KEYWORD_ml_table: parseMLTable (token); break; case KEYWORD_ml_table_lang: parseMLTable (token); break; @@ -2051,6 +2309,7 @@ case KEYWORD_ml_conn_dnet: parseMLConn (token); break; case KEYWORD_ml_conn_java: parseMLConn (token); break; case KEYWORD_ml_conn_chk: parseMLConn (token); break; + case KEYWORD_ml_prop: parseMLProp (token); break; case KEYWORD_package: parsePackage (token); break; case KEYWORD_procedure: parseSubProgram (token); break; case KEYWORD_publication: parsePublication (token); break; Index: tex.c =================================================================== --- tex.c (.../tags/ctags-5.8) +++ tex.c (.../trunk) @@ -2,6 +2,7 @@ * $Id: tex.c 666 2008-05-15 17:47:31Z dfishburn $ * * Copyright (c) 2008, David Fishburn + * Copyright (c) 2012, Jan Larres * * This source code is released for free distribution under the terms of the * GNU General Public License. @@ -47,13 +48,15 @@ */ typedef enum eKeywordId { KEYWORD_NONE = -1, + KEYWORD_part, KEYWORD_chapter, KEYWORD_section, KEYWORD_subsection, KEYWORD_subsubsection, - KEYWORD_part, KEYWORD_paragraph, - KEYWORD_subparagraph + KEYWORD_subparagraph, + KEYWORD_label, + KEYWORD_include } keywordId; /* Used to determine whether keyword is valid for the token language and @@ -68,27 +71,15 @@ TOKEN_UNDEFINED, TOKEN_CHARACTER, TOKEN_CLOSE_PAREN, - TOKEN_SEMICOLON, - TOKEN_COLON, TOKEN_COMMA, TOKEN_KEYWORD, TOKEN_OPEN_PAREN, - TOKEN_OPERATOR, TOKEN_IDENTIFIER, TOKEN_STRING, - TOKEN_PERIOD, TOKEN_OPEN_CURLY, TOKEN_CLOSE_CURLY, - TOKEN_EQUAL_SIGN, - TOKEN_EXCLAMATION, - TOKEN_FORWARD_SLASH, TOKEN_OPEN_SQUARE, TOKEN_CLOSE_SQUARE, - TOKEN_OPEN_MXML, - TOKEN_CLOSE_MXML, - TOKEN_CLOSE_SGML, - TOKEN_LESS_THAN, - TOKEN_GREATER_THAN, TOKEN_QUESTION_MARK, TOKEN_STAR } tokenType; @@ -110,36 +101,48 @@ static jmp_buf Exception; +static vString *lastPart; +static vString *lastChapter; +static vString *lastSection; +static vString *lastSubS; +static vString *lastSubSubS; + typedef enum { + TEXTAG_PART, TEXTAG_CHAPTER, TEXTAG_SECTION, TEXTAG_SUBSECTION, TEXTAG_SUBSUBSECTION, - TEXTAG_PART, TEXTAG_PARAGRAPH, TEXTAG_SUBPARAGRAPH, + TEXTAG_LABEL, + TEXTAG_INCLUDE, TEXTAG_COUNT } texKind; static kindOption TexKinds [] = { + { TRUE, 'p', "part", "parts" }, { TRUE, 'c', "chapter", "chapters" }, { TRUE, 's', "section", "sections" }, { TRUE, 'u', "subsection", "subsections" }, { TRUE, 'b', "subsubsection", "subsubsections" }, - { TRUE, 'p', "part", "parts" }, { TRUE, 'P', "paragraph", "paragraphs" }, - { TRUE, 'G', "subparagraph", "subparagraphs" } + { TRUE, 'G', "subparagraph", "subparagraphs" }, + { TRUE, 'l', "label", "labels" }, + { TRUE, 'i', "include", "includes" } }; static const keywordDesc TexKeywordTable [] = { /* keyword keyword ID */ + { "part", KEYWORD_part }, { "chapter", KEYWORD_chapter }, { "section", KEYWORD_section }, { "subsection", KEYWORD_subsection }, { "subsubsection", KEYWORD_subsubsection }, - { "part", KEYWORD_part }, { "paragraph", KEYWORD_paragraph }, - { "subparagraph", KEYWORD_subparagraph } + { "subparagraph", KEYWORD_subparagraph }, + { "label", KEYWORD_label }, + { "include", KEYWORD_include } }; /* @@ -149,8 +152,8 @@ static boolean isIdentChar (const int c) { return (boolean) - (isalpha (c) || isdigit (c) || c == '$' || - c == '_' || c == '#'); + (isalpha (c) || isdigit (c) || c == '$' || + c == '_' || c == '#' || c == '-' || c == '.' || c == ':'); } static void buildTexKeywordHash (void) @@ -186,15 +189,76 @@ eFree (token); } +static void getScopeInfo(texKind kind, vString *const parentKind, + vString *const parentName) +{ + int i; + + /* + * Put labels separately instead of under their scope. + * Is this The Right Thing To Do? + */ + if (kind >= TEXTAG_LABEL) { + return; + } + + /* + * This abuses the enum internals somewhat, but it should be ok in this + * case. + */ + for (i = kind - 1; i >= TEXTAG_PART; --i) { + if (i == TEXTAG_SUBSECTION && vStringLength(lastSubS) > 0) { + vStringCopyS(parentKind, "subsection"); + break; + } else if (i == TEXTAG_SECTION && vStringLength(lastSection) > 0) { + vStringCopyS(parentKind, "section"); + break; + } else if (i == TEXTAG_CHAPTER && vStringLength(lastChapter) > 0) { + vStringCopyS(parentKind, "chapter"); + break; + } else if (i == TEXTAG_PART && vStringLength(lastPart) > 0) { + vStringCopyS(parentKind, "part"); + break; + } + } + + /* + * Is '""' the best way to separate scopes? It has to be something that + * should ideally never occur in normal LaTeX text. + */ + for (i = TEXTAG_PART; i < (int)kind; ++i) { + if (i == TEXTAG_PART && vStringLength(lastPart) > 0) { + vStringCat(parentName, lastPart); + } else if (i == TEXTAG_CHAPTER && vStringLength(lastChapter) > 0) { + if (vStringLength(parentName) > 0) { + vStringCatS(parentName, "\"\""); + } + vStringCat(parentName, lastChapter); + } else if (i == TEXTAG_SECTION && vStringLength(lastSection) > 0) { + if (vStringLength(parentName) > 0) { + vStringCatS(parentName, "\"\""); + } + vStringCat(parentName, lastSection); + } else if (i == TEXTAG_SUBSECTION && vStringLength(lastSubS) > 0) { + if (vStringLength(parentName) > 0) { + vStringCatS(parentName, "\"\""); + } + vStringCat(parentName, lastSubS); + } + } +} + /* * Tag generation functions */ -static void makeConstTag (tokenInfo *const token, const texKind kind) +static void makeTexTag (tokenInfo *const token, texKind kind) { - if (TexKinds [kind].enabled ) + if (TexKinds [kind].enabled) { const char *const name = vStringValue (token->string); + vString *parentKind = vStringNew(); + vString *parentName = vStringNew(); tagEntryInfo e; initTagEntry (&e, name); @@ -203,60 +267,21 @@ e.kindName = TexKinds [kind].name; e.kind = TexKinds [kind].letter; + getScopeInfo(kind, parentKind, parentName); + if (vStringLength(parentKind) > 0) { + e.extensionFields.scope [0] = vStringValue(parentKind); + e.extensionFields.scope [1] = vStringValue(parentName); + } + makeTagEntry (&e); } } -static void makeTexTag (tokenInfo *const token, texKind kind) -{ - vString * fulltag; - - if (TexKinds [kind].enabled) - { - /* - * If a scope has been added to the token, change the token - * string to include the scope when making the tag. - */ - if ( vStringLength (token->scope) > 0 ) - { - fulltag = vStringNew (); - vStringCopy (fulltag, token->scope); - vStringCatS (fulltag, "."); - vStringCatS (fulltag, vStringValue (token->string)); - vStringTerminate (fulltag); - vStringCopy (token->string, fulltag); - vStringDelete (fulltag); - } - makeConstTag (token, kind); - } -} - /* * 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 == '\\') - { - c = fileGetc(); /* This maybe a ' or ". */ - vStringPut (string, c); - } - else if (c == delimiter) - end = TRUE; - else - vStringPut (string, c); - } - vStringTerminate (string); -} - -/* +/* * Read a C identifier beginning with "firstChar" and places it into * "name". */ @@ -297,26 +322,13 @@ case EOF: longjmp (Exception, (int)ExceptionEOF); break; case '(': token->type = TOKEN_OPEN_PAREN; break; case ')': token->type = TOKEN_CLOSE_PAREN; break; - case ';': token->type = TOKEN_SEMICOLON; break; case ',': token->type = TOKEN_COMMA; break; - case '.': token->type = TOKEN_PERIOD; break; - case ':': token->type = TOKEN_COLON; break; case '{': token->type = TOKEN_OPEN_CURLY; break; case '}': token->type = TOKEN_CLOSE_CURLY; break; - case '=': token->type = TOKEN_EQUAL_SIGN; break; case '[': token->type = TOKEN_OPEN_SQUARE; break; case ']': token->type = TOKEN_CLOSE_SQUARE; break; - case '?': token->type = TOKEN_QUESTION_MARK; break; case '*': token->type = TOKEN_STAR; break; - case '\'': - case '"': - token->type = TOKEN_STRING; - parseString (token->string, c); - token->lineNumber = getSourceLineNumber (); - token->filePosition = getInputFilePosition (); - break; - case '\\': /* * All Tex tags start with a backslash. @@ -427,7 +439,8 @@ readToken (token); while (! isType (token, TOKEN_CLOSE_CURLY) ) { - if (isType (token, TOKEN_IDENTIFIER) && useLongName) + /* if (isType (token, TOKEN_IDENTIFIER) && useLongName) */ + if (useLongName) { if (fullname->length > 0) vStringCatS (fullname, " "); @@ -435,7 +448,7 @@ } readToken (token); } - if (useLongName) + if (useLongName) { vStringTerminate (fullname); vStringCopy (name->string, fullname); @@ -443,6 +456,41 @@ } } + /* + * save the name of the last section definitions for scope-resolution + * later + */ + switch (kind) + { + case TEXTAG_PART: + vStringCopy(lastPart, fullname); + vStringClear(lastChapter); + vStringClear(lastSection); + vStringClear(lastSubS); + vStringClear(lastSubSubS); + break; + case TEXTAG_CHAPTER: + vStringCopy(lastChapter, fullname); + vStringClear(lastSection); + vStringClear(lastSubS); + vStringClear(lastSubSubS); + break; + case TEXTAG_SECTION: + vStringCopy(lastSection, fullname); + vStringClear(lastSubS); + vStringClear(lastSubSubS); + break; + case TEXTAG_SUBSECTION: + vStringCopy(lastSubS, fullname); + vStringClear(lastSubSubS); + break; + case TEXTAG_SUBSUBSECTION: + vStringCopy(lastSubSubS, fullname); + break; + default: + break; + } + deleteToken (name); vStringDelete (fullname); return TRUE; @@ -458,31 +506,37 @@ { switch (token->keyword) { - case KEYWORD_chapter: - parseTag (token, TEXTAG_CHAPTER); + case KEYWORD_part: + parseTag (token, TEXTAG_PART); break; - case KEYWORD_section: - parseTag (token, TEXTAG_SECTION); + case KEYWORD_chapter: + parseTag (token, TEXTAG_CHAPTER); break; - case KEYWORD_subsection: - parseTag (token, TEXTAG_SUBSUBSECTION); + case KEYWORD_section: + parseTag (token, TEXTAG_SECTION); break; - case KEYWORD_subsubsection: - parseTag (token, TEXTAG_SUBSUBSECTION); + case KEYWORD_subsection: + parseTag (token, TEXTAG_SUBSECTION); break; - case KEYWORD_part: - parseTag (token, TEXTAG_PART); + case KEYWORD_subsubsection: + parseTag (token, TEXTAG_SUBSUBSECTION); break; - case KEYWORD_paragraph: - parseTag (token, TEXTAG_PARAGRAPH); + case KEYWORD_paragraph: + parseTag (token, TEXTAG_PARAGRAPH); break; - case KEYWORD_subparagraph: - parseTag (token, TEXTAG_SUBPARAGRAPH); + case KEYWORD_subparagraph: + parseTag (token, TEXTAG_SUBPARAGRAPH); break; + case KEYWORD_label: + parseTag (token, TEXTAG_LABEL); + break; + case KEYWORD_include: + parseTag (token, TEXTAG_INCLUDE); + break; default: break; } - } + } } while (TRUE); } @@ -491,6 +545,12 @@ Assert (sizeof (TexKinds) / sizeof (TexKinds [0]) == TEXTAG_COUNT); Lang_js = language; buildTexKeywordHash (); + + lastPart = vStringNew(); + lastChapter = vStringNew(); + lastSection = vStringNew(); + lastSubS = vStringNew(); + lastSubSubS = vStringNew(); } static void findTexTags (void) @@ -497,7 +557,7 @@ { tokenInfo *const token = newToken (); exception_t exception; - + exception = (exception_t) (setjmp (Exception)); while (exception == ExceptionNone) parseTexFile (token); Index: entry.c =================================================================== --- entry.c (.../tags/ctags-5.8) +++ entry.c (.../trunk) @@ -772,6 +772,8 @@ boolean newlineTerminated; int length = 0; + if (line == NULL) + error (FATAL, "bad tag in %s", vStringValue (File.name)); if (tag->truncateLine) truncateTagLine (line, tag->name, FALSE); newlineTerminated = (boolean) (line [strlen (line) - 1] == '\n'); Index: read.c =================================================================== --- read.c (.../tags/ctags-5.8) +++ read.c (.../trunk) @@ -271,7 +271,6 @@ fgetpos (File.fp, &StartOfLine); fgetpos (File.fp, &File.filePosition); File.currentLine = NULL; - File.language = language; File.lineNumber = 0L; File.eof = FALSE; File.newLine = TRUE; Index: read.h =================================================================== --- read.h (.../tags/ctags-5.8) +++ read.h (.../trunk) @@ -77,7 +77,6 @@ int ungetch; /* a single character that was ungotten */ boolean eof; /* have we reached the end of file? */ boolean newLine; /* will the next character begin a new line? */ - langType language; /* language of input file */ /* Contains data pertaining to the original source file in which the tag * was defined. This may be different from the input file when #line Index: configure.ac =================================================================== --- configure.ac (.../tags/ctags-5.8) +++ configure.ac (.../trunk) @@ -248,6 +248,7 @@ AC_PROG_LN_S AC_CHECK_PROG(STRIP, strip, strip, :) +AC_SYS_LARGEFILE # Checks for operating environment Index: parsers.h =================================================================== --- parsers.h (.../tags/ctags-5.8) +++ parsers.h (.../trunk) @@ -38,6 +38,7 @@ LuaParser, \ MakefileParser, \ MatLabParser, \ + ObjcParser , \ OcamlParser, \ PascalParser, \ PerlParser, \ Index: php.c =================================================================== --- php.c (.../tags/ctags-5.8) +++ php.c (.../trunk) @@ -64,18 +64,18 @@ static void installPHPRegex (const langType language) { - addTagRegex(language, "(^|[ \t])class[ \t]+([" ALPHA "_][" ALNUM "_]*)", - "\\2", "c,class,classes", NULL); - addTagRegex(language, "(^|[ \t])interface[ \t]+([" ALPHA "_][" ALNUM "_]*)", - "\\2", "i,interface,interfaces", NULL); - addTagRegex(language, "(^|[ \t])define[ \t]*\\([ \t]*['\"]?([" ALPHA "_][" ALNUM "_]*)", - "\\2", "d,define,constant definitions", NULL); - addTagRegex(language, "(^|[ \t])function[ \t]+&?[ \t]*([" ALPHA "_][" ALNUM "_]*)", - "\\2", "f,function,functions", NULL); - addTagRegex(language, "(^|[ \t])(\\$|::\\$|\\$this->)([" ALPHA "_][" ALNUM "_]*)[ \t]*=", + addTagRegex(language, "^[ \t]*((final|abstract)[ \t]+)*class[ \t]+([" ALPHA "_][" ALNUM "_]*)", + "\\3", "c,class,classes", NULL); + addTagRegex(language, "^[ \t]*interface[ \t]+([" ALPHA "_][" ALNUM "_]*)", + "\\1", "i,interface,interfaces", NULL); + addTagRegex(language, "^[ \t]*define[ \t]*\\([ \t]*['\"]?([" ALPHA "_][" ALNUM "_]*)", + "\\1", "d,define,constant definitions", NULL); + addTagRegex(language, "^[ \t]*((static|public|protected|private)[ \t]+)*function[ \t]+&?[ \t]*([" ALPHA "_][" ALNUM "_]*)", + "\\3", "f,function,functions", NULL); + addTagRegex(language, "^[ \t]*(\\$|::\\$|\\$this->)([" ALPHA "_][" ALNUM "_]*)[ \t]*=", + "\\2", "v,variable,variables", NULL); + addTagRegex(language, "^[ \t]*((var|public|protected|private|static)[ \t]+)+\\$([" ALPHA "_][" ALNUM "_]*)[ \t]*[=;]", "\\3", "v,variable,variables", NULL); - addTagRegex(language, "(^|[ \t])(var|public|protected|private|static)[ \t]+\\$([" ALPHA "_][" ALNUM "_]*)[ \t]*[=;]", - "\\3", "v,variable,variables", NULL); /* function regex is covered by PHP regex */ addTagRegex (language, "(^|[ \t])([A-Za-z0-9_]+)[ \t]*[=:][ \t]*function[ \t]*\\(", Index: NEWS =================================================================== Index: flex.c =================================================================== --- flex.c (.../tags/ctags-5.8) +++ flex.c (.../trunk) @@ -78,9 +78,12 @@ KEYWORD_static, KEYWORD_class, KEYWORD_id, + KEYWORD_name, KEYWORD_script, KEYWORD_cdata, - KEYWORD_mx + KEYWORD_mx, + KEYWORD_fx, + KEYWORD_override } keywordId; /* Used to determine whether keyword is valid for the token language and @@ -116,7 +119,8 @@ TOKEN_CLOSE_SGML, TOKEN_LESS_THAN, TOKEN_GREATER_THAN, - TOKEN_QUESTION_MARK + TOKEN_QUESTION_MARK, + TOKEN_OPEN_NAMESPACE } tokenType; typedef struct sTokenInfo { @@ -182,9 +186,12 @@ { "static", KEYWORD_static }, { "class", KEYWORD_class }, { "id", KEYWORD_id }, + { "name", KEYWORD_name }, { "script", KEYWORD_script }, { "cdata", KEYWORD_cdata }, - { "mx", KEYWORD_mx } + { "mx", KEYWORD_mx }, + { "fx", KEYWORD_fx }, + { "override", KEYWORD_override } }; /* @@ -196,6 +203,7 @@ static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent); static boolean parseLine (tokenInfo *const token); static boolean parseActionScript (tokenInfo *const token); +static boolean parseMXML (tokenInfo *const token); static boolean isIdentChar (const int c) { @@ -518,13 +526,15 @@ if ( (d != '!' ) && /* is this the start of a comment? */ (d != '/' ) && /* is this the start of a closing mx tag */ - (d != 'm' ) ) /* is this the start of a mx tag */ + (d != 'm' ) && /* is this the start of a mx tag */ + (d != 'f' ) && /* is this the start of a fx tag */ + (d != 's' ) ) /* is this the start of a spark tag */ { fileUngetc (d); token->type = TOKEN_LESS_THAN; token->lineNumber = getSourceLineNumber (); token->filePosition = getInputFilePosition (); - + break; } else { @@ -582,10 +592,10 @@ } } } - else if (d == 'm') + else if (d == 'm' || d == 'f' || d == 's' ) { int e = fileGetc (); - if ( e != 'x' ) /* continuing an mx tag */ + if ( (d == 'm' || d == 'f') && e != 'x' ) /* continuing an mx or fx tag */ { fileUngetc (e); fileUngetc (d); @@ -592,13 +602,14 @@ token->type = TOKEN_LESS_THAN; token->lineNumber = getSourceLineNumber (); token->filePosition = getInputFilePosition (); + break; } else { - if (e == 'x') + if ( (d == 'm' || d == 'f') && e == 'x' ) { int f = fileGetc (); - if ( f != ':' ) /* is this the start of a comment? */ + if ( f != ':' ) /* start of the tag */ { fileUngetc (f); fileUngetc (e); @@ -606,23 +617,38 @@ token->type = TOKEN_LESS_THAN; token->lineNumber = getSourceLineNumber (); token->filePosition = getInputFilePosition (); + break; } else { - if (f == ':') - { - token->type = TOKEN_OPEN_MXML; - token->lineNumber = getSourceLineNumber (); - token->filePosition = getInputFilePosition (); - } + token->type = TOKEN_OPEN_MXML; + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; } } + if ( d == 's' && e == ':') /* continuing a spark tag */ + { + token->type = TOKEN_OPEN_MXML; + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + fileUngetc (e); + fileUngetc (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } } } else if (d == '/') { int e = fileGetc (); - if ( e != 'm' ) /* continuing an mx tag */ + if ( !(e == 'm' || e == 'f' || e == 's' )) { fileUngetc (e); fileUngetc (d); @@ -629,11 +655,12 @@ token->type = TOKEN_LESS_THAN; token->lineNumber = getSourceLineNumber (); token->filePosition = getInputFilePosition (); + break; } else { int f = fileGetc (); - if ( f != 'x' ) /* continuing an mx tag */ + if ( (e == 'm' || e == 'f') && f != 'x' ) /* continuing an mx or fx tag */ { fileUngetc (f); fileUngetc (e); @@ -640,6 +667,7 @@ token->type = TOKEN_LESS_THAN; token->lineNumber = getSourceLineNumber (); token->filePosition = getInputFilePosition (); + break; } else { @@ -654,17 +682,32 @@ token->type = TOKEN_LESS_THAN; token->lineNumber = getSourceLineNumber (); token->filePosition = getInputFilePosition (); + break; } else { - if (g == ':') - { - token->type = TOKEN_CLOSE_MXML; - token->lineNumber = getSourceLineNumber (); - token->filePosition = getInputFilePosition (); - } + token->type = TOKEN_CLOSE_MXML; + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; } } + if ( e == 's' && f == ':') /* continuing a spark tag */ + { + token->type = TOKEN_CLOSE_MXML; + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + fileUngetc (f); + fileUngetc (e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } } } } @@ -1423,7 +1466,7 @@ boolean is_class = FALSE; boolean is_terminated = TRUE; boolean is_global = FALSE; - boolean is_prototype = FALSE; + /* boolean is_prototype = FALSE; */ vString * fulltag; vStringClear(saveScope); @@ -1571,7 +1614,7 @@ */ makeClassTag (name); is_class = TRUE; - is_prototype = TRUE; + /* is_prototype = TRUE; */ /* * There should a ".function_name" next. @@ -1982,10 +2025,81 @@ return TRUE; } +static boolean parseNamespace (tokenInfo *const token) +{ + /* + * If we have found a <, we know it is not a TOKEN_OPEN_MXML + * but it could potentially be a different namespace. + * This means it will also have a closing tag, which will + * mess up the parser if we do not properly recurse + * through these tags. + */ + + if (isType (token, TOKEN_LESS_THAN)) + { + readToken (token); + } + + /* + * Check if we have reached a other namespace tag + * + * or + * + * + */ + if (isType (token, TOKEN_IDENTIFIER)) + { + readToken (token); + if (isType (token, TOKEN_COLON)) + { + readToken (token); + if ( ! isType (token, TOKEN_IDENTIFIER)) + { + return TRUE; + } + } + else + { + return TRUE; + } + } + else + { + return TRUE; + } + + /* + * Confirmed we are inside a namespace tag, so + * process it until the close tag. + * + * But also check for new tags, which will either + * be recursive namespaces or MXML tags + */ + do + { + if (isType (token, TOKEN_LESS_THAN)) + { + parseNamespace (token); + readToken (token); + } + if (isType (token, TOKEN_OPEN_MXML)) + { + parseMXML (token); + } + else + { + readToken (token); + } + } while (! (isType (token, TOKEN_CLOSE_SGML) || isType (token, TOKEN_CLOSE_MXML)) ); + + return TRUE; +} + static boolean parseMXML (tokenInfo *const token) { tokenInfo *const name = newToken (); tokenInfo *const type = newToken (); + boolean inside_attributes = TRUE; /* * Detect the common statements, if, while, for, do, ... * This is necessary since the last statement within a block "{}" @@ -2059,21 +2173,44 @@ readToken (token); do { - if (isType (token, TOKEN_OPEN_MXML)) + if (isType (token, TOKEN_GREATER_THAN)) { - parseMXML (token); + inside_attributes = FALSE; } - else if (isKeyword (token, KEYWORD_id)) + if (isType (token, TOKEN_LESS_THAN)) { - /* = */ + parseNamespace (token); readToken (token); + } + else if (isType (token, TOKEN_OPEN_MXML)) + { + parseMXML (token); readToken (token); + } + else if (inside_attributes && (isKeyword (token, KEYWORD_id) || isKeyword (token, KEYWORD_name))) + { + if (vStringLength(name->string) == 0 ) + { + /* + * If we have already created the tag based on either "name" + * or "id" do not do it again. + */ + readToken (token); + readToken (token); - copyToken (name, token); - addToScope (name, type->string); - makeMXTag (name); + copyToken (name, token); + addToScope (name, type->string); + makeMXTag (name); + } + else + { + readToken (token); + } } - readToken (token); + else + { + readToken (token); + } } while (! (isType (token, TOKEN_CLOSE_SGML) || isType (token, TOKEN_CLOSE_MXML)) ); if (isType (token, TOKEN_CLOSE_MXML)) @@ -2154,6 +2291,33 @@ { if (isType(token, TOKEN_KEYWORD)) { + if (isKeyword (token, KEYWORD_private) || + isKeyword (token, KEYWORD_public) || + isKeyword (token, KEYWORD_override) ) + { + /* + * Methods can be defined as: + * private function f_name + * public override function f_name + * override private function f_name + * Ignore these keywords if present. + */ + readToken (token); + } + if (isKeyword (token, KEYWORD_private) || + isKeyword (token, KEYWORD_public) || + isKeyword (token, KEYWORD_override) ) + { + /* + * Methods can be defined as: + * private function f_name + * public override function f_name + * override private function f_name + * Ignore these keywords if present. + */ + readToken (token); + } + switch (token->keyword) { case KEYWORD_function: parseFunction (token); break; @@ -2178,11 +2342,14 @@ { parseMXML (token); } - if (isType (token, TOKEN_LESS_THAN)) + else if (isType (token, TOKEN_LESS_THAN)) { readToken (token); if (isType (token, TOKEN_QUESTION_MARK)) { + /* + * + */ readToken (token); while (! isType (token, TOKEN_QUESTION_MARK) ) { @@ -2190,6 +2357,19 @@ } readToken (token); } + else if (isKeyword (token, KEYWORD_NONE)) + { + /* + * This is a simple XML tag, read until the closing statement + * + * + */ + readToken (token); + while (! isType (token, TOKEN_GREATER_THAN) ) + { + readToken (token); + } + } } else { Index: verilog.c =================================================================== --- verilog.c (.../tags/ctags-5.8) +++ verilog.c (.../trunk) @@ -232,6 +232,7 @@ c = skipWhite (c); if (c == '=') { + c = skipWhite (vGetc ()); if (c == '{') skipPastMatch ("{}"); else Index: routines.c =================================================================== --- routines.c (.../tags/ctags-5.8) +++ routines.c (.../trunk) @@ -757,13 +757,13 @@ else if (cp [0] != PATH_SEPARATOR) cp = slashp; #endif - strcpy (cp, slashp + 3); + memmove (cp, slashp + 3, strlen (slashp + 3) + 1); slashp = cp; continue; } else if (slashp [2] == PATH_SEPARATOR || slashp [2] == '\0') { - strcpy (slashp, slashp + 2); + memmove (slashp, slashp + 2, strlen (slashp + 2) + 1); continue; } } Index: maintainer.mak =================================================================== Index: website/whatis.html =================================================================== Index: website/tools.html =================================================================== Index: website/desire.html =================================================================== Index: source.mak =================================================================== --- source.mak (.../tags/ctags-5.8) +++ source.mak (.../trunk) @@ -33,6 +33,7 @@ main.c \ make.c \ matlab.c \ + objc.c \ ocaml.c \ options.c \ parse.c \ @@ -95,6 +96,7 @@ main.$(OBJEXT) \ make.$(OBJEXT) \ matlab.$(OBJEXT) \ + objc.$(OBJEXT) \ ocaml.$(OBJEXT) \ options.$(OBJEXT) \ parse.$(OBJEXT) \ Index: sort.c =================================================================== --- sort.c (.../tags/ctags-5.8) +++ sort.c (.../trunk) @@ -109,7 +109,7 @@ if (fp != NULL) fclose (fp); if (msg == NULL) - error (FATAL | PERROR, cannotSort); + error (FATAL | PERROR, "%s", cannotSort); else error (FATAL, "%s: %s", msg, cannotSort); } Index: vim.c =================================================================== --- vim.c (.../tags/ctags-5.8) +++ vim.c (.../trunk) @@ -47,7 +47,8 @@ K_COMMAND, K_FUNCTION, K_MAP, - K_VARIABLE + K_VARIABLE, + K_FILENAME } vimKind; static kindOption VimKinds [] = { @@ -56,6 +57,7 @@ { TRUE, 'f', "function", "function definitions" }, { TRUE, 'm', "map", "maps" }, { TRUE, 'v', "variable", "variable definitions" }, + { TRUE, 'n', "filename", "vimball filename" }, }; /* @@ -226,6 +228,18 @@ return line; } +static const unsigned char * readVimballLine (void) +{ + const unsigned char *line; + + while ((line = fileReadLine ()) != NULL) + { + break; + } + + return line; +} + static void parseFunction (const unsigned char *line) { vString *name = vStringNew (); @@ -362,6 +376,17 @@ if ((int) *cp == '!') ++cp; + if ((int) *cp != ' ') + { + /* + * :command must be followed by a space. If it is not, it is + * not a valid command. + * Treat the line as processed and continue. + */ + cmdProcessed = TRUE; + goto cleanUp; + } + while (*cp && isspace ((int) *cp)) ++cp; } @@ -389,11 +414,15 @@ else if (*cp == '-') { /* - * Read until the next space which sparates options or the name + * Read until the next space which separates options or the name */ while (*cp && !isspace ((int) *cp)) ++cp; } + else + { + ++cp; + } } while ( *cp && !isalnum ((int) *cp) ); if ( ! *cp ) @@ -600,7 +629,6 @@ static void parseVimFile (const unsigned char *line) { boolean readNextLine = TRUE; - line = readVimLine(); while (line != NULL) { @@ -612,19 +640,105 @@ } } +static void parseVimBallFile (const unsigned char *line) +{ + vString *fname = vStringNew (); + const unsigned char *cp; + int file_line_count; + int i; + + /* + * Vimball Archives follow this format + * " Vimball Archiver comment + * UseVimball + * finish + * filename + * line count (n) for filename + * (n) lines + * filename + * line count (n) for filename + * (n) lines + * ... + */ + + /* Next line should be "finish" */ + line = readVimLine(); + if (line == NULL) + { + return; + } + while (line != NULL) + { + /* Next line should be a filename */ + line = readVimLine(); + if (line == NULL) + { + return; + } + else + { + cp = line; + do + { + vStringPut (fname, (int) *cp); + ++cp; + } while (isalnum ((int) *cp) || *cp == '.' || *cp == '/' || *cp == '\\'); + vStringTerminate (fname); + makeSimpleTag (fname, VimKinds, K_FILENAME); + vStringClear (fname); + } + + file_line_count = 0; + /* Next line should be the line count of the file */ + line = readVimLine(); + if (line == NULL) + { + return; + } + else + { + file_line_count = atoi( (const char *) line ); + } + + /* Read all lines of the file */ + for ( i=0; ikinds = VimKinds; def->kindCount = KIND_COUNT (VimKinds); Index: Test/bug2777310.js =================================================================== Index: Test/objectivec_property.h =================================================================== Index: Test/bug2959889.mak =================================================================== Index: Test/ocaml_onlystr.ml =================================================================== Index: Test/bug3571233.js =================================================================== Index: Test/bug2747828.v =================================================================== Index: Test/1850914.js =================================================================== Index: Test/objectivec_interface.h =================================================================== Index: Test/bug359.vim =================================================================== Index: Test/3548393.vim =================================================================== Index: Test/bug2886870.tex =================================================================== Index: Test/flex_override.mxml =================================================================== Index: Test/objectivec_protocol.h =================================================================== Index: Test/3184782.sql =================================================================== Index: Test/bug3032253.vim =================================================================== Index: Test/jsFunc_tutorial.js =================================================================== Index: Test/regexp.js =================================================================== Index: Test/tabindent.py =================================================================== Index: Test/bug2075402.py =================================================================== Index: Test/ocaml_empty.ml =================================================================== Index: Test/ocamlAllKinds.ml =================================================================== Index: Test/secondary_fcn_name.js =================================================================== Index: Test/ocaml_stringTsts.ml =================================================================== Index: Test/ocamlCommentInStringAllowed.ml =================================================================== Index: Test/1878155.js =================================================================== Index: Test/intro.tex =================================================================== Index: Test/simple.pl =================================================================== Index: Test/bug2888482.js =================================================================== Index: Test/simple.py =================================================================== Index: Test/2023624.js =================================================================== Index: Test/ui5.controller.js =================================================================== Index: Test/3214129.vim =================================================================== Index: Test/1880687.js =================================================================== Index: Test/bug3168705.py =================================================================== Index: Test/bug2961855.sql =================================================================== Index: Test/bug3036476.js =================================================================== Index: Test/3470609.js =================================================================== Index: Test/ingres_procedures.sql =================================================================== Index: Test/3526726.tex =================================================================== Index: Test/bug358.vim =================================================================== Index: Test/1795612.js =================================================================== Index: Test/objectivec_implementation.m =================================================================== Index: perl.c =================================================================== --- perl.c (.../tags/ctags-5.8) +++ perl.c (.../trunk) @@ -14,6 +14,7 @@ * INCLUDE FILES */ #include "general.h" /* must always come first */ +#include "debug.h" #include @@ -64,28 +65,34 @@ static boolean isPodWord (const char *word) { - boolean result = FALSE; - if (isalpha (*word)) - { - const char *const pods [] = { - "head1", "head2", "head3", "head4", "over", "item", "back", - "pod", "begin", "end", "for" - }; - const size_t count = sizeof (pods) / sizeof (pods [0]); - const char *white = strpbrk (word, " \t"); - const size_t len = (white!=NULL) ? (size_t)(white-word) : strlen (word); - char *const id = (char*) eMalloc (len + 1); - size_t i; - strncpy (id, word, len); - id [len] = '\0'; - for (i = 0 ; i < count && ! result ; ++i) - { - if (strcmp (id, pods [i]) == 0) - result = TRUE; - } - eFree (id); + /* Perl POD words are three to eight characters in size. We use this + * fact to find (or not find) the right side of the word and then + * perform comparisons, if necessary, of POD words of that size. + */ + size_t len; + for (len = 0; len < 9; ++len) + if ('\0' == word[len] || ' ' == word[len] || '\t' == word[len]) + break; + switch (len) { + case 3: + return 0 == strncmp(word, "end", 3) + || 0 == strncmp(word, "for", 3) + || 0 == strncmp(word, "pod", 3); + case 4: + return 0 == strncmp(word, "back", 4) + || 0 == strncmp(word, "item", 4) + || 0 == strncmp(word, "over", 4); + case 5: + return 0 == strncmp(word, "begin", 5) + || 0 == strncmp(word, "head1", 5) + || 0 == strncmp(word, "head2", 5) + || 0 == strncmp(word, "head3", 5) + || 0 == strncmp(word, "head4", 5); + case 8: + return 0 == strncmp(word, "encoding", 8); + default: + return FALSE; } - return result; } /* @@ -164,6 +171,98 @@ return FALSE; } +/* `end' points to the equal sign. Parse from right to left to get the + * identifier. Assume we're dealing with something of form \s*\w+\s*=> + */ +static void makeTagFromLeftSide (const char *begin, const char *end, + vString *name, vString *package) +{ + tagEntryInfo entry; + const char *b, *e; + for (e = end - 1; e > begin && isspace(*e); --e) + ; + if (e < begin) + return; + for (b = e; b >= begin && isIdentifier(*b); --b) + ; + /* Identifier must be either beginning of line of have some whitespace + * on its left: + */ + if (b < begin || isspace(*b) || ',' == *b) + ++b; + else if (b != begin) + return; + Assert(e - b + 1 > 0); + vStringClear(name); + vStringNCatS(name, b, e - b + 1); + initTagEntry(&entry, vStringValue(name)); + entry.kind = PerlKinds[K_CONSTANT].letter; + entry.kindName = PerlKinds[K_CONSTANT].name; + makeTagEntry(&entry); + if (Option.include.qualifiedTags && package && vStringLength(package)) { + vStringClear(name); + vStringCopy(name, package); + vStringNCatS(name, b, e - b + 1); + initTagEntry(&entry, vStringValue(name)); + entry.kind = PerlKinds[K_CONSTANT].letter; + entry.kindName = PerlKinds[K_CONSTANT].name; + makeTagEntry(&entry); + } +} + +enum const_state { CONST_STATE_NEXT_LINE, CONST_STATE_HIT_END }; + +/* Parse a single line, find as many NAME => VALUE pairs as we can and try + * to detect the end of the hashref. + */ +static enum const_state parseConstantsFromLine (const char *cp, + vString *name, vString *package) +{ + while (1) { + const size_t sz = strcspn(cp, "#}="); + switch (cp[sz]) { + case '=': + if (cp[sz + 1] && '>' == cp[sz + 1]) + makeTagFromLeftSide(cp, cp + sz, name, package); + break; + case '}': /* Assume this is the end of the hashref. */ + return CONST_STATE_HIT_END; + case '\0': /* End of the line. */ + case '#': /* Assume this is a comment and thus end of the line. */ + return CONST_STATE_NEXT_LINE; + } + cp += sz + 1; + } +} + +/* Parse constants declared via hash reference, like this: + * use constant { + * A => 1, + * B => 2, + * }; + * The approach we take is simplistic, but it covers the vast majority of + * cases well. There can be some false positives. + * Returns 0 if found the end of the hashref, -1 if we hit EOF + */ +static int parseConstantsFromHashRef (const unsigned char *cp, + vString *name, vString *package) +{ + while (1) { + enum const_state state = + parseConstantsFromLine((const char *) cp, name, package); + switch (state) { + case CONST_STATE_NEXT_LINE: + cp = fileReadLine(); + if (cp) + break; + else + return -1; + case CONST_STATE_HIT_END: + return 0; + } + } +} + /* Algorithm adapted from from GNU etags. * Perl support by Bart Robinson * Perl sub names: look for /^ [ \t\n]sub [ \t\n]+ [^ \t\n{ (]+/ @@ -175,6 +274,18 @@ boolean skipPodDoc = FALSE; const unsigned char *line; + /* Core modules AutoLoader and SelfLoader support delayed compilation + * by allowing Perl code that follows __END__ and __DATA__ tokens, + * respectively. When we detect that one of these modules is used + * in the file, we continue processing even after we see the + * corresponding token that would usually terminate parsing of the + * file. + */ + enum { + RESPECT_END = (1 << 0), + RESPECT_DATA = (1 << 1), + } respect_token = RESPECT_END | RESPECT_DATA; + while ((line = fileReadLine ()) != NULL) { boolean spaceRequired = FALSE; @@ -195,9 +306,19 @@ continue; } else if (strcmp ((const char*) line, "__DATA__") == 0) - break; + { + if (respect_token & RESPECT_DATA) + break; + else + continue; + } else if (strcmp ((const char*) line, "__END__") == 0) - break; + { + if (respect_token & RESPECT_END) + break; + else + continue; + } else if (line [0] == '#') continue; @@ -219,11 +340,39 @@ continue; while (*cp && isspace (*cp)) ++cp; + if (strncmp((const char*) cp, "AutoLoader", (size_t) 10) == 0) { + respect_token &= ~RESPECT_END; + continue; + } + if (strncmp((const char*) cp, "SelfLoader", (size_t) 10) == 0) { + respect_token &= ~RESPECT_DATA; + continue; + } if (strncmp((const char*) cp, "constant", (size_t) 8) != 0) continue; cp += 8; + /* Skip up to the first non-space character, skipping empty + * and comment lines. + */ + while (isspace(*cp)) + cp++; + while (!*cp || '#' == *cp) { + cp = fileReadLine (); + if (!cp) + goto END_MAIN_WHILE; + while (isspace (*cp)) + cp++; + } + if ('{' == *cp) { + ++cp; + if (0 == parseConstantsFromHashRef(cp, name, package)) { + vStringClear(name); + continue; + } else + goto END_MAIN_WHILE; + } kind = K_CONSTANT; - spaceRequired = TRUE; + spaceRequired = FALSE; qualified = TRUE; } else if (strncmp((const char*) cp, "package", (size_t) 7) == 0) @@ -238,7 +387,7 @@ vStringClear (package); while (isspace (*cp)) cp++; - while ((int) *cp != ';' && !isspace ((int) *cp)) + while (*cp && (int) *cp != ';' && !isspace ((int) *cp)) { vStringPut (package, (int) *cp); cp++; Index: objc.c =================================================================== --- objc.c (.../tags/ctags-5.8) (nonexistent) +++ objc.c (.../trunk) @@ -0,0 +1,1151 @@ + +/* +* 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 "routines.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 */ +static parseNext toDoNext; + +/* Special variable used by parser eater to + * determine which action to put after their + * job is finished. */ +static parseNext comeAfter; + +/* Used by some parsers detecting certain token + * to revert to previous parser. */ +static 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); + } +} + +static void pushEnclosingContext (const vString * parent, objcKind type) +{ + vStringCopy (parentName, parent); + parentType = type; +} + +static 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); +} + +static 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 int ignoreBalanced_count = 0; +static void ignoreBalanced (vString * const UNUSED (ident), objcToken what) +{ + + switch (what) + { + case Tok_PARL: + case Tok_CurlL: + case Tok_SQUAREL: + ignoreBalanced_count++; + break; + + case Tok_PARR: + case Tok_CurlR: + case Tok_SQUARER: + ignoreBalanced_count--; + break; + + default: + /* don't care */ + break; + } + + if (ignoreBalanced_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 boolean parseStruct_gotName = FALSE; +static void parseStruct (vString * const ident, objcToken what) +{ + switch (what) + { + case ObjcIDENTIFIER: + if (!parseStruct_gotName) + { + addTag (ident, K_STRUCT); + pushEnclosingContext (ident, K_STRUCT); + parseStruct_gotName = TRUE; + } + else + { + parseStruct_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 (parseStruct_gotName) + popEnclosingContext (); + + toDoNext = comeAfter; + comeAfter (ident, what); + break; + + default: + /* we don't care */ + break; + } +} + +/* Parse enumeration members, ignoring potential initialization */ +static parseNext parseEnumFields_prev = NULL; +static void parseEnumFields (vString * const ident, objcToken what) +{ + if (parseEnumFields_prev != NULL) + { + comeAfter = parseEnumFields_prev; + parseEnumFields_prev = NULL; + } + + switch (what) + { + case ObjcIDENTIFIER: + addTag (ident, K_ENUM); + parseEnumFields_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 boolean parseEnum_named = FALSE; +static void parseEnum (vString * const ident, objcToken what) +{ + switch (what) + { + case ObjcIDENTIFIER: + if (!parseEnum_named) + { + addTag (ident, K_ENUM); + pushEnclosingContext (ident, K_ENUM); + parseEnum_named = TRUE; + } + else + { + parseEnum_named = FALSE; + popEnclosingContext (); + toDoNext = comeAfter; + comeAfter (ident, what); + } + break; + + case Tok_CurlL: /* '{' */ + toDoNext = &parseEnumFields; + parseEnum_named = FALSE; + break; + + case Tok_semi: /* ';' */ + if (parseEnum_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 boolean ignorePreprocStuff_escaped = FALSE; +static void ignorePreprocStuff (vString * const UNUSED (ident), objcToken what) +{ + switch (what) + { + case Tok_Backslash: + ignorePreprocStuff_escaped = TRUE; + break; + + case Tok_EOL: + if (ignorePreprocStuff_escaped) + { + ignorePreprocStuff_escaped = FALSE; + } + else + { + toDoNext = &globalScope; + } + break; + + default: + ignorePreprocStuff_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 (); + + /* (Re-)initialize state variables, this might be a second file */ + comeAfter = NULL; + fallback = NULL; + parentType = K_INTERFACE; + ignoreBalanced_count = 0; + methodKind = 0; + parseStruct_gotName = FALSE; + parseEnumFields_prev = NULL; + parseEnum_named = FALSE; + ignorePreprocStuff_escaped = FALSE; + + 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; +} Index: ctags.1 =================================================================== --- ctags.1 (.../tags/ctags-5.8) +++ ctags.1 (.../trunk) @@ -387,7 +387,7 @@ Specifies whether to include extra tag entries for certain kinds of information. The parameter \fIflags\fP is a set of one-letter flags, each representing one kind of extra tag entry to include in the tag file. If -\fIflags\fP is preceded by by either the '+' or '\-' character, the effect of +\fIflags\fP is preceded by either the '+' or '\-' character, the effect of each flag is added to, or removed from, those currently enabled; otherwise the flags replace any current settings. The meaning of each flag is as follows: @@ -494,7 +494,7 @@ file name parsed when the \fB\-\-filter\fP option is enabled. This may permit an application reading the output of ctags to determine when the output for each file is finished. Note that if the file name read is a directory and -\fB\-\-recurse\fP is enabled, this string will be printed only one once at the +\fB\-\-recurse\fP is enabled, this string will be printed only once at the end of all tags found for by descending the directory. This string will always be separated from the last tag line for the file by its terminating newline. This option is quite esoteric and is empty by default. This option must appear @@ -1006,7 +1006,7 @@ .SH "HOW TO USE WITH NEDIT" NEdit version 5.1 and later can handle the new extended tag file format (see \fB\-\-format\fP). To make NEdit use the tag file, select "File\->Load Tags -File". To jump to the definition for a tag, highlight the word, the press +File". To jump to the definition for a tag, highlight the word, then press Ctrl-D. NEdit 5.1 can can read multiple tag files from different directories. Setting the X resource nedit.tagFile to the name of a tag file instructs NEdit to automatically load that tag file at startup time. Index: python.c =================================================================== --- python.c (.../tags/ctags-5.8) +++ python.c (.../trunk) @@ -135,7 +135,7 @@ * extract all relevant information and create a tag. */ static void makeFunctionTag (vString *const function, - vString *const parent, int is_class_parent, const char *arglist __unused__) + vString *const parent, int is_class_parent, const char *arglist) { tagEntryInfo tag; initTagEntry (&tag, vStringValue (function)); @@ -142,7 +142,7 @@ tag.kindName = "function"; tag.kind = 'f'; - /* tag.extensionFields.arglist = arglist; */ + tag.extensionFields.signature = arglist; if (vStringLength (parent) > 0) { @@ -238,10 +238,31 @@ /* Skip everything up to an identifier start. */ static const char *skipEverything (const char *cp) { + int match; for (; *cp; cp++) { - if (*cp == '"' || *cp == '\'') + match = 0; + if (*cp == '"' || *cp == '\'' || *cp == '#') + match = 1; + + /* these checks find unicode, binary (Python 3) and raw strings */ + if (!match && ( + !strncasecmp(cp, "u'", 2) || !strncasecmp(cp, "u\"", 2) || + !strncasecmp(cp, "r'", 2) || !strncasecmp(cp, "r\"", 2) || + !strncasecmp(cp, "b'", 2) || !strncasecmp(cp, "b\"", 2))) { + match = 1; + cp += 1; + } + if (!match && ( + !strncasecmp(cp, "ur'", 3) || !strncasecmp(cp, "ur\"", 3) || + !strncasecmp(cp, "br'", 3) || !strncasecmp(cp, "br\"", 3))) + { + match = 1; + cp += 2; + } + if (match) + { cp = skipString(cp); if (!*cp) break; } @@ -373,6 +394,8 @@ { char *start, *end; int level; + char *arglist, *from, *to; + int len; if (NULL == buf) return NULL; if (NULL == (start = strchr(buf, '('))) @@ -387,7 +410,20 @@ -- level; } *end = '\0'; - return strdup(start); + + len = strlen(start) + 1; + arglist = eMalloc(len); + from = start; + to = arglist; + while (*from != '\0') { + if (*from == '\t') + ; /* tabs are illegal in field values */ + else + *to++ = *from; + ++from; + } + *to = '\0'; + return arglist; } static void parseFunction (const char *cp, vString *const def, @@ -398,7 +434,9 @@ cp = parseIdentifier (cp, def); arglist = parseArglist (cp); makeFunctionTag (def, parent, is_class_parent, arglist); - eFree (arglist); + if (arglist != NULL) { + eFree (arglist); + } } /* Get the combined name of a nested symbol. Classes are separated with ".", @@ -453,9 +491,9 @@ { n = nls->levels + i; /* is there a better way to compare two vStrings? */ - if (strcmp(vStringValue(parent), vStringValue(n->name)) == 0) + if (n && strcmp(vStringValue(parent), vStringValue(n->name)) == 0) { - if (n && indent <= n->indentation) + if (indent <= n->indentation) { /* remove this level by clearing its name */ vStringClear(n->name); @@ -499,6 +537,8 @@ for (; *cp; cp++) { + if (*cp == '#') + break; if (*cp == '"' || *cp == '\'') { if (strncmp(cp, doubletriple, 3) == 0) @@ -608,6 +648,49 @@ 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 boolean varIsLambda (const char *cp, char **arglist) +{ + boolean 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, ')'); + vStringTerminate (args); + if (arglist) + *arglist = strdup (vStringValue (args)); + vStringDelete (args); + is_lambda = TRUE; + } + } + } + return is_lambda; +} + static void findPythonTags (void) { vString *const continuation = vStringNew (); @@ -652,8 +735,6 @@ indent = cp - line; line_skip = 0; - checkParent(nesting_levels, indent, parent); - /* Deal with multiline string ending. */ if (longStringLiteral) { @@ -660,6 +741,8 @@ find_triple_end(cp, &longStringLiteral); continue; } + + checkParent(nesting_levels, indent, parent); /* Deal with multiline string start. */ longstring = find_triple_start(cp, &longStringLiteral); @@ -730,6 +813,7 @@ if (variable) { const char *start = variable; + char *arglist; boolean parent_is_class; vStringClear (name); @@ -741,11 +825,22 @@ vStringTerminate (name); parent_is_class = constructParentString(nesting_levels, indent, parent); - /* skip variables in methods */ - if (! parent_is_class && vStringLength(parent) > 0) - continue; - makeVariableTag (name, 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) + continue; + + makeVariableTag (name, parent); + } } /* Find and parse imports */ parseImports(line); Index: ant.c =================================================================== --- ant.c (.../tags/ctags-5.8) +++ ant.c (.../trunk) @@ -24,9 +24,9 @@ static void installAntRegex (const langType language) { addTagRegex (language, - "^[ \t]*<[ \t]*project.*name=\"([^\"]+)\".*", "\\1", "p,project,projects", NULL); + "^[ \t]*<[ \t]*project[^>]+name=\"([^\"]+)\".*", "\\1", "p,project,projects", NULL); addTagRegex (language, - "^[ \t]*<[ \t]*target.*name=\"([^\"]+)\".*", "\\1", "t,target,targets", NULL); + "^[ \t]*<[ \t]*target[^>]+name=\"([^\"]+)\".*", "\\1", "t,target,targets", NULL); } extern parserDefinition* AntParser () Index: lregex.c =================================================================== --- lregex.c (.../tags/ctags-5.8) +++ lregex.c (.../trunk) @@ -408,7 +408,7 @@ const char* regexfile = parameter + 1; FILE* const fp = fopen (regexfile, "r"); if (fp == NULL) - error (WARNING | PERROR, regexfile); + error (WARNING | PERROR, "%s", regexfile); else { vString* const regex = vStringNew (); Index: ocaml.c =================================================================== --- ocaml.c (.../tags/ctags-5.8) +++ ocaml.c (.../trunk) @@ -72,6 +72,7 @@ OcaKEYWORD_if, OcaKEYWORD_in, OcaKEYWORD_let, + OcaKEYWORD_value, OcaKEYWORD_match, OcaKEYWORD_method, OcaKEYWORD_module, @@ -145,7 +146,7 @@ { "try" , OcaKEYWORD_try }, { "type" , OcaKEYWORD_type }, { "val" , OcaKEYWORD_val }, - { "value" , OcaKEYWORD_let }, /* just to handle revised syntax */ + { "value" , OcaKEYWORD_value }, /* just to handle revised syntax */ { "virtual" , OcaKEYWORD_virtual }, { "while" , OcaKEYWORD_while }, { "with" , OcaKEYWORD_with }, @@ -297,7 +298,6 @@ if (st->cp == NULL) return; c = st->cp; - continue; } /* we've reached the end of the comment */ else if (*c == ')' && lastIsStar) @@ -308,13 +308,33 @@ { st->cp = c; eatComment (st); + c = st->cp; + if (c == NULL) + return; + lastIsStar = FALSE; + c++; } + /* OCaml has a rule which says : + * + * "Comments do not occur inside string or character literals. + * Nested comments are handled correctly." + * + * So if we encounter a string beginning, we must parse it to + * get a good comment nesting (bug ID: 3117537) + */ + else if (*c == '"') + { + st->cp = c; + eatString (st); + c = st->cp; + } else + { lastIsStar = '*' == *c; - - c++; + c++; + } } st->cp = c; @@ -554,8 +574,7 @@ for (i = stackIndex - 1; i >= 0; --i) { - if (stack[i].contextName->buffer && - strlen (stack[i].contextName->buffer) > 0) + if (vStringLength (stack[i].contextName) > 0) { return i; } @@ -866,6 +885,11 @@ tag->kindName = OcamlKinds[kind].name; tag->kind = OcamlKinds[kind].letter; + if (kind == K_MODULE) + { + tag->lineNumberEntry = TRUE; + tag->lineNumber = 1; + } parentIndex = getLastNamedIndex (); if (parentIndex >= 0) { @@ -880,9 +904,12 @@ * more information to it in the future */ static void addTag (vString * const ident, int kind) { - tagEntryInfo toCreate; - prepareTag (&toCreate, ident, kind); - makeTagEntry (&toCreate); + if (OcamlKinds [kind].enabled && ident != NULL && vStringLength (ident) > 0) + { + tagEntryInfo toCreate; + prepareTag (&toCreate, ident, kind); + makeTagEntry (&toCreate); + } } boolean needStrongPoping = FALSE; @@ -942,7 +969,7 @@ } /* handle : - * exception ExceptionName ... */ + * exception ExceptionName of ... */ static void exceptionDecl (vString * const ident, ocaToken what) { if (what == OcaIDENTIFIER) @@ -949,8 +976,10 @@ { addTag (ident, K_EXCEPTION); } - /* don't know what to do on else... */ - + else /* probably ill-formed, give back to global scope */ + { + globalScope (ident, what); + } toDoNext = &globalScope; } @@ -1006,7 +1035,6 @@ */ static void typeDecl (vString * const ident, ocaToken what) { - switch (what) { /* parameterized */ @@ -1046,7 +1074,6 @@ * let typeRecord handle it. */ static void typeSpecification (vString * const ident, ocaToken what) { - switch (what) { case OcaIDENTIFIER: @@ -1243,8 +1270,14 @@ * than the let definitions. * Used after a match ... with, or a function ... or fun ... * because their syntax is similar. */ -static void matchPattern (vString * const UNUSED (ident), ocaToken what) +static void matchPattern (vString * const ident, ocaToken what) { + /* keep track of [], as it + * can be used in patterns and can + * mean the end of match expression in + * revised syntax */ + static int braceCount = 0; + switch (what) { case Tok_To: @@ -1252,7 +1285,15 @@ toDoNext = &mayRedeclare; break; + case Tok_BRL: + braceCount++; + break; + case OcaKEYWORD_value: + popLastNamed (); + globalScope (ident, what); + break; + case OcaKEYWORD_in: popLastNamed (); break; @@ -1269,6 +1310,11 @@ { switch (what) { + case OcaKEYWORD_value: + /* let globalScope handle it */ + globalScope (ident, what); + break; + case OcaKEYWORD_let: case OcaKEYWORD_val: toDoNext = localLet; @@ -1388,6 +1434,7 @@ * nearly a copy/paste of globalLet. */ static void methodDecl (vString * const ident, ocaToken what) { + switch (what) { case Tok_PARL: @@ -1435,6 +1482,7 @@ */ static void moduleSpecif (vString * const ident, ocaToken what) { + switch (what) { case OcaKEYWORD_functor: @@ -1566,7 +1614,7 @@ { /* Do not touch, this is used only by the global scope * to handle an 'and' */ - static parseNext previousParser = NULL; + static parseNext previousParser = &globalScope; switch (what) { @@ -1608,6 +1656,7 @@ /* val is mixed with let as global * to be able to handle mli & new syntax */ case OcaKEYWORD_val: + case OcaKEYWORD_value: case OcaKEYWORD_let: cleanupPreviousParser (); toDoNext = &globalLet; @@ -1617,7 +1666,7 @@ case OcaKEYWORD_exception: cleanupPreviousParser (); toDoNext = &exceptionDecl; - previousParser = NULL; + previousParser = &globalScope; break; /* must be a #line directive, discard the @@ -1683,8 +1732,14 @@ break; case OcaKEYWORD_and: - popLastNamed (); - toDoNext = &localLet; + popSoftContext (); + if (toDoNext != &mayRedeclare) + toDoNext(ident, what); + else + { + pushEmptyContext(localScope); + toDoNext = &localLet; + } break; case OcaKEYWORD_else: @@ -1769,7 +1824,7 @@ if (isLowerAlpha (moduleName->buffer[0])) moduleName->buffer[0] += ('A' - 'a'); - makeSimpleTag (moduleName, OcamlKinds, K_MODULE); + addTag (moduleName, K_MODULE); vStringDelete (moduleName); } @@ -1779,6 +1834,7 @@ int i; for (i = 0; i < OCAML_MAX_STACK_SIZE; ++i) stack[i].contextName = vStringNew (); + stackIndex = 0; } static void clearStack ( void ) @@ -1794,8 +1850,8 @@ lexingState st; ocaToken tok; + initStack (); computeModuleName (); - initStack (); tempIdent = vStringNew (); lastModule = vStringNew (); lastClass = vStringNew (); Index: make.c =================================================================== --- make.c (.../tags/ctags-5.8) +++ make.c (.../trunk) @@ -100,7 +100,7 @@ ++matchLevel; else if (c == end) --matchLevel; - else if (c == '\n') + else if (c == '\n' || c == EOF) break; } if (c == EOF) Index: jscript.c =================================================================== --- jscript.c (.../tags/ctags-5.8) +++ jscript.c (.../trunk) @@ -25,6 +25,7 @@ #include #endif +#include #include "debug.h" #include "entry.h" #include "keyword.h" @@ -57,7 +58,6 @@ KEYWORD_NONE = -1, KEYWORD_function, KEYWORD_capital_function, - KEYWORD_object, KEYWORD_capital_object, KEYWORD_prototype, KEYWORD_var, @@ -71,7 +71,8 @@ KEYWORD_switch, KEYWORD_try, KEYWORD_catch, - KEYWORD_finally + KEYWORD_finally, + KEYWORD_sap } keywordId; /* Used to determine whether keyword is valid for the token language and @@ -100,7 +101,8 @@ TOKEN_EQUAL_SIGN, TOKEN_FORWARD_SLASH, TOKEN_OPEN_SQUARE, - TOKEN_CLOSE_SQUARE + TOKEN_CLOSE_SQUARE, + TOKEN_REGEXP } tokenType; typedef struct sTokenInfo { @@ -118,6 +120,8 @@ * DATA DEFINITIONS */ +static tokenType LastTokenType; + static langType Lang_js; static jmp_buf Exception; @@ -143,7 +147,6 @@ /* keyword keyword ID */ { "function", KEYWORD_function }, { "Function", KEYWORD_capital_function }, - { "object", KEYWORD_object }, { "Object", KEYWORD_capital_object }, { "prototype", KEYWORD_prototype }, { "var", KEYWORD_var }, @@ -157,7 +160,8 @@ { "switch", KEYWORD_switch }, { "try", KEYWORD_try }, { "catch", KEYWORD_catch }, - { "finally", KEYWORD_finally } + { "finally", KEYWORD_finally }, + { "sap", KEYWORD_sap } }; /* @@ -168,11 +172,12 @@ static void parseFunction (tokenInfo *const token); static boolean parseBlock (tokenInfo *const token, tokenInfo *const parent); static boolean parseLine (tokenInfo *const token, boolean is_inside_class); +static void parseUI5 (tokenInfo *const token); static boolean isIdentChar (const int c) { return (boolean) - (isalpha (c) || isdigit (c) || c == '$' || + (isalpha (c) || isdigit (c) || c == '$' || c == '@' || c == '_' || c == '#'); } @@ -215,12 +220,23 @@ * Tag generation functions */ -static void makeConstTag (tokenInfo *const token, const jsKind kind) +static void makeJsTag (tokenInfo *const token, const jsKind kind) { if (JsKinds [kind].enabled && ! token->ignoreTag ) { - const char *const name = vStringValue (token->string); + const char *name = vStringValue (token->string); + vString *fullscope = vStringNewCopy (token->scope); + const char *p; tagEntryInfo e; + + if ( (p = strrchr (name, '.')) != NULL ) + { + if (vStringLength (fullscope) > 0) + vStringPut (fullscope, '.'); + vStringNCatS (fullscope, name, (size_t) (p - name)); + name = p + 1; + } + initTagEntry (&e, name); e.lineNumber = token->lineNumber; @@ -228,36 +244,28 @@ e.kindName = JsKinds [kind].name; e.kind = JsKinds [kind].letter; - makeTagEntry (&e); - } -} + if ( vStringLength(fullscope) > 0 ) + { + jsKind parent_kind = JSTAG_CLASS; -static void makeJsTag (tokenInfo *const token, const jsKind kind) -{ - vString * fulltag; + /* + * If we're creating a function (and not a method), + * guess we're inside another function + */ + if (kind == JSTAG_FUNCTION) + parent_kind = JSTAG_FUNCTION; - if (JsKinds [kind].enabled && ! token->ignoreTag ) - { - /* - * If a scope has been added to the token, change the token - * string to include the scope when making the tag. - */ - if ( vStringLength(token->scope) > 0 ) - { - fulltag = vStringNew (); - vStringCopy(fulltag, token->scope); - vStringCatS (fulltag, "."); - vStringCatS (fulltag, vStringValue(token->string)); - vStringTerminate(fulltag); - vStringCopy(token->string, fulltag); - vStringDelete (fulltag); + e.extensionFields.scope[0] = JsKinds [parent_kind].name; + e.extensionFields.scope[1] = vStringValue (fullscope); } - makeConstTag (token, kind); + + makeTagEntry (&e); + vStringDelete (fullscope); } } static void makeClassTag (tokenInfo *const token) -{ +{ vString * fulltag; if ( ! token->ignoreTag ) @@ -284,7 +292,7 @@ } static void makeFunctionTag (tokenInfo *const token) -{ +{ vString * fulltag; if ( ! token->ignoreTag ) @@ -324,7 +332,8 @@ end = TRUE; else if (c == '\\') { - c = fileGetc(); /* This maybe a ' or ". */ + /* This maybe a ' or ". */ + c = fileGetc(); vStringPut(string, c); } else if (c == delimiter) @@ -335,6 +344,32 @@ vStringTerminate (string); } +static void parseRegExp (void) +{ + int c; + boolean in_range = FALSE; + + do + { + c = fileGetc (); + if (! in_range && c == '/') + { + do /* skip flags */ + { + c = fileGetc (); + } while (isalpha (c)); + fileUngetc (c); + break; + } + else if (c == '\\') + c = fileGetc (); /* skip next character */ + else if (c == '[') + in_range = TRUE; + else if (c == ']') + in_range = FALSE; + } while (c != EOF); +} + /* Read a C identifier beginning with "firstChar" and places it into * "name". */ @@ -364,11 +399,12 @@ do { c = fileGetc (); - token->lineNumber = getSourceLineNumber (); - token->filePosition = getInputFilePosition (); } while (c == '\t' || c == ' ' || c == '\n'); + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + switch (c) { case EOF: longjmp (Exception, (int)ExceptionEOF); break; @@ -407,8 +443,26 @@ if ( (d != '*') && /* is this the start of a comment? */ (d != '/') ) /* is a one line comment? */ { - token->type = TOKEN_FORWARD_SLASH; fileUngetc (d); + switch (LastTokenType) + { + case TOKEN_CHARACTER: + case TOKEN_KEYWORD: + case TOKEN_IDENTIFIER: + case TOKEN_STRING: + case TOKEN_CLOSE_CURLY: + case TOKEN_CLOSE_PAREN: + case TOKEN_CLOSE_SQUARE: + token->type = TOKEN_FORWARD_SLASH; + break; + + default: + token->type = TOKEN_REGEXP; + parseRegExp (); + token->lineNumber = getSourceLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } } else { @@ -450,6 +504,8 @@ } break; } + + LastTokenType = token->type; } static void copyToken (tokenInfo *const dest, tokenInfo *const src) @@ -495,7 +551,7 @@ nest_level--; } } - } + } readToken (token); } } @@ -527,7 +583,7 @@ nest_level--; } } - } + } readToken (token); } } @@ -559,7 +615,7 @@ static void findCmdTerm (tokenInfo *const token) { /* - * Read until we find either a semicolon or closing brace. + * Read until we find either a semicolon or closing brace. * Any nested braces will be handled within. */ while (! ( isType (token, TOKEN_SEMICOLON) || @@ -569,22 +625,48 @@ if ( isType (token, TOKEN_OPEN_CURLY)) { parseBlock (token, token); - } + readToken (token); + } else if ( isType (token, TOKEN_OPEN_PAREN) ) { skipArgumentList(token); } - else + else { readToken (token); } - } + } } +static void findMatchingToken (tokenInfo *const token, tokenType begin_token, tokenType end_token) +{ + int nest_level = 0; + + if ( ! isType (token, end_token)) + { + nest_level++; + while (! (isType (token, end_token) && (nest_level == 0))) + { + readToken (token); + if (isType (token, begin_token)) + { + nest_level++; + } + if (isType (token, end_token)) + { + if (nest_level > 0) + { + nest_level--; + } + } + } + } +} + static void parseSwitch (tokenInfo *const token) { /* - * switch (expression){ + * switch (expression) { * case value1: * statement; * break; @@ -597,7 +679,7 @@ readToken (token); - if (isType (token, TOKEN_OPEN_PAREN)) + if (isType (token, TOKEN_OPEN_PAREN)) { /* * Handle nameless functions, these will only @@ -606,15 +688,9 @@ skipArgumentList(token); } - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { - /* - * This will be either a function or a class. - * We can only determine this by checking the body - * of the function. If we find a "this." we know - * it is a class, otherwise it is a function. - */ - parseBlock (token, token); + findMatchingToken (token, TOKEN_OPEN_CURLY, TOKEN_CLOSE_CURLY); } } @@ -625,17 +701,17 @@ * Handles these statements * for (x=0; x<3; x++) * document.write("This text is repeated three times
"); - * + * * for (x=0; x<3; x++) * { * document.write("This text is repeated three times
"); * } - * + * * while (number<5){ * document.write(number+"
"); * number++; * } - * + * * do{ * document.write(number+"
"); * number++; @@ -647,7 +723,7 @@ { readToken(token); - if (isType (token, TOKEN_OPEN_PAREN)) + if (isType (token, TOKEN_OPEN_PAREN)) { /* * Handle nameless functions, these will only @@ -656,7 +732,7 @@ skipArgumentList(token); } - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { /* * This will be either a function or a class. @@ -665,17 +741,17 @@ * it is a class, otherwise it is a function. */ parseBlock (token, token); - } - else + } + else { parseLine(token, FALSE); } - } + } else if (isKeyword (token, KEYWORD_do)) { readToken(token); - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { /* * This will be either a function or a class. @@ -684,8 +760,8 @@ * it is a class, otherwise it is a function. */ parseBlock (token, token); - } - else + } + else { parseLine(token, FALSE); } @@ -696,7 +772,7 @@ { readToken(token); - if (isType (token, TOKEN_OPEN_PAREN)) + if (isType (token, TOKEN_OPEN_PAREN)) { /* * Handle nameless functions, these will only @@ -716,11 +792,11 @@ * if ( ... ) * one line; * - * if ( ... ) + * if ( ... ) * statement; * else * statement - * + * * if ( ... ) { * multiple; * statements; @@ -746,7 +822,7 @@ * without a semi-colon. Currently this messes up * the parsing of blocks. * Need to somehow detect this has happened, and either - * backup a token, or skip reading the next token if + * backup a token, or skip reading the next token if * that is possible from all code locations. * */ @@ -761,9 +837,9 @@ readToken (token); } - if (isType (token, TOKEN_OPEN_PAREN)) + if (isType (token, TOKEN_OPEN_PAREN)) { - /* + /* * Handle nameless functions, these will only * be considered methods. */ @@ -770,7 +846,7 @@ skipArgumentList(token); } - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { /* * This will be either a function or a class. @@ -779,41 +855,14 @@ * it is a class, otherwise it is a function. */ parseBlock (token, token); - } - else + } + else { findCmdTerm (token); - /* - * The IF could be followed by an ELSE statement. - * This too could have two formats, a curly braced - * multiline section, or another single line. - */ - - if (isType (token, TOKEN_CLOSE_CURLY)) - { - /* - * This statement did not have a line terminator. - */ - read_next_token = FALSE; - } - else - { - readToken (token); - - if (isType (token, TOKEN_CLOSE_CURLY)) - { - /* - * This statement did not have a line terminator. - */ - read_next_token = FALSE; - } - else - { - if (isKeyword (token, KEYWORD_else)) - read_next_token = parseIf (token); - } - } + /* The next token should only be read if this statement had its own + * terminator */ + read_next_token = isType (token, TOKEN_SEMICOLON); } return read_next_token; } @@ -833,17 +882,14 @@ addToScope(name, token->scope); readToken (token); - if (isType (token, TOKEN_PERIOD)) + while (isType (token, TOKEN_PERIOD)) { - do + readToken (token); + if ( isKeyword(token, KEYWORD_NONE) ) { + addContext (name, token); readToken (token); - if ( isKeyword(token, KEYWORD_NONE) ) - { - addContext (name, token); - readToken (token); - } - } while (isType (token, TOKEN_PERIOD)); + } } if ( isType (token, TOKEN_OPEN_PAREN) ) @@ -852,9 +898,9 @@ if ( isType (token, TOKEN_OPEN_CURLY) ) { is_class = parseBlock (token, name); - if ( is_class ) + if ( is_class ) makeClassTag (name); - else + else makeFunctionTag (name); } @@ -874,7 +920,7 @@ * Make this routine a bit more forgiving. * If called on an open_curly advance it */ - if ( isType (token, TOKEN_OPEN_CURLY) && + if ( isType (token, TOKEN_OPEN_CURLY) && isKeyword(token, KEYWORD_NONE) ) readToken(token); @@ -881,7 +927,7 @@ if (! isType (token, TOKEN_CLOSE_CURLY)) { /* - * Read until we find the closing brace, + * Read until we find the closing brace, * any nested braces will be handled within */ do @@ -904,7 +950,7 @@ parseLine (token, is_class); vStringCopy(token->scope, saveScope); - } + } else if (isKeyword (token, KEYWORD_var)) { /* @@ -915,7 +961,7 @@ addToScope (token, parent->string); parseLine (token, is_class); vStringCopy(token->scope, saveScope); - } + } else if (isKeyword (token, KEYWORD_function)) { vStringCopy(saveScope, token->scope); @@ -922,13 +968,13 @@ addToScope (token, parent->string); parseFunction (token); vStringCopy(token->scope, saveScope); - } + } else if (isType (token, TOKEN_OPEN_CURLY)) { /* Handle nested blocks */ parseBlock (token, parent); - } - else + } + else { /* * It is possible for a line to have no terminator @@ -943,11 +989,11 @@ * Always read a new token unless we find a statement without * a ending terminator */ - if( read_next_token ) + if( read_next_token ) readToken(token); /* - * If we find a statement without a terminator consider the + * If we find a statement without a terminator consider the * block finished, otherwise the stack will be off by one. */ } while (! isType (token, TOKEN_CLOSE_CURLY) && read_next_token ); @@ -959,9 +1005,10 @@ return is_class; } -static void parseMethods (tokenInfo *const token, tokenInfo *const class) +static boolean parseMethods (tokenInfo *const token, tokenInfo *const class) { tokenInfo *const name = newToken (); + boolean has_methods = FALSE; /* * This deals with these formats @@ -968,12 +1015,22 @@ * validProperty : 2, * validMethod : function(a,b) {} * 'validMethod2' : function(a,b) {} - * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false} + * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false} */ do { readToken (token); + if (isType (token, TOKEN_CLOSE_CURLY)) + { + /* + * This was most likely a variable declaration of a hash table. + * indicate there were no methods and return. + */ + has_methods = FALSE; + goto cleanUp; + } + if (isType (token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE)) { copyToken(name, token); @@ -990,8 +1047,9 @@ skipArgumentList(token); } - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { + has_methods = TRUE; addToScope (name, class->string); makeJsTag (name, JSTAG_METHOD); parseBlock (token, name); @@ -1005,14 +1063,43 @@ } else { + vString * saveScope = vStringNew (); + boolean has_child_methods = FALSE; + + /* skip whatever is the value */ + while (! isType (token, TOKEN_COMMA) && + ! isType (token, TOKEN_CLOSE_CURLY)) + { + if (isType (token, TOKEN_OPEN_CURLY)) + { + /* Recurse to find child properties/methods */ + vStringCopy (saveScope, token->scope); + addToScope (token, class->string); + has_child_methods = parseMethods (token, name); + vStringCopy (token->scope, saveScope); + readToken (token); + } + else if (isType (token, TOKEN_OPEN_PAREN)) + { + skipArgumentList (token); + } + else if (isType (token, TOKEN_OPEN_SQUARE)) + { + skipArrayList (token); + } + else + { + readToken (token); + } + } + vStringDelete (saveScope); + + has_methods = TRUE; addToScope (name, class->string); - makeJsTag (name, JSTAG_PROPERTY); - - /* - * Read the next token, if a comma - * we must loop again - */ - readToken (token); + if (has_child_methods) + makeJsTag (name, JSTAG_CLASS); + else + makeJsTag (name, JSTAG_PROPERTY); } } } @@ -1020,7 +1107,10 @@ findCmdTerm (token); +cleanUp: deleteToken (name); + + return has_methods; } static boolean parseStatement (tokenInfo *const token, boolean is_inside_class) @@ -1027,11 +1117,12 @@ { tokenInfo *const name = newToken (); tokenInfo *const secondary_name = newToken (); + tokenInfo *const method_body_token = newToken (); vString * saveScope = vStringNew (); boolean is_class = FALSE; boolean is_terminated = TRUE; boolean is_global = FALSE; - boolean is_prototype = FALSE; + boolean has_methods = FALSE; vString * fulltag; vStringClear(saveScope); @@ -1047,7 +1138,7 @@ * var D3 = new Function("a", "b", "return a+b;"); * Class * testlib.extras.ValidClassOne = function(a,b) { - * this.a = a; + * this.a = a; * } * Class Methods * testlib.extras.ValidClassOne.prototype = { @@ -1054,7 +1145,7 @@ * 'validMethodOne' : function(a,b) {}, * 'validMethodTwo' : function(a,b) {} * } - * ValidClassTwo = function () + * ValidClassTwo = function () * { * this.validMethodThree = function() {} * // unnamed method @@ -1063,7 +1154,7 @@ * Database.prototype.validMethodThree = Database_getTodaysDate; */ - if ( is_inside_class ) + if ( is_inside_class ) is_class = TRUE; /* * var can preceed an inner function @@ -1112,11 +1203,11 @@ { vStringCopy(saveScope, token->scope); addToScope(token, name->string); - } - else + } + else addContext (name, token); - } - else if ( isKeyword(token, KEYWORD_prototype) ) + } + else if ( isKeyword(token, KEYWORD_prototype) ) { /* * When we reach the "prototype" tag, we infer: @@ -1124,12 +1215,12 @@ * "build" is a method * * function BindAgent( repeatableIdName, newParentIdName ) { - * } + * } * * CASE 1 * Specified function name: "build" * BindAgent.prototype.build = function( mode ) { - * ignore everything within this function + * maybe parse nested functions * } * * CASE 2 @@ -1142,7 +1233,6 @@ */ makeClassTag (name); is_class = TRUE; - is_prototype = TRUE; /* * There should a ".function_name" next. @@ -1158,28 +1248,32 @@ { vStringCopy(saveScope, token->scope); addToScope(token, name->string); + makeJsTag (token, JSTAG_METHOD); - makeJsTag (token, JSTAG_METHOD); - /* - * We can read until the end of the block / statement. - * We need to correctly parse any nested blocks, but - * we do NOT want to create any tags based on what is - * within the blocks. - */ - token->ignoreTag = TRUE; - /* - * Find to the end of the statement - */ - findCmdTerm (token); - token->ignoreTag = FALSE; + readToken (method_body_token); + vStringCopy (method_body_token->scope, token->scope); + + while (! ( isType (method_body_token, TOKEN_SEMICOLON) || + isType (method_body_token, TOKEN_CLOSE_CURLY) || + isType (method_body_token, TOKEN_OPEN_CURLY)) ) + { + if ( isType (method_body_token, TOKEN_OPEN_PAREN) ) + skipArgumentList(method_body_token); + else + readToken (method_body_token); + } + + if ( isType (method_body_token, TOKEN_OPEN_CURLY)) + parseBlock (method_body_token, token); + is_terminated = TRUE; goto cleanUp; } - } - else if (isType (token, TOKEN_EQUAL_SIGN)) + } + else if (isType (token, TOKEN_EQUAL_SIGN)) { readToken (token); - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { /* * Handle CASE 2 @@ -1192,7 +1286,7 @@ */ parseMethods(token, name); /* - * Find to the end of the statement + * Find to the end of the statement */ findCmdTerm (token); token->ignoreTag = FALSE; @@ -1241,10 +1335,10 @@ * Handles this syntax: * var g_var2; */ - if (isType (token, TOKEN_SEMICOLON)) + if (isType (token, TOKEN_SEMICOLON)) makeJsTag (name, JSTAG_VARIABLE); } - /* + /* * Statement has ended. * This deals with calls to functions, like: * alert(..); @@ -1260,16 +1354,16 @@ { readToken (token); - if ( isKeyword (token, KEYWORD_NONE) && + if ( isKeyword (token, KEYWORD_NONE) && ! isType (token, TOKEN_OPEN_PAREN) ) { /* * Functions of this format: - * var D2A = function theAdd(a, b) - * { + * var D2A = function theAdd(a, b) + * { * return a+b; - * } - * Are really two separate defined functions and + * } + * Are really two separate defined functions and * can be referenced in two ways: * alert( D2A(1,2) ); // produces 3 * alert( theAdd(1,2) ); // also produces 3 @@ -1287,7 +1381,7 @@ if ( isType (token, TOKEN_OPEN_PAREN) ) skipArgumentList(token); - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { /* * This will be either a function or a class. @@ -1295,19 +1389,19 @@ * of the function. If we find a "this." we know * it is a class, otherwise it is a function. */ - if ( is_inside_class ) + if ( is_inside_class ) { makeJsTag (name, JSTAG_METHOD); if ( vStringLength(secondary_name->string) > 0 ) makeFunctionTag (secondary_name); parseBlock (token, name); - } - else + } + else { is_class = parseBlock (token, name); - if ( is_class ) + if ( is_class ) makeClassTag (name); - else + else makeFunctionTag (name); if ( vStringLength(secondary_name->string) > 0 ) @@ -1314,13 +1408,13 @@ makeFunctionTag (secondary_name); /* - * Find to the end of the statement + * Find to the end of the statement */ goto cleanUp; } } - } - else if (isType (token, TOKEN_OPEN_PAREN)) + } + else if (isType (token, TOKEN_OPEN_PAREN)) { /* * Handle nameless functions @@ -1328,7 +1422,7 @@ */ skipArgumentList(token); - if (isType (token, TOKEN_OPEN_CURLY)) + if (isType (token, TOKEN_OPEN_CURLY)) { /* * Nameless functions are only setup as methods. @@ -1336,8 +1430,10 @@ makeJsTag (name, JSTAG_METHOD); parseBlock (token, name); } - } - else if (isType (token, TOKEN_OPEN_CURLY)) + else if (isType (token, TOKEN_CLOSE_CURLY)) + is_terminated = FALSE; + } + else if (isType (token, TOKEN_OPEN_CURLY)) { /* * Creates tags for each of these class methods @@ -1345,11 +1441,59 @@ * 'validMethodOne' : function(a,b) {}, * 'validMethodTwo' : function(a,b) {} * } + * Or checks if this is a hash variable. + * var z = {}; */ - parseMethods(token, name); - if (isType (token, TOKEN_CLOSE_CURLY)) + has_methods = parseMethods(token, name); + if (has_methods) + makeJsTag (name, JSTAG_CLASS); + else { /* + * Only create variables for global scope + */ + if ( token->nestLevel == 0 && is_global ) + { + /* + * A pointer can be created to the function. + * If we recognize the function/class name ignore the variable. + * This format looks identical to a variable definition. + * A variable defined outside of a block is considered + * a global variable: + * var g_var1 = 1; + * var g_var2; + * This is not a global variable: + * var g_var = function; + * This is a global variable: + * var g_var = different_var_name; + */ + fulltag = vStringNew (); + if (vStringLength (token->scope) > 0) + { + vStringCopy(fulltag, token->scope); + vStringCatS (fulltag, "."); + vStringCatS (fulltag, vStringValue(token->string)); + } + else + { + vStringCopy(fulltag, token->string); + } + vStringTerminate(fulltag); + if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) && + ! stringListHas(ClassNames, vStringValue (fulltag)) ) + { + readToken (token); + if ( ! isType (token, TOKEN_SEMICOLON)) + findCmdTerm (token); + if (isType (token, TOKEN_SEMICOLON)) + makeJsTag (name, JSTAG_VARIABLE); + } + vStringDelete (fulltag); + } + } + if (isType (token, TOKEN_CLOSE_CURLY)) + { + /* * Assume the closing parantheses terminates * this statements. */ @@ -1359,13 +1503,11 @@ else if (isKeyword (token, KEYWORD_new)) { readToken (token); - if ( isKeyword (token, KEYWORD_function) || + if ( isKeyword (token, KEYWORD_function) || isKeyword (token, KEYWORD_capital_function) || - isKeyword (token, KEYWORD_object) || isKeyword (token, KEYWORD_capital_object) ) { - if ( isKeyword (token, KEYWORD_object) || - isKeyword (token, KEYWORD_capital_object) ) + if ( isKeyword (token, KEYWORD_capital_object) ) is_class = TRUE; readToken (token); @@ -1372,7 +1514,7 @@ if ( isType (token, TOKEN_OPEN_PAREN) ) skipArgumentList(token); - if (isType (token, TOKEN_SEMICOLON)) + if (isType (token, TOKEN_SEMICOLON)) { if ( token->nestLevel == 0 ) { @@ -1384,6 +1526,8 @@ } } } + else if (isType (token, TOKEN_CLOSE_CURLY)) + is_terminated = FALSE; } } else if (isKeyword (token, KEYWORD_NONE)) @@ -1394,7 +1538,7 @@ if ( token->nestLevel == 0 && is_global ) { /* - * A pointer can be created to the function. + * A pointer can be created to the function. * If we recognize the function/class name ignore the variable. * This format looks identical to a variable definition. * A variable defined outside of a block is considered @@ -1422,7 +1566,7 @@ ! stringListHas(ClassNames, vStringValue (fulltag)) ) { findCmdTerm (token); - if (isType (token, TOKEN_SEMICOLON)) + if (isType (token, TOKEN_SEMICOLON)) makeJsTag (name, JSTAG_VARIABLE); } vStringDelete (fulltag); @@ -1429,33 +1573,87 @@ } } } - findCmdTerm (token); + /* if we aren't already at the cmd end, advance to it and check whether + * the statement was terminated */ + if (! isType (token, TOKEN_CLOSE_CURLY) && + ! isType (token, TOKEN_SEMICOLON)) + { + findCmdTerm (token); - /* - * Statements can be optionally terminated in the case of - * statement prior to a close curly brace as in the - * document.write line below: - * - * function checkForUpdate() { - * if( 1==1 ) { - * document.write("hello from checkForUpdate
") - * } - * return 1; - * } - */ - if ( ! is_terminated && isType (token, TOKEN_CLOSE_CURLY)) - is_terminated = FALSE; + /* + * Statements can be optionally terminated in the case of + * statement prior to a close curly brace as in the + * document.write line below: + * + * function checkForUpdate() { + * if( 1==1 ) { + * document.write("hello from checkForUpdate
") + * } + * return 1; + * } + */ + if (isType (token, TOKEN_CLOSE_CURLY)) + is_terminated = FALSE; + } - cleanUp: vStringCopy(token->scope, saveScope); deleteToken (name); deleteToken (secondary_name); + deleteToken (method_body_token); vStringDelete(saveScope); return is_terminated; } +static void parseUI5 (tokenInfo *const token) +{ + tokenInfo *const name = newToken (); + /* + * SAPUI5 is built on top of jQuery. + * It follows a standard format: + * sap.ui.controller("id.of.controller", { + * method_name : function... { + * }, + * + * method_name : function ... { + * } + * } + * + * Handle the parsing of the initial controller (and the + * same for "view") and then allow the methods to be + * parsed as usual. + */ + + readToken (token); + + if (isType (token, TOKEN_PERIOD)) + { + readToken (token); + while (! isType (token, TOKEN_OPEN_PAREN) ) + { + readToken (token); + } + readToken (token); + + if (isType (token, TOKEN_STRING)) + { + copyToken(name, token); + readToken (token); + } + + if (isType (token, TOKEN_COMMA)) + readToken (token); + + do + { + parseMethods (token, name); + } while (! isType (token, TOKEN_CLOSE_CURLY) ); + } + + deleteToken (name); +} + static boolean parseLine (tokenInfo *const token, boolean is_inside_class) { boolean is_terminated = TRUE; @@ -1473,10 +1671,10 @@ { switch (token->keyword) { - case KEYWORD_for: + case KEYWORD_for: case KEYWORD_while: case KEYWORD_do: - parseLoop (token); + parseLoop (token); break; case KEYWORD_if: case KEYWORD_else: @@ -1484,17 +1682,17 @@ case KEYWORD_catch: case KEYWORD_finally: /* Common semantics */ - is_terminated = parseIf (token); + is_terminated = parseIf (token); break; case KEYWORD_switch: - parseSwitch (token); + parseSwitch (token); break; - default: - parseStatement (token, is_inside_class); + default: + parseStatement (token, is_inside_class); break; } - } - else + } + else { /* * Special case where single line statements may not be @@ -1501,7 +1699,7 @@ * SEMICOLON terminated. parseBlock needs to know this * so that it does not read the next token. */ - is_terminated = parseStatement (token, is_inside_class); + is_terminated = parseStatement (token, is_inside_class); } return is_terminated; } @@ -1512,18 +1710,12 @@ { readToken (token); - if (isType(token, TOKEN_KEYWORD)) - { - switch (token->keyword) - { - case KEYWORD_function: parseFunction (token); break; - default: parseLine (token, FALSE); break; - } - } - else - { - parseLine (token, FALSE); - } + if (isType (token, TOKEN_KEYWORD) && token->keyword == KEYWORD_function) + parseFunction (token); + else if (isType (token, TOKEN_KEYWORD) && token->keyword == KEYWORD_sap) + parseUI5 (token); + else + parseLine (token, FALSE); } while (TRUE); } @@ -1538,10 +1730,11 @@ { tokenInfo *const token = newToken (); exception_t exception; - + ClassNames = stringListNew (); FunctionNames = stringListNew (); - + LastTokenType = TOKEN_UNDEFINED; + exception = (exception_t) (setjmp (Exception)); while (exception == ExceptionNone) parseJsFile (token); Index: c.c =================================================================== --- c.c (.../tags/ctags-5.8) +++ c.c (.../trunk) @@ -2121,6 +2121,9 @@ switch (c) { + case '^': + break; + case '&': case '*': info->isPointer = TRUE; Index: FAQ =================================================================== --- FAQ (.../tags/ctags-5.8) +++ FAQ (.../trunk) @@ -1,4 +1,4 @@ -Frequently Asked Questions +vberthoux@users.sourceforge.netFrequently Asked Questions ========================== * 1. Why do you call it "Exuberant Ctags"? @@ -354,7 +354,7 @@ And replace the configuration of step 3 with this: - :set tags=./tags,./../tags,./../../tags,./../../../tags,tags + :set tags=./tags;$HOME,tags As a caveat, it should be noted that step 2 builds a global tag file whose file names will be relative to the directory in which the global tag file Index: eiffel.c =================================================================== --- eiffel.c (.../tags/ctags-5.8) +++ eiffel.c (.../trunk) @@ -53,13 +53,15 @@ */ typedef enum eKeywordId { KEYWORD_NONE = -1, - KEYWORD_alias, KEYWORD_all, KEYWORD_and, KEYWORD_as, KEYWORD_assign, + KEYWORD_alias, KEYWORD_all, KEYWORD_and, + KEYWORD_as, KEYWORD_assign, KEYWORD_attached, KEYWORD_check, KEYWORD_class, KEYWORD_convert, KEYWORD_create, KEYWORD_creation, KEYWORD_Current, - KEYWORD_debug, KEYWORD_deferred, KEYWORD_do, KEYWORD_else, - KEYWORD_elseif, KEYWORD_end, KEYWORD_ensure, KEYWORD_expanded, - KEYWORD_export, KEYWORD_external, KEYWORD_false, KEYWORD_feature, - KEYWORD_from, KEYWORD_frozen, KEYWORD_if, KEYWORD_implies, + KEYWORD_debug, KEYWORD_deferred, KEYWORD_detachable, KEYWORD_do, + KEYWORD_else, KEYWORD_elseif, KEYWORD_end, KEYWORD_ensure, + KEYWORD_expanded, KEYWORD_export, KEYWORD_external, + KEYWORD_false, KEYWORD_feature, KEYWORD_from, KEYWORD_frozen, + KEYWORD_if, KEYWORD_implies, KEYWORD_indexing, KEYWORD_infix, KEYWORD_inherit, KEYWORD_inspect, KEYWORD_invariant, KEYWORD_is, KEYWORD_like, KEYWORD_local, KEYWORD_loop, KEYWORD_not, KEYWORD_obsolete, KEYWORD_old, KEYWORD_once, @@ -154,6 +156,7 @@ { "and", KEYWORD_and }, { "as", KEYWORD_as }, { "assign", KEYWORD_assign }, + { "attached", KEYWORD_attached }, { "check", KEYWORD_check }, { "class", KEYWORD_class }, { "convert", KEYWORD_convert }, @@ -162,6 +165,7 @@ { "current", KEYWORD_Current }, { "debug", KEYWORD_debug }, { "deferred", KEYWORD_deferred }, + { "detachable", KEYWORD_detachable }, { "do", KEYWORD_do }, { "else", KEYWORD_else }, { "elseif", KEYWORD_elseif }, @@ -870,7 +874,9 @@ } else { - if (isKeyword (id, KEYWORD_expanded)) + if (isKeyword (id, KEYWORD_attached) || + isKeyword (id, KEYWORD_detachable) || + isKeyword (id, KEYWORD_expanded)) { copyToken (id, token); readToken (token);