UNPKG

jsx

Version:

a faster, safer, easier JavaScript

1,518 lines (1,375 loc) 108 kB
/* * Copyright (c) 2012 DeNA Co., Ltd. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ import "./type.jsx"; import "./analysis.jsx"; import "./classdef.jsx"; import "./statement.jsx"; import "./expression.jsx"; import "./doc.jsx"; import "./util.jsx"; import "./completion.jsx"; import _CompletionCandidatesWithLocal, _CompletionCandidatesOfProperty, _CompletionCandidatesOfNamespace from "completion.jsx"; class Token { var _value : string; var _isIdentifier : boolean; var _filename : Nullable.<string>; var _lineNumber : number; var _columnNumber : number; function constructor (value : string, isIdentifier : boolean) { this(value, isIdentifier, null, NaN, NaN); } function constructor (value : string, isIdentifier : boolean, filename : Nullable.<string>, lineNumber : number, columnNumber : number) { this._value = value; this._isIdentifier = isIdentifier; this._filename = filename; this._lineNumber = lineNumber; this._columnNumber = columnNumber; } function getValue () : string { return this._value; } function isIdentifier () : boolean { return this._isIdentifier; } function getFilename () : Nullable.<string> { return this._filename; } function getLineNumber () : number { return this._lineNumber; } function getColumnNumber () : number { return this._columnNumber; } function serialize () : variant { return [ this._value, this._isIdentifier, this._filename, this._lineNumber, this._columnNumber ] : variant[]; } } class _Lexer { static const ident = " [a-zA-Z_] [a-zA-Z0-9_]* "; static const doubleQuoted = ' " [^"\\\\]* (?: \\\\. [^"\\\\]* )* " '; static const singleQuoted = " ' [^'\\\\]* (?: \\\\. [^'\\\\]* )* ' "; static const stringLiteral = _Lexer.makeAlt([_Lexer.singleQuoted, _Lexer.doubleQuoted]); static const regexpLiteral = _Lexer.doubleQuoted.replace(/"/g, "/") + "[mgi]*"; // ECMA 262 compatible, // see also ECMA 262 5th (7.8.3) Numeric Literals static const decimalIntegerLiteral = "(?: 0 | [1-9][0-9]* )"; static const exponentPart = "(?: [eE] [+-]? [0-9]+ )"; static const numberLiteral = _Lexer.makeAlt([ "(?: " + _Lexer.decimalIntegerLiteral + " \\. " + "[0-9]* " + _Lexer.exponentPart + "? )", "(?: \\. [0-9]+ " + _Lexer.exponentPart + "? )", "(?: " + _Lexer.decimalIntegerLiteral + _Lexer.exponentPart + " )", "NaN", "Infinity" ]) + "\\b"; static const integerLiteral = _Lexer.makeAlt([ "(?: 0 [xX] [0-9a-fA-F]+ )", // hex _Lexer.decimalIntegerLiteral ]) + "(?![\\.0-9eE])\\b"; // regular expressions static const rxIdent = _Lexer.rx("^" + _Lexer.ident); static const rxStringLiteral = _Lexer.rx("^" + _Lexer.stringLiteral); static const rxNumberLiteral = _Lexer.rx("^" + _Lexer.numberLiteral); static const rxIntegerLiteral = _Lexer.rx("^" + _Lexer.integerLiteral); static const rxRegExpLiteral = _Lexer.rx("^" + _Lexer.regexpLiteral); static const rxNewline = /(?:\r\n?|\n)/; // blacklists of identifiers static const keywords = _Lexer.asMap([ // literals shared with ECMA 262 "null", "true", "false", "NaN", "Infinity", // keywords shared with ECMA 262 "break", "do", "instanceof", "typeof", "case", "else", "new", "var", "catch", "finally", "return", "void", /*"continue",*/ // contextual "for", "switch", "while", "function", "this", /* "default", */ // contextual keywords "if", "throw", /* "assert", "log", // contextual keywords */ /*"delete",*/ // contextual "in", "try", // keywords of JSX "class", "extends", "super", "import", "implements", // "interface", // contextual keywords "static", "__FILE__", "__LINE__", "undefined" ]); static const reserved = _Lexer.asMap([ // literals of ECMA 262 but not used by JSX "debugger", "with", // future reserved words of ECMA 262 "const", "export", // future reserved words within strict mode of ECMA 262 "let", "private", "public", "yield", "protected", // JSX specific reserved words "extern", "native", "as", "operator" ]); static function makeAlt (patterns : string[]) : string { return "(?: \n" + patterns.join("\n | \n") + "\n)\n"; } static function quoteMeta (pattern : string) : string { return pattern.replace(/([^0-9A-Za-z_])/g, '\\$1'); } static function asMap (array : string[]) : Map.<boolean> { var hash = new Map.<boolean>; for (var i = 0; i < array.length; ++i) hash[array[i]] = true; return hash; } /// compile a regular expression static function rx (pat : string) : RegExp { return new RegExp(pat.replace(/[ \t\r\n]/g, "")); } } class Import { var _filenameToken : Token; var _aliasToken : Token; var _classNames : Token[]; var _sourceParsers : Parser[]; function constructor(parser : Parser) { // for built-in classes this._filenameToken = null; this._aliasToken = null; this._classNames = null; this._sourceParsers = [ parser ]; } function constructor (filenameToken : Token, aliasToken : Token, classNames : Token[]) { this._filenameToken = filenameToken; this._aliasToken = aliasToken; this._classNames = classNames; this._sourceParsers = [] : Parser[]; } function getFilenameToken () : Token { return this._filenameToken; } function getAlias () : Nullable.<string> { if (this._aliasToken) { return this._aliasToken.getValue(); } else { return null; } } function getClassNames () : string[] { if (this._classNames == null) return null; var names = new string[]; for (var i = 0; i < this._classNames.length; ++i) names[i] = this._classNames[i].getValue(); return names; } function serialize () : variant { return [ "Import", Serializer.<Token>.serializeNullable(this._filenameToken), Serializer.<Token>.serializeNullable(this._aliasToken), Serializer.<Token>.serializeArray(this._classNames) ] : variant[]; } function checkNameConflict (errors : CompileError[], nameToken : Token) : boolean { if (this._aliasToken != null) { if (this._aliasToken.getValue() == nameToken.getValue()) { errors.push(new CompileError(nameToken, "an alias with the same name is already declared")); return false; } } else { if (this._classNames != null) { for (var i = 0; i < this._classNames.length; ++i) { if (this._classNames[i].getValue() == nameToken.getValue()) { errors.push(new CompileError(nameToken, "a class with the same name has already been explicitely imported")); return false; } } } } return true; } function addSource (parser : Parser) : void { this._sourceParsers.push(parser); } function getSources () : Parser[] { return this._sourceParsers; } function assertExistenceOfNamedClasses (errors : CompileError[]) : void { if (this._classNames == null) { // no named classes return; } // list all classses var allClassNames = new string[]; for (var i = 0; i < this._sourceParsers.length; ++i) { allClassNames = allClassNames.concat(this._sourceParsers[i].getClassDefs().map.<string>(function (classDef) { return classDef.className(); })); allClassNames = allClassNames.concat(this._sourceParsers[i].getTemplateClassDefs().map.<string>(function (classDef) { return classDef.className(); })); } function countNumberOfClassesByName(className : string) : number { var num = 0; for (var i = 0; i < allClassNames.length; ++i) { if (allClassNames[i] == className) { ++num; } } return num; } for (var i = 0; i < this._classNames.length; ++i) { switch (countNumberOfClassesByName(this._classNames[i].getValue())) { case 0: errors.push(new CompileError(this._classNames[i], "no definition for class '" + this._classNames[i].getValue() + "'")); break; case 1: // ok break; default: errors.push(new CompileError(this._classNames[i], "multiple candidates for class '" + this._classNames[i].getValue() + "'")); break; } } } function getClasses (name : string) : ClassDefinition[] { if (! this._classIsImportable(name)) { return [] : ClassDefinition[]; } var found = [] : ClassDefinition[]; for (var i = 0; i < this._sourceParsers.length; ++i) { var classDefs = this._sourceParsers[i].getClassDefs(); for (var j = 0; j < classDefs.length; ++j) { var classDef = classDefs[j]; if (classDef.className() == name) { found.push(classDef); break; } } } return found; } function createGetTemplateClassCallbacks (errors : CompileError[], request : TemplateInstantiationRequest, postInstantiationCallback : function(:Parser,:ClassDefinition):ClassDefinition) : Array.<function(:CompileError[],:TemplateInstantiationRequest,:function(:Parser,:ClassDefinition):ClassDefinition):ClassDefinition> { if (! this._classIsImportable(request.getClassName())) { return new Array.<function(:CompileError[],:TemplateInstantiationRequest,:function(:Parser,:ClassDefinition):ClassDefinition):ClassDefinition>; } var callbacks = new Array.<function(:CompileError[],:TemplateInstantiationRequest,:function(:Parser,:ClassDefinition):ClassDefinition):ClassDefinition>; for (var i = 0; i < this._sourceParsers.length; ++i) { var callback = this._sourceParsers[i].createGetTemplateClassCallback(errors, request, postInstantiationCallback); if (callback != null) { callbacks.push(callback); } } return callbacks; } function _classIsImportable (name : string) : boolean { if (this._classNames != null) { for (var i = 0; i < this._classNames.length; ++i) if (this._classNames[i].getValue() == name) break; if (i == this._classNames.length) return false; } else { if (name.charAt(0) == '_') return false; } return true; } static function create (errors : CompileError[], filenameToken : Token, aliasToken : Token, classNames : Token[]) : Import { var filename = Util.decodeStringLiteral(filenameToken.getValue()); if (filename.indexOf("*") != -1) { // read the files from a directory var match = filename.match(/^([^\*]*)\/\*(\.[^\/\*]*)$/); if (match == null) { errors.push(new CompileError(filenameToken, "invalid use of wildcard")); return null; } return new WildcardImport(filenameToken, aliasToken, classNames, match[1], match[2]); } return new Import(filenameToken, aliasToken, classNames); } } class WildcardImport extends Import { var _directory : string; var _suffix : string; function constructor (filenameToken : Token, aliasToken : Token, classNames : Token[], directory : string, suffix : string) { super(filenameToken, aliasToken, classNames); this._directory = directory; this._suffix = suffix; } function getDirectory () : string { return this._directory; } function getSuffix () : string { return this._suffix; } } class QualifiedName { var _token : Token; // _import and _enclosingType are exclusive var _import : Import; var _enclosingType : ParsedObjectType; function constructor (token : Token) { this._token = token; this._import = null; this._enclosingType = null; } function constructor (token : Token, imprt : Import) { this._token = token; this._import = imprt; this._enclosingType = null; } function constructor (token : Token, enclosingType : ParsedObjectType) { this._token = token; this._import = null; this._enclosingType = enclosingType; } function getToken () : Token { return this._token; } function getImport () : Import { return this._import; } function getEnclosingType () : ParsedObjectType { return this._enclosingType; } function serialize () : variant { return [ "QualifiedName", this._token.serialize(), Serializer.<Import>.serializeNullable(this._import), Serializer.<ParsedObjectType>.serializeNullable(this._enclosingType) ] : variant[]; } function equals (x : QualifiedName) : boolean { if (x == null) return false; if (this._token.getValue() != x._token.getValue()) return false; if (this._import != x._import) return false; if (this._enclosingType == null) { if (x._enclosingType != null) return false; } else { if (! this._enclosingType.equals(x._enclosingType)) return false; } return true; } function getClass (context : AnalysisContext, typeArguments : Type[]) : ClassDefinition { var classDef = null : ClassDefinition; if (this._import != null) { // && this._enclosingType == null if (typeArguments.length == 0) { var classDefs = this._import.getClasses(this._token.getValue()); switch (classDefs.length) { case 1: classDef = classDefs[0]; break; case 0: context.errors.push(new CompileError(this._token, "no definition for class '" + this.toString() + "' in file '" + this._import.getFilenameToken().getValue() + "'")); return null; default: context.errors.push(new CompileError(this._token, "multiple candidates")); return null; } } else { var callbacks = this._import.createGetTemplateClassCallbacks(context.errors, new TemplateInstantiationRequest(this._token, this._token.getValue(), typeArguments), function (parser : Parser, classDef : ClassDefinition) : ClassDefinition { return null; }); switch (callbacks.length) { case 1: return callbacks[0](null, null, null); case 0: context.errors.push(new CompileError(this._token, "no definition for template class '" + this.toString() + "' in file '" + this._import.getFilenameToken().getValue() + "'")); return null; default: context.errors.push(new CompileError(this._token, "multiple canditates")); return null; } } } else if (this._enclosingType != null) { this._enclosingType.resolveType(context); var enclosingClassDef; if ((enclosingClassDef = this._enclosingType.getClassDef()) == null) return null; if (typeArguments.length == 0) { if ((classDef = enclosingClassDef.lookupInnerClass(this._token.getValue())) == null) { context.errors.push(new CompileError(this._token, "no class definition for '" + this.toString() + "'")); return null; } } else { if ((classDef = enclosingClassDef.lookupTemplateInnerClass(context.errors, new TemplateInstantiationRequest(this._token, this._token.getValue(), typeArguments), (parser, classDef) -> { return null; })) == null) { context.errors.push(new CompileError(this._token, "failed to instantiate class")); return null; } } } else { if (typeArguments.length == 0) { if ((classDef = context.parser.lookup(context.errors, this._token, this._token.getValue())) == null) { context.errors.push(new CompileError(this._token, "no class definition for '" + this.toString() + "'")); return null; } } else { if ((classDef = context.parser.lookupTemplate(context.errors, new TemplateInstantiationRequest(this._token, this._token.getValue(), typeArguments), function (parser : Parser, classDef : ClassDefinition) : ClassDefinition { return null; })) == null) { context.errors.push(new CompileError(this._token, "failed to instantiate class")); return null; } } } return classDef; } function getTemplateClass (parser : Parser) : TemplateClassDefinition { var foundClassDefs = new TemplateClassDefinition[]; var checkClassDef = function (classDef : TemplateClassDefinition) : void { if (classDef.className() == this._token.getValue()) { foundClassDefs.push(classDef); } }; if (this._import != null) { this._import.getSources().forEach(function (parser) { parser.getTemplateClassDefs().forEach(checkClassDef); }); } else { parser.getTemplateClassDefs().forEach(checkClassDef); if (foundClassDefs.length == 0) { parser.getImports().forEach(function (imprt) { imprt.getSources().forEach(function (parser) { parser.getTemplateClassDefs().forEach(checkClassDef); }); }); } } return foundClassDefs.length == 1 ? foundClassDefs[0] : null; } override function toString () : string { return this._enclosingType != null ? this._enclosingType.toString() + "." + this._token.getValue() : this._token.getValue(); } } class ParserState { var lineNumber : number; var columnOffset : number; var docComment : DocComment; var tokenLength : number; var isGenerator : boolean; var numErrors : number; var numClosures : number; var numObjectTypesUsed : number; var numTemplateInstantiationRequests : number; function constructor (lineNumber : number, columnNumber : number, docComment : DocComment, tokenLength : number, isGenerator : boolean, numErrors : number, numClosures : number, numObjectTypesUsed : number, numTemplateInstantiationRequests : number) { this.lineNumber = lineNumber; this.columnOffset = columnNumber; this.docComment = docComment; this.tokenLength = tokenLength; this.isGenerator = isGenerator; this.numErrors = numErrors; this.numClosures = numClosures; this.numObjectTypesUsed = numObjectTypesUsed; this.numTemplateInstantiationRequests = numTemplateInstantiationRequests; } } class ClassState { var outer : ClassState; var classType : ParsedObjectType; var typeArgs : Token[]; var extendType : ParsedObjectType; var implementTypes : ParsedObjectType[]; var objectTypesUsed : ParsedObjectType[]; var classFlags : number; var inners : ClassDefinition[]; var templateInners : TemplateClassDefinition[]; function constructor (outer : ClassState, classType : ParsedObjectType, typeArgs : Token[], extendType : ParsedObjectType, implementTypes : ParsedObjectType[], objectTypesUsed : ParsedObjectType[], classFlags : number, inners : ClassDefinition[], templateInners : TemplateClassDefinition[]) { this.outer = outer; this.classType = classType; this.typeArgs = typeArgs; this.extendType = extendType; this.implementTypes = implementTypes; this.objectTypesUsed = objectTypesUsed; this.classFlags = classFlags; this.inners = inners; this.templateInners = templateInners; } } class Scope { var prev : Scope; var locals : LocalVariable[]; var funcLocal : LocalVariable; // the name of current closure, can be null var arguments : ArgumentDeclaration[]; var statements : Statement[]; var closures : MemberFunctionDefinition[]; var isGenerator : boolean; function constructor (prev : Scope, locals : LocalVariable[], funcLocal : LocalVariable, args : ArgumentDeclaration[], statements : Statement[], closures : MemberFunctionDefinition[], isGenerator : boolean) { this.prev = prev; this.locals = locals; this.funcLocal = funcLocal; this.arguments = args; this.statements = statements; this.closures = closures; this.isGenerator = isGenerator; } } class Parser { var _sourceToken : Token; var _filename : string; var _completionRequest : CompletionRequest; var _input : string; var _lines : string[]; var _tokenLength : number; var _lineNumber : number; // one origin var _columnOffset : number; // zero origin var _fileLevelDocComment : DocComment; var _docComment : DocComment; var _errors : CompileError[]; var _templateClassDefs : TemplateClassDefinition[]; var _classDefs : ClassDefinition[]; var _imports : Import[]; var _isGenerator : boolean; var _locals : LocalVariable[]; var _statements : Statement[]; var _closures : MemberFunctionDefinition[]; var _outerClass : ClassState; var _classType : ParsedObjectType; var _extendType : ParsedObjectType; var _implementTypes : ParsedObjectType[]; var _objectTypesUsed : ParsedObjectType[]; var _inners : ClassDefinition[]; var _templateInners : TemplateClassDefinition[]; var _templateInstantiationRequests : TemplateInstantiationRequest[]; var _prevScope : Scope = null; var _funcLocal : LocalVariable = null; var _arguments : ArgumentDeclaration[] = null; var _classFlags : number; var _typeArgs : Token[]; function constructor (sourceToken : Token, filename : string, completionRequest : CompletionRequest) { this._sourceToken = sourceToken; this._filename = filename; this._completionRequest = completionRequest; } function parse (input : string, errors : CompileError[]) : boolean { // lexer properties this._input = input; this._lines = this._input.split(_Lexer.rxNewline); this._tokenLength = 0; this._lineNumber = 1; // one origin this._columnOffset = 0; // zero origin this._fileLevelDocComment = null; this._docComment = null; // insert a marker so that at the completion location we would always get _expectIdentifierOpt called, whenever possible if (this._completionRequest != null) { var compLineNumber = Math.min(this._completionRequest.getLineNumber(), this._lines.length + 1); var line = this._lines[compLineNumber - 1] ?: ''; this._lines[compLineNumber - 1] = line.substring(0, this._completionRequest.getColumnOffset()) + "Q," + // use a character that is permitted within an identifier, but never appears in keywords line.substring(this._completionRequest.getColumnOffset()); } // output this._errors = errors; this._templateClassDefs = new TemplateClassDefinition[]; this._classDefs = new ClassDefinition[]; this._imports = new Import[]; // use for function parsing this._isGenerator = false; this._locals = null; this._statements = null; this._closures = null; this._classType = null; this._extendType = null; this._implementTypes = null; this._objectTypesUsed = new ParsedObjectType[]; this._inners = new ClassDefinition[]; this._templateInners = new TemplateClassDefinition[]; this._templateInstantiationRequests = new TemplateInstantiationRequest[]; // doit while (! this._isEOF()) { var importToken = this._expectOpt("import"); if (importToken == null) break; this._importStatement(importToken); } while (! this._isEOF()) { if (this._classDefinition() == null) return false; } if (this._errors.length != 0) return false; return true; } function _getInput () : string { return this._lines[this._lineNumber - 1].substring(this._columnOffset); } function _getInputByLength (length : number) : string { return this._lines[this._lineNumber - 1].substring(this._columnOffset, this._columnOffset + length); } function _forwardPos (len : number) : void { this._columnOffset += len; } function getSourceToken () : Token { return this._sourceToken; } function getPath () : string { return this._filename; } function getDocComment () : DocComment { return this._fileLevelDocComment; } function getClassDefs () : ClassDefinition[] { return this._classDefs; } function getTemplateClassDefs () : TemplateClassDefinition[] { return this._templateClassDefs; } function getTemplateInstantiationRequests () : TemplateInstantiationRequest[] { return this._templateInstantiationRequests; } function getImports () : Import[] { return this._imports; } function registerBuiltinImports (parsers : Parser[]) : void { for (var i = parsers.length - 1; i >= 0; --i) this._imports.unshift(new Import(parsers[i])); } function lookupImportAlias (name : string) : Import { for (var i = 0; i < this._imports.length; ++i) { var alias = this._imports[i].getAlias(); if (alias != null && alias == name) return this._imports[i]; } return null; } function lookup (errors : CompileError[], contextToken : Token, className : string) : ClassDefinition { // class within the file is preferred for (var i = 0; i < this._classDefs.length; ++i) { var classDef = this._classDefs[i]; if (classDef.className() == className) return classDef; } // classnames within the imported files may conflict var found = new ClassDefinition[]; for (var i = 0; i < this._imports.length; ++i) { if (this._imports[i].getAlias() == null) found = found.concat(this._imports[i].getClasses(className)); } if (found.length == 1) return found[0]; if (found.length >= 2) errors.push(new CompileError(contextToken, "multiple candidates exist for class name '" + className + "'")); return null; } function lookupTemplate (errors : CompileError[], request : TemplateInstantiationRequest, postInstantiationCallback : function(:Parser,:ClassDefinition):ClassDefinition) : ClassDefinition { // lookup within the source file var instantiateCallback = this.createGetTemplateClassCallback(errors, request, postInstantiationCallback); if (instantiateCallback != null) { return instantiateCallback(errors, request, postInstantiationCallback); } // lookup within the imported files var candidateCallbacks = new Array.<function(:CompileError[],:TemplateInstantiationRequest,:function(:Parser,:ClassDefinition):ClassDefinition):ClassDefinition>; for (var i = 0; i < this._imports.length; ++i) { candidateCallbacks = candidateCallbacks.concat(this._imports[i].createGetTemplateClassCallbacks(errors, request, postInstantiationCallback)); } if (candidateCallbacks.length == 0) { errors.push(new CompileError(request.getToken(), "could not find definition for template class: '" + request.getClassName() + "'")); return null; } else if (candidateCallbacks.length >= 2) { errors.push(new CompileError(request.getToken(), "multiple candidates exist for template class name '" + request.getClassName() + "'")); return null; } return candidateCallbacks[0](null,null,null); } function createGetTemplateClassCallback (errors : CompileError[], request : TemplateInstantiationRequest, postInstantiationCallback : function(:Parser,:ClassDefinition):ClassDefinition) : function(:CompileError[],:TemplateInstantiationRequest,:function(:Parser,:ClassDefinition):ClassDefinition):ClassDefinition { // lookup the already-instantiated class for (var i = 0; i < this._classDefs.length; ++i) { var classDef = this._classDefs[i]; if (classDef instanceof InstantiatedClassDefinition && (classDef as InstantiatedClassDefinition).getTemplateClassName() == request.getClassName() && Util.typesAreEqual((classDef as InstantiatedClassDefinition).getTypeArguments(), request.getTypeArguments())) { return function (_ : CompileError[], __ : TemplateInstantiationRequest, ___ : function(:Parser,:ClassDefinition):ClassDefinition) : ClassDefinition { return classDef; }; } } // create instantiation callback for (var i = 0; i < this._templateClassDefs.length; ++i) { var templateDef = this._templateClassDefs[i]; if (templateDef.className() == request.getClassName()) { return function (_ : CompileError[], __ : TemplateInstantiationRequest, ___ : function(:Parser,:ClassDefinition):ClassDefinition) : ClassDefinition { var classDef = templateDef.instantiateTemplateClass(errors, request); if (classDef == null) { return null; } this._classDefs.push(classDef); classDef.setParser(this); classDef.resolveTypes(new AnalysisContext(errors, this, null)); postInstantiationCallback(this, classDef); return classDef; }; } } return null; } function _pushClassState () : void { this._outerClass = new ClassState ( this._outerClass, this._classType, this._typeArgs, this._extendType, this._implementTypes, this._objectTypesUsed, this._classFlags, this._inners, this._templateInners ); } function _popClassState () : void { this._classType = this._outerClass.classType; this._typeArgs = this._outerClass.typeArgs; this._extendType = this._outerClass.extendType; this._implementTypes = this._outerClass.implementTypes; this._objectTypesUsed = this._outerClass.objectTypesUsed; this._classFlags = this._outerClass.classFlags; this._inners = this._outerClass.inners; this._templateInners = this._outerClass.templateInners; this._outerClass = this._outerClass.outer; } function _pushScope (funcLocal : LocalVariable, args : ArgumentDeclaration[]) : void { this._prevScope = new Scope ( this._prevScope, this._locals, this._funcLocal, this._arguments, this._statements, this._closures, this._isGenerator ); this._locals = new LocalVariable[]; this._funcLocal = funcLocal; this._arguments = args; this._statements = new Statement[]; this._closures = new MemberFunctionDefinition[]; this._isGenerator = false; } function _popScope () : void { this._locals = this._prevScope.locals; this._funcLocal = this._prevScope.funcLocal; this._arguments = this._prevScope.arguments; this._statements = this._prevScope.statements; this._closures = this._prevScope.closures; this._isGenerator = this._prevScope.isGenerator; this._prevScope = this._prevScope.prev; } function _registerLocal (identifierToken : Token, type : Type) : LocalVariable { function isEqualTo (local : LocalVariable) : boolean { if (local.getName().getValue() == identifierToken.getValue()) { if (type != null && ! local.getType().equals(type)) this._newError("conflicting types for variable " + identifierToken.getValue()); return true; } return false; } if (this._arguments == null) { this._newError(Util.format("cannot declare variable %1 outside of a function", [identifierToken.getValue()])); // FIXME should we allow this? return null; } if (this._funcLocal != null) { if (isEqualTo(this._funcLocal)) { return this._funcLocal; } } for (var i = 0; i < this._arguments.length; ++i) { if (isEqualTo(this._arguments[i])) { return this._arguments[i]; } } for (var i = 0; i < this._locals.length; i++) { if (isEqualTo(this._locals[i])) { return this._locals[i]; } } var newLocal = new LocalVariable(identifierToken, type); this._locals.push(newLocal); return newLocal; } function _preserveState () : ParserState { return new ParserState( // lexer properties this._lineNumber, this._columnOffset, this._docComment, this._tokenLength, this._isGenerator, // errors this._errors.length, // closures this._closures != null ? this._closures.length : 0, // objectTypesUsed this._objectTypesUsed.length, // templateInstantiationrequests this._templateInstantiationRequests.length ); } function _restoreState (state : ParserState) : void { this._lineNumber = state.lineNumber; this._columnOffset = state.columnOffset; this._docComment = state.docComment; this._tokenLength = state.tokenLength; this._isGenerator = state.isGenerator; this._errors.length = state.numErrors; if (this._closures != null) this._closures.splice(state.numClosures, this._closures.length - state.numClosures); this._objectTypesUsed.splice(state.numObjectTypesUsed, this._objectTypesUsed.length - state.numObjectTypesUsed); this._templateInstantiationRequests.splice(state.numTemplateInstantiationRequests, this._templateInstantiationRequests.length - state.numTemplateInstantiationRequests); } // this is column offset, and is thus zero-origin function _getColumn () : number { return this._columnOffset; } function _newError (message : string) : void { this._errors.push(new CompileError(this._filename, this._lineNumber, this._getColumn(), message)); } function _newDeprecatedWarning (message : string) : void { this._errors.push(new DeprecatedWarning(this._filename, this._lineNumber, this._getColumn(), message)); } function _advanceToken () : void { if (this._tokenLength != 0) { this._forwardPos(this._tokenLength); this._tokenLength = 0; this._docComment = null; } while (true) { // skip espaces and comments in-line while (true) { var matched = this._getInput().match(/^[ \t]+/); if (matched != null) this._forwardPos(matched[0].length); if (this._columnOffset != this._lines[this._lineNumber - 1].length) break; if (this._lineNumber == this._lines.length) break; this._lineNumber++; this._columnOffset = 0; } switch (this._getInputByLength(2)) { case "/*": if (this._getInputByLength(4) == "/***") { this._forwardPos(3); // skip to the last *, since the input might be: /***/ var fileLevelDocComment = this._parseDocComment(); if (fileLevelDocComment == null) { return; } // the first "/***" comment is the file-level doc comment if (this._fileLevelDocComment == null) { this._fileLevelDocComment = fileLevelDocComment; } } else if (this._getInputByLength(3) == "/**") { this._forwardPos(2); // skip to the last *, the input might be: /**/ if ((this._docComment = this._parseDocComment()) == null) { return; } } else { this._forwardPos(2); // skip "/*" this._docComment = null; if (! this._skipMultilineComment()) { return; } } break; case "//": this._docComment = null; if (this._lineNumber == this._lines.length) { this._columnOffset = this._lines[this._lineNumber - 1].length; } else { this._lineNumber++; this._columnOffset = 0; } break; default: return; } } } function _skipMultilineComment () : boolean { var startLineNumber = this._lineNumber; var startColumnOffset = this._columnOffset; while (true) { var endAt = this._getInput().indexOf("*/"); if (endAt != -1) { this._forwardPos(endAt + 2); return true; } if (this._lineNumber == this._lines.length) { this._columnOffset = this._lines[this._lineNumber - 1].length; this._errors.push(new CompileError(this._filename, startLineNumber, startColumnOffset, "could not find the end of the comment")); return false; } ++this._lineNumber; this._columnOffset = 0; } return false; // dummy } function _parseDocComment () : DocComment { var docComment = new DocComment(); var node : DocCommentNode = docComment; while (true) { // skip " * ", or return if "*/" this._parseDocCommentAdvanceWhiteSpace(); if (this._getInputByLength(2) == "*/") { this._forwardPos(2); return docComment; } else if (this._getInputByLength(1) == "*") { this._forwardPos(1); this._parseDocCommentAdvanceWhiteSpace(); } // fetch tag (and paramName), and setup the target node to push content into var tagMatch = this._getInput().match(/^\@([0-9A-Za-z_]+)[ \t]*/); if (tagMatch != null) { this._forwardPos(tagMatch[0].length); var tag = tagMatch[1]; switch (tag) { case "param": var nameMatch = this._getInput().match(/[0-9A-Za-z_]+/); if (nameMatch != null) { var token = new Token(nameMatch[0], false, this._filename, this._lineNumber, this._getColumn()); this._forwardPos(nameMatch[0].length); node = new DocCommentParameter(token); docComment.getParams().push(node as DocCommentParameter); } else { this._newError("name of the parameter not found after @param"); node = null; } break; default: node = new DocCommentTag(tag); docComment.getTags().push(node as DocCommentTag); break; } } var endAt = this._getInput().indexOf("*/"); if (endAt != -1) { if (node != null) { node.appendDescription(this._getInput().substring(0, endAt)); } this._forwardPos(endAt + 2); return docComment; } if (node != null) { node.appendDescription(this._getInput()); } if (this._lineNumber == this._lines.length) { this._columnOffset = this._lines[this._lineNumber - 1].length; this._newError("could not find the end of the doccomment"); return null; } ++this._lineNumber; this._columnOffset = 0; } return null; // dummy } function _parseDocCommentAdvanceWhiteSpace () : void { while (true) { var ch = this._getInputByLength(1); if (ch == " " || ch == "\t") { this._forwardPos(1); } else { break; } } } function _isEOF () : boolean { this._advanceToken(); return this._lineNumber == this._lines.length && this._columnOffset == this._lines[this._lines.length - 1].length; } function _expectIsNotEOF () : boolean { if (this._isEOF()) { this._newError("unexpected EOF"); return false; } return true; } function _expectOpt (expected : string) : Token { return this._expectOpt([ expected ], null); } function _expectOpt (expected : string[]) : Token { return this._expectOpt(expected, null); } function _expectOpt (expected : string, excludePattern : RegExp) : Token { return this._expectOpt([ expected ], excludePattern); } function _expectOpt (expected : string[], excludePattern : RegExp) : Token { this._advanceToken(); for (var i = 0; i < expected.length; ++i) { if (this._completionRequest != null) { var offset = this._completionRequest.isInRange(this._lineNumber, this._columnOffset, expected[i].length); if (offset != -1) { // && expected[i].match(/[A-Za-z]/) != null) { this._completionRequest.pushCandidates(new KeywordCompletionCandidate(expected[i]).setPrefix(this._getInputByLength(offset))); } } if (this._getInputByLength(expected[i].length) == expected[i]) { if (expected[i].match(_Lexer.rxIdent) != null && this._getInput().match(_Lexer.rxIdent)[0].length != expected[i].length) { // part of a longer token } else if (excludePattern != null && this._getInput().match(excludePattern) != null) { // skip if the token matches the exclude pattern } else { // found this._tokenLength = expected[i].length; return new Token(expected[i], false, this._filename, this._lineNumber, this._getColumn()); } } } return null; } function _expect (expected : string) : Token { return this._expect([ expected ], null); } function _expect (expected : string[]) : Token { return this._expect(expected, null); } function _expect (expected : string, excludePattern : RegExp) : Token { return this._expect([ expected ], excludePattern); } function _expect (expected : string[], excludePattern : RegExp) : Token { var token = this._expectOpt(expected, excludePattern); if (token == null) { this._newError("expected keyword: " + expected.join(" ")); return null; } return token; } function _expectIdentifierOpt () : Token { return this._expectIdentifierOpt(null); } function _expectIdentifierOpt (completionCb : function(:Parser):CompletionCandidates) : Token { this._advanceToken(); var matched = this._getInput().match(_Lexer.rxIdent); if (completionCb != null && this._completionRequest != null) { var offset = this._completionRequest.isInRange(this._lineNumber, this._columnOffset, matched != null ? matched[0].length : 0); if (offset != -1) { this._completionRequest.pushCandidates(completionCb(this).setPrefix(matched[0].substring(0, offset))); } } if (matched == null) return null; if (_Lexer.keywords.hasOwnProperty(matched[0])) { this._newError("expected an identifier but found a keyword"); return null; } if (_Lexer.reserved.hasOwnProperty(matched[0])) { this._newError("expected an identifier but found a reserved word"); return null; } this._tokenLength = matched[0].length; return new Token(matched[0], true, this._filename, this._lineNumber, this._getColumn()); } function _expectIdentifier () : Token { return this._expectIdentifier(null); } function _expectIdentifier (completionCb : function(:Parser):CompletionCandidates) : Token { var token = this._expectIdentifierOpt(completionCb); if (token != null) return token; this._newError("expected an identifier"); return null; } function _expectStringLiteralOpt () : Token { this._advanceToken(); var matched = this._getInput().match(_Lexer.rxStringLiteral); if (matched == null) return null; this._tokenLength = matched[0].length; return new Token(matched[0], false, this._filename, this._lineNumber, this._getColumn()); } function _expectStringLiteral () : Token { var token = this._expectStringLiteralOpt(); if (token != null) return token; this._newError("expected a string literal"); return null; } function _expectNumberLiteralOpt () : Token { this._advanceToken(); var matched = this._getInput().match(_Lexer.rxIntegerLiteral); if (matched == null) matched = this._getInput().match(_Lexer.rxNumberLiteral); if (matched == null) return null; this._tokenLength = matched[0].length; return new Token(matched[0], false, this._filename, this._lineNumber, this._getColumn()); } function _expectRegExpLiteralOpt () : Token { this._advanceToken(); var matched = this._getInput().match(_Lexer.rxRegExpLiteral); if (matched == null) return null; this._tokenLength = matched[0].length; return new Token(matched[0], false, this._filename, this._lineNumber, this._getColumn()); } function _skipStatement () : void { var advanced = false; while (! this._isEOF()) { switch (this._getInputByLength(1)) { case ";": // return after the semicolon this._tokenLength = 1; this._advanceToken(); return; case "{": if (! advanced) { this._tokenLength = 1; this._advanceToken(); } return; case "}": // return before the block token return; } this._tokenLength = 1; this._advanceToken(); advanced = true; } } function _importStatement (importToken : Token) : boolean { // parse var classes = null : Token[]; var token = this._expectIdentifierOpt(null); if (token != null) { classes = [ token ]; while (true) { if ((token = this._expect([ ",", "from" ])) == null) return false; if (token.getValue() == "from") break; if ((token = this._expectIdentifier(null)) == null) return false; classes.push(token); } } var filenameToken = this._expectStringLiteral(); if (filenameToken == null) return false; var alias = null : Token; if (this._expectOpt("into") != null) { if ((alias = this._expectIdentifier(null)) == null) return false; } if (this._expect(";") == null) return false; // check conflict if (alias != null && Parser._isReservedClassName(alias.getValue())) { this._errors.push(new CompileError(alias, "cannot use name of a built-in class as an alias")); return false; } if (classes != null) { var success = true; for (var i = 0; i < this._imports.length; ++i) for (var j = 0; j < classes.length; ++j) if (! this._imports[i].checkNameConflict(this._errors, classes[j])) success = false; if (! success) return false; } else { for (var i = 0; i < this._imports.length; ++i) { if (alias == null) { if (this._imports[i].getAlias() == null && this._imports[i].getFilenameToken().getValue() == filenameToken.getValue()) { this._errors.push(new CompileError(filenameToken, "cannot import the same file more than once (unless using an alias)")); return false; } } else { if (! this._imports[i].checkNameConflict(this._errors, alias)) return false; } } } // push var imprt = Import.create(this._errors, filenameToken, alias, classes); if (imprt == null) return false; this._imports.push(imprt); return true; } function _expectClassDefOpt () : boolean { var state = this._preserveState(); try { while (true) { var token = this._expectOpt([ "class", "interface", "mixin", "abstract", "final" ]); if (token == null) return false; if (token.getValue() == "class" || token.getValue() == "interface" || token.getValue() == "mixin") return true; } } finally { this._restoreState(state); } return true; // dummy } function _classDefinition () : ClassDefinition { this._classType = null; this._extendType = null; this._implementTypes = new ParsedObjectType[]; this._objectTypesUsed = new ParsedObjectType[]; this._inners = new ClassDefinition[]; this._templateInners = new TemplateClassDefinition[]; // attributes* class this._classFlags = 0; var nativeSource = null : Token; var docComment = null : DocComment; while (true) { var token = this._expect([ "class", "interface", "mixin", "abstract", "final", "native", "__fake__", "__export__" ]); if (token == null) return null; if (this._classFlags == 0) docComment = this._docComment; // "class", "interface", or "mixin" if (token.getValue() == "class") { break; } else if (token.getValue() == "interface") { if ((this._classFlags & (ClassDefinition.IS_FINAL | ClassDefinition.IS_NATIVE)) != 0) { this._newError("interface cannot have final or native attribute set"); return null; } this._classFlags |= ClassDefinition.IS_INTERFACE; break; } else if (token.getValue() == "mixin") { if ((this._classFlags & (ClassDefinition.IS_FINAL | ClassDefinition.IS_NATIVE | ClassDefinition.IS_EXPORT)) != 0) { this._newError("mixin cannot have final, native, or __export__ attribute set"); return null; } this._classFlags |= ClassDefinition.IS_MIXIN; break; } // class attributes var newFlag = 0; switch (token.getValue()) { case "abstract": newFlag = ClassDefinition.IS_ABSTRACT; break; case "final": newFlag = ClassDefinition.IS_FINAL; break; case "native": if (this._expectOpt("(") != null) { // native("...") nativeSource = this._expectStringLiteral(); this._expect(")"); } newFlag = ClassDefinition.IS_NATIVE; break; case "__fake__": newFlag = ClassDefinition.IS_FAKE; break; case "__export__": newFlag = ClassDefinition.IS_EXPORT; break; default: throw new Error("logic flaw"); } if ((this._classFlags & newFlag) != 0) { this._newError("same attribute cannot be specified more than once"); return null; } this._classFlags |= newFlag; } var className = this._expectIdentifier(null); if (className == null) return null; // template if ((this._typeArgs = this._formalTypeArguments()) == null) { return null; } this._classType = new ParsedObjectType( new QualifiedName(className, (this._outerClass != null) ? this._outerClass.classType : null), this._typeArgs.map.<Type>(function (token : Token) : Type { // convert formal typearg (Token) to actual typearg (Type) return new ParsedObjectType(new QualifiedName(token), new Type[]); })); this._objectTypesUsed.push(this._classType); // extends if ((this._classFlags & (ClassDefinition.IS_INTERFACE | ClassDefinition.IS_MIXIN)) == 0) { if (this._expectOpt("extends") != null) { this._extendType = this._objectTypeDeclaration( null, true, function (classDef) { return (classDef.flags() & (ClassDefinition.IS_MIXIN | ClassDefinition.IS_INTERFACE | ClassDefinition.IS_FINAL)) == 0; }); } if (this._extendType == null && className.getValue() != "Object") { this._extendType = new ParsedObjectType(new QualifiedName(new Token("Object", true)), new Type[]); this._objectTypesUsed.push(this._extendType); } } else { if ((this._classFlags & (ClassDefinition.IS_ABSTRACT | ClassDefinition.IS_FINAL | ClassDefinition.IS_NATIVE)) != 0) { this._newError("interface or mixin cannot have attributes: 'abstract', 'final', 'native"); this._classFlags &= ~ (ClassDefinition.IS_ABSTRACT | ClassDefinition.IS_FINAL | ClassDefinition.IS_NATIVE); // erase the flags and continue } } // implements if (this._expectOpt("implements") != null) { do { var implementType = this._objectTypeDeclaration( null, true, function (classDef) { return (classDef.flags() & (ClassDefinition.IS_MIXIN | ClassDefinition.IS_INTERFACE)) != 0; }); if (implementType != null) { this._implementTypes.push(implementType); } } while (this._expectOpt(",") != null); } // body if (this._expect("{") == null) return null; var members = new MemberDefinition[]; var success = true; while (this._expectOpt("}") == null) { if (! this._expectIsNotEOF()) break; if (this._expectClassDefOpt()) { this._pushClassState(); // parse inner class if (this._classDefinition() == null) this._skipStatement(); this._popClassState(); continue; } var member = this._memberDefinition(); if (member != null) { for (var i = 0; i < members.length; ++i) { if (member.name() == members[i].name() && (member.flags() & ClassDefinition.IS_STATIC) == (members[i].flags() & ClassDefinition.IS_STATIC)) { if (member instanceof MemberFunctionDefinition && members[i] instanceof MemberFunctionDefinition) { if (Util.typesAreEqual((member as MemberFunctionDefinition).getArgumentTypes(), (members[i] as MemberFunctionDefinition).getArgumentTypes())) { this._errors.push(new CompileError( member.getNameToken(), "a " + ((member.flags() & ClassDefinition.IS_STATIC) != 0 ? "static" : "member") + " function with same name and arguments is already defined")); success = false; break; } } else { this._errors.push(new CompileError(member.getNameToken(), "