UNPKG

jsx

Version:

a faster, safer, easier JavaScript

503 lines (466 loc) 16.6 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 "./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: