as2dts
Version:
A command line tool that converts ActionScript 3 classes and interfaces to TypeScript definitions (d.ts files)
1,381 lines (1,244 loc) • 77.1 kB
text/typescript
/**
* 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.chil