diff --git a/ctags/Makefile.am b/ctags/Makefile.am index 806bff0054..3b4e191b14 100644 --- a/ctags/Makefile.am +++ b/ctags/Makefile.am @@ -11,7 +11,6 @@ noinst_LTLIBRARIES = libctags.la parsers = \ parsers/abaqus.c \ parsers/abc.c \ - parsers/actionscript.c \ parsers/asciidoc.c \ parsers/asm.c \ parsers/basic.c \ @@ -22,6 +21,7 @@ parsers = \ parsers/diff.c \ parsers/docbook.c \ parsers/erlang.c \ + parsers/flex.c \ parsers/fortran.c \ parsers/go.c \ parsers/haskell.c \ diff --git a/ctags/main/parsers.h b/ctags/main/parsers.h index 254278fef4..b261f80e88 100644 --- a/ctags/main/parsers.h +++ b/ctags/main/parsers.h @@ -50,7 +50,7 @@ GLSLParser, \ MatLabParser, \ ValaParser, \ - ActionScriptParser, \ + FlexParser, \ NsisParser, \ MarkdownParser, \ Txt2tagsParser, \ diff --git a/ctags/parsers/actionscript.c b/ctags/parsers/actionscript.c deleted file mode 100644 index 6006cb4a7f..0000000000 --- a/ctags/parsers/actionscript.c +++ /dev/null @@ -1,99 +0,0 @@ -/* -* $Id: actionscript.c,v 1.1 2004/01/03 03:59:19 svoisen Exp $ -* -* Original file copyright (c) 2004, Sean Voisen -* -* Modified October 8, 2007 By Mike Fahy (VeryVito) of www.turdhead.com -* - Added initial AS3 support -* - Threw in some "TODO" and "NOTE" bits -* -* Modified October 9, 2007 By Ali Rantakari of hasseg.org: -* - Added more allowed AS3 attribute keywords (override, final, internal -* etc...) for classes, getters & setters, variables -* - Allowed varying versions of "note" and "todo" spellings -* - Allowed points (.) in package names so that they would display the -* whole package name instead of just the first level -* - Added interfaces matching support -* - Reformatted some name parameters: -* - Getters and setters: display either "get" or "set" in front -* of the property name -* - Todos & notes: made the name be the text that comes after the -* "todo" or "note" text -* - Variables: Moved the variable type after the name and separated -* them with " : " according to ActionScript syntax -* Modified March 6, 2009 by Chris Macksey (cmacksey@users.sourceforge.net) -* - Tweaked to work better with Geany -* -* 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 ActionScript language -* files. -*/ - -/* -* INCLUDE FILES -*/ -#include "general.h" /* must always come first */ -#include "parse.h" -#include "routines.h" - -static tagRegexTable actionscriptTagRegexTable[] = { - /* Functions */ - {"^[ \t]*[(private|public|static|protected|internal|final|override)( \t)]*function[ \t]+([A-Za-z0-9_]+)[ \t]*\\(([^\\{]*)", - "\\1 (\\2", "f,function,functions,methods", NULL, NULL}, - - /* Getters and setters */ - {"^[ \t]*[(public|static|internal|final|override)( \t)]*function[ \t]+(set|get)[ \t]+([A-Za-z0-9_]+)[ \t]*\\(", - "\\2 \\1", "l,field,fields", NULL, NULL}, - - /* Variables */ - {"^[ \t]*[(private|public|static|protected|internal)( \t)]*var[ \t]+([A-Za-z0-9_]+)([ \t]*\\:[ \t]*([A-Za-z0-9_]+))*[ \t]*", - "\\1 \\: \\3", "v,variable,variables", NULL, NULL}, - - /* Constants */ - {"^[ \t]*[(private|public|static|protected|internal)( \t)]*const[ \t]+([A-Za-z0-9_]+)([ \t]*\\:[ \t]*([A-Za-z0-9_]+))*[ \t]*", - "\\1 : \\3", "m,macro,macros", NULL, NULL}, - - /* Classes */ - {"^[ \t]*[(private|public|static|dynamic|final|internal)( \t)]*class[ \t]+([A-Za-z0-9_]+)[ \t]*([^\\{]*)", - "\\1 (\\2)", "c,class,classes", NULL, NULL}, - - /* Interfaces */ - {"^[ \t]*[(private|public|static|dynamic|final|internal)( \t)]*interface[ \t]+([A-Za-z0-9_]+)[ \t]*([^\\{]*)", - "\\1 (\\2)", "i,interface,interfaces", NULL, NULL}, - - /* Packages */ - {"^[ \t]*[(private|public|static)( \t)]*package[ \t]+([A-Za-z0-9_.]+)[ \t]*", - "\\1", "p,package", NULL, NULL}, - - /* Notes */ - {"\\/\\/[ \t]*(NOTE|note|Note)[ \t]*\\:*(.*)", - "\\2", "o,other", NULL, NULL}, - - /* Todos */ - {"\\/\\/[ \t]*(TODO|todo|ToDo|Todo)[ \t]*\\:*(.*)", - "\\2", "o,other", NULL, NULL}, - - /* Prototypes (Put this in for AS1 compatibility...) */ - {".*\\.prototype\\.([A-Za-z0-9 ]+)[ \t]*\\=([ \t]*)function( [ \t]?)*\\(", - "\\1", "r,prototype", NULL, NULL} -}; - -/* -* FUNCTION DEFINITIONS -* -*/ - -/* Create parser definition structure */ -extern parserDefinition* ActionScriptParser (void) -{ - static const char *const extensions [] = { "as", NULL }; - parserDefinition *const def = parserNew ("ActionScript"); - def->extensions = extensions; - def->tagRegexTable = actionscriptTagRegexTable; - def->tagRegexCount = ARRAY_SIZE (actionscriptTagRegexTable); - def->method = METHOD_NOT_CRAFTED|METHOD_REGEX; - return def; -} - diff --git a/ctags/parsers/flex.c b/ctags/parsers/flex.c new file mode 100644 index 0000000000..379ec854ec --- /dev/null +++ b/ctags/parsers/flex.c @@ -0,0 +1,2636 @@ +/* + * Copyright (c) 2008, David Fishburn + * + * This source code is released for free distribution under the terms of the + * GNU General Public License version 2 or (at your option) any later version. + * + * This module contains functions for generating tags for Adobe languages. + * There are a number of different ones, but this will begin with: + * Flex + * MXML files (*.mMacromedia XML) + * ActionScript files (*.as) + * + * The ActionScript code was copied from the JavaScript parser, with some + * adaptations e.g. for classes and type specifiers. + * + * Flex 3 language reference + * http://livedocs.adobe.com/flex/3/langref/index.html + * https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/language-elements.html + * https://www.adobe.com/devnet/actionscript/learning/as3-fundamentals/packages.html + */ + +/* + * INCLUDE FILES + */ +#include "general.h" /* must always come first */ +#include /* to define isalpha () */ +#ifdef DEBUG +#include +#endif + +#include "debug.h" +#include "entry.h" +#include "keyword.h" +#include "parse.h" +#include "read.h" +#include "routines.h" +#include "vstring.h" +#include "strlist.h" + +/* + * MACROS + */ +#define isType(token,t) (bool) ((token)->type == (t)) +#define isKeyword(token,k) (bool) ((token)->keyword == (k)) +#define isEOF(token) (isType ((token), TOKEN_EOF)) +#define isIdentChar(c) \ + (isalpha (c) || isdigit (c) || (c) == '$' || \ + (c) == '@' || (c) == '_' || (c) == '#' || \ + (c) >= 0x80) + +/* + * DATA DECLARATIONS + */ + +/* + * Tracks class and function names already created + */ +static stringList *ClassNames; +static stringList *FunctionNames; + +/* Used to specify type of keyword. +*/ +enum eKeywordId { + KEYWORD_function, + KEYWORD_capital_function, + KEYWORD_object, + KEYWORD_capital_object, + KEYWORD_prototype, + KEYWORD_var, + KEYWORD_const, + KEYWORD_new, + KEYWORD_this, + KEYWORD_for, + KEYWORD_while, + KEYWORD_do, + KEYWORD_if, + KEYWORD_else, + KEYWORD_switch, + KEYWORD_try, + KEYWORD_catch, + KEYWORD_finally, + KEYWORD_return, + KEYWORD_public, + KEYWORD_private, + KEYWORD_protected, + KEYWORD_internal, + KEYWORD_final, + KEYWORD_native, + KEYWORD_dynamic, + KEYWORD_class, + KEYWORD_interface, + KEYWORD_package, + KEYWORD_extends, + KEYWORD_static, + KEYWORD_implements, + KEYWORD_get, + KEYWORD_set, + KEYWORD_import, + KEYWORD_id, + KEYWORD_name, + KEYWORD_script, + KEYWORD_cdata, + KEYWORD_mx, + KEYWORD_fx, + KEYWORD_override +}; +typedef int keywordId; /* to allow KEYWORD_NONE */ + +typedef enum eTokenType { + TOKEN_UNDEFINED, + TOKEN_EOF, + TOKEN_CHARACTER, + TOKEN_CLOSE_PAREN, + TOKEN_SEMICOLON, + TOKEN_COLON, + TOKEN_COMMA, + TOKEN_KEYWORD, + TOKEN_OPEN_PAREN, + 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_OPEN_NAMESPACE, + TOKEN_POSTFIX_OPERATOR, + TOKEN_STAR, + TOKEN_BINARY_OPERATOR +} tokenType; + +typedef struct sTokenInfo { + tokenType type; + keywordId keyword; + vString * string; + vString * scope; + unsigned long lineNumber; + MIOPos filePosition; + int nestLevel; + bool ignoreTag; + bool isClass; +} tokenInfo; + +/* + * DATA DEFINITIONS + */ +static tokenType LastTokenType; +static tokenInfo *NextToken; + +static langType Lang_flex; + +typedef enum { + FLEXTAG_FUNCTION, + FLEXTAG_CLASS, + FLEXTAG_INTERFACE, + FLEXTAG_PACKAGE, + FLEXTAG_METHOD, + FLEXTAG_PROPERTY, + FLEXTAG_VARIABLE, + FLEXTAG_LOCALVAR, + FLEXTAG_CONST, + FLEXTAG_IMPORT, + FLEXTAG_MXTAG, + FLEXTAG_COUNT +} flexKind; + +typedef enum { + FLEX_IMPORT_ROLE_IMPORTED, +} flexImportRole; + +static roleDefinition FlexImportRoles [] = { + { true, "import", "imports" }, +}; + +static kindDefinition FlexKinds [] = { + { true, 'f', "function", "functions" }, + { true, 'c', "class", "classes" }, + { true, 'i', "interface", "interfaces" }, + { true, 'P', "package", "packages" }, + { true, 'm', "method", "methods" }, + { true, 'p', "property", "properties" }, + { true, 'v', "variable", "global variables" }, + { /*false*/ true, 'l', "localvar", "local variables" }, + { true, 'C', "constant", "constants" }, + { true, 'I', "import", "imports", + .referenceOnly = true, ATTACH_ROLES (FlexImportRoles) }, + { true, 'x', "mxtag", "mxtags" } +}; + +/* Used to determine whether keyword is valid for the token language and + * what its ID is. + */ +static const keywordTable FlexKeywordTable [] = { + /* keyword keyword ID */ + { "function", KEYWORD_function }, + { "Function", KEYWORD_capital_function }, + { "object", KEYWORD_object }, + { "Object", KEYWORD_capital_object }, + { "prototype", KEYWORD_prototype }, + { "var", KEYWORD_var }, + { "const", KEYWORD_const }, + { "new", KEYWORD_new }, + { "this", KEYWORD_this }, + { "for", KEYWORD_for }, + { "while", KEYWORD_while }, + { "do", KEYWORD_do }, + { "if", KEYWORD_if }, + { "else", KEYWORD_else }, + { "switch", KEYWORD_switch }, + { "try", KEYWORD_try }, + { "catch", KEYWORD_catch }, + { "finally", KEYWORD_finally }, + { "return", KEYWORD_return }, + { "public", KEYWORD_public }, + { "private", KEYWORD_private }, + { "protected", KEYWORD_protected }, + { "internal", KEYWORD_internal }, + { "final", KEYWORD_final }, + { "native", KEYWORD_native }, + { "dynamic", KEYWORD_dynamic }, + { "class", KEYWORD_class }, + { "interface", KEYWORD_interface }, + { "package", KEYWORD_package }, + { "extends", KEYWORD_extends }, + { "static", KEYWORD_static }, + { "implements", KEYWORD_implements }, + { "get", KEYWORD_get }, + { "set", KEYWORD_set }, + { "import", KEYWORD_import }, + { "id", KEYWORD_id }, + { "name", KEYWORD_name }, + { "script", KEYWORD_script }, + { "cdata", KEYWORD_cdata }, + { "mx", KEYWORD_mx }, + { "fx", KEYWORD_fx }, + { "override", KEYWORD_override } +}; + +/* + * FUNCTION DEFINITIONS + */ + +/* Recursive functions */ +static void parseFunction (tokenInfo *const token); +static bool parseBlock (tokenInfo *const token, const vString *const parentScope); +static bool parseLine (tokenInfo *const token); +static bool parseActionScript (tokenInfo *const token, bool readNext); +static bool parseMXML (tokenInfo *const token); + +static tokenInfo *newToken (void) +{ + tokenInfo *const token = xMalloc (1, tokenInfo); + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + token->string = vStringNew (); + token->scope = vStringNew (); + token->nestLevel = 0; + token->isClass = false; + token->ignoreTag = false; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + + return token; +} + +static void deleteToken (tokenInfo *const token) +{ + vStringDelete (token->string); + vStringDelete (token->scope); + eFree (token); +} + +static void copyToken (tokenInfo *const dest, tokenInfo *const src, + bool const include_non_read_info) +{ + dest->lineNumber = src->lineNumber; + dest->filePosition = src->filePosition; + dest->type = src->type; + dest->keyword = src->keyword; + dest->isClass = src->isClass; + vStringCopy(dest->string, src->string); + if (include_non_read_info) + { + dest->nestLevel = src->nestLevel; + vStringCopy(dest->scope, src->scope); + } +} + +/* + * Tag generation functions + */ + +static vString *buildQualifiedName (const tokenInfo *const token) +{ + vString *qualified = vStringNew (); + + if (vStringLength (token->scope) > 0) + { + vStringCopy (qualified, token->scope); + vStringPut (qualified, '.'); + } + vStringCat (qualified, token->string); + + return qualified; +} + +static void makeConstTag (tokenInfo *const token, const flexKind kind) +{ + if (FlexKinds [kind].enabled && ! token->ignoreTag ) + { + const char *const name = vStringValue (token->string); + tagEntryInfo e; + int role = ROLE_INDEX_DEFINITION; + + /* Geany diff: reftags are not enabled but should be + if (kind == FLEXTAG_IMPORT) + role = FLEX_IMPORT_ROLE_IMPORTED; + */ + + initRefTagEntry (&e, name, kind, role); + + e.lineNumber = token->lineNumber; + e.filePosition = token->filePosition; + + if ( vStringLength(token->scope) > 0 ) + { + /* FIXME: proper parent type */ + flexKind parent_kind = FLEXTAG_CLASS; + + /* + * If we're creating a function (and not a method), + * guess we're inside another function + */ + if (kind == FLEXTAG_FUNCTION) + parent_kind = FLEXTAG_FUNCTION; + /* mxtags can only be nested inside other mxtags */ + else if (kind == FLEXTAG_MXTAG) + parent_kind = kind; + + e.extensionFields.scopeKindIndex = parent_kind; + e.extensionFields.scopeName = vStringValue (token->scope); + } + + makeTagEntry (&e); + + /* make qualified tags for compatibility if requested */ + if (isXtagEnabled (XTAG_QUALIFIED_TAGS)) + { + vString *qualified = buildQualifiedName (token); + + markTagExtraBit (&e, XTAG_QUALIFIED_TAGS); + e.name = vStringValue (qualified); + makeTagEntry (&e); + vStringDelete (qualified); + } + } +} + +static void makeFlexTag (tokenInfo *const token, flexKind kind) +{ + if (FlexKinds [kind].enabled && ! token->ignoreTag ) + { + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n makeFlexTag start: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + if (kind == FLEXTAG_FUNCTION && token->isClass ) + { + kind = FLEXTAG_METHOD; + } + makeConstTag (token, kind); + } +} + +static void makeClassTag (tokenInfo *const token) +{ + if ( ! token->ignoreTag ) + { + vString *fulltag = buildQualifiedName (token); + + if ( ! stringListHas(ClassNames, vStringValue (fulltag)) ) + { + stringListAdd (ClassNames, vStringNewCopy (fulltag)); + makeFlexTag (token, FLEXTAG_CLASS); + } + vStringDelete (fulltag); + } +} + +static void makeMXTag (tokenInfo *const token) +{ + if ( ! token->ignoreTag ) + { + makeFlexTag (token, FLEXTAG_MXTAG); + } +} + +static void makeFunctionTag (tokenInfo *const token) +{ + if ( ! token->ignoreTag ) + { + vString *fulltag = buildQualifiedName (token); + + if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) ) + { + stringListAdd (FunctionNames, vStringNewCopy (fulltag)); + makeFlexTag (token, FLEXTAG_FUNCTION); + } + vStringDelete (fulltag); + } +} + +/* + * Parsing functions + */ + +static void parseString (vString *const string, const int delimiter) +{ + bool end = false; + while (! end) + { + int c = getcFromInputFile (); + if (c == EOF) + end = true; + else if (c == '\\') + { + c = getcFromInputFile(); /* This maybe a ' or ". */ + vStringPut(string, c); + } + else if (c == delimiter) + end = true; + else + vStringPut (string, c); + } +} + +/* Read a C identifier beginning with "firstChar" and places it into + * "name". + */ +static void parseIdentifier (vString *const string, const int firstChar) +{ + int c = firstChar; + Assert (isIdentChar (c)); + do + { + vStringPut (string, c); + c = getcFromInputFile (); + } while (isIdentChar (c)); + ungetcToInputFile (c); /* unget non-identifier character */ +} + +static void readTokenFull (tokenInfo *const token, bool include_newlines) +{ + int c; + int i; + bool newline_encountered = false; + + /* if we've got a token held back, emit it */ + if (NextToken) + { + copyToken (token, NextToken, false); + deleteToken (NextToken); + NextToken = NULL; + return; + } + + token->type = TOKEN_UNDEFINED; + token->keyword = KEYWORD_NONE; + vStringClear (token->string); + +getNextChar: + i = 0; + do + { + c = getcFromInputFile (); + if (include_newlines && (c == '\r' || c == '\n')) + newline_encountered = true; + i++; + } + while (c == '\t' || c == ' ' || c == '\r' || c == '\n'); + + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + + switch (c) + { + case EOF: token->type = TOKEN_EOF; 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 '+': + case '-': + { + int d = getcFromInputFile (); + if (d == c) /* ++ or -- */ + token->type = TOKEN_POSTFIX_OPERATOR; + else + { + ungetcToInputFile (d); + token->type = TOKEN_BINARY_OPERATOR; + } + break; + } + + case '*': + token->type = TOKEN_STAR; + break; + case '%': + case '^': + case '|': + case '&': + token->type = TOKEN_BINARY_OPERATOR; + break; + + case '\'': + case '"': + token->type = TOKEN_STRING; + parseString (token->string, c); + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + + case '\\': + c = getcFromInputFile (); + if (c != '\\' && c != '"' && !isspace (c)) + ungetcToInputFile (c); + token->type = TOKEN_CHARACTER; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + + case '/': + { + int d = getcFromInputFile (); + if ( (d != '*') && /* is this the start of a comment? */ + (d != '/') && /* is a one line comment? */ + (d != '>') ) /* is this a close XML tag? */ + { + ungetcToInputFile (d); + token->type = TOKEN_FORWARD_SLASH; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + } + else + { + if (d == '*') + { + do + { + skipToCharacterInInputFile ('*'); + c = getcFromInputFile (); + if (c == '/') + break; + else + ungetcToInputFile (c); + } while (c != EOF); + goto getNextChar; + } + else if (d == '/') /* is this the start of a comment? */ + { + skipToCharacterInInputFile ('\n'); + /* if we care about newlines, put it back so it is seen */ + if (include_newlines) + ungetcToInputFile ('\n'); + goto getNextChar; + } + else if (d == '>') /* is this the start of a comment? */ + { + token->type = TOKEN_CLOSE_SGML; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + } + } + break; + } + + case '<': + { + /* + * An XML comment looks like this + * + */ + int d = getcFromInputFile (); + + 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 != 'f' ) && /* is this the start of a fx tag */ + (d != 's' ) ) /* is this the start of a spark tag */ + { + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + if (d == '!') + { + int e = getcFromInputFile (); + if ( e != '-' ) /* is this the start of a comment? */ + { + ungetcToInputFile (e); + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + } + else + { + if (e == '-') + { + int f = getcFromInputFile (); + if ( f != '-' ) /* is this the start of a comment? */ + { + ungetcToInputFile (f); + ungetcToInputFile (e); + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + } + else + { + if (f == '-') + { + do + { + skipToCharacterInInputFile ('-'); + c = getcFromInputFile (); + if (c == '-') + { + d = getcFromInputFile (); + if (d == '>') + break; + else + { + ungetcToInputFile (d); + ungetcToInputFile (c); + } + break; + } + else + ungetcToInputFile (c); + } while (c != EOF && c != '\0'); + goto getNextChar; + } + } + } + } + } + else if (d == 'm' || d == 'f' || d == 's' ) + { + int e = getcFromInputFile (); + if ( (d == 'm' || d == 'f') && e != 'x' ) /* continuing an mx or fx tag */ + { + ungetcToInputFile (e); + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + if ( (d == 'm' || d == 'f') && e == 'x' ) + { + int f = getcFromInputFile (); + if ( f != ':' ) /* start of the tag */ + { + ungetcToInputFile (f); + ungetcToInputFile (e); + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + token->type = TOKEN_OPEN_MXML; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + } + if ( d == 's' && e == ':') /* continuing a spark tag */ + { + token->type = TOKEN_OPEN_MXML; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + ungetcToInputFile (e); + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + } + } + else if (d == '/') + { + int e = getcFromInputFile (); + if ( !(e == 'm' || e == 'f' || e == 's' )) + { + ungetcToInputFile (e); + ungetcToInputFile (d); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + int f = getcFromInputFile (); + if ( (e == 'm' || e == 'f') && f != 'x' ) /* continuing an mx or fx tag */ + { + ungetcToInputFile (f); + ungetcToInputFile (e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + if (f == 'x') + { + int g = getcFromInputFile (); + if ( g != ':' ) /* is this the start of a comment? */ + { + ungetcToInputFile (g); + ungetcToInputFile (f); + ungetcToInputFile (e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + token->type = TOKEN_CLOSE_MXML; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + } + if ( e == 's' && f == ':') /* continuing a spark tag */ + { + token->type = TOKEN_CLOSE_MXML; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + else + { + ungetcToInputFile (f); + ungetcToInputFile (e); + token->type = TOKEN_LESS_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + } + } + } + } + } + break; + } + + case '>': + token->type = TOKEN_GREATER_THAN; + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + break; + + case '!': + token->type = TOKEN_EXCLAMATION; + /*token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition ();*/ + break; + + default: + if (! isIdentChar (c)) + token->type = TOKEN_UNDEFINED; + else + { + parseIdentifier (token->string, c); + token->lineNumber = getInputLineNumber (); + token->filePosition = getInputFilePosition (); + token->keyword = lookupCaseKeyword (vStringValue (token->string), Lang_flex); + if (isKeyword (token, KEYWORD_NONE)) + token->type = TOKEN_IDENTIFIER; + else + token->type = TOKEN_KEYWORD; + } + break; + } + + if (include_newlines && newline_encountered) + { + /* This isn't strictly correct per the standard, but following the + * real rules means understanding all statements, and that's not + * what the parser currently does. What we do here is a guess, by + * avoiding inserting semicolons that would make the statement on + * the left or right obviously invalid. Hopefully this should not + * have false negatives (e.g. should not miss insertion of a semicolon) + * but might have false positives (e.g. it will wrongfully emit a + * semicolon sometimes, i.e. for the newline in "foo\n(bar)"). + * This should however be mostly harmless as we only deal with + * newlines in specific situations where we know a false positive + * wouldn't hurt too bad. */ + + /* these already end a statement, so no need to duplicate it */ + #define IS_STMT_SEPARATOR(t) ((t) == TOKEN_SEMICOLON || \ + (t) == TOKEN_EOF || \ + (t) == TOKEN_COMMA || \ + (t) == TOKEN_OPEN_CURLY) + /* these cannot be the start or end of a statement */ + #define IS_BINARY_OPERATOR(t) ((t) == TOKEN_EQUAL_SIGN || \ + (t) == TOKEN_COLON || \ + (t) == TOKEN_PERIOD || \ + (t) == TOKEN_STAR || \ + (t) == TOKEN_FORWARD_SLASH || \ + (t) == TOKEN_QUESTION_MARK || \ + (t) == TOKEN_LESS_THAN || \ + (t) == TOKEN_GREATER_THAN || \ + (t) == TOKEN_BINARY_OPERATOR) + + if (! IS_STMT_SEPARATOR(LastTokenType) && + ! IS_STMT_SEPARATOR(token->type) && + ! IS_BINARY_OPERATOR(LastTokenType) && + ! IS_BINARY_OPERATOR(token->type) && + /* these cannot be followed by a semicolon */ + ! (LastTokenType == TOKEN_OPEN_PAREN || + LastTokenType == TOKEN_OPEN_SQUARE)) + { + /* hold the token... */ + Assert (NextToken == NULL); + NextToken = newToken (); + copyToken (NextToken, token, false); + + /* ...and emit a semicolon instead */ + token->type = TOKEN_SEMICOLON; + token->keyword = KEYWORD_NONE; + vStringClear (token->string); + } + + #undef IS_STMT_SEPARATOR + #undef IS_BINARY_OPERATOR + } + + LastTokenType = token->type; +} + +static void readToken (tokenInfo *const token) +{ + readTokenFull (token, false); +} + +/* + * Token parsing functions + */ + +static void skipArgumentList (tokenInfo *const token, bool include_newlines) +{ + int nest_level = 0; + + if (isType (token, TOKEN_OPEN_PAREN)) /* arguments? */ + { + nest_level++; + while (nest_level > 0 && ! isType (token, TOKEN_EOF)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_PAREN)) + nest_level++; + else if (isType (token, TOKEN_CLOSE_PAREN)) + nest_level--; + } + readTokenFull (token, include_newlines); + } +} + +static void skipArrayList (tokenInfo *const token, bool include_newlines) +{ + int nest_level = 0; + + /* + * Handle square brackets + * var name[1] + * So we must check for nested open and closing square brackets + */ + + if (isType (token, TOKEN_OPEN_SQUARE)) /* arguments? */ + { + nest_level++; + while (nest_level > 0 && ! isType (token, TOKEN_EOF)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_SQUARE)) + nest_level++; + else if (isType (token, TOKEN_CLOSE_SQUARE)) + nest_level--; + } + readTokenFull (token, include_newlines); + } +} + +static void addContext (tokenInfo* const parent, const tokenInfo* const child) +{ + if (vStringLength (parent->string) > 0) + { + vStringPut (parent->string, '.'); + } + vStringCat (parent->string, child->string); +} + +static void addToScope (tokenInfo* const token, const vString* const extra) +{ + if (vStringLength (token->scope) > 0) + { + vStringPut (token->scope, '.'); + } + vStringCat (token->scope, extra); +} + +/* + * Scanning functions + */ + +static bool findCmdTerm (tokenInfo *const token, bool include_newlines, + bool include_commas) +{ + /* + * Read until we find either a semicolon or closing brace. + * Any nested braces will be handled within. + */ + while (! isType (token, TOKEN_SEMICOLON) && + ! isType (token, TOKEN_CLOSE_CURLY) && + ! (include_commas && isType (token, TOKEN_COMMA)) && + ! isType (token, TOKEN_EOF)) + { + /* Handle nested blocks */ + if ( isType (token, TOKEN_OPEN_CURLY)) + { + parseBlock (token, NULL); + readTokenFull (token, include_newlines); + } + else if ( isType (token, TOKEN_OPEN_PAREN) ) + { + skipArgumentList(token, include_newlines); + } + else if ( isType (token, TOKEN_OPEN_SQUARE) ) + { + skipArrayList(token, include_newlines); + } + else + { + readTokenFull (token, include_newlines); + } + } + + return isType (token, TOKEN_SEMICOLON); +} + +static void parseSwitch (tokenInfo *const token) +{ + /* + * switch (expression) { + * case value1: + * statement; + * break; + * case value2: + * statement; + * break; + * default : statement; + * } + */ + + readToken (token); + + if (isType (token, TOKEN_OPEN_PAREN)) + { + skipArgumentList(token, false); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + parseBlock (token, NULL); + } +} + +static bool parseLoop (tokenInfo *const token) +{ + /* + * 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++; + * } + * while (number<5); + */ + bool is_terminated = true; + + if (isKeyword (token, KEYWORD_for) || isKeyword (token, KEYWORD_while)) + { + readToken(token); + + if (isType (token, TOKEN_OPEN_PAREN)) + { + skipArgumentList(token, false); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + parseBlock (token, NULL); + } + else + { + is_terminated = parseLine(token); + } + } + else if (isKeyword (token, KEYWORD_do)) + { + readToken(token); + + if (isType (token, TOKEN_OPEN_CURLY)) + { + parseBlock (token, NULL); + } + else + { + is_terminated = parseLine(token); + } + + if (is_terminated) + readToken(token); + + if (isKeyword (token, KEYWORD_while)) + { + readToken(token); + + if (isType (token, TOKEN_OPEN_PAREN)) + { + skipArgumentList(token, true); + } + if (! isType (token, TOKEN_SEMICOLON)) + { + /* oddly enough, `do {} while (0) var foo = 42` is perfectly + * valid AS, so explicitly handle the remaining of the line + * for the sake of the root scope handling (as parseActionScript() + * always advances a token not to ever get stuck) */ + is_terminated = parseLine(token); + } + } + } + + return is_terminated; +} + +static bool parseIf (tokenInfo *const token) +{ + bool read_next_token = true; + /* + * If statements have two forms + * if ( ... ) + * one line; + * + * if ( ... ) + * statement; + * else + * statement + * + * if ( ... ) { + * multiple; + * statements; + * } + * + * + * if ( ... ) { + * return elem + * } + * + * This example if correctly written, but the + * else contains only 1 statement without a terminator + * since the function finishes with the closing brace. + * + * function a(flag){ + * if(flag) + * test(1); + * else + * test(2) + * } + * + * TODO: Deal with statements that can optional end + * 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 + * that is possible from all code locations. + * + */ + + readToken (token); + + if (isKeyword (token, KEYWORD_if)) + { + /* + * Check for an "else if" and consume the "if" + */ + readToken (token); + } + + if (isType (token, TOKEN_OPEN_PAREN)) + { + skipArgumentList(token, false); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + parseBlock (token, NULL); + } + else + { + /* The next token should only be read if this statement had its own + * terminator */ + read_next_token = findCmdTerm (token, true, false); + } + return read_next_token; +} + +static bool parseImport (tokenInfo *const token) +{ + if (! isKeyword (token, KEYWORD_import)) + return false; + + readToken (token); + + if (isType (token, TOKEN_IDENTIFIER)) + { + tokenInfo *const name = newToken (); + + copyToken (name, token, true); + readToken (token); + while (isType (token, TOKEN_PERIOD)) + { + vStringPut (name->string, '.'); + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + vStringCat (name->string, token->string); + else if (isType (token, TOKEN_STAR)) + vStringPut (name->string, '*'); + if (isType (token, TOKEN_IDENTIFIER) || isType (token, TOKEN_STAR)) + readToken (token); + } + + makeFlexTag (name, FLEXTAG_IMPORT); + deleteToken (name); + } + + return isType (token, TOKEN_SEMICOLON); +} + +static void parseFunction (tokenInfo *const token) +{ + tokenInfo *const name = newToken (); + flexKind kind = FLEXTAG_FUNCTION; + + /* + * This deals with these formats + * private static function ioErrorHandler( event:IOErrorEvent ):void { + * public function get prop():String {} + * public function set prop(param:String):void {} + */ + + if ( isKeyword(token, KEYWORD_function) ) + { + readToken (token); + } + + /* getter and setter */ + if (isKeyword (token, KEYWORD_get) || + isKeyword (token, KEYWORD_set)) + { + kind = FLEXTAG_PROPERTY; + readToken (token); + } + + copyToken (name, token, true); + /* Add scope in case this is an INNER function + addToScope(name, token->scope); + */ + + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseFunction: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseFunction: name isClass:%d scope:%s name:%s\n" + , name->isClass + , vStringValue(name->scope) + , vStringValue(name->string) + ); + ); + + readToken (token); + + if ( isType (token, TOKEN_OPEN_PAREN) ) + skipArgumentList(token, false); + + if ( isType (token, TOKEN_COLON) ) + { + /* + * function fname ():ReturnType + */ + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + readToken (token); + } + + if ( isType (token, TOKEN_OPEN_CURLY) ) + { + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseFunction end: name isClass:%d scope:%s name:%s\n" + , name->isClass + , vStringValue(name->scope) + , vStringValue(name->string) + ); + ); + parseBlock (token, name->string); + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseFunction end2: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseFunction end2: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseFunction end3: name isClass:%d scope:%s name:%s\n" + , name->isClass + , vStringValue(name->scope) + , vStringValue(name->string) + ); + ); + if (kind == FLEXTAG_FUNCTION) + makeFunctionTag (name); + else + makeFlexTag (name, kind); + } + + findCmdTerm (token, false, false); + + deleteToken (name); +} + +/* Parses a block surrounded by curly braces. + * @p parentScope is the scope name for this block, or NULL for unnamed scopes */ +static bool parseBlock (tokenInfo *const token, const vString *const parentScope) +{ + bool read_next_token = true; + vString * saveScope = vStringNew (); + + vStringCopy (saveScope, token->scope); + if (parentScope) + { + addToScope (token, parentScope); + token->nestLevel++; + } + + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseBlock start: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + /* + * Make this routine a bit more forgiving. + * If called on an open_curly advance it + */ + if (isType (token, TOKEN_OPEN_CURLY)) + readToken(token); + + if (! isType (token, TOKEN_CLOSE_CURLY)) + { + /* + * Read until we find the closing brace, + * any nested braces will be handled within + */ + do + { + if (isType (token, TOKEN_OPEN_CURLY)) + { + /* Handle nested blocks */ + parseBlock (token, NULL); + } + else + { + /* + * It is possible for a line to have no terminator + * if the following line is a closing brace. + * parseLine will detect this case and indicate + * whether we should read an additional token. + */ + read_next_token = parseLine (token); + } + + /* + * Always read a new token unless we find a statement without + * a ending terminator + */ + if( read_next_token ) + readToken(token); + + /* + * If we find a statement without a terminator consider the + * block finished, otherwise the stack will be off by one. + */ + } while (! isType (token, TOKEN_EOF) && + ! isType (token, TOKEN_CLOSE_CURLY) && read_next_token); + } + + vStringCopy(token->scope, saveScope); + vStringDelete(saveScope); + if (parentScope) + token->nestLevel--; + + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseBlock end: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + return false; +} + +static void parseMethods (tokenInfo *const token, const tokenInfo *const class) +{ + tokenInfo *const name = newToken (); + vString *saveScope = vStringNew (); + + vStringCopy (saveScope, token->scope); + addToScope (token, class->string); + + /* + * This deals with these formats + * validProperty : 2, + * validMethod : function(a,b) {} + * 'validMethod2' : function(a,b) {} + * container.dirtyTab = {'url': false, 'title':false, 'snapshot':false, '*': false} + */ + + do + { + readToken (token); + if (isType (token, TOKEN_CLOSE_CURLY)) + { + goto cleanUp; + } + + if (isType (token, TOKEN_STRING) || isKeyword(token, KEYWORD_NONE)) + { + copyToken (name, token, true); + + readToken (token); + if ( isType (token, TOKEN_COLON) ) + { + readToken (token); + if ( isKeyword (token, KEYWORD_function) ) + { + readToken (token); + if ( isType (token, TOKEN_OPEN_PAREN) ) + { + skipArgumentList(token, false); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + makeFlexTag (name, FLEXTAG_METHOD); + parseBlock (token, name->string); + + /* + * Read to the closing curly, check next + * token, if a comma, we must loop again + */ + readToken (token); + } + } + else + { + makeFlexTag (name, FLEXTAG_PROPERTY); + + /* + * Read the next token, if a comma + * we must loop again + */ + readToken (token); + } + } + } + } while ( isType(token, TOKEN_COMMA)); + + findCmdTerm (token, false, false); + +cleanUp: + vStringCopy (token->scope, saveScope); + vStringDelete (saveScope); + deleteToken (name); +} + +static bool parseVar (tokenInfo *const token, bool is_public) +{ + tokenInfo *const name = newToken (); + bool is_terminated = true; + flexKind kind = is_public ? FLEXTAG_VARIABLE : FLEXTAG_LOCALVAR; + + /* + * Variables are defined as: + * private static var lastFaultMessage:Date = new Date( 0 ); + * private static var webRequests:ArrayCollection = new ArrayCollection(); + */ + + if ( isKeyword(token, KEYWORD_var) ) + { + readToken(token); + } + else if (isKeyword(token, KEYWORD_const)) + { + kind = FLEXTAG_CONST; + readToken(token); + } + + /* Variable name */ + copyToken (name, token, true); + readToken(token); + + if ( isType (token, TOKEN_COLON) ) + { + /* + * var vname ():DataType = new Date(); + * var vname ():DataType; + */ + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + readToken (token); + } + + is_terminated = findCmdTerm (token, true, false); + + if ( isType (token, TOKEN_SEMICOLON) ) + { + makeFlexTag (name, kind); + } + + deleteToken (name); + + return is_terminated; +} + +static void parsePackage (tokenInfo *const token) +{ + tokenInfo *name = NULL; + + if (isKeyword (token, KEYWORD_package)) + readToken(token); + + /* name is optional and can be qualified */ + if (isType (token, TOKEN_IDENTIFIER)) + { + name = newToken (); + copyToken (name, token, true); + readToken (token); + + while (isType (token, TOKEN_PERIOD)) + { + vStringPut (name->string, '.'); + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + { + vStringCat (name->string, token->string); + readToken (token); + } + } + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + if (name) + makeFlexTag (name, FLEXTAG_PACKAGE); + parseBlock (token, name ? name->string : NULL); + } + + if (name) + deleteToken (name); +} + +static bool parseClass (tokenInfo *const token) +{ + tokenInfo *const name = newToken (); + bool saveIsClass = token->isClass; + + /* + * Variables are defined as: + * private static var lastFaultMessage:Date = new Date( 0 ); + * private static var webRequests:ArrayCollection = new ArrayCollection(); + */ + + if ( isKeyword(token, KEYWORD_class) ) + { + readToken(token); + } + + token->isClass = true; + /* Class name */ + copyToken (name, token, true); + readToken(token); + + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseClass start: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + + if (isKeyword (token, KEYWORD_extends)) + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + readToken (token); + } + + if (isKeyword (token, KEYWORD_implements)) + { + do + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + readToken (token); + } + while (isType (token, TOKEN_COMMA)); + } + + if ( isType (token, TOKEN_OPEN_CURLY) ) + { + makeClassTag (name); + parseBlock (token, name->string); + } + + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseClass end: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + token->isClass = saveIsClass; + deleteToken (name); + + return true; +} + +static void parseInterface (tokenInfo *const token) +{ + tokenInfo *const name = newToken (); + bool saveIsClass = token->isClass; + + if (isKeyword(token, KEYWORD_interface)) + readToken(token); + + token->isClass = true; + /* interface name */ + copyToken (name, token, true); + readToken (token); + + /* interfaces can extend multiple interfaces */ + if (isKeyword (token, KEYWORD_extends)) + { + do + { + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + readToken (token); + } + while (isType (token, TOKEN_COMMA)); + } + + if (isType (token, TOKEN_OPEN_CURLY)) + { + makeFlexTag (name, FLEXTAG_INTERFACE); + parseBlock (token, name->string); + } + + token->isClass = saveIsClass; + deleteToken (name); +} + +static bool parseStatement (tokenInfo *const token) +{ + tokenInfo *const name = newToken (); + tokenInfo *const secondary_name = newToken (); + vString * saveScope = vStringNew (); + bool is_public = false; + bool is_class = false; + bool is_terminated = true; + bool is_global = false; + /* bool is_prototype = false; */ + vString * fulltag; + + vStringCopy (saveScope, token->scope); + DebugStatement ( + debugPrintf (DEBUG_PARSE + , "\n parseStatement: token isClass:%d scope:%s name:%s\n" + , token->isClass + , vStringValue(token->scope) + , vStringValue(token->string) + ); + ); + /* + * Functions can be named or unnamed. + * This deals with these formats: + * Function + * validFunctionOne = function(a,b) {} + * testlib.validFunctionFive = function(a,b) {} + * var innerThree = function(a,b) {} + * var innerFour = (a,b) {} + * var D2 = secondary_fcn_name(a,b) {} + * var D3 = new Function("a", "b", "return a+b;"); + * Class + * testlib.extras.ValidClassOne = function(a,b) { + * this.a = a; + * } + * Class Methods + * testlib.extras.ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * ValidClassTwo = function () + * { + * this.validMethodThree = function() {} + * // unnamed method + * this.validMethodFour = () {} + * } + * Database.prototype.validMethodThree = Database_getTodaysDate; + */ + + /* skip attributes */ + while (isKeyword (token, KEYWORD_public) || + isKeyword (token, KEYWORD_protected) || + isKeyword (token, KEYWORD_private) || + isKeyword (token, KEYWORD_override) || + isKeyword (token, KEYWORD_static) || + isKeyword (token, KEYWORD_internal) || + isKeyword (token, KEYWORD_native) || + isKeyword (token, KEYWORD_dynamic) || + isKeyword (token, KEYWORD_final)) + { + if (isKeyword(token, KEYWORD_public)) + is_public = true; + + readToken (token); + } + + if (isType(token, TOKEN_KEYWORD)) + { + switch (token->keyword) + { + case KEYWORD_for: + case KEYWORD_while: + case KEYWORD_do: + is_terminated = parseLoop (token); + break; + case KEYWORD_if: + case KEYWORD_else: + case KEYWORD_try: + case KEYWORD_catch: + case KEYWORD_finally: + /* Common semantics */ + is_terminated = parseIf (token); + break; + case KEYWORD_switch: + parseSwitch (token); + break; + case KEYWORD_package: + parsePackage (token); + goto cleanUp; + break; + case KEYWORD_class: + parseClass (token); + goto cleanUp; + break; + case KEYWORD_interface: + parseInterface (token); + goto cleanUp; + break; + case KEYWORD_function: + parseFunction (token); + goto cleanUp; + break; + case KEYWORD_var: + case KEYWORD_const: + is_terminated = parseVar (token, is_public); + goto cleanUp; + break; + default: + readToken(token); + break; + } + } + +nextVar: + copyToken (name, token, true); + + while (! isType (token, TOKEN_CLOSE_CURLY) && + ! isType (token, TOKEN_SEMICOLON) && + ! isType (token, TOKEN_EQUAL_SIGN) && + ! isType (token, TOKEN_COMMA) && + ! isType (token, TOKEN_EOF)) + { + if (isType (token, TOKEN_OPEN_CURLY)) + parseBlock (token, NULL); + + /* Potentially the name of the function */ + if (isType (token, TOKEN_PERIOD)) + { + /* + * Cannot be a global variable is it has dot references in the name + */ + is_global = false; + /* Assume it's an assignment to a global name (e.g. a class) using + * its fully qualified name, so strip the scope. + * FIXME: resolve the scope so we can make more than an assumption. */ + vStringClear (token->scope); + vStringClear (name->scope); + do + { + readToken (token); + if (! isType(token, TOKEN_KEYWORD)) + { + if ( is_class ) + { + addToScope(token, name->string); + } + else + addContext (name, token); + + readToken (token); + } + else if ( isKeyword(token, KEYWORD_prototype) ) + { + /* + * When we reach the "prototype" tag, we infer: + * "BindAgent" is a class + * "build" is a method + * + * function BindAgent( repeatableIdName, newParentIdName ) { + * } + * + * CASE 1 + * Specified function name: "build" + * BindAgent.prototype.build = function( mode ) { + * ignore everything within this function + * } + * + * CASE 2 + * Prototype listing + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + * + */ + makeClassTag (name); + is_class = true; + /* is_prototype = true; */ + + /* + * There should a ".function_name" next. + */ + readToken (token); + if (isType (token, TOKEN_PERIOD)) + { + /* + * Handle CASE 1 + */ + readToken (token); + if (! isType(token, TOKEN_KEYWORD)) + { + addToScope(token, name->string); + + makeFlexTag (token, FLEXTAG_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, false, false); + token->ignoreTag = false; + is_terminated = true; + goto cleanUp; + } + } + else if (isType (token, TOKEN_EQUAL_SIGN)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_CURLY)) + { + /* + * Handle CASE 2 + * + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + */ + parseMethods(token, name); + /* + * Find to the end of the statement + */ + findCmdTerm (token, false, false); + token->ignoreTag = false; + is_terminated = true; + goto cleanUp; + } + } + } + else + readToken (token); + } while (isType (token, TOKEN_PERIOD)); + } + else + readTokenFull (token, true); + + if ( isType (token, TOKEN_OPEN_PAREN) ) + skipArgumentList(token, false); + + if ( isType (token, TOKEN_COLON) ) + { + /* + * Functions are of this form: + * function fname ():ReturnType { + */ + readToken (token); + if (isType (token, TOKEN_IDENTIFIER)) + readToken (token); + } + + if ( isType (token, TOKEN_OPEN_SQUARE) ) + skipArrayList(token, false); + } + + if ( isType (token, TOKEN_CLOSE_CURLY) ) + { + /* + * Reaching this section without having + * processed an open curly brace indicates + * the statement is most likely not terminated. + */ + is_terminated = false; + goto cleanUp; + } + + if ( isType (token, TOKEN_SEMICOLON) || + isType (token, TOKEN_EOF) || + isType (token, TOKEN_COMMA) ) + { + /* + * Only create variables for global scope + */ + if ( token->nestLevel == 0 && is_global ) + { + /* + * Handles this syntax: + * var g_var2; + */ + makeFlexTag (name, FLEXTAG_VARIABLE); + } + /* + * Statement has ended. + * This deals with calls to functions, like: + * alert(..); + */ + if (isType (token, TOKEN_COMMA)) + { + readToken (token); + goto nextVar; + } + goto cleanUp; + } + + if ( isType (token, TOKEN_EQUAL_SIGN) ) + { + readToken (token); + + if ( isKeyword (token, KEYWORD_function) ) + { + readToken (token); + + if (! isType (token, TOKEN_KEYWORD) && + ! isType (token, TOKEN_OPEN_PAREN)) + { + /* + * Functions of this format: + * var D2A = function theAdd(a, b) + * { + * return a+b; + * } + * 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 + * So it must have two tags: + * D2A + * theAdd + * Save the reference to the name for later use, once + * we have established this is a valid function we will + * create the secondary reference to it. + */ + copyToken (secondary_name, token, true); + readToken (token); + } + + if ( isType (token, TOKEN_OPEN_PAREN) ) + skipArgumentList(token, false); + + 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. + */ + if ( token->isClass ) + { + makeFlexTag (name, FLEXTAG_METHOD); + if ( vStringLength(secondary_name->string) > 0 ) + makeFunctionTag (secondary_name); + parseBlock (token, name->string); + } + else + { + parseBlock (token, name->string); + makeFunctionTag (name); + + if ( vStringLength(secondary_name->string) > 0 ) + makeFunctionTag (secondary_name); + } + } + } + else if (isType (token, TOKEN_OPEN_PAREN)) + { + /* + * Handle nameless functions + * this.method_name = () {} + */ + skipArgumentList(token, false); + + if (isType (token, TOKEN_OPEN_CURLY)) + { + /* + * Nameless functions are only setup as methods. + */ + makeFlexTag (name, FLEXTAG_METHOD); + parseBlock (token, name->string); + } + } + else if (isType (token, TOKEN_OPEN_CURLY)) + { + /* + * Creates tags for each of these class methods + * ValidClassOne.prototype = { + * 'validMethodOne' : function(a,b) {}, + * 'validMethodTwo' : function(a,b) {} + * } + */ + parseMethods(token, name); + /* Here we should be at the end of the block, on the close curly. + * If so, read the next token not to confuse that close curly with + * the end of the current statement. */ + if (isType (token, TOKEN_CLOSE_CURLY)) + { + readTokenFull(token, true); + is_terminated = isType (token, TOKEN_SEMICOLON); + } + } + else if (isKeyword (token, KEYWORD_new)) + { + readToken (token); + 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) ) + is_class = true; + + readToken (token); + if ( isType (token, TOKEN_OPEN_PAREN) ) + skipArgumentList(token, true); + + if (isType (token, TOKEN_SEMICOLON)) + { + if ( token->nestLevel == 0 ) + { + if ( is_class ) + { + makeClassTag (name); + } else { + makeFunctionTag (name); + } + } + } + else if (isType (token, TOKEN_CLOSE_CURLY)) + is_terminated = false; + } + } + else if (! isType (token, TOKEN_KEYWORD)) + { + /* + * 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); + vStringPut (fulltag, '.'); + vStringCat (fulltag, token->string); + } + else + { + vStringCopy(fulltag, token->string); + } + if ( ! stringListHas(FunctionNames, vStringValue (fulltag)) && + ! stringListHas(ClassNames, vStringValue (fulltag)) ) + { + makeFlexTag (name, FLEXTAG_VARIABLE); + } + vStringDelete (fulltag); + } + } + } + /* 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)) + { + /* + * 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; + * } + */ + is_terminated = findCmdTerm (token, true, true); + /* if we're at a comma, try and read a second var */ + if (isType (token, TOKEN_COMMA)) + { + readToken (token); + goto nextVar; + } + } + +cleanUp: + vStringCopy(token->scope, saveScope); + deleteToken (name); + deleteToken (secondary_name); + vStringDelete(saveScope); + + return is_terminated; +} + +static bool parseLine (tokenInfo *const token) +{ + bool is_terminated = true; + /* + * Detect the common statements, if, while, for, do, ... + * This is necessary since the last statement within a block "{}" + * can be optionally terminated. + * + * If the statement is not terminated, we need to tell + * the calling routine to prevent reading an additional token + * looking for the end of the statement. + */ + + if (isType(token, TOKEN_KEYWORD)) + { + switch (token->keyword) + { + case KEYWORD_for: + case KEYWORD_while: + case KEYWORD_do: + is_terminated = parseLoop (token); + break; + case KEYWORD_if: + case KEYWORD_else: + case KEYWORD_try: + case KEYWORD_catch: + case KEYWORD_finally: + /* Common semantics */ + is_terminated = parseIf (token); + break; + case KEYWORD_switch: + parseSwitch (token); + break; + case KEYWORD_return: + readToken (token); + is_terminated = parseLine (token); + break; + case KEYWORD_function: + parseFunction (token); + break; + case KEYWORD_import: + is_terminated = parseImport (token); + /* to properly support unterminated imports at top level, + * recurse here because parseActionScript() will *always* + * advance to avoid ever getting stuck. */ + if (! is_terminated) + return parseLine (token); + break; + default: + is_terminated = parseStatement (token); + break; + } + } + else + { + /* + * Special case where single line statements may not be + * SEMICOLON terminated. parseBlock needs to know this + * so that it does not read the next token. + */ + is_terminated = parseStatement (token); + } + return is_terminated; +} + +static bool parseCDATA (tokenInfo *const token) +{ + if (isType (token, TOKEN_LESS_THAN)) + { + /* + * Handle these tags + * + */ + readToken (token); + if (isType (token, TOKEN_EXCLAMATION)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_SQUARE)) + { + readToken (token); + if (isKeyword (token, KEYWORD_cdata)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_SQUARE)) + { + parseActionScript (token, true); + if (isType (token, TOKEN_CLOSE_SQUARE)) + { + readToken (token); + if (isType (token, TOKEN_CLOSE_SQUARE)) + { + readToken (token); + } + } + } + } + } + } + } + else + { + parseActionScript (token, false); + } + return true; +} + +static bool 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) || + isEOF (token)) ); + return true; +} + +static bool parseMXML (tokenInfo *const token) +{ + tokenInfo *const name = newToken (); + tokenInfo *const type = newToken (); + bool inside_attributes = true; + /* + * Detect the common statements, if, while, for, do, ... + * This is necessary since the last statement within a block "{}" + * can be optionally terminated. + * + * If the statement is not terminated, we need to tell + * the calling routine to prevent reading an additional token + * looking for the end of the statement. + */ + + readToken (token); + + if (isKeyword (token, KEYWORD_script)) + { + /* + * These tags can be of this form: + * + */ + do + { + readToken (token); + } while (! (isType (token, TOKEN_CLOSE_SGML) || + isType (token, TOKEN_CLOSE_MXML) || + isType (token, TOKEN_GREATER_THAN) || + isEOF (token)) ); + + if (isType (token, TOKEN_CLOSE_MXML)) + { + /* + * We have found a tag + * Finish reading the "type" and ">" + */ + readToken (token); + readToken (token); + goto cleanUp; + } + if (isType (token, TOKEN_CLOSE_SGML)) + { + /* + * We have found a + */ + goto cleanUp; + } + + /* + * This is a beginning of an embedded script. + * These typically are of this format: + * + * + * + */ + readToken (token); + parseCDATA (token); + + readToken (token); + if (isType (token, TOKEN_CLOSE_MXML)) + { + /* + * We have found a tag + * Finish reading the "type" and ">" + */ + readToken (token); + readToken (token); + } + goto cleanUp; + } + + copyToken (type, token, true); + + readToken (token); + do + { + if (isType (token, TOKEN_GREATER_THAN)) + { + inside_attributes = false; + } + 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, true); + addToScope (name, type->string); + makeMXTag (name); + } + else + { + readToken (token); + } + } + else + { + readToken (token); + } + } while (! (isType (token, TOKEN_CLOSE_SGML) || + isType (token, TOKEN_CLOSE_MXML) || + isEOF (token)) ); + + if (isType (token, TOKEN_CLOSE_MXML)) + { + /* + * We have found a tag + * Finish reading the "type" and ">" + */ + readToken (token); + readToken (token); + } + +cleanUp: + deleteToken (name); + deleteToken (type); + return true; +} + +static bool parseActionScript (tokenInfo *const token, bool readNext) +{ + LastTokenType = TOKEN_UNDEFINED; + + do + { + if (! readNext) + readNext = true; + else + readToken (token); + + if (isType (token, TOKEN_LESS_THAN)) + { + /* + * Handle these tags + * + */ + readToken (token); + if (isType (token, TOKEN_EQUAL_SIGN)) + { + if (isType (token, TOKEN_OPEN_SQUARE)) + { + readToken (token); + if (isKeyword (token, KEYWORD_cdata)) + { + readToken (token); + } + } + } + } + if (isType (token, TOKEN_CLOSE_SQUARE)) + { + /* + * Handle these tags + * + */ + readToken (token); + if (isType (token, TOKEN_CLOSE_SQUARE)) + { + readToken (token); + if (isType (token, TOKEN_GREATER_THAN)) + { + return true; + } + } + } + else if (isType (token, TOKEN_CLOSE_MXML)) + { + /* + * Read the Script> tags + */ + readToken (token); + readToken (token); + return true; + } + else if (isType (token, TOKEN_OPEN_MXML)) + { + parseMXML (token); + } + else + { + parseLine (token); + } + } while (!isEOF (token)); + return true; +} + +static void parseFlexFile (tokenInfo *const token) +{ + do + { + readToken (token); + + if (isType (token, TOKEN_OPEN_MXML)) + { + parseMXML (token); + } + else if (isType (token, TOKEN_LESS_THAN)) + { + readToken (token); + if (isType (token, TOKEN_QUESTION_MARK)) + { + /* + * + */ + readToken (token); + while (! (isType (token, TOKEN_QUESTION_MARK) || isEOF (token))) + { + readToken (token); + } + 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) || isEOF (token))) + { + readToken (token); + } + } + } + else + { + parseActionScript (token, false); + } + } while (!isEOF (token)); +} + +static void initialize (const langType language) +{ + Assert (ARRAY_SIZE (FlexKinds) == FLEXTAG_COUNT); + Lang_flex = language; +} + +static void findFlexTags (void) +{ + tokenInfo *const token = newToken (); + + NextToken = NULL; + ClassNames = stringListNew (); + FunctionNames = stringListNew (); + + parseFlexFile (token); + + stringListDelete (ClassNames); + stringListDelete (FunctionNames); + ClassNames = NULL; + FunctionNames = NULL; + deleteToken (token); +} + +/* Create parser definition structure */ +extern parserDefinition* FlexParser (void) +{ + static const char *const extensions [] = { "as", "mxml", NULL }; + parserDefinition *const def = parserNew ("Flex"); + def->extensions = extensions; + /* + * New definitions for parsing instead of regex + */ + def->kindTable = FlexKinds; + def->kindCount = ARRAY_SIZE (FlexKinds); + def->parser = findFlexTags; + def->initialize = initialize; + def->keywordTable = FlexKeywordTable; + def->keywordCount = ARRAY_SIZE (FlexKeywordTable); + + return def; +} diff --git a/src/symbols.c b/src/symbols.c index bed454d318..76952a1dc6 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -718,6 +718,7 @@ static void add_top_level_items(GeanyDocument *doc) case GEANY_FILETYPES_AS: { tag_list_add_groups(tag_store, + &(tv_iters.tag_externvar), _("Imports"), ICON_NAMESPACE, &(tv_iters.tag_namespace), _("Package"), ICON_NAMESPACE, &(tv_iters.tag_interface), _("Interfaces"), ICON_STRUCT, &(tv_iters.tag_class), _("Classes"), ICON_CLASS, diff --git a/src/tagmanager/tm_parser.c b/src/tagmanager/tm_parser.c index 5d326fbe86..b312d22a7a 100644 --- a/src/tagmanager/tm_parser.c +++ b/src/tagmanager/tm_parser.c @@ -371,14 +371,16 @@ static TMParserMapEntry map_VALA[] = { /* not in universal-ctags */ static TMParserMapEntry map_ACTIONSCRIPT[] = { {'f', tm_tag_function_t}, - {'l', tm_tag_field_t}, - {'v', tm_tag_variable_t}, - {'m', tm_tag_macro_t}, {'c', tm_tag_class_t}, {'i', tm_tag_interface_t}, - {'p', tm_tag_package_t}, - {'o', tm_tag_other_t}, - {'r', tm_tag_prototype_t}, + {'P', tm_tag_package_t}, + {'m', tm_tag_method_t}, + {'p', tm_tag_member_t}, + {'v', tm_tag_variable_t}, + {'l', tm_tag_variable_t}, + {'C', tm_tag_macro_t}, + {'I', tm_tag_externvar_t}, + {'x', tm_tag_other_t}, }; /* not in universal-ctags */ @@ -765,6 +767,7 @@ gboolean tm_parser_has_full_context(TMParserType lang) switch (lang) { /* These parsers include full hierarchy in the tag scope, separated by tm_parser_context_separator() */ + case TM_PARSER_ACTIONSCRIPT: case TM_PARSER_C: case TM_PARSER_CPP: case TM_PARSER_CSHARP: diff --git a/tests/ctags/Makefile.am b/tests/ctags/Makefile.am index a934b517cc..ebea62cee1 100644 --- a/tests/ctags/Makefile.am +++ b/tests/ctags/Makefile.am @@ -15,6 +15,13 @@ test_sources = \ 3470609.js \ 3526726.tex \ 68hc11.asm \ + actionscript/as-first-token.as \ + actionscript/classes.as \ + actionscript/const2.as \ + actionscript/const.as \ + actionscript/method-attributes.as \ + actionscript/packages.as \ + actionscript/sampler.as \ angle_bracket.cpp \ anonymous_functions.php \ arraylist.js \ diff --git a/tests/ctags/actionscript/as-first-token.as b/tests/ctags/actionscript/as-first-token.as new file mode 100644 index 0000000000..2b7624858c --- /dev/null +++ b/tests/ctags/actionscript/as-first-token.as @@ -0,0 +1 @@ +function f1():void {} diff --git a/tests/ctags/actionscript/as-first-token.as.tags b/tests/ctags/actionscript/as-first-token.as.tags new file mode 100644 index 0000000000..4ab21c40d3 --- /dev/null +++ b/tests/ctags/actionscript/as-first-token.as.tags @@ -0,0 +1,2 @@ +# format=tagmanager +f1Ì16Ö0 diff --git a/tests/ctags/actionscript/classes.as b/tests/ctags/actionscript/classes.as new file mode 100644 index 0000000000..f88e97dc8c --- /dev/null +++ b/tests/ctags/actionscript/classes.as @@ -0,0 +1,16 @@ +package { + class C1 { + public function m1():Boolean { return 0; } + } + class C2 extends C1 {} + class C3 {} + interface I1 {} + interface I2 {} + interface I3 extends I1, I2 {} + interface I4 extends I3 {} + class C4 implements I1 {} + class C5 extends C3 implements I1 {} + class C6 extends C3 implements I1, I2 {} + + dynamic class C7{} +} diff --git a/tests/ctags/actionscript/classes.as.tags b/tests/ctags/actionscript/classes.as.tags new file mode 100644 index 0000000000..4cb6119ba2 --- /dev/null +++ b/tests/ctags/actionscript/classes.as.tags @@ -0,0 +1,13 @@ +# format=tagmanager +C1Ì1Ö0 +C2Ì1Ö0 +C3Ì1Ö0 +C4Ì1Ö0 +C5Ì1Ö0 +C6Ì1Ö0 +C7Ì1Ö0 +I1Ì32Ö0 +I2Ì32Ö0 +I3Ì32Ö0 +I4Ì32Ö0 +m1Ì128ÎC1Ö0 diff --git a/tests/ctags/actionscript/const.as b/tests/ctags/actionscript/const.as new file mode 100644 index 0000000000..2852f7b166 --- /dev/null +++ b/tests/ctags/actionscript/const.as @@ -0,0 +1,8 @@ +// https://www.oreilly.com/library/view/essential-actionscript-30/0596526946/ch04s02.html +public class AlarmClock { + public static const MODE_VISUAL = 1; + public static const MODE_AUDIO = 2; + public static const MODE_BOTH = 3; + + private var mode = AlarmClock.MODE_AUDIO; +} diff --git a/tests/ctags/actionscript/const.as.tags b/tests/ctags/actionscript/const.as.tags new file mode 100644 index 0000000000..93bf603b33 --- /dev/null +++ b/tests/ctags/actionscript/const.as.tags @@ -0,0 +1,6 @@ +# format=tagmanager +AlarmClockÌ1Ö0 +MODE_AUDIOÌ65536ÎAlarmClockÖ0 +MODE_BOTHÌ65536ÎAlarmClockÖ0 +MODE_VISUALÌ65536ÎAlarmClockÖ0 +modeÌ16384ÎAlarmClockÖ0 diff --git a/tests/ctags/actionscript/const2.as b/tests/ctags/actionscript/const2.as new file mode 100644 index 0000000000..ff916010de --- /dev/null +++ b/tests/ctags/actionscript/const2.as @@ -0,0 +1,7 @@ +// https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/statements.html#const +const MIN_AGE:int = 21; + +const product_array:Array = new Array("Studio", "Dreamweaver", "Flash", "ColdFusion", "Contribute", "Breeze"); +product_array.push("Flex"); // array operations are allowed +product_array = ["Other"]; // assignment is an error +trace(product_array); diff --git a/tests/ctags/actionscript/const2.as.tags b/tests/ctags/actionscript/const2.as.tags new file mode 100644 index 0000000000..2dd239c824 --- /dev/null +++ b/tests/ctags/actionscript/const2.as.tags @@ -0,0 +1,3 @@ +# format=tagmanager +MIN_AGEÌ65536Ö0 +product_arrayÌ65536Ö0 diff --git a/tests/ctags/actionscript/method-attributes.as b/tests/ctags/actionscript/method-attributes.as new file mode 100644 index 0000000000..a7802e193b --- /dev/null +++ b/tests/ctags/actionscript/method-attributes.as @@ -0,0 +1,12 @@ +/* Not sure it's really valid, but the goal is to check not choking on + * attributes, so so long as it's valid attributes it's fine */ +class C { + public function f1():void {} + private function f2():void {} + protected function f3():void {} + internal function f4():void {} + public function f5():void {} + public override function f6():void {} + final function f7():void {} + native function f8():void {} +} diff --git a/tests/ctags/actionscript/method-attributes.as.tags b/tests/ctags/actionscript/method-attributes.as.tags new file mode 100644 index 0000000000..cde550136b --- /dev/null +++ b/tests/ctags/actionscript/method-attributes.as.tags @@ -0,0 +1,10 @@ +# format=tagmanager +CÌ1Ö0 +f1Ì128ÎCÖ0 +f2Ì128ÎCÖ0 +f3Ì128ÎCÖ0 +f4Ì128ÎCÖ0 +f5Ì128ÎCÖ0 +f6Ì128ÎCÖ0 +f7Ì128ÎCÖ0 +f8Ì128ÎCÖ0 diff --git a/tests/ctags/actionscript/packages.as b/tests/ctags/actionscript/packages.as new file mode 100644 index 0000000000..48038e115f --- /dev/null +++ b/tests/ctags/actionscript/packages.as @@ -0,0 +1,9 @@ +package P1 {} +package P2 { + function f1() {} +} +package P3 { + class C1 {} +} +package qualified.test . pkg { +} diff --git a/tests/ctags/actionscript/packages.as.tags b/tests/ctags/actionscript/packages.as.tags new file mode 100644 index 0000000000..da89be80e6 --- /dev/null +++ b/tests/ctags/actionscript/packages.as.tags @@ -0,0 +1,7 @@ +# format=tagmanager +C1Ì1ÎP3Ö0 +P1Ì512Ö0 +P2Ì512Ö0 +P3Ì512Ö0 +f1Ì16ÎP2Ö0 +qualified.test.pkgÌ512Ö0 diff --git a/tests/ctags/actionscript/sampler.as b/tests/ctags/actionscript/sampler.as new file mode 100644 index 0000000000..1d233da366 --- /dev/null +++ b/tests/ctags/actionscript/sampler.as @@ -0,0 +1,66 @@ +// https://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/sampler/Sample.html +package +{ + import flash.sampler.* + import flash.system.* + import flash.utils.* + import flash.display.Sprite + public class sampleTypes extends Sprite + { + var b:Boolean = true + public function sampleTypes() { + flash.sampler.startSampling(); + for(var i:int=0;i<10000;i++) + new Object(); + + var cpuSamples:Array=[]; + var newSamples:Array=[]; + var delSamples:Array=[]; + var ids:Array=[] + + var lastTime:Number=0; + for each(var s:Sample in getSamples()) { + + assert(s.time > 0); // positive + assert(Math.floor(s.time) == s.time, s.time); // integral + assert(s.time >= lastTime, s.time + ":" + lastTime); // ascending + assert(s.stack == null || s.stack is Array) + if(s.stack) { + assert(s.stack[0] is StackFrame); + assert(s.stack[0].name is String); + } + + if(s is NewObjectSample) { + var nos = NewObjectSample(s); + assert(s.id > 0, s.id); + assert(s.type is Class, getQualifiedClassName(s.type)); + newSamples.push(s); + ids[s.id] = "got one"; + } else if(s is DeleteObjectSample) { + var dos = DeleteObjectSample(s); + delSamples.push(s); + assert(ids[dos.id] == "got one"); + } else if(s is Sample) + cpuSamples.push(s); + else { + assert(false); + } + lastTime = s.time; + } + + trace(b) + trace(newSamples.length > 0) + trace(cpuSamples.length > 0) + trace(delSamples.length > 0) + + } + + private function assert(e:Boolean, mess:String=null):void { + b = e && b; + if(true && !e) { + if(mess) trace(mess); + trace(new Error().getStackTrace()); + } + } + } +} diff --git a/tests/ctags/actionscript/sampler.as.tags b/tests/ctags/actionscript/sampler.as.tags new file mode 100644 index 0000000000..28d37b3b9c --- /dev/null +++ b/tests/ctags/actionscript/sampler.as.tags @@ -0,0 +1,16 @@ +# format=tagmanager +assertÌ128ÎsampleTypesÖ0 +bÌ16384ÎsampleTypesÖ0 +cpuSamplesÌ16384ÎsampleTypes.sampleTypesÖ0 +delSamplesÌ16384ÎsampleTypes.sampleTypesÖ0 +dosÌ16384ÎsampleTypes.sampleTypesÖ0 +flash.display.SpriteÌ32768Ö0 +flash.sampler.*Ì32768Ö0 +flash.system.*Ì32768Ö0 +flash.utils.*Ì32768Ö0 +idsÌ16384ÎsampleTypes.sampleTypesÖ0 +lastTimeÌ16384ÎsampleTypes.sampleTypesÖ0 +newSamplesÌ16384ÎsampleTypes.sampleTypesÖ0 +nosÌ16384ÎsampleTypes.sampleTypesÖ0 +sampleTypesÌ1Ö0 +sampleTypesÌ128ÎsampleTypesÖ0