jsx
Version:
a faster, safer, easier JavaScript
503 lines (466 loc) • 16.6 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 "./analysis.jsx";
import "./parser.jsx";
import "./classdef.jsx";
import "./type.jsx";
import "./emitter.jsx";
import "./platform.jsx";
import "./util.jsx";
import "./optimizer.jsx";
import "./completion.jsx";
import "./instruments.jsx";
class Compiler {
static const MODE_COMPILE = 0;
static const MODE_PARSE = 1;
static const MODE_COMPLETE = 2;
static const MODE_DOC = 3;
var _platform : Platform;
var _mode : number;
var _optimizer : Optimizer;
var _warningFilters : Array.<function(:CompileWarning):Nullable.<boolean>>;
var _warningAsError : boolean;
var _parsers : Parser[];
var _fileCache : Map.<string>;
var _searchPaths : string[];
var _builtinParsers : Parser[];
var _emitter : Emitter;
function constructor (platform : Platform) {
this._platform = platform;
this._mode = Compiler.MODE_COMPILE;
this._optimizer = null;
this._warningFilters = [] : Array.<function(:CompileWarning):Nullable.<boolean>>;
this._warningAsError = false;
this._parsers = new Parser[];
this._fileCache = new Map.<string>;
this._searchPaths = [ this._platform.getRoot() + "/lib/common" ];
// load the built-in classes
this.addSourceFile(null, this._platform.getRoot() + "/lib/built-in.jsx");
this._builtinParsers = this._parsers.concat(new Parser[]); // shallow clone
}
function addSearchPath (path : string) : void {
this._searchPaths.unshift(path);
}
function getPlatform () : Platform {
return this._platform;
}
function getMode () : number {
return this._mode;
}
function setMode (mode : number) : Compiler {
this._mode = mode;
return this;
}
function getEmitter () : Emitter {
return this._emitter;
}
function setEmitter (emitter : Emitter) : void {
this._emitter = emitter;
}
function setOptimizer (optimizer : Optimizer) : void {
this._optimizer = optimizer;
}
function getWarningFilters () : Array.<function(:CompileWarning):Nullable.<boolean>> {
return this._warningFilters;
}
function setWarningAsError(f : boolean) : void {
this._warningAsError = f;
}
function getParsers () : Parser[] {
return this._parsers;
}
function addSourceFile (token : Token, path : string) : Parser {
return this.addSourceFile(token, path, null);
}
function addSourceFile (token : Token, path : string, completionRequest : CompletionRequest) : Parser {
var parser;
if ((parser = this.findParser(path)) == null) {
parser = new Parser(token, path, completionRequest);
this._parsers.push(parser);
}
return parser;
}
function findParser (path : string) : Parser {
for (var i = 0; i < this._parsers.length; ++i)
if (this._parsers[i].getPath() == path)
return this._parsers[i];
return null;
}
function compile () : boolean {
var errors = new CompileError[];
// parse all files
for (var i = 0; i < this._parsers.length; ++i) {
if (! this.parseFile(errors, this._parsers[i])) {
if (! this._handleErrors(errors))
return false;
}
}
switch (this._mode) {
case Compiler.MODE_PARSE:
return true;
}
// resolve imports
this._resolveImports(errors);
if (! this._handleErrors(errors))
return false;
// register backing class for primitives
var builtins = this._builtinParsers[0];
BooleanType._classDef = builtins.lookup(errors, null, "Boolean");
NumberType._classDef = builtins.lookup(errors, null, "Number");
StringType._classDef = builtins.lookup(errors, null, "String");
FunctionType._classDef = builtins.lookup(errors, null, "Function");
// prepare generator stuff
CodeTransformer.stopIterationType = new ObjectType(builtins.lookup(errors, null, "g_StopIteration"));
for (var i = 0; i < builtins._templateClassDefs.length; ++i)
if (builtins._templateClassDefs[i].className() == "__jsx_generator")
CodeTransformer.jsxGeneratorClassDef = builtins._templateClassDefs[i];
if (! this._handleErrors(errors))
return false;
// semantic analysis
this._resolveTypes(errors);
if (! this._handleErrors(errors))
return false;
this._exportEntryPoints();
this._analyze(errors);
if (! this._handleErrors(errors))
return false;
switch (this._mode) {
case Compiler.MODE_COMPLETE:
return true;
case Compiler.MODE_DOC:
return true;
}
// transformation
var transformer = new CodeTransformer;
this.forEachClassDef(function (parser, classDef) {
return classDef.forEachMember(function onMember(member) {
if (member instanceof MemberFunctionDefinition) {
var funcDef = member as MemberFunctionDefinition;
if (funcDef.isGenerator()) {
transformer.transformFunctionDefinition(funcDef);
}
}
return member.forEachClosure(function (funcDef) {
return onMember(funcDef);
});
});
});
// optimization
this._optimize();
// TODO peep-hole and dead store optimizations, etc.
this._generateCode(errors);
if (! this._handleErrors(errors))
return false;
return true;
}
/**
* Returns a JSON data structure of parsed class definitions
*/
function getAST () : variant {
var classDefs = new ClassDefinition[];
for (var i = 0; i < this._parsers.length; ++i) {
classDefs = classDefs.concat(this._parsers[i].getClassDefs());
}
return ClassDefinition.serialize(classDefs);
}
function getFileContent (errors : CompileError[], sourceToken : Token, path : string) : Nullable.<string> {
assert path != "";
if(this._fileCache[path] == null) {
try {
this._fileCache[path] = this._platform.load(path);
} catch (e : Error) {
errors.push(new CompileError(sourceToken, "could not open file: " + path + ", " + e.toString()));
this._fileCache[path] = null;
}
}
return this._fileCache[path];
}
function parseFile (errors : CompileError[], parser : Parser) : boolean {
// read file
var content = this.getFileContent(errors, parser.getSourceToken(), parser.getPath());
if (content == null) {
// call parse() to initialize parser's state
// because some compilation mode continues to run after errors.
parser.parse("", new CompileError[]);
return false;
}
// parse
parser.parse(content, errors);
// register imported files
if (this._mode != Compiler.MODE_PARSE) {
var imports = parser.getImports();
for (var i = 0; i < imports.length; ++i) {
if (! this._handleImport(errors, parser, imports[i]))
return false;
}
}
return true;
}
function _handleImport (errors : CompileError[], parser : Parser, imprt : Import) : boolean {
if (imprt instanceof WildcardImport) {
var wildImprt = imprt as WildcardImport;
// read the files from a directory
var resolvedDir = this._resolvePath(wildImprt.getFilenameToken().getFilename(), wildImprt.getDirectory());
var files = new string[];
try {
files = this._platform.getFilesInDirectory(resolvedDir);
} catch (e : Error) {
errors.push(new CompileError(wildImprt.getFilenameToken(), "could not read files in directory: " + resolvedDir + ", " + e.toString()));
return false;
}
var found = false;
for (var i = 0; i < files.length; ++i) {
if (files[i].length >= wildImprt.getSuffix().length
&& files[i].charAt(0) != "."
&& files[i].substring(files[i].length - wildImprt.getSuffix().length) == wildImprt.getSuffix()) {
var path = resolvedDir + "/" + files[i];
if (path != parser.getPath()) {
var newParser = this.addSourceFile(wildImprt.getFilenameToken(), resolvedDir + "/" + files[i], null);
wildImprt.addSource(newParser);
found = true;
}
}
}
if (! found) {
errors.push(new CompileError(wildImprt.getFilenameToken(), "no matching files found in directory: " + resolvedDir));
return false;
}
} else {
// read one file
var path = this._resolvePath(imprt.getFilenameToken().getFilename(), Util.decodeStringLiteral(imprt.getFilenameToken().getValue()));
if (path == parser.getPath()) {
errors.push(new CompileError(imprt.getFilenameToken(), "cannot import itself"));
return false;
}
var newParser = this.addSourceFile(imprt.getFilenameToken(), path, null);
imprt.addSource(newParser);
}
return true;
}
function forEachClassDef (f : function(:Parser, :ClassDefinition):boolean) : boolean {
function onClassDef (parser : Parser, classDef : ClassDefinition) : boolean {
if (! f(parser, classDef))
return false;
var inners = classDef.getInnerClasses();
for (var i = 0; i < inners.length; ++i) {
if (! onClassDef(parser, inners[i]))
return false;
}
return true;
}
for (var i = 0; i < this._parsers.length; ++i) {
var parser = this._parsers[i];
var classDefs = parser.getClassDefs();
for (var j = 0; j < classDefs.length; ++j) {
if (! onClassDef(parser, classDefs[j]))
return false;
}
}
return true;
}
function _resolveImports (errors : CompileError[]) : void {
for (var i = 0; i < this._parsers.length; ++i) {
// built-in classes become implicit imports
this._parsers[i].registerBuiltinImports(this._builtinParsers);
// set source of every import
var imports = this._parsers[i].getImports();
for (var j = 0; j < imports.length; ++j) {
imports[j].assertExistenceOfNamedClasses(errors);
}
}
}
function _resolveTypes (errors : CompileError[]) : void {
this.forEachClassDef(function (parser : Parser, classDef : ClassDefinition) : boolean {
classDef.resolveTypes(new AnalysisContext(errors, parser, null));
return true;
});
}
function _analyze (errors : CompileError[]) : void {
var createContext = function (parser : Parser) : AnalysisContext {
return new AnalysisContext(
errors,
parser,
function (parser : Parser, classDef : ClassDefinition) : ClassDefinition {
classDef.setAnalysisContextOfVariables(createContext(parser));
classDef.analyze(createContext(parser));
return classDef;
});
};
// set analyzation context of every variable
this.forEachClassDef(function (parser : Parser, classDef : ClassDefinition) {
classDef.setAnalysisContextOfVariables(createContext(parser));
return true;
});
// analyze every classdef
this.forEachClassDef(function (parser : Parser, classDef : ClassDefinition) {
classDef.analyze(createContext(parser));
return true;
});
// analyze unused variables in every classdef
this.forEachClassDef(function (parser : Parser, classDef : ClassDefinition) {
classDef.analyzeUnusedVariables();
return true;
});
}
function _optimize () : void {
if (this._optimizer != null)
this._optimizer.setCompiler(this).performOptimization();
}
function _generateCode (errors : CompileError[]) : void {
// build list of all classDefs
var classDefs = new ClassDefinition[];
for (var i = 0; i < this._parsers.length; ++i)
classDefs = classDefs.concat(this._parsers[i].getClassDefs());
for (var i = 0; i < classDefs.length; ++i) {
if (classDefs[i].getInnerClasses().length != 0)
classDefs = classDefs.concat(classDefs[i].getInnerClasses());
}
// check that there are no conflict of names bet. native classes
var nativeClassNames = new Map.<ClassDefinition>;
var foundConflict = false;
classDefs.forEach(function (classDef) {
if ((classDef.flags() & ClassDefinition.IS_NATIVE) == 0) {
return;
}
if (nativeClassNames.hasOwnProperty(classDef.className())) {
errors.push(
new CompileError(classDef.getToken(), "native class with same name is already defined")
.addCompileNote(new CompileNote(nativeClassNames[classDef.className()].getToken(), "here")));
foundConflict = true;
return;
}
nativeClassNames[classDef.className()] = classDef;
});
if (foundConflict) {
return;
}
// reorder the classDefs so that base classes would come before their children
var getMaxIndexOfClasses = function (deps : ClassDefinition[]) : number {
deps = deps.concat([]); // clone the array
if (deps.length == 0)
return -1;
for (var i = 0; i < classDefs.length; ++i) {
for (var j = 0; j < deps.length; ++j) {
if (classDefs[i] == deps[j]) {
deps.splice(j, 1);
if (deps.length == 0)
return i;
}
}
}
throw new Error("logic flaw, could not find class definition of '" + deps[0].className() + "'");
};
for (var i = 0; i < classDefs.length;) {
var deps = classDefs[i].implementTypes().map.<ClassDefinition>(function (t) { return t.getClassDef(); }).concat([]);
if (classDefs[i].extendType() != null)
deps.unshift(classDefs[i].extendType().getClassDef());
if (classDefs[i].getOuterClassDef() != null)
deps.unshift(classDefs[i].getOuterClassDef());
var maxIndexOfClasses = getMaxIndexOfClasses(deps);
if (maxIndexOfClasses > i) {
classDefs.splice(maxIndexOfClasses + 1, 0, classDefs[i]);
classDefs.splice(i, 1);
} else {
++i;
}
}
// emit
this._emitter.emit(classDefs);
}
function _exportEntryPoints() : void {
this.forEachClassDef(function (parser, classDef) {
switch (classDef.classFullName()) {
case "_Main":
classDef.setFlags(classDef.flags() | ClassDefinition.IS_EXPORT);
classDef.forEachMemberFunction(function (funcDef) {
if ((funcDef.flags() & ClassDefinition.IS_STATIC) != 0
&& funcDef.name() == "main"
&& funcDef.getArguments().length == 1
&& Util.isArrayOf(funcDef.getArgumentTypes()[0].getClassDef(), Type.stringType)) {
funcDef.setFlags(funcDef.flags() | ClassDefinition.IS_EXPORT);
}
return true;
});
break;
case "_Test":
classDef.setFlags(classDef.flags() | ClassDefinition.IS_EXPORT);
classDef.forEachMemberFunction(function (funcDef) {
if ((funcDef.flags() & ClassDefinition.IS_STATIC) == 0
&& (funcDef.name().match(/^test/) || funcDef.name() == "constructor")
&& funcDef.getArguments().length == 0) {
funcDef.setFlags(funcDef.flags() | ClassDefinition.IS_EXPORT);
}
return true;
});
break;
}
return true;
});
}
function _handleErrors (errors : CompileError[]) : boolean {
// ignore all messages on completion mode
if (this._mode == Compiler.MODE_COMPLETE) {
errors.splice(0, errors.length);
return true;
}
// print issues
var isFatal = false;
errors.forEach(function (error) {
if (error instanceof CompileWarning) {
var warning = error as CompileWarning;
var doWarn;
for (var i = 0; i < this._warningFilters.length; ++i) {
if ((doWarn = this._warningFilters[i](warning)) != null)
break;
}
if (doWarn != false) {
this._platform.warn(warning.format(this.getPlatform()));
isFatal = this._warningAsError;
}
} else {
this._platform.error(error.format(this.getPlatform()));
error.getCompileNotes().forEach(function (note) {
this._platform.error(note.format(this.getPlatform()));
});
isFatal = true;
}
});
// clear all errors
errors.splice(0, errors.length);
return ! isFatal;
}
function _resolvePath (srcPath : string, givenPath : string) : string {
if (givenPath.match(/^\.{1,2}\//) == null) {
var searchPaths = this._searchPaths.concat(this._emitter.getSearchPaths());
for (var i = 0; i < searchPaths.length; ++i) {
var path = Util.resolvePath(searchPaths[i] + "/" + givenPath);
// check the existence of the file, at the same time filling the cache
if (this._platform.fileExists(path))
return path;
}
}
var lastSlashAt = srcPath.lastIndexOf("/");
path = Util.resolvePath((lastSlashAt != -1 ? srcPath.substring(0, lastSlashAt + 1) : "") + givenPath);
return path;
}
}
// vim: set noexpandtab: