[geany/geany] ec6e23: Add new upstream candidate Flex parser

Colomban Wendling git-noreply at xxxxx
Sat Apr 27 12:59:30 UTC 2019


Branch:      refs/heads/master
Author:      Colomban Wendling <ban at herbesfolles.org>
Committer:   Colomban Wendling <ban at herbesfolles.org>
Date:        Sat, 27 Apr 2019 12:59:30 UTC
Commit:      ec6e2320dd48bb4043b9d373c05ac24b97e3d7f5
             https://github.com/geany/geany/commit/ec6e2320dd48bb4043b9d373c05ac24b97e3d7f5

Log Message:
-----------
Add new upstream candidate Flex parser

See: https://github.com/universal-ctags/ctags/pull/2084

This import has 3 difference with upstream, not to use newer API than
current Geany has, and to workaround current limitations of Geany ctags
calls: imports are enabled by default and don't have a specific role.


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(-)
===================================================================
@@ -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,


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 */
@@ -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:


Modified: tests/ctags/Makefile.am
7 lines changed, 7 insertions(+), 0 deletions(-)
===================================================================
@@ -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					\


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