jsx
Version:
a faster, safer, easier JavaScript
1,518 lines (1,375 loc) • 108 kB
JSX
/*
* 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(), "