/**
 *    Copyright (c) 2009, Adobe Systems, Incorporated
 *    All rights reserved.
 *
 *    Redistribution  and  use  in  source  and  binary  forms, with or without
 *    modification,  are  permitted  provided  that  the  following  conditions
 *    are met:
 *
 *      * Redistributions  of  source  code  must  retain  the  above copyright
 *        notice, this list of conditions and the following disclaimer.
 *      * Redistributions  in  binary  form  must reproduce the above copyright
 *        notice,  this  list  of  conditions  and  the following disclaimer in
 *        the    documentation   and/or   other  materials  provided  with  the
 *        distribution.
 *      * Neither the name of the Adobe Systems, Incorporated. nor the names of
 *        its  contributors  may be used to endorse or promote products derived
 *        from this software without specific prior written permission.
 *
 *    THIS  SOFTWARE  IS  PROVIDED  BY THE  COPYRIGHT  HOLDERS AND CONTRIBUTORS
 *    "AS IS"  AND  ANY  EXPRESS  OR  IMPLIED  WARRANTIES,  INCLUDING,  BUT NOT
 *    LIMITED  TO,  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 *    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,  INCIDENTAL,  SPECIAL,
 *    EXEMPLARY,  OR  CONSEQUENTIAL  DAMAGES  (INCLUDING,  BUT  NOT  LIMITED TO,
 *    PROCUREMENT  OF  SUBSTITUTE   GOODS  OR   SERVICES;  LOSS  OF  USE,  DATA,
 *    OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *    LIABILITY,  WHETHER  IN  CONTRACT,  STRICT  LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE  OR  OTHERWISE)  ARISING  IN  ANY  WAY  OUT OF THE USE OF THIS
 *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import AS3Scanner = require('./scanner');
import Token = require('./token');
import NodeKind = require('./nodeKind');
import Operators = require('./operators');
import KeyWords = require('./keywords');
import Node = require('./node');


var AS2TS_COMMENT:RegExp = /\/\*\/\s*([^\/\*]*?)\s*\/\*\//;

var ASDOC_COMMENT: string = "/**";

var MULTIPLE_LINES_COMMENT: string = "/*";

var NEW_LINE: string = "\n";

var SINGLE_LINE_COMMENT: string = "//";

var VECTOR: string = "Vector";

function isTemplate(str:string):boolean {
	return str && str.charAt(0) === '<' && str.charAt(str.length - 1) === '>';
}

function startsWith(string: string, prefix: string) {
    return string.indexOf(prefix) === 0;
};

function endsWith(string: string, suffix: string) {
    return string.indexOf(suffix, string.length - suffix.length) !== -1;
};

var CARRIAGE_RETURN = 13,
    LINE_FEED = 10,
    LINE_SEPARATOR = 0x2028,
    PARAGRAPH_SEPARATOR = 0x2029,
    NEXT_LINE = 0x0085;

function isAnyLineBreakCharacter(c: number): boolean {
    return c === LINE_FEED ||
           c === CARRIAGE_RETURN ||
           c === NEXT_LINE ||
           c === LINE_SEPARATOR ||
           c === PARAGRAPH_SEPARATOR;
}

function getLengthOfLineBreak(text: string, index: number): number {
    var c = text.charCodeAt(index);

    // common case - ASCII & not a line break
    if (c > CARRIAGE_RETURN && c <= 127) {
        return 0;
    }

    if (c === CARRIAGE_RETURN) {
        var next = index + 1;
        return (next < text.length) && LINE_FEED === text.charCodeAt(next) ? 2 : 1;
    }
    else if (isAnyLineBreakCharacter(c)) {
        return 1;
    }
    else {
        return 0;
    }
}

function buildLineMap(text: string): number[] {
    var length = text.length;

    // Corner case check
    if (0 === length) {
        var result = new Array<number>();
        result.push(0);
        return result;
    }

    var position = 0;
    var index = 0;
    var arrayBuilder = new Array<number>();
    var lineNumber = 0;

    // The following loop goes through every character in the text. It is highly
    // performance critical, and thus inlines knowledge about common line breaks
    // and non-line breaks.
    while (index < length) {
        var c = text.charCodeAt(index);
        var lineBreakLength: number;

        // common case - ASCII & not a line break
        if (c > 13 && c <= 127) {
            index++;
            continue;
        }
        else if (c === 13 && index + 1 < length && text.charCodeAt(index + 1) === 10 ) {
            lineBreakLength = 2;
        }
        else if (c === 10 ) {
            lineBreakLength = 1;
        }
        else {
            lineBreakLength = getLengthOfLineBreak(text, index);
        }

        if (0 === lineBreakLength) {
            index++;
        }
        else {
            arrayBuilder.push(position);
            index += lineBreakLength;
            position = index;
            lineNumber++;
        }
    }

    // Create a start for the final line.  
    arrayBuilder.push(position);

    return arrayBuilder;
}

function getLineAndCharacterFromPosition(position: number, lineStarts: number[]): { col: number; line:number } {
    if (position < 0 || position > this.length) {
        throw Error("invalid position" + position);
    }

    var lineNumber: number = -1;
    if (position === this.length) {
        lineNumber = lineStarts.length - 1;
    } else {
        for (var i = 0; i < lineStarts.length; i++) {
            if (lineStarts[i] > position) {
                break;
            }
            lineNumber = i;
        }
        lineNumber = lineNumber > 0 ? lineNumber : 0;
    }
    return { line: lineNumber, col: position - lineStarts[lineNumber] };
}

/**
 * @author xagnetti
 */
class AS3Parser {
	private tsReplacement: string;
	private tsTemplate: string;
    private currentAsDoc: Node;
    private currentFunctionNode: Node;
    private currentMultiLineComment: Node;
    private fileName: string;
    private isInFor: boolean;
    private scn: AS3Scanner;
    private tok: Token;
    private lineMap: number[];

    /**
     * 
     */
    constructor() {
        this.scn = new AS3Scanner();
        this.isInFor = false;
    }

    /*
     * (non-Javadoc)
     * @see com.adobe.ac.pmd.parser.IAS3Parser#buildAst(java.lang.String,
     * java.lang.String[])
     */
    public buildAst(filePath: string, content: string): Node {
        this.lineMap = buildLineMap(content);
        return this.parseFile(filePath, content);
    }
    
    

	/**
	 * @param ignoreDocumentation skip over documentation tokens
     * @param allowNewLine Get the next token Skip comments but keep newlines.
     *                     We need this option for beeing able to decide if a returnStatement has an expression.
	 */
    private nextToken(ignoreDocumentation: boolean= false, allowNewLine:boolean = false): void {
        do {
            this.tok = this.scn.nextToken();

            if (this.tok == null) {
                throw new Error(this.fileName)//TODO NullTokenException(fileName);
            }
            if (this.tok.text == null) {
                throw new Error(this.fileName)//TODO throw new NullTokenException(fileName);
            }
			
			var as2ts = AS2TS_COMMENT.exec(this.tok.text);
			if (as2ts)
			{
				var ts = as2ts[1];
				if (isTemplate(ts))
					this.tsTemplate = ts;
				else
					this.tsReplacement = ts;
			}
        }
        while (
			as2ts
			|| startsWith(this.tok.text, SINGLE_LINE_COMMENT)
			|| (!allowNewLine && this.tok.text === NEW_LINE)
			|| (ignoreDocumentation && startsWith(this.tok.text, MULTIPLE_LINES_COMMENT))
		);
    }
    
    private tryParse<T>(func: () => T): T {
        var tok = this.tok;
        var checkPoint = this.scn.getCheckPoint();
        try {
            return func();
        } catch(e) {
            this.scn.rewind(checkPoint);
            return null;
        }
    }


    /**
     * tok is first content token
     * 
     * @throws TokenException
     */
    private parseClassContent(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.CONTENT, this.tok.index, -1);
        var modifiers: Token[] = [];
        var meta: Node[] = [];

        while (!this.tokIs(Operators.RIGHT_CURLY_BRACKET)) {
            if (this.tokIs(Operators.LEFT_CURLY_BRACKET)) {
                result.children.push(this.parseBlock());
            }
            if (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
                meta.push(this.parseMetaData());
            }
            else if (this.tokIs(KeyWords.VAR)) {
                this.parseClassField(result, modifiers, meta);
            }
            else if (this.tokIs(KeyWords.CONST)) {
                this.parseClassConstant(result, modifiers, meta);
            }
            else if (this.tokIs(KeyWords.IMPORT)) {
                result.children.push(this.parseImport());
            }
            else if (this.tokIs(KeyWords.INCLUDE) || this.tokIs(KeyWords.INCLUDE_AS2)) {
                result.children.push(this.parseIncludeExpression());
            }
            else if (this.tokIs(KeyWords.FUNCTION)) {
                this.parseClassFunctions(result, modifiers, meta);
            }
            else {
                this.tryToParseCommentNode(result, modifiers);
            }
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result;
    }

    /**
     * tok is empty, since nextToken has not been called before
     * 
     * @throws UnExpectedTokenException
     */
    private parseCompilationUnit(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.COMPILATION_UNIT, -1, -1);

        this.nextToken(true);
        if (this.tokIs(KeyWords.PACKAGE))
		{
            result.children.push(this.parsePackage());
        }
		else
		{
			throw new Error('"package" is expected to be the first token in a file');
		}
        result.children.push(this.parsePackageContent());
        return result;
    }

    /**
     * @return
     * @throws TokenException
     */
    private parseExpression(): Node {
        return this.parseAssignmentExpression();
    }

    /**
     * tok is first content token
     * 
     * @throws TokenException
     */
    private parseInterfaceContent(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.CONTENT, this.tok.index, -1);

        while (!this.tokIs(Operators.RIGHT_CURLY_BRACKET)) {
            if (this.tokIs(KeyWords.IMPORT)) {
                result.children.push(this.parseImport());
            }
            else if (this.tokIs(KeyWords.FUNCTION)) {
                result.children.push(this.parseFunctionSignature());
            }
            else if (this.tokIs(KeyWords.INCLUDE) || this.tokIs(KeyWords.INCLUDE_AS2)) {
                result.children.push(this.parseIncludeExpression());
            }
            else if (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
                while (!this.tokIs(Operators.RIGHT_SQUARE_BRACKET)) {
                    this.nextToken(true);
                }
                this.nextToken(true);
            }
            else {
                this.tryToParseCommentNode(result, null);
            }
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result;
    }

    /**
     * tok is first token of content
     * 
     * @throws UnExpectedTokenException
     */
    private parsePackageContent(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.CONTENT, this.tok.index, -1);
        var modifiers: Token[] = [];
        var meta: Node[] = [];

        while (!this.tokIs(Operators.RIGHT_CURLY_BRACKET) && !this.tokIs(KeyWords.EOF)) {
            if (this.tokIs(KeyWords.IMPORT)) {
                result.children.push(this.parseImport());
            }
            else if (this.tokIs(KeyWords.USE)) {
                result.children.push(this.parseUse());
            }
            else if (this.tokIs(KeyWords.INCLUDE) || this.tokIs(KeyWords.INCLUDE_AS2)) {
                result.children.push(this.parseIncludeExpression());
            }
            else if (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
                meta.push(this.parseMetaData());
            }
            else if (this.tokIs(KeyWords.CLASS)) {
                result.children.push(this.parseClass(meta, modifiers));
                modifiers.length = 0;
                meta.length = 0;
            }
            else if (this.tokIs(KeyWords.INTERFACE)) {
                result.children.push(this.parseInterface(meta, modifiers));
                modifiers.length = 0;
                meta.length = 0;
            }
            else if (this.tokIs(KeyWords.FUNCTION)) {
                this.parseClassFunctions(result, modifiers, meta);
            }
            else if (startsWith(this.tok.text, ASDOC_COMMENT)) {
                this.currentAsDoc = new Node(this.scn.content, NodeKind.AS_DOC, this.tok.index,
                    this.tok.index + this.tok.index - 1, this.tok.text);
                this.nextToken();
            }
            else if (startsWith(this.tok.text, MULTIPLE_LINES_COMMENT)) {
                this.currentMultiLineComment = new Node(this.scn.content, NodeKind.MULTI_LINE_COMMENT, this.tok.index,
                    this.tok.index + this.tok.index - 1, this.tok.text);
                this.nextToken();
            }
            else {
                modifiers.push(this.tok);
                this.nextToken(true);
            }
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result;
    }

    /**
     * @return
     * @throws TokenException
     */
    private parsePrimaryExpression(): Node {
        var result:Node;
        
        if (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
            return this.parseArrayLiteral();
        } else if (this.tokIs(Operators.LEFT_CURLY_BRACKET)) {
            return this.parseObjectLiteral();
        } else if (this.tokIs(KeyWords.FUNCTION)) {
            return this.parseLambdaExpression();
        } else if (this.tokIs(KeyWords.NEW)) {
            return this.parseNewExpression();
        } else if (this.tokIs(Operators.LEFT_PARENTHESIS)) {
            return this.parseEncapsulatedExpression();
        } else if (this.tok.text === 'Vector') {
            return this.parseVector();
        } else if (this.tokIs(Operators.LESSTHAN)) {
            var res = this.tryParse(() => this.parseShortVector());
            if (res) {
                return res;
            }
        }
        
        if (this.tok.text === '/' || this.tok.text === '/=') {
            var tok = this.scn.scanRegExp();
            if (tok) {
                this.nextToken(true);
                return new Node(this.scn.content, NodeKind.LITERAL, tok.index, tok.end, tok.text)
            }
        }

        if (this.tok.isXML) {
            result = new Node(this.scn.content, NodeKind.XML_LITERAL, this.tok.index, this.tok.end, this.tok.text);
        }
        else if (this.tok.isNumeric || /('|")/.test(this.tok.text[0])) {
            result = new Node(this.scn.content, NodeKind.LITERAL, this.tok.index, this.tok.end, this.tok.text);
        } else {
            result = new Node(this.scn.content, NodeKind.IDENTIFIER,  this.tok.index, this.tok.end, this.tok.text);
        }
        this.nextToken(true);
        return result;
    }

    /**
     * tok is the first token of a statement
     * 
     * @throws TokenException
     */
    private parseStatement(): Node {
        var result: Node;

        if (this.tokIs(KeyWords.FOR)) {
            result = this.parseFor();
        }
        else if (this.tokIs(KeyWords.IF)) {
            result = this.parseIf();
        }
        else if (this.tokIs(KeyWords.SWITCH)) {
            result = this.parseSwitch();
        }
        else if (this.tokIs(KeyWords.DO)) {
            result = this.parseDo();
        }
        else if (this.tokIs(KeyWords.WHILE)) {
            result = this.parseWhile();
        }
        else if (this.tokIs(KeyWords.TRY)) {
            result = this.parseTry();
        }
        else if (this.tokIs(KeyWords.CATCH)) {
            result = this.parseCatch();
        }
        else if (this.tokIs(KeyWords.FINALLY)) {
            result = this.parseFinally();
        }
        else if (this.tokIs(Operators.LEFT_CURLY_BRACKET)) {
            result = this.parseBlock();
        }
        else if (this.tokIs(KeyWords.VAR)) {
            result = this.parseVar();
        }
        else if (this.tokIs(KeyWords.CONST)) {
            result = this.parseConst();
        }
        else if (this.tokIs(KeyWords.RETURN)) {
            result = this.parseReturnStatement();
        }
        else if (this.tokIs(KeyWords.THROW)) {
            result = this.parseThrowStatement();
        }
        else if (this.tokIs(KeyWords.BREAK) || this.tokIs(KeyWords.CONTINUE)) {
            result = this.parseBreakOrContinueStatement();
        }
        else if (this.tokIs(Operators.SEMICOLON)) {
            result = this.parseEmptyStatement();
        }
        else {
            result = this.parseExpressionList();
            this.skip(Operators.SEMICOLON);
        }
        return result;
    }

    /**
     * @return
     * @throws TokenException
     */
    private parseUnaryExpression(): Node {
        var result: Node,
            index = this.tok.index;
        if (this.tokIs(Operators.INCREMENT)) {
            this.nextToken(true);
            result = new Node(this.scn.content, NodeKind.PRE_INC, this.tok.index, index, null, [this.parseUnaryExpression()]);
        }
        else if (this.tokIs(Operators.DECREMENT)) {
            this.nextToken(true);
            result = new Node(this.scn.content, NodeKind.PRE_DEC, this.tok.index, index, null, [this.parseUnaryExpression()]);
        }
        else if (this.tokIs(Operators.MINUS)) {
            this.nextToken(true);
            result = new Node(this.scn.content, NodeKind.MINUS, this.tok.index, index, null, [this.parseUnaryExpression()]);
        }
        else if (this.tokIs(Operators.PLUS) || this.tokIs(Operators.PLUS_AS2)) {
            this.nextToken(true);
            result = new Node(this.scn.content, NodeKind.PLUS, this.tok.index, index, null, [this.parseUnaryExpression()]);
        }
        else {
            return this.parseUnaryExpressionNotPlusMinus();
        }
        return result;
    }

    private collectVarListContent(result: Node): Node {
        result.children.push(this.parseNameTypeInit());
        while (this.tokIs(Operators.COMMA)) {
            this.nextToken(true);
            result.children.push(this.parseNameTypeInit());
        }
        return result;
    }


    /**
     * Compare the current token to the parameter. If it equals, get the next
     * token. If not, throw a runtime exception.
     * 
     * @param text
     * @throws UnExpectedTokenException
     */
    consume(text: string, ignoreDocumentation: boolean= false): Token {
        while (startsWith(this.tok.text, "//")) {
            this.nextToken(ignoreDocumentation);
        }

        if (!this.tokIs(text)) {
            /*throw new UnExpectedTokenException(this.tok.text,
                new Position(this.tok.index, this.tok.getColumn()),
                fileName,
                text);*/
            
			this.unexpectedToken(text);
        }
        var result = this.tok;
        this.nextToken(ignoreDocumentation);
        return result;
    }

    private convertMeta(metadataList: Node[]): Node {
        if (metadataList == null || metadataList.length === 0) {
            return null;
        }

        var result: Node = new Node(this.scn.content, NodeKind.META_LIST, this.tok.index, -1);
        result.children = metadataList ? metadataList.slice(0) : [];
        if (result.lastChild) { result.end = result.lastChild.end }
        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, result.start);
        return result;
    }

    private convertModifiers(modifierList: Token[]): Node {
        if (modifierList == null) {
            return null;
        }

        var result: Node = new Node(this.scn.content, NodeKind.MOD_LIST, this.tok.index, -1);

        var end = this.tok.index;
        result.children = modifierList.map(tok => {
            end = tok.index + tok.text.length;
            return new Node(this.scn.content, NodeKind.MODIFIER, tok.index, end, tok.text)
        })
        result.end = end;
        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, result.start);
        return result;
    }

    private doParseSignature(): Node[] {
        var tok = this.consume(KeyWords.FUNCTION);
        var type: Node = new Node(this.scn.content, NodeKind.TYPE, tok.index, tok.end, KeyWords.FUNCTION);
        var checkpoint = this.scn.getCheckPoint();
        if (this.tokIs(KeyWords.SET) || this.tokIs(KeyWords.GET)) {
            var possibleType = new Node(this.scn.content, NodeKind.TYPE, tok.index, this.tok.end, this.tok.text);
            this.nextToken(true); // set or get
            
            if (this.tokIs(Operators.LEFT_PARENTHESIS))
            	this.scn.rewind(checkpoint);
            else
            	type = possibleType;
        }
        
        var name: Node = new Node(this.scn.content, NodeKind.NAME, this.tok.index, this.tok.end, this.tok.text);
        this.nextToken(true); // name
		
		// append template type if present
		if (this.tsTemplate)
		{
			name.text += this.tsTemplate;
			name.end = this.tok.index;
		}
		this.tsTemplate = null;
		
        var params: Node = this.parseParameterList();
        var returnType: Node = this.parseOptionalType();
        return [type, name, params, returnType];
    }

    private findFunctionTypeFromSignature(signature: Node[]): string {
        for (var i = 0; i < signature.length; i++) {
            var node = signature[i];
            if (node.kind === NodeKind.TYPE) {
                if (node.text === "set") {
                    return NodeKind.SET;
                }
                if (node.text === "get") {
                    return NodeKind.GET;
                }
                return NodeKind.FUNCTION;
            }
        }
        return NodeKind.FUNCTION;
    }

    /**
     * Get the next token Skip comments but keep newlines We need this method for
     * beeing able to decide if a returnStatement has an expression
     * 
     * @throws UnExpectedTokenException
     */
    private nextTokenAllowNewLine(): void {
        do {
            this.tok = this.scn.nextToken();

            if (this.tok == null) {
                throw new Error(this.fileName)//TODO NullTokenException(fileName);

            }
            if (this.tok.text == null) {
                 throw new Error(this.fileName)//TODO throw new NullTokenException(fileName);
            }
        }
        while (startsWith(this.tok.text, SINGLE_LINE_COMMENT));
    }

    private parseAdditiveExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.ADD, this.tok.index, this.tok.end, null, [this.parseMultiplicativeExpression()]);
        while (this.tokIs(Operators.PLUS) || this.tokIs(Operators.PLUS_AS2) || this.tokIs(Operators.MINUS)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseMultiplicativeExpression());
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result.children.length > 1 ? result : result.lastChild;
    }

    // ------------------------------------------------------------------------
    // language specific recursive descent parsing
    // ------------------------------------------------------------------------

    private parseAndExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.AND, this.tok.index, this.tok.end, null, [this.parseBitwiseOrExpression()]);
        while (this.tokIs(Operators.AND) || this.tokIs(Operators.AND_AS2)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseBitwiseOrExpression());
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result.children.length > 1 ? result : result.lastChild;
    }

    /**
     * tok is ( exit tok is first token after )
     */
    private parseArgumentList(): Node {
        var tok = this.consume(Operators.LEFT_PARENTHESIS);
        var result: Node = new Node(this.scn.content, NodeKind.ARGUMENTS, tok.index, -1);
        while (!this.tokIs(Operators.RIGHT_PARENTHESIS)) {
            result.children.push(this.parseExpression());
            this.skip(Operators.COMMA, true);
        }
        tok = this.consume(Operators.RIGHT_PARENTHESIS);
        result.end = tok.end;
        return result;
    }

    private parseArrayAccessor(node: Node): Node {
        var result: Node = new Node(this.scn.content, NodeKind.ARRAY_ACCESSOR, node.start, -1);
        result.children.push(node);
        while (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
            this.nextToken(true);
            result.children.push(this.parseExpression());
            result.end = this.consume(Operators.RIGHT_SQUARE_BRACKET).end;
        }
        return result;
    }

    /**
     * tok is [
     */
    private parseArrayLiteral(): Node {
        var tok = this.consume(Operators.LEFT_SQUARE_BRACKET);
        var result: Node = new Node(this.scn.content, NodeKind.ARRAY, tok.index, -1);
        while (!this.tokIs(Operators.RIGHT_SQUARE_BRACKET)) {
            result.children.push(this.parseExpression());
            this.skip(Operators.COMMA, true);
        }
        result.end = this.consume(Operators.RIGHT_SQUARE_BRACKET, true).end;
        return result;
    }

    private parseAssignmentExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.ASSIGN, this.tok.index, this.tok.end, null, [this.parseConditionalExpression()]);
        while (this.tokIs(Operators.EQUAL)
            || this.tokIs(Operators.PLUS_EQUAL) || this.tokIs(Operators.MINUS_EQUAL)
            || this.tokIs(Operators.TIMES_EQUAL) || this.tokIs(Operators.DIVIDED_EQUAL)
            || this.tokIs(Operators.MODULO_EQUAL) || this.tokIs(Operators.AND_EQUAL) || this.tokIs(Operators.OR_EQUAL)
            || this.tokIs(Operators.XOR_EQUAL)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseExpression());
        }
        if (result.lastChild) { 
            result.end = result.lastChild.end;
        }
        return result.children.length > 1 ? result : result.lastChild;
    }

    private parseBitwiseAndExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.B_AND, this.tok.index, this.tok.end, this.tok.text, [this.parseEqualityExpression()]);
        while (this.tokIs(Operators.B_AND)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseEqualityExpression());
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result.children.length > 1 ? result : result.lastChild;
    }

    private parseBitwiseOrExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.B_OR, this.tok.index, this.tok.end, this.tok.text, [this.parseBitwiseXorExpression()]);
        while (this.tokIs(Operators.B_OR)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseBitwiseXorExpression());
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result.children.length > 1 ? result : result.lastChild;
    }

    private parseBitwiseXorExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.B_XOR, this.tok.index, this.tok.end, this.tok.text, [this.parseBitwiseAndExpression()]);
        while (this.tokIs(Operators.B_XOR)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseBitwiseAndExpression());
        }
        if (result.lastChild) { result.end = result.lastChild.end }
        return result.children.length > 1 ? result : result.lastChild;
    }


    private parseBlock(result?: Node): Node {

        var tok = this.consume(Operators.LEFT_CURLY_BRACKET);
        if (!result) {
            result = new Node(this.scn.content, NodeKind.BLOCK, tok.index, this.tok.end)
        } else {
            result.start = tok.index;
        }
        while (!this.tokIs(Operators.RIGHT_CURLY_BRACKET)) {
            if (startsWith(this.tok.text, MULTIPLE_LINES_COMMENT)) {
                this.currentFunctionNode.children.push(
                    new Node(this.scn.content, NodeKind.MULTI_LINE_COMMENT, this.tok.index, this.tok.end, this.tok.text)
                    );
                this.nextToken();
            }
            else {
                result.children.push(this.parseStatement());
            }
        }
        result.end = this.consume(Operators.RIGHT_CURLY_BRACKET).end;
        return result;
    }

    /**
     * tok is catch
     * 
     * @throws TokenException
     */
    private parseCatch(): Node {
        var tok = this.consume(KeyWords.CATCH);
        this.consume(Operators.LEFT_PARENTHESIS);
        var result: Node = new Node(this.scn.content, NodeKind.CATCH, tok.index, tok.end, null, [
            new Node(this.scn.content, NodeKind.NAME, this.tok.index, this.tok.end, this.tok.text)
        ]);
        this.nextToken(true); // name
        if (this.tokIs(Operators.COLON)) {
            this.nextToken(true); // :
            result.children.push(new Node(this.scn.content, NodeKind.TYPE, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true); // type
        }
        this.consume(Operators.RIGHT_PARENTHESIS);
        var parseBlock = this.parseBlock()
        result.children.push(parseBlock);
        result.end = parseBlock.end;
        return result;
    }

    /**
     * tok is class
     * 
     * @param meta
     * @param modifier
     * @throws TokenException
     */
    private parseClass(meta: Node[], modifier: Token[]): Node {
        var tok = this.consume(KeyWords.CLASS);
        var result: Node = new Node(this.scn.content, NodeKind.CLASS, tok.index, tok.end);

        if (this.currentAsDoc != null) {
            result.children.push(this.currentAsDoc);
            this.currentAsDoc = null;
        }
        if (this.currentMultiLineComment != null) {
            result.children.push(this.currentMultiLineComment);
            this.currentMultiLineComment = null;
        }

		var index = this.tok.index;
		
        result.children.push(this.parseQualifiedName(NodeKind.NAME, true));

        result.children.push(this.convertMeta(meta));
        result.children.push(this.convertModifiers(modifier));

        // this.nextToken( true ); // name

        do {
            if (this.tokIs(KeyWords.EXTENDS)) {
                this.nextToken(true); // extends
                result.children.push(this.parseQualifiedName(NodeKind.EXTENDS, false));
            }
            else if (this.tokIs(KeyWords.IMPLEMENTS)) {
                result.children.push(this.parseImplementsList());
            }
        }
        while (!this.tokIs(Operators.LEFT_CURLY_BRACKET));
        this.consume(Operators.LEFT_CURLY_BRACKET);
        result.children.push(this.parseClassContent());
        var tok = this.consume(Operators.RIGHT_CURLY_BRACKET);

        result.end = tok.end;
        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, index);

        return result;
    }

    private parseClassConstant(result: Node, modifiers: Token[], meta: Node[]): void {
        result.children.push(this.parseConstList(meta, modifiers));
        if (this.tokIs(Operators.SEMICOLON)) {
            this.nextToken();
        }
        meta.length = 0;
        modifiers.length = 0;
    }

    private parseClassField(result: Node, modifiers: Token[], meta: Node[]): void {
        var varList: Node = this.parseVarList(meta, modifiers);
        result.children.push(varList);
        if (this.currentAsDoc != null) {
            varList.children.push(this.currentAsDoc);
            this.currentAsDoc = null;
        }
        if (this.currentMultiLineComment != null) {
            result.children.push(this.currentMultiLineComment);
            this.currentMultiLineComment = null;
        }
        if (this.tokIs(Operators.SEMICOLON)) {
            this.nextToken();
        }
        meta.length = 0;
        modifiers.length = 0;
    }

    private parseClassFunctions(result: Node, modifiers: Token[], meta: Node[]): void {
        result.children.push(this.parseFunction(meta, modifiers));
        meta.length = 0;
        modifiers.length = 0;
        
    }

    /**
     * tok is (
     * 
     * @throws TokenException
     */
    private parseCondition(): Node {
        var tok = this.consume(Operators.LEFT_PARENTHESIS);
        var result: Node = new Node(this.scn.content, NodeKind.CONDITION, tok.index, -1, null, [this.parseExpression()]);
        tok = this.consume(Operators.RIGHT_PARENTHESIS);
        result.end = tok.end;
        return result;
    }

    private parseConditionalExpression(): Node {
        var result: Node = this.parseOrExpression();
        if (this.tokIs(Operators.QUESTION_MARK)) {
            var conditional: Node = new Node(this.scn.content, NodeKind.CONDITIONAL, result.start, -1, null, [result]);
            this.nextToken(true); // ?
            conditional.children.push(this.parseExpression());
            this.nextToken(true); // :
            conditional.children.push(this.parseExpression());
            conditional.end = conditional.lastChild.start;
            return conditional;
        }
        return result;
    }

    private parseConst(): Node {
        var result = this.parseConstList(null, null);
        this.skip(Operators.SEMICOLON);
        return result;
    }

    /**
     * tok is const
     * 
     * @param modifiers
     * @param meta
     * @throws TokenException
     */
    private parseConstList(meta: Node[], modifiers: Token[]): Node {
        var tok = this.consume(KeyWords.CONST);
        var result: Node = new Node(this.scn.content, NodeKind.CONST_LIST, tok.index, -1);
        result.children.push(this.convertMeta(meta));
        result.children.push(this.convertModifiers(modifiers));
        this.collectVarListContent(result);

        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, tok.index);
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);

        return result;
    }

    private parseDecrement(node: Node): Node {
        this.nextToken(true);
        var result: Node = new Node(this.scn.content, NodeKind.POST_DEC, node.start, this.tok.end);
        result.children.push(node);
        result.end = node.end;
        return result;
    }

    /**
     * tok is do
     * 
     * @throws TokenException
     */
    private parseDo(): Node {
        var tok = this.consume(KeyWords.DO);
        var result: Node = new Node(this.scn.content, NodeKind.DO, tok.index, -1, null, [this.parseStatement()]);
        this.consume(KeyWords.WHILE);
        var cond = this.parseCondition()
        result.children.push(cond);
        result.end = cond.end;
        if (this.tokIs(Operators.SEMICOLON)) {
            this.nextToken(true);
        }
        return result;
    }

    private parseDot(node: Node): Node {
        this.nextToken(true);
        if (this.tokIs(Operators.LEFT_PARENTHESIS)) {
            var index = this.tok.index
            this.nextToken(true);
            var result: Node = new Node(this.scn.content, NodeKind.E4X_FILTER, this.tok.index, -1);
            result.children.push(node);
            result.children.push(this.parseExpression());
            result.end = this.consume(Operators.RIGHT_PARENTHESIS).end;
            return result;
        }
        else if (this.tokIs(Operators.TIMES)) {
            var result: Node = new Node(this.scn.content, NodeKind.E4X_STAR, this.tok.index, -1);
            result.children.push(node);
            result.end = node.end;
            return result;
        }
        var result: Node = new Node(this.scn.content, NodeKind.DOT, node.start, -1);
        result.children.push(node);
        result.children.push(new Node(this.scn.content, NodeKind.LITERAL, this.tok.index, this.tok.end, this.tok.text))
        this.nextToken(true);
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result;
    }

    private parseEmptyStatement(): Node {
        var result: Node;
        result = new Node(this.scn.content, NodeKind.STMT_EMPTY, this.tok.index, this.tok.end, Operators.SEMICOLON);
        this.nextToken(true);
        return result;
    }

    private parseEncapsulatedExpression(): Node {
        var tok = this.consume(Operators.LEFT_PARENTHESIS);
        var result: Node = new Node(this.scn.content, NodeKind.ENCAPSULATED, tok.index, -1);
        result.children.push(this.parseExpressionList());
        tok = this.consume(Operators.RIGHT_PARENTHESIS);
        result.end = tok.end;
        return result;
    }

    private parseEqualityExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.EQUALITY, this.tok.index, -1, null, [this.parseRelationalExpression()]);
        while (
            this.tokIs(Operators.DOUBLE_EQUAL) || this.tokIs(Operators.DOUBLE_EQUAL_AS2) ||
            this.tokIs(Operators.STRICTLY_EQUAL) || this.tokIs(Operators.NOT_EQUAL) ||
            this.tokIs(Operators.NOT_EQUAL_AS2_1) || this.tokIs(Operators.NOT_EQUAL_AS2_2) ||
            this.tokIs(Operators.NOT_STRICTLY_EQUAL)
            ) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseRelationalExpression());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result.children.length > 1 ? result : result.children[0];
    }

    private parseExpressionList(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.EXPR_LIST, this.tok.index, -1, null, [this.parseAssignmentExpression()]);
        while (this.tokIs(Operators.COMMA)) {
            this.nextToken(true);
            result.children.push(this.parseAssignmentExpression());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result.children.length > 1 ? result : result.children[0];
    }

    private parseFinally(): Node {
        var result: Node;
        var index = this.tok.index;
        this.nextToken(true);
        var block = this.parseBlock()
        result = new Node(this.scn.content, NodeKind.FINALLY, index, block.end, null, [block]);
        return result;
    }

    /**
     * tok is for
     * 
     * @throws TokenException
     */
    private parseFor(): Node {
        var tok = this.consume(KeyWords.FOR);

        if (this.tokIs(KeyWords.EACH)) {
            this.nextToken(true);
            return this.parseForEach(tok.index);
        }
        else {
            return this.parseTraditionalFor(tok.index);
        }
    }

    /**
     * tok is ( for each( var obj : Type in List )
     * 
     * @throws TokenException
     */
    private parseForEach(index: number): Node {
        this.consume(Operators.LEFT_PARENTHESIS);

        var result: Node = new Node(this.scn.content, NodeKind.FOREACH, index, -1);
        if (this.tokIs(KeyWords.VAR)) {
            var node: Node = new Node(this.scn.content, NodeKind.VAR, this.tok.index, -1);
            this.nextToken(true);
            var child = this.parseNameTypeInit();
            node.children.push(child);
            node.end = child.end;
            result.children.push(node);
        }
        else {
            result.children.push(new Node(this.scn.content, NodeKind.NAME, this.tok.index, this.tok.end, this.tok.text));
            // names allowed?
            this.nextToken(true);
        }
        var index = this.tok.index;
        this.consume(KeyWords.IN, true);
        var expr = this.parseExpression()
        result.children.push(new Node(this.scn.content, NodeKind.IN, index, expr.end, null, [expr]));
        this.consume(Operators.RIGHT_PARENTHESIS);
        var statement = this.parseStatement()
        result.children.push(statement);
        result.end = statement.end;
        return result;
    }

    private parseForIn(result: Node): Node {
        var index = this.tok.index
        this.nextToken(true);
        var expr = this.parseExpression()
        result.children.push(new Node(this.scn.content, NodeKind.IN, index, expr.end, null, [expr]));
        result.kind = NodeKind.FORIN;
        this.consume(Operators.RIGHT_PARENTHESIS);
        return result;
    }

    /**
     * tok is function
     * 
     * @param modifiers
     * @param meta
     * @throws TokenException
     */
    private parseFunction(meta: Node[], modifiers: Token[]): Node {
        var signature: Node[] = this.doParseSignature();
        var result: Node = new Node(this.scn.content, 
            this.findFunctionTypeFromSignature(signature), signature[0].start,
            -1, signature[0].text
        );

        if (this.currentAsDoc != null) {
            result.children.push(this.currentAsDoc);
            this.currentAsDoc = null;
        }
        if (this.currentMultiLineComment != null) {
            result.children.push(this.currentMultiLineComment);
            this.currentMultiLineComment = null;
        }
        result.children.push(this.convertMeta(meta));
        result.children.push(this.convertModifiers(modifiers));
        result.children.push(signature[1]);
        result.children.push(signature[2]);
        result.children.push(signature[3]);
        if (this.tokIs(Operators.SEMICOLON)) {
            this.consume(Operators.SEMICOLON);
        }
        else {
            result.children.push(this.parseFunctionBlock());
        }
        this.currentFunctionNode = null;
        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, result.start);
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result;
    }

    /**
     * tok is { exit tok is the first tok after }
     * 
     * @throws TokenException
     * @throws TokenException
     */

    private parseFunctionBlock(): Node {
        var block: Node = new Node(this.scn.content, NodeKind.BLOCK, this.tok.index, -1);

        this.currentFunctionNode = block;

        this.parseBlock(block);

        return block;
    }

    private parseFunctionCall(node: Node): Node {
        var result: Node = new Node(this.scn.content, NodeKind.CALL, node.start, -1);
        result.children.push(node);
        while (this.tokIs(Operators.LEFT_PARENTHESIS)) {
            result.children.push(this.parseArgumentList());
        }
        while (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
            result.children.push(this.parseArrayLiteral());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result;
    }

    /**
     * tok is function exit tok is the first token after the optional ;
     * 
     * @throws TokenException
     */
    private parseFunctionSignature(): Node {
        var signature: Node[] = this.doParseSignature();
        this.skip(Operators.SEMICOLON, true);
        var result: Node = new Node(this.scn.content, 
            this.findFunctionTypeFromSignature(signature), signature[0].start,
            -1, signature[0].text
            );
        result.children.push(signature[1]);
        result.children.push(signature[2]);
        result.children.push(signature[3]);
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
		//console.log('\n\nparseFunctionSignature()',result,'\n\n');
        return result;
    }

    /**
     * tok is if
     * 
     * @throws TokenException
     */
    private parseIf(): Node {
        var tok = this.consume(KeyWords.IF);
        var result: Node = new Node(this.scn.content, NodeKind.IF, tok.index, -1, null, [this.parseCondition()]);
        result.children.push(this.parseStatement());
        if (this.tokIs(KeyWords.ELSE)) {
            this.nextToken(true);
            result.children.push(this.parseStatement());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result;
    }

    /**
     * tok is implements implements a,b,c exit tok is the first token after the
     * list of qualfied names
     * 
     * @throws TokenException
     */
    private parseImplementsList(): Node {
        this.consume(KeyWords.IMPLEMENTS);
        var result: Node = new Node(this.scn.content, NodeKind.IMPLEMENTS_LIST, this.tok.index, -1);
        result.children.push(this.parseQualifiedName(NodeKind.IMPLEMENTS, true));
        while (this.tokIs(Operators.COMMA)) {
            this.nextToken(true);
            result.children.push(this.parseQualifiedName(NodeKind.IMPLEMENTS, true));
        }
        return result;
    }

    /**
     * tok is import
     * 
     * @throws TokenException
     */
    private parseImport(): Node {
        var tok = this.consume(KeyWords.IMPORT);
        var name = this.parseImportName()
        var result: Node = new Node(this.scn.content, NodeKind.IMPORT, tok.index, tok.index + name.length, name);
        this.skip(Operators.SEMICOLON);
        return result;
    }

    /**
     * tok is the first part of a name the last part can be a star exit tok is
     * the first token, which doesn't belong to the name
     * 
     * @throws TokenException
     */
    private parseImportName(): string {
        var result = ''

        result += this.tok.text;
        this.nextToken(true);
        while (this.tokIs(Operators.DOT)) {
            result += Operators.DOT;
            this.nextToken(true); // .
            result += this.tok.text;
            this.nextToken(true); // part of name
        }
        return result;
    }

    private parseIncludeExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.INCLUDE, this.tok.index, -1);
        var tok: Token;
        if (this.tokIs(KeyWords.INCLUDE)) {
            tok = this.consume(KeyWords.INCLUDE);
        }
        else if (this.tokIs(KeyWords.INCLUDE_AS2)) {
            tok = this.consume(KeyWords.INCLUDE_AS2);
        }
        if (tok) {
            result.start = tok.index;
        }
        result.children.push(this.parseExpression());
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, 0);
        return result;
    }

    private parseIncrement(node: Node): Node {
        this.nextToken(true);
        var result: Node = new Node(this.scn.content, NodeKind.POST_INC, node.start, this.tok.end);
        result.children.push(node);
        return result;
    }

    /**
     * tok is interface
     * 
     * @param meta
     * @param modifier
     * @throws TokenException
     */
    private parseInterface(meta: Node[], modifier: Token[]): Node {
        var tok = this.consume(KeyWords.INTERFACE)
        var result: Node = new Node(this.scn.content, NodeKind.INTERFACE, tok.index, -1);

        if (this.currentAsDoc != null) {
            result.children.push(this.currentAsDoc);
            this.currentAsDoc = null;
        }
        if (this.currentMultiLineComment != null) {
            result.children.push(this.currentMultiLineComment);
            this.currentMultiLineComment = null;
        }
        result.children.push(this.parseQualifiedName(NodeKind.NAME, true));

        result.children.push(this.convertMeta(meta));
        result.children.push(this.convertModifiers(modifier));

        if (this.tokIs(KeyWords.EXTENDS)) {
            this.nextToken(true); // extends
            result.children.push(this.parseQualifiedName(NodeKind.EXTENDS, false));
        }
        while (this.tokIs(Operators.COMMA)) {
            this.nextToken(true); // comma
            result.children.push(this.parseQualifiedName(NodeKind.EXTENDS, false));
        }
        this.consume(Operators.LEFT_CURLY_BRACKET);
        result.children.push(this.parseInterfaceContent());
        tok = this.consume(Operators.RIGHT_CURLY_BRACKET);
        result.end = tok.end;
        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, tok.index);
        return result;
    }

    /**
     * tok is function
     * 
     * @throws TokenException
     */
    private parseLambdaExpression(): Node {
        var tok = this.consume(KeyWords.FUNCTION);
        var result: Node;

        //if (this.tok.text.compareTo("(") == 0) {
        if (this.tok.text === "(") {
            result = new Node(this.scn.content, NodeKind.LAMBDA, tok.index, this.tok.end);
        }
        else {
            result = new Node(this.scn.content, NodeKind.FUNCTION, tok.index, this.tok.end, this.tok.text);
            this.nextToken(true);
        }
        result.children.push(this.parseParameterList());
        result.children.push(this.parseOptionalType());
        result.children.push(this.parseBlock());
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result;
    }

    private parseFile(filePath: string, content: string): Node {
        this.setFileName(filePath);
        this.scn = new AS3Scanner();
        this.scn.setContent(content);
        return this.parseCompilationUnit();
    }

    /**
     * tok is [ [id] [id ("test")] [id (name="test",type="a.b.c.Event")] exit
     * token is the first token after ]
     * 
     * @throws TokenException
     */
    private parseMetaData(): Node {
        var buffer = '';

        var index = this.tok.index;

        var index = this.consume(Operators.LEFT_SQUARE_BRACKET).index;
        while (!this.tokIs(Operators.RIGHT_SQUARE_BRACKET)) {
            buffer += this.tok.text;
            this.nextToken(true);
        }
        var end = this.tok.end;
        this.skip(Operators.RIGHT_SQUARE_BRACKET);
        var metaDataNode: Node = new Node(this.scn.content, NodeKind.META, index, end, '[' + buffer + ']');

        return metaDataNode;
    }

    private parseMultiplicativeExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.MULTIPLICATION, this.tok.index, -1, null, [this.parseUnaryExpression()]);
        while (this.tokIs(Operators.TIMES) || this.tokIs(Operators.SLASH) || this.tokIs(Operators.MODULO)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseUnaryExpression());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result.children.length > 1 ? result : result.children[0];
    }

    private parseNamespaceName(): string {
        var name: string = this.tok.text;
        this.nextToken(true); // simple name for now
        return name;
    }

    private parseNameTypeInit(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.NAME_TYPE_INIT, this.tok.index, -1);
        result.children.push(new Node(this.scn.content, NodeKind.NAME, this.tok.index, this.tok.end, this.tok.text));
        this.nextToken(true); // name
        result.children.push(this.parseOptionalType());
        result.children.push(this.parseOptionalInit());
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result;
    }

    private parseNewExpression(): Node {
        var tok = this.consume(KeyWords.NEW);

        var result: Node = new Node(this.scn.content, NodeKind.NEW, tok.index, -1);
        result.children.push(this.parseExpression()); // name
        if (this.tokIs(Operators.VECTOR_START)) {
            var index = this.tok.index
            var vec = this.parseVector()
            result.children.push(new Node(this.scn.content, NodeKind.VECTOR, index, vec.end, null, [vec]));
        }
        if (this.tokIs(Operators.LEFT_PARENTHESIS)) {
            result.children.push(this.parseArgumentList());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result;
    }

    /**
     * tok is {
     */
    private parseObjectLiteral(): Node {
        var tok = this.consume(Operators.LEFT_CURLY_BRACKET);
        var result: Node = new Node(this.scn.content, NodeKind.OBJECT, tok.index, tok.end);
        while (!this.tokIs(Operators.RIGHT_CURLY_BRACKET)) {
            result.children.push(this.parseObjectLiteralPropertyDeclaration());
            this.skip(Operators.COMMA, true);
        }
        tok = this.consume(Operators.RIGHT_CURLY_BRACKET);
        result.end = tok.end;
        return result;
    }

    /*
     * tok is name
     */
    private parseObjectLiteralPropertyDeclaration(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.PROP, this.tok.index, this.tok.end);
        var name: Node = new Node(this.scn.content, NodeKind.NAME, this.tok.index, this.tok.end, this.tok.text);
        result.children.push(name);
        this.nextToken(true); // name
        this.consume(Operators.COLON);
        var expr = this.parseExpression();
        var val = new Node(this.scn.content, NodeKind.VALUE, expr.start, expr.end, null, [expr])
        result.children.push(val);
        result.end = val.end;
        return result;
    }

    /**
     * if tok is "=" parse the expression otherwise do nothing
     * 
     * @return
     */
    private parseOptionalInit(): Node {
        var result: Node = null;
        if (this.tokIs(Operators.EQUAL)) {
            this.nextToken(true);
            var index = this.tok.index;
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.INIT, index, expr.end, null, [expr]);
        }
        return result;
    }

    /**
     * if tok is ":" parse the type otherwise do nothing
     * 
     * @return
     * @throws TokenException
     */
    private parseOptionalType(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.TYPE, this.tok.index, this.tok.index, "");
        if (this.tokIs(Operators.COLON)) {
            this.nextToken(true);
            result = this.parseType();
        }
        return result;
    }

    private parseOrExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.OR, this.tok.index, -1, null, [this.parseAndExpression()]);
        while (this.tokIs(Operators.LOGICAL_OR) || this.tokIs(Operators.LOGICAL_OR_AS2)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseAndExpression());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result.children.length > 1 ? result : result.children[0];
    }

    /**
     * tok is package
     * 
     * @throws UnExpectedTokenException
     */
    private parsePackage(): Node {
        var tok = this.consume(KeyWords.PACKAGE);
        var result: Node = new Node(this.scn.content, NodeKind.PACKAGE, tok.index, -1);
        var nameBuffer = '';

        var index = this.tok.index;
        while (!this.tokIs(Operators.LEFT_CURLY_BRACKET)) {
            nameBuffer += this.tok.text;
            this.nextToken(true);
        }
        result.children.push(new Node(this.scn.content, NodeKind.NAME, index, index + nameBuffer.length, nameBuffer));
        this.consume(Operators.LEFT_CURLY_BRACKET);
        result.children.push(this.parsePackageContent());
        tok = this.consume(Operators.RIGHT_CURLY_BRACKET);
        result.end = tok.end
        return result;
    }

    /**
     * tok is the name of a parameter or ...
     */
    private parseParameter(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.PARAMETER, this.tok.index, -1);
        if (this.tokIs(Operators.REST_PARAMETERS)) {
            var index = this.tok.index;
            this.nextToken(true); // ...
            var rest: Node = new Node(this.scn.content, NodeKind.REST, index, this.tok.end, this.tok.text);
            this.nextToken(true); // rest
			
			// tsReplacement or tsTemplate may appear after ...rest
			var type:string = this.tsReplacement || 'Array' + (this.tsTemplate || '');
			rest.children.push(new Node(this.scn.content, NodeKind.TYPE, rest.end, rest.end, type));
			this.tsReplacement = null;
			this.tsTemplate = null;
			
            result.children.push(rest);
        }
        else {
            result.children.push(this.parseNameTypeInit());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result;
    }

    /**
     * tok is (
     * 
     * @throws TokenException
     */
    private parseParameterList(): Node {
        var tok = this.consume(Operators.LEFT_PARENTHESIS);

        var result: Node = new Node(this.scn.content, NodeKind.PARAMETER_LIST, tok.index, -1);
        while (!this.tokIs(Operators.RIGHT_PARENTHESIS)) {
            result.children.push(this.parseParameter());
            if (this.tokIs(Operators.COMMA)) {
                this.nextToken(true);
            }
            else {
                break;
            }
        }
        tok = this.consume(Operators.RIGHT_PARENTHESIS);
        result.end = tok.end;
        return result;
    }

    /**
     * tok is first part of the name exit tok is the first token after the name
     * 
     * @throws TokenException
     */
    private parseQualifiedName(kind:string, skipPackage:boolean): Node {
		var index = this.tok.index;
        var buffer = '';
		var tsReplacement = this.tsReplacement;
		this.tsReplacement = null;

        buffer += this.tok.text;
		var nameEnd = this.tok.end;
        this.nextToken(true);
		
        while (this.tokIs(Operators.DOT) || this.tokIs(Operators.DOUBLE_COLON)) {
            buffer += this.tok.text;
            this.nextToken(true);
			
            buffer += this.tok.text;
			nameEnd = this.tok.end;
            this.nextToken(true); // name
        }

		if (tsReplacement)
			buffer = tsReplacement;
        else if (skipPackage)
            buffer = buffer.substring(buffer.lastIndexOf(Operators.DOT) + 1);
		
		if (this.tsTemplate)
			buffer += this.tsTemplate;
		this.tsTemplate = null;
        
		return new Node(this.scn.content, kind, index, nameEnd, buffer);
    }

    private parseRelationalExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.RELATION, this.tok.index, -1, null, [this.parseShiftExpression()]);
        while (this.tokIs(Operators.LESSTHAN)
            || this.tokIs(Operators.LESSTHAN_AS2) || this.tokIs(Operators.LESSTHAN_EQUAL)
            || this.tokIs(Operators.LESSTHAN_EQUAL_AS2) || this.tokIs(Operators.GREATERTHAN)
            || this.tokIs(Operators.GREATERTHAN_AS2) || this.tokIs(Operators.GREATERTHAN_EQUAL)
            || this.tokIs(Operators.GREATERTHAN_EQUAL_AS2) || this.tokIs(KeyWords.IS) || this.tokIs(KeyWords.IN)
            && !this.isInFor || this.tokIs(KeyWords.AS) || this.tokIs(KeyWords.INSTANCE_OF)) {
            if (!this.tokIs(KeyWords.AS)) {
                result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            }
            else {
                result.children.push(new Node(this.scn.content, NodeKind.AS, this.tok.index, this.tok.end, this.tok.text));
            }
            this.nextToken(true);
            result.children.push(this.parseShiftExpression());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result.children.length > 1 ? result : result.children[0];
    }

    private parseReturnStatement(): Node {
        var result: Node;

        var index = this.tok.index,
            end = this.tok.end;
        this.nextTokenAllowNewLine();
        if (this.tokIs(NEW_LINE) || this.tokIs(Operators.SEMICOLON)) {
            this.nextToken(true);
            result = new Node(this.scn.content, NodeKind.RETURN, index, end, "");
        }
        else {
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.RETURN, index, expr.end, null, [expr]);
            this.skip(Operators.SEMICOLON);
        }
        return result;
    }
    
    private parseThrowStatement(): Node {
        var tok = this.consume(KeyWords.THROW);
        var expr = this.parseExpression();
        
        return  new Node(this.scn.content, NodeKind.RETURN, tok.index, expr.end, null, [expr]);;
    }
    
    private parseBreakOrContinueStatement(): Node {
        var tok: Token = this.tok;
        var kind: string;
        if (this.tokIs(KeyWords.BREAK) || this.tokIs(KeyWords.CONTINUE)) {
            kind = this.tokIs(KeyWords.BREAK)? NodeKind.BREAK : NodeKind.CONTINUE;
            this.nextToken(true);
        } else {
			this.unexpectedToken('continue or break');
        }
        var result: Node;
        if (this.tokIs(NEW_LINE) || this.tokIs(Operators.SEMICOLON)) {
            this.nextToken(true);
            result = new Node(this.scn.content, kind, tok.index, tok.end, "");
        } else {
            var ident = this.tryParse(() => {
                var expr = this.parsePrimaryExpression();
                if (expr.kind === NodeKind.IDENTIFIER) {
                    return expr;
                } else {
                    throw new Error();
                }
            })
            if (!ident) {
				this.unexpectedToken('indent');
            }
            result = new Node(this.scn.content, kind, tok.index, ident.end, null, [ident]);
        }
        this.skip(Operators.SEMICOLON);
        return result;
    }

    private parseShiftExpression(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.SHIFT, this.tok.index, -1, null, [this.parseAdditiveExpression()]);
        while (this.tokIs(Operators.DOUBLE_SHIFT_LEFT)
            || this.tokIs(Operators.TRIPLE_SHIFT_LEFT) || this.tokIs(Operators.DOUBLE_SHIFT_RIGHT)
            || this.tokIs(Operators.TRIPLE_SHIFT_RIGHT)) {
            result.children.push(new Node(this.scn.content, NodeKind.OP, this.tok.index, this.tok.end, this.tok.text));
            this.nextToken(true);
            result.children.push(this.parseAdditiveExpression());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result.children.length > 1 ? result : result.children[0];
    }

    /**
     * tok is switch
     * 
     * @throws TokenException
     */
    private parseSwitch(): Node {
        var tok = this.consume(KeyWords.SWITCH);
        var result: Node = new Node(this.scn.content, NodeKind.SWITCH, tok.index, tok.end, null, [this.parseCondition()]);
        if (this.tokIs(Operators.LEFT_CURLY_BRACKET)) {
            this.nextToken(true);
            result.children.push(this.parseSwitchCases());
            result.end = this.consume(Operators.RIGHT_CURLY_BRACKET).end;
        }
        return result;
    }

    /**
     * tok is case, default or the first token of the first statement
     * 
     * @throws TokenException
     */
    private parseSwitchBlock(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.SWITCH_BLOCK, this.tok.index, this.tok.end);
        while (!this.tokIs(KeyWords.CASE) && !this.tokIs(KeyWords.DEFAULT) && !this.tokIs(Operators.RIGHT_CURLY_BRACKET)) {
            result.children.push(this.parseStatement());
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result;
    }

    /**
     * tok is { exit tok is }
     * 
     * @throws TokenException
     */
    private parseSwitchCases(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.CASES, this.tok.index, this.tok.end);
        for (; ;) {
            if (this.tokIs(Operators.RIGHT_CURLY_BRACKET)) {
                break;
            }
            else if (this.tokIs(KeyWords.CASE)) {
                var index = this.tok.index;
                this.nextToken(true); // case
                var expr = this.parseExpression();
                var caseNode: Node = new Node(this.scn.content, NodeKind.CASE, index, expr.end, null, [expr]);
                this.consume(Operators.COLON);
                var block = this.parseSwitchBlock();
                caseNode.children.push(block);
                caseNode.end = block.end
                result.children.push(caseNode);
            }
            else if (this.tokIs(KeyWords.DEFAULT)) {
                var index = this.tok.index;
                this.nextToken(true); // default
                this.consume(Operators.COLON);
                var caseNode: Node = new Node(this.scn.content, NodeKind.CASE, index, -1, null,
                    [new Node(this.scn.content, NodeKind.DEFAULT, index, this.tok.end, KeyWords.DEFAULT)]);
                var block = this.parseSwitchBlock();
                caseNode.end = block.end
                caseNode.children.push(block);
                result.children.push(caseNode);
            }
        }
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, result.end);
        return result;
    }

    /**
     * tok is ( for( var x : number = 0; i < length; i++ ) for( var s : string in
     * Object )
     * 
     * @throws TokenException
     */
    private parseTraditionalFor(index: number): Node {
        this.consume(Operators.LEFT_PARENTHESIS);

        var result: Node = new Node(this.scn.content, NodeKind.FOR, index, -1);
        if (!this.tokIs(Operators.SEMICOLON)) {
            if (this.tokIs(KeyWords.VAR)) {
                var varList = this.parseVarList(null, null)
                result.children.push(new Node(this.scn.content, NodeKind.INIT, varList.start, varList.end, null, [varList]));
            }
            else {
                this.isInFor = true;
                var expr = this.parseExpressionList();
                result.children.push(new Node(this.scn.content, NodeKind.INIT, expr.start, expr.end, null, [expr]));
                this.isInFor = false;
            }
            if (this.tokIs(NodeKind.IN)) {
                return this.parseForIn(result);
            }
        }
        this.consume(Operators.SEMICOLON);
        if (!this.tokIs(Operators.SEMICOLON)) {
            var expr = this.parseExpression()
            result.children.push(new Node(this.scn.content, NodeKind.COND, expr.start, expr.end, null, [expr]));
        }
        this.consume(Operators.SEMICOLON);
        if (!this.tokIs(Operators.RIGHT_PARENTHESIS)) {
            var expr = this.parseExpressionList()
            result.children.push(new Node(this.scn.content, NodeKind.ITER, expr.start, expr.end, null, [expr]));
        }
        this.consume(Operators.RIGHT_PARENTHESIS);
        result.children.push(this.parseStatement());
        return result;
    }

    private parseTry(): Node {
        var result: Node;
        var index = this.tok.index;
        this.nextToken(true);
        var block = this.parseBlock();
        result = new Node(this.scn.content, NodeKind.TRY, index, block.end, null, [block]);
        return result;
    }

    private parseType(): Node {
        var result: Node;
        if (this.tok.text === VECTOR) {
            result = this.parseVector();
        }
        else {
            result = this.parseQualifiedName(NodeKind.TYPE, true);
            // this.nextToken( true );
        }
        return result;
    }

    private parseUnaryExpressionNotPlusMinus(): Node {
        var result: Node;
        var index = this.tok.index
        if (this.tokIs(KeyWords.DELETE)) {
            this.nextToken(true);
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.DELETE, index, expr.end, null, [expr]);
        }
        else if (this.tokIs(KeyWords.VOID)) {
            this.nextToken(true);
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.VOID, index, expr.end, null, [expr]);
        }
        else if (this.tokIs(KeyWords.TYPEOF)) {
            this.nextToken(true);
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.TYPEOF, index, expr.end, null, [expr]);
        }
        else if (this.tokIs("!") || this.tokIs("not")) {
            this.nextToken(true);
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.NOT, index, expr.end, null, [expr]);
        }
        else if (this.tokIs("~")) {
            this.nextToken(true);
            var expr = this.parseExpression();
            result = new Node(this.scn.content, NodeKind.B_NOT, index, expr.end, null, [expr]);
        }
        else {
            result = this.parseUnaryPostfixExpression();
        }
        return result;
    }

    private parseUnaryPostfixExpression(): Node {
        var node: Node = this.parseAccessExpresion();

        if (this.tokIs(Operators.INCREMENT)) {
            node = this.parseIncrement(node);
        }
        else if (this.tokIs(Operators.DECREMENT)) {
            node = this.parseDecrement(node);
        }
        return node;
    }
    
    private parseAccessExpresion(): Node {
        var node: Node = this.parsePrimaryExpression();

        while (true) {
            if (this.tokIs(Operators.LEFT_PARENTHESIS)) {
                node = this.parseFunctionCall(node);
            }
            if (this.tokIs(Operators.DOT) || this.tokIs(Operators.DOUBLE_COLON)) {
                node = this.parseDot(node);
            } else if (this.tokIs(Operators.LEFT_SQUARE_BRACKET)) {
                node = this.parseArrayAccessor(node);
            } else {
                break;
            }
        }
        return node;
    }

    private parseUse(): Node {
        var tok = this.consume(KeyWords.USE);
        this.consume(KeyWords.NAMESPACE);
        var nameIndex = this.tok.index;
        var namespace = this.parseNamespaceName();
        var result: Node = new Node(this.scn.content, NodeKind.USE, tok.index, nameIndex + namespace.length, namespace);
        this.skip(Operators.SEMICOLON);
        return result;
    }

    private parseVar(): Node {
        var result: Node;
        result = this.parseVarList(null, null);
        this.skip(Operators.SEMICOLON);
        return result;
    }

    /**
     * tok is var var x, y : String, z : number = 0;
     * 
     * @param modifiers
     * @param meta
     * @throws TokenException
     */
    private parseVarList(meta: Node[], modifiers: Token[]): Node {
        var tok = this.consume(KeyWords.VAR);
        var result: Node = new Node(this.scn.content, NodeKind.VAR_LIST, tok.index, tok.end);
        result.children.push(this.convertMeta(meta));
        result.children.push(this.convertModifiers(modifiers));
        this.collectVarListContent(result);
        result.start = result.children.reduce((index: number, child: Node) => {
            return Math.min(index, child ? child.start : Infinity);
        }, tok.index);
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, tok.end);
        return result;
    }

    private parseVector(): Node {
        var result: Node = new Node(this.scn.content, NodeKind.VECTOR, this.tok.index, -1, "");
        if (this.tok.text === "Vector") {
            this.nextToken(true);
        }
        this.consume(Operators.VECTOR_START);

        result.children.push(this.parseType());

        result.end = this.consume(Operators.GREATERTHAN).end;

        return result;
    }
    
    private parseShortVector(): Node {
        var vector: Node = new Node(this.scn.content, NodeKind.VECTOR, this.tok.index, -1, "");
        this.consume(Operators.LESSTHAN);
        vector.children.push(this.parseType());
        vector.end = this.consume(Operators.GREATERTHAN).end;
        
        var arrayLiteral = this.parseArrayLiteral()
        
        return new Node(this.scn.content, NodeKind.SHORT_VECTOR, vector.start, arrayLiteral.end, null, [vector, arrayLiteral]);
    }

    /**
     * tok is while
     * 
     * @throws TokenException
     */
    private parseWhile(): Node {
        var tok = this.consume(KeyWords.WHILE);
        var result: Node = new Node(this.scn.content, NodeKind.WHILE, tok.index, tok.end);
        result.children.push(this.parseCondition());
        result.children.push(this.parseStatement());
        result.end = result.children.reduce((index: number, child: Node) => {
            return Math.max(index, child ? child.end : 0);
        }, tok.end);
        return result;
    }

    private setFileName(fileNameToParse: string): void {
        this.fileName = fileNameToParse;
    }


    /**
     * Skip the current token, if it equals to the parameter
     * 
     * @param text
     * @throws UnExpectedTokenException
     */
    private skip(text: string, ignoreDocumentation: boolean= false): void {
        if (this.tokIs(text)) {
            this.nextToken(ignoreDocumentation);
        }
    }

    /**
     * Compare the current token to the parameter
     * 
     * @param text
     * @return true, if tok's text property equals the parameter
     */
    private tokIs(text: string): boolean {
        return this.tok.text === text;
    }

    private tryToParseCommentNode(result: Node, modifiers: Token[]): void {
        if (startsWith(this.tok.text, ASDOC_COMMENT)) {
            this.currentAsDoc = new Node(this.scn.content, NodeKind.AS_DOC, this.tok.index, -1, this.tok.text);
            this.nextToken();
        }
        else if (startsWith(this.tok.text, MULTIPLE_LINES_COMMENT)) {
            result.children.push(new Node(this.scn.content, NodeKind.MULTI_LINE_COMMENT, this.tok.index, -1, this.tok.text));
            this.nextToken();
        }
        else {
            if (modifiers != null) {
                modifiers.push(this.tok);
            }
            this.nextToken(true);
        }
    }
	
	private unexpectedToken(expected: string): void {
		var pos = getLineAndCharacterFromPosition(this.tok.index, this.lineMap);
		throw new Error('unexpected token: ' + this.tok.text + ' expected: ' + expected + ' in file ' + this.fileName + '(' + pos.line + ',' + pos.col + ')');
	}
}

export = AS3Parser;