[geany/geany] 349b8c: Merge pull request #2134 from b4n/ctags/new-flex-parser
Colomban Wendling
git-noreply at xxxxx
Sun Apr 28 09:14:33 UTC 2019
Branch: refs/heads/master
Author: Colomban Wendling <ban at herbesfolles.org>
Committer: Colomban Wendling <ban at herbesfolles.org>
Date: Sun, 28 Apr 2019 09:14:33 UTC
Commit: 349b8c40e252ddf1b335e0849ffcd37c403d72e9
https://github.com/geany/geany/commit/349b8c40e252ddf1b335e0849ffcd37c403d72e9
Log Message:
-----------
Merge pull request #2134 from b4n/ctags/new-flex-parser
Add new upstream candidate Flex parser
Modified Paths:
--------------
ctags/Makefile.am
ctags/main/parsers.h
ctags/parsers/actionscript.c
ctags/parsers/flex.c
src/symbols.c
src/tagmanager/tm_parser.c
tests/ctags/Makefile.am
tests/ctags/actionscript/as-first-token.as
tests/ctags/actionscript/as-first-token.as.tags
tests/ctags/actionscript/classes.as
tests/ctags/actionscript/classes.as.tags
tests/ctags/actionscript/const.as
tests/ctags/actionscript/const.as.tags
tests/ctags/actionscript/const2.as
tests/ctags/actionscript/const2.as.tags
tests/ctags/actionscript/method-attributes.as
tests/ctags/actionscript/method-attributes.as.tags
tests/ctags/actionscript/packages.as
tests/ctags/actionscript/packages.as.tags
tests/ctags/actionscript/sampler.as
tests/ctags/actionscript/sampler.as.tags
Modified: ctags/Makefile.am
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -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 \
Modified: ctags/main/parsers.h
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -50,7 +50,7 @@
GLSLParser, \
MatLabParser, \
ValaParser, \
- ActionScriptParser, \
+ FlexParser, \
NsisParser, \
MarkdownParser, \
Txt2tagsParser, \
Modified: ctags/parsers/actionscript.c
99 lines changed, 0 insertions(+), 99 deletions(-)
===================================================================
@@ -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 at 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;
-}
-
Modified: ctags/parsers/flex.c
2636 lines changed, 2636 insertions(+), 0 deletions(-)
===================================================================
@@ -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 <ctype.h> /* to define isalpha () */
+#ifdef DEBUG
+#include <stdio.h>
+#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
+ * <!-- anything over multiple lines -->
+ */
+ 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<br>");
+ *
+ * for (x=0; x<3; x++)
+ * {
+ * document.write("This text is repeated three times<br>");
+ * }
+ *
+ * while (number<5){
+ * document.write(number+"<br>");
+ * number++;
+ * }
+ *
+ * do{
+ * document.write(number+"<br>");
+ * 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<br>")
+ * }
+ * 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
+ * <![CDATA[
+ * ...
+ * ]]>
+ */
+ 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
+ * <views:Object ... />
+ * or
+ * <views:Object ... >
+ * </views:Object>
+ */
+ 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:
+ * <mx:Script src="filename.as" />
+ */
+ 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 </mx:type> tag
+ * Finish reading the "type" and ">"
+ */
+ readToken (token);
+ readToken (token);
+ goto cleanUp;
+ }
+ if (isType (token, TOKEN_CLOSE_SGML))
+ {
+ /*
+ * We have found a <mx:Script src="filename.as" />
+ */
+ goto cleanUp;
+ }
+
+ /*
+ * This is a beginning of an embedded script.
+ * These typically are of this format:
+ * <mx:Script>
+ * <![CDATA[
+ * ... ActionScript ...
+ * ]]>
+ * </mx:Script>
+ */
+ readToken (token);
+ parseCDATA (token);
+
+ readToken (token);
+ if (isType (token, TOKEN_CLOSE_MXML))
+ {
+ /*
+ * We have found a </mx:type> 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 </mx:type> 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
+ * <![CDATA[
+ * ...
+ * ]]>
+ */
+ 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
+ * <![CDATA[
+ * ...
+ * ]]>
+ */
+ 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))
+ {
+ /*
+ * <?xml version="1.0" encoding="utf-8"?>
+ */
+ 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
+ * <something .... >
+ * </something>
+ */
+ 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;
+}
Modified: src/symbols.c
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -720,6 +720,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,
Modified: src/tagmanager/tm_parser.c
15 lines changed, 9 insertions(+), 6 deletions(-)
===================================================================
@@ -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 */
@@ -767,6 +769,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:
Modified: tests/ctags/Makefile.am
7 lines changed, 7 insertions(+), 0 deletions(-)
===================================================================
@@ -12,6 +12,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 \
Modified: tests/ctags/actionscript/as-first-token.as
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1 @@
+function f1():void {}
Modified: tests/ctags/actionscript/as-first-token.as.tags
2 lines changed, 2 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,2 @@
+# format=tagmanager
+f1�16�0
Modified: tests/ctags/actionscript/classes.as
16 lines changed, 16 insertions(+), 0 deletions(-)
===================================================================
@@ -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{}
+}
Modified: tests/ctags/actionscript/classes.as.tags
13 lines changed, 13 insertions(+), 0 deletions(-)
===================================================================
@@ -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
Modified: tests/ctags/actionscript/const.as
8 lines changed, 8 insertions(+), 0 deletions(-)
===================================================================
@@ -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;
+}
Modified: tests/ctags/actionscript/const.as.tags
6 lines changed, 6 insertions(+), 0 deletions(-)
===================================================================
@@ -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
Modified: tests/ctags/actionscript/const2.as
7 lines changed, 7 insertions(+), 0 deletions(-)
===================================================================
@@ -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);
Modified: tests/ctags/actionscript/const2.as.tags
3 lines changed, 3 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,3 @@
+# format=tagmanager
+MIN_AGE�65536�0
+product_array�65536�0
Modified: tests/ctags/actionscript/method-attributes.as
12 lines changed, 12 insertions(+), 0 deletions(-)
===================================================================
@@ -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 {}
+}
Modified: tests/ctags/actionscript/method-attributes.as.tags
10 lines changed, 10 insertions(+), 0 deletions(-)
===================================================================
@@ -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
Modified: tests/ctags/actionscript/packages.as
9 lines changed, 9 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,9 @@
+package P1 {}
+package P2 {
+ function f1() {}
+}
+package P3 {
+ class C1 {}
+}
+package qualified.test . pkg {
+}
Modified: tests/ctags/actionscript/packages.as.tags
7 lines changed, 7 insertions(+), 0 deletions(-)
===================================================================
@@ -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
Modified: tests/ctags/actionscript/sampler.as
66 lines changed, 66 insertions(+), 0 deletions(-)
===================================================================
@@ -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());
+ }
+ }
+ }
+}
Modified: tests/ctags/actionscript/sampler.as.tags
16 lines changed, 16 insertions(+), 0 deletions(-)
===================================================================
@@ -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
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
More information about the Commits
mailing list