UNPKG

as2dts

Version:

A command line tool that converts ActionScript 3 classes and interfaces to TypeScript definitions (d.ts files)

635 lines (541 loc) 19.8 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 Token = require('./token'); var sax: any = require('sax'); var END: string = '__END__'; function isDecimalChar(currentCharacter: string): boolean { return currentCharacter >= '0' && currentCharacter <= '9'; } function endsWith(string: string, suffix: string) { return string.indexOf(suffix, string.length - suffix.length) !== -1; }; function verifyXML(string: string) { var parser = sax.parser(true); try { parser.write(string).close(); return true; } catch (e) { return false; } } /** * convert a actionscript to a stream of tokens * * @author rbokel * @author xagnetti */ class AS3Scanner { private inVector: boolean; private index: number; public content: string = ''; /** * @return */ public moveToNextToken(): Token { return this.nextToken(); } /** * @param linesToBeSet */ public setContent(content: string): void { this.content = content; this.index = -1; } isHexChar(currentCharacter: string): boolean { return (currentCharacter >= '0' && currentCharacter <= '9') || (currentCharacter >= 'A' && currentCharacter <= 'Z') || (currentCharacter >= 'a' && currentCharacter <= 'z') } /** * @return */ public nextToken(): Token { var currentCharacter: string; if (this.content != null && this.index < this.content.length) { currentCharacter = this.nextNonWhitespaceCharacter(); } else { return new Token(END, this.index); } if (currentCharacter === '\n') { return new Token('\n', this.index); } else if (currentCharacter === '/') { return this.scanCommentRegExpOrOperator(); } else if (currentCharacter === '"') { return this.scanString(currentCharacter); } else if (currentCharacter === '\'') { return this.scanString(currentCharacter); } else if (currentCharacter === '<') { return this.scanXMLOrOperator(currentCharacter); } else if (currentCharacter >= '0' && currentCharacter <= '9' || currentCharacter === '.') { return this.scanNumberOrDots(currentCharacter); } else if ( currentCharacter === '{' || currentCharacter === '}' || currentCharacter === '(' || currentCharacter === ')' || currentCharacter === '[' || currentCharacter === ']' || currentCharacter === ';'|| currentCharacter === ',' || currentCharacter === '?' || currentCharacter === '~' ) { return this.scanSingleCharacterToken(currentCharacter); } else if (currentCharacter === ':') { return this.scanCharacterSequence(currentCharacter, ['::']); } else if (currentCharacter === '*') { //UGLY HACK but .... seems working if(this.getPreviousCharacter() !== ':' ) { return this.scanCharacterSequence(currentCharacter, ['*=']); } else { return new Token('*', this.index); } } else if (currentCharacter === '+') { return this.scanCharacterSequence(currentCharacter, ['++', '+=']); } else if (currentCharacter === '-') { return this.scanCharacterSequence(currentCharacter, ['--', '-=']); } else if (currentCharacter === '%') { return this.scanCharacterSequence(currentCharacter, ['%=']); } else if (currentCharacter === '&') { return this.scanCharacterSequence(currentCharacter, ['&&', '&=']); } else if (currentCharacter === '|') { return this.scanCharacterSequence(currentCharacter, ['||', '|=']); } else if (currentCharacter === '^') { return this.scanCharacterSequence(currentCharacter, ['^=']); } else if (currentCharacter === '>') { if (this.inVector) { this.inVector = false; } else { return this.scanCharacterSequence(currentCharacter, ['>>>=', '>>>', '>>=', '>>', '>=']); } } else if (currentCharacter === '=') { return this.scanCharacterSequence(currentCharacter, ['===', '==']); } else if (currentCharacter === '!') { return this.scanCharacterSequence(currentCharacter, ['!==', '!=']); } var token = this.scanWord(currentCharacter); return token.text.length === 0? this.nextToken() : token; } public scanRegExp(): Token { var currentIndex = this.index; var token: Token = this.scanUntilDelimiter('/'); var peekPos = 1; var flagBuffer = ''; for (;;) { var currentCharacter: string = this.peekChar(peekPos++); if (/[a-z]/.test(currentCharacter)) { flagBuffer += currentCharacter; } else { break; } } if (flagBuffer.length) { token.text += flagBuffer; this.index += flagBuffer.length } if (token != null && this.isValidRegExp(token.text)) { return token; } else { this.index = currentIndex; } return null; } public getCheckPoint() { return { index: this.index , inVector : this.inVector}; } public rewind(checkpoint: { index: number; inVector: boolean}): void { this.index = checkpoint.index; this.inVector = checkpoint.inVector; } private computePossibleMatchesMaxLength(possibleMatches: string[]): number { return possibleMatches.reduce((max: number, possibleMatch: string) => { return Math.max(max, possibleMatch.length); }, 0); } private getPreviousCharacter(): string { var currentIndex = -1, currentChar: string; do { currentChar = this.peekChar(currentIndex--); } while (currentChar == ' '); return currentChar; } private isIdentifierCharacter(currentCharacter: string): boolean { return currentCharacter >= 'A' && currentCharacter <= 'Z' || currentCharacter >= 'a' && currentCharacter <= 'z' || currentCharacter >= '0' && currentCharacter <= '9' || currentCharacter === '_'|| currentCharacter === '$'; } private isProcessingInstruction(text: string): boolean { return text.indexOf('<?') === 0; } private isValidRegExp(pattern: string): boolean { try { return eval(pattern) instanceof RegExp; } catch (e) { return false; } } private nextChar(): string { this.index++; var currentChar = this.content.charAt(this.index); while (currentChar == '\uFEFF') { this.index++; currentChar = this.content.charAt(this.index); } return currentChar; } private nextNonWhitespaceCharacter(): string { var result: string; do { result = this.nextChar(); } while (result == ' ' || result == '\t' || result.charCodeAt(0) == 13); return result; } private peekChar(offset: number): string { var index = this.index + offset; if (index == -1) { return '\0'; } return this.content.charAt(index); } /** * find the longest matching sequence * * @param currentCharacter * @param possibleMatches * @param maxLength * @return */ private scanCharacterSequence(currentCharacter: string, possibleMatches: string[]): Token { var peekPos = 1; var buffer: string = ''; var maxLength: number = this.computePossibleMatchesMaxLength(possibleMatches); buffer += currentCharacter; var found: string = buffer.toString(); while (peekPos < maxLength) { buffer += this.peekChar(peekPos); peekPos++; for (var i = 0; i < possibleMatches.length; i++) { var possibleMatche = possibleMatches[i]; if (buffer.toString() === possibleMatche) { found = buffer.toString(); } } } var result: Token = new Token(found, this.index); this.skipChars(found.length - 1); return result; } /** * Something started with a slash This might be a comment, a regexp or a * operator * * @param currentCharacter * @return */ private scanCommentRegExpOrOperator (): Token { var firstCharacter: string = this.peekChar(1); if (firstCharacter == '/') { return this.scanSingleLineComment(); } if (firstCharacter == '*') { return this.scanMultiLineComment(); } var result: Token; if (firstCharacter == '=') { result = new Token('/=', this.index); this.skipChars(1); return result; } result = new Token('/', this.index); return result; } /** * c is either a dot or a number * * @return */ private scanDecimal(currentCharacter: string): Token { var currentChar = currentCharacter; var buffer: string = ''; var peekPos = 1; while (isDecimalChar(currentChar)) { buffer += currentChar; currentChar = this.peekChar(peekPos++); } if (currentChar == '.') { buffer += currentChar; currentChar = this.peekChar(peekPos++); while (isDecimalChar(currentChar)) { buffer += currentChar; currentChar = this.peekChar(peekPos++); } if (currentChar == 'E') { buffer += currentChar; currentChar = this.peekChar(peekPos++); while (isDecimalChar(currentChar)) { buffer += currentChar; currentChar = this.peekChar(peekPos++); } } } var result: Token = new Token(buffer.toString(), this.index, true); this.skipChars(result.text.length - 1); return result; } /** * The first dot has been scanned Are the next chars dots as well? * * @return */ private scanDots(): Token { var secondCharacter: string = this.peekChar(1); if (secondCharacter == '.') { var thirdCharacter: string = this.peekChar(2); var text: string = thirdCharacter == '.' ? '...' : '..'; var result: Token = new Token(text, this.index); this.skipChars(text.length - 1); return result; } else if (secondCharacter == '<') { var result: Token = new Token('.<', this.index); this.skipChars(1); this.inVector = true; return result; } return null; } /** * we have seen the 0x prefix * * @return */ private scanHex(): Token { var buffer = ''; buffer+= '0x'; var peekPos = 2; for (; ;) { var character: string = this.peekChar(peekPos++); if (!this.isHexChar(character)) { break; } buffer+= character; } var result: Token = new Token(buffer, this.index, true); this.skipChars(result.text.length - 1); return result; } /** * the current string is the first slash plus we know, that a * is following * * @return */ private scanMultiLineComment(): Token { var buffer = ''; var currentCharacter = ' '; var previousCharacter = ' '; buffer += '/*'; this.skipChar(); do { previousCharacter = currentCharacter; currentCharacter = this.nextChar(); buffer += currentCharacter; } while (currentCharacter && !(currentCharacter === '/' && previousCharacter == '*')); return new Token(buffer.toString(), this.index); } /** * Something started with a number or a dot. * * @param characterToBeScanned * @return */ private scanNumberOrDots(characterToBeScanned: string): Token { if (characterToBeScanned == '.') { var result: Token = this.scanDots(); if (result != null) { return result; } var firstCharacter: string = this.peekChar(1); if (!isDecimalChar(firstCharacter)) { return new Token('.', this.index); } } if (characterToBeScanned == '0') { var firstCharacter: string = this.peekChar(1); if (firstCharacter == 'x' || firstCharacter == 'X') { return this.scanHex(); } } return this.scanDecimal(characterToBeScanned); } private scanSingleCharacterToken(character: string): Token { return new Token(character, this.index); } /** * the current string is the first slash plus we know, that another slash is * following * * @return */ private scanSingleLineComment(): Token { /*var result: Token = new Token(this.lines[this.line].substring(this.column), this.index); this.skipChars(result.text.length - 1); return result;*/ var char: string, buffer = this.content[this.index]; do { char = this.nextChar(); buffer += char; } while (char && char !== '\n'); return new Token(buffer.toString(), this.index); } /** * Something started with a quote or number quote consume characters until * the quote/double quote shows up again and is not escaped * * @param startingCharacter * @return */ private scanString(startingCharacter: string): Token { return this.scanUntilDelimiter(startingCharacter); } private scanUntilDelimiter(start: string, delimiter?: string): Token { if (typeof delimiter === 'undefined') { delimiter = start; } var buffer = '';; var peekPos = 1; var numberOfBackslashes = 0; buffer += start; for (; ;) { var currentCharacter: string = this.peekChar(peekPos++); if (currentCharacter === '\n' || (this.index + peekPos >= this.content.length)) { return null; } buffer += currentCharacter; if ((currentCharacter === delimiter && numberOfBackslashes == 0) ) { var result = new Token(buffer, this.index); this.skipChars(buffer.toString().length - 1); return result; } numberOfBackslashes = currentCharacter === '\\' ? (numberOfBackslashes + 1) % 2 : 0; } } private scanWord(startingCharacter: string): Token { var currentChar = startingCharacter; var buffer = '';; buffer += currentChar; var peekPos = 1; for (; ;) { currentChar = this.peekChar(peekPos++); if (!this.isIdentifierCharacter(currentChar)) { break; } buffer += currentChar; } var result: Token = new Token(buffer.toString(), this.index); this.skipChars(buffer.toString().length - 1); return result; } /** * Try to parse a XML document * * @return */ private scanXML(): Token { var currentIndex: number = this.index; var level = 0; var buffer = '';; var currentCharacter = '<'; for (; ;) { var currentToken: Token = null; do { currentToken = this.scanUntilDelimiter('<', '>'); if (currentToken == null) { this.index = currentIndex; return null; } buffer += currentToken.text; if (this.isProcessingInstruction(currentToken.text)) { currentCharacter = this.nextChar(); if (currentCharacter === '\n') { buffer += '\n'; this.skipChar(); } currentToken = null; } } while (currentToken == null); if (currentToken.text.indexOf('</') === 0) { level--; } else if (!endsWith(currentToken.text, '/>') && currentToken.text !== '<>') // NOT operator in AS2 { level++; } if (level <= 0) { return new Token(buffer.toString(), currentIndex, false, true); } for (; ;) { currentCharacter = this.nextChar(); if (currentCharacter === '<' || this.index >= this.content.length) { break; } buffer += currentCharacter; } } } /** * Something started with a lower sign < * * @param startingCharacterc * @return */ private scanXMLOrOperator(startingCharacterc: string): Token { var xmlToken: Token = this.scanXML(); if (xmlToken != null && verifyXML(xmlToken.text)) { return xmlToken; } return this.scanCharacterSequence(startingCharacterc, ['<<<=', '<<<', '<<=', '<<','<=']); } private skipChar(): void { this.nextChar(); } private skipChars(count: number): void { var decrementCount = count; while (decrementCount-- > 0) { this.nextChar(); } } } export = AS3Scanner;