UNPKG

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