UNPKG

jsx

Version:

a faster, safer, easier JavaScript

623 lines (555 loc) 19.2 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 "./expression.jsx"; import "./statement.jsx"; import "./analysis.jsx"; import "./classdef.jsx"; import "./type.jsx"; import "./platform.jsx"; import Token from "./parser.jsx"; class Cloner.<T> { static function cloneArray (a : T[]) : T[] { var r = [] : T[]; for (var i = 0; i < a.length; ++i) r[i] = a[i].clone() as T; return r; } static function cloneNullable (o : T) : T { return o == null ? (null) : (o.clone() as T); } } class Serializer.<T> { static function serializeArray (a : T[]) : variant { if (a == null) return null; var ret = [] : variant[]; for (var i = 0; i < a.length; ++i) ret[i] = a[i].serialize(); return ret; } static function serializeNullable (v : T) : variant { if (v == null) return null; return v.serialize(); } } class Util { static function repeat (c : string, n : number) : string { var s = ""; for(var i = 0; i < n; ++i) { s += c; } return s; } /** * Usage: Util.format("%1 %% %2", ["foo", "bar"]) -> "foo % bar" * @param fmt format template * @param args parameters which are expanded into <code>fmt</code> */ static function format (fmt : string, args : string[]) : string { assert args != null; var i = 0; return fmt.replace(/%(\d+|%)/g, function(m) { if (m == "%%") { return "%"; } else { var arg = args[(m.substring(1) as int) - 1]; return arg == null ? "null" : arg; } }); } static function instantiateTemplate (context : AnalysisContext, token : Token, className : string, typeArguments : Type[]) : ClassDefinition { return context.parser.lookupTemplate(context.errors, new TemplateInstantiationRequest(token, className, typeArguments), context.postInstantiationCallback); } static function analyzeArgs (context : AnalysisContext, args : Expression[], parentExpr : Expression, expectedTypes : Type[][]) : Type[] { var argTypes = [] : Type[]; for (var i = 0; i < args.length; ++i) { if (args[i] instanceof FunctionExpression && ! (args[i] as FunctionExpression).argumentTypesAreIdentified()) { // find the only expected types, by counting the number of arguments var funcDef = (args[i] as FunctionExpression).getFuncDef(); var expectedCallbackType = null : Type; for (var j = 0; j < expectedTypes.length; ++j) { if (expectedTypes[j][i] != null && expectedTypes[j][i] instanceof FunctionType && (expectedTypes[j][i] as ResolvedFunctionType).getArgumentTypes().length == funcDef.getArguments().length) { if (expectedCallbackType == null) { expectedCallbackType = expectedTypes[j][i]; } else if (Util.typesAreEqual((expectedCallbackType as ResolvedFunctionType).getArgumentTypes(), (expectedTypes[j][i] as ResolvedFunctionType).getArgumentTypes()) && (expectedCallbackType as ResolvedFunctionType).getReturnType().equals((expectedTypes[j][i] as ResolvedFunctionType).getReturnType())) { // function signatures are equal } else { break; } } } if (j != expectedTypes.length) { // multiple canditates, skip } else if (expectedCallbackType != null) { if (! (args[i] as FunctionExpression).deductTypeIfUnknown(context, expectedCallbackType as ResolvedFunctionType)) return null; } } else if (args[i] instanceof ArrayLiteralExpression && (args[i] as ArrayLiteralExpression).getExprs().length == 0 && (args[i] as ArrayLiteralExpression).getType() == null) { var arrayExpr = args[i] as ArrayLiteralExpression; var expectedArrayType = null : Type; for (var j = 0; j < expectedTypes.length; ++j) { if (expectedTypes[j][i] != null && expectedTypes[j][i] instanceof ObjectType && expectedTypes[j][i].getClassDef() instanceof InstantiatedClassDefinition && (expectedTypes[j][i].getClassDef() as InstantiatedClassDefinition).getTemplateClassName() == 'Array') { if (expectedArrayType == null) { expectedArrayType = expectedTypes[j][i]; } else if (expectedArrayType.equals(expectedTypes[j][i])) { // type parameters are equal } else { break; } } } if (j != expectedTypes.length) { // multiple canditates, skip } else if (expectedArrayType != null) { arrayExpr.setType(expectedArrayType); } } else if (args[i] instanceof MapLiteralExpression && (args[i] as MapLiteralExpression).getElements().length == 0 && (args[i] as MapLiteralExpression).getType() == null) { var mapExpr = args[i] as MapLiteralExpression; var expectedMapType = null : Type; for (var j = 0; j < expectedTypes.length; ++j) { if (expectedTypes[j][i] != null && expectedTypes[j][i] instanceof ObjectType && expectedTypes[j][i].getClassDef() instanceof InstantiatedClassDefinition && (expectedTypes[j][i].getClassDef() as InstantiatedClassDefinition).getTemplateClassName() == 'Map') { if (expectedMapType == null) { expectedMapType = expectedTypes[j][i]; } else if (expectedMapType.equals(expectedTypes[j][i])) { // type parameters are equal } else { break; } } } if (j != expectedTypes.length) { // multiple canditates, skip } else if (expectedMapType != null) { mapExpr.setType(expectedMapType); } } if (! args[i].analyze(context, parentExpr)) return null; argTypes[i] = args[i].getType(); } return argTypes; } static function typesAreEqual (x : Type[], y : Type[]) : boolean { if (x.length != y.length) return false; for (var i = 0; i < x.length; ++i) if (! x[i].equals(y[i])) return false; return true; } static function forEachStatement (cb : function(:Statement):boolean, statements : Statement[]) : boolean { if (statements != null) for (var i = 0; i < statements.length; ++i) if (! cb(statements[i])) return false; return true; } static function forEachExpression (cb : function(:Expression):boolean, exprs : Expression[]) : boolean { return Util.forEachExpression(function(expr, _) { return cb(expr); }, exprs); } static function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean, exprs : Expression[]) : boolean { if (exprs != null) for (var i = 0; i < exprs.length; ++i) { if (! cb(exprs[i], function (exprs : Expression[], index : number) : function(:Expression):void { return function (expr : Expression) : void { exprs[index] = expr; }; }(exprs, i))) { return false; } } return true; } static function findFunctionInClass (classDef : ClassDefinition, funcName : string, argTypes : Type[], isStatic : boolean) : MemberFunctionDefinition { var found = null : MemberFunctionDefinition; classDef.forEachMemberFunction(function (funcDef) { if (isStatic == ((funcDef.flags() & ClassDefinition.IS_STATIC) != 0) && funcDef.name() == funcName && Util.typesAreEqual(funcDef.getArgumentTypes(), argTypes)) { found = funcDef; return false; } return true; }); return found; } static function findVariableInClass(classDef : ClassDefinition, name : string, isStatic : boolean) : MemberVariableDefinition { var found = null : MemberVariableDefinition; classDef.forEachMemberVariable(function (def) { if (isStatic == ((def.flags() & ClassDefinition.IS_STATIC) != 0) && def.name() == name) { found = def; return false; } return true; }); return found; } static function findMemberInClass(classDef : ClassDefinition, name : string, argTypes : Type[], isStatic : boolean) : MemberDefinition { if (argTypes != null) { return Util.findFunctionInClass(classDef, name, argTypes, isStatic); } else { return Util.findVariableInClass(classDef, name, isStatic); } } static function memberRootIsNative(classDef : ClassDefinition, name : string, argTypes : Type[], isStatic : boolean) : boolean { if (isStatic) { // TODO check "_Main.main" for minification return (classDef.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_FAKE)) != 0; } function rootIsNativeNonStatic(classDef : ClassDefinition, name : string, argTypes : Type[]) : boolean { var found = Util.findMemberInClass(classDef, name, argTypes, false); if (found != null && (found.flags() & ClassDefinition.IS_OVERRIDE) == 0) { // found base def return (classDef.flags() & (ClassDefinition.IS_NATIVE | ClassDefinition.IS_FAKE)) != 0; } if (classDef.extendType() == null) { // no base def found in the "extend" chain, means that the base def exists in Interface / Mixin which are guaranteed to be non-native return false; } return rootIsNativeNonStatic(classDef.extendType().getClassDef(), name, argTypes); } return rootIsNativeNonStatic(classDef, name, argTypes); } static function propertyRootIsNative(expr : PropertyExpression) : boolean { var baseExpr = expr.getExpr(); return Util.memberRootIsNative( baseExpr.getType().getClassDef(), expr.getIdentifierToken().getValue(), Util.isReferringToFunctionDefinition(expr) ? (expr.getType() as ResolvedFunctionType).getArgumentTypes() : null, baseExpr instanceof ClassExpression); } static function memberIsExported(classDef : ClassDefinition, name : string, argTypes : Type[], isStatic : boolean) : boolean { if (isStatic) { var found = Util.findMemberInClass(classDef, name, argTypes, true); return (found.flags() & ClassDefinition.IS_EXPORT) != 0; } function check(classDef : ClassDefinition) : boolean { // check in myself if ((classDef.flags() & ClassDefinition.IS_NATIVE) != 0) { // native classes never "export" return false; } var found = Util.findMemberInClass(classDef, name, argTypes, false); if (found != null && (found.flags() & ClassDefinition.IS_EXPORT) != 0) { return true; } // check in base if (classDef.extendType() != null) { if (check(classDef.extendType().getClassDef())) { return true; } } var isExportedInImpl = false; classDef.implementTypes().forEach(function (implType) { if (check(implType.getClassDef())) { isExportedInImpl = true; } }); return isExportedInImpl; } return check(classDef); } static function isReferringToFunctionDefinition(expr : PropertyExpression) : boolean { var exprType = expr.getType(); if (! (exprType instanceof FunctionType)) { return false; } if (exprType.isAssignable()) { return false; } return true; } static const _stringLiteralEncodingMap = { "\0" : "\\0", "\r" : "\\r", "\n" : "\\n", "\t" : "\\t", "\"" : "\\\"", "\'" : "\\\'", "\\" : "\\\\" }; static function encodeStringLiteral (str : string) : string { var escaped = str.replace(/[\0-\x19\\'"\u007f-\uffff]/g, function (ch) { if (ch in Util._stringLiteralEncodingMap) { return Util._stringLiteralEncodingMap[ch]; } else { var t = "000" + ch.charCodeAt(0).toString(16); t = t.substring(t.length - 4); return "\\u" + t; } }); return "\"" + escaped + "\""; } static function decodeStringLiteral (literal : string) : string { var matched = literal.match(/^([\'\"]).*([\'\"])$/); if (matched == null || matched[1] != matched[2]) throw new Error("input string is not quoted properly: " + literal); var src = literal.substring(1, literal.length - 1); var decoded = ""; var pos = 0, backslashAt; while ((backslashAt = src.indexOf("\\", pos)) != -1) { // copy the string before backslash decoded += src.substring(pos, backslashAt); pos = backslashAt + 1; // decode if (pos == src.length) throw new Error("last character within a string literal cannot be a backslash: " + literal); var escapeChar = src.charAt(pos++); switch (escapeChar) { case "'": case "\"": case "\\": decoded += escapeChar; break; case "b": decoded += "\b"; break; case "f": decoded += "\f"; break; case "n": decoded += "\n"; break; case "r": decoded += "\r"; break; case "t": decoded += "\t"; break; case "v": decoded += "\v"; break; case "u": var matched = src.substring(pos).match(/^([0-9A-Fa-f]{4})/); if (matched == null) throw new Error("expected four hexdigits after \\u: " + literal); decoded += String.fromCharCode(Number.parseInt(matched[1], 16)); pos += 4; break; case "0": if (pos == src.length || src.charAt(pos).match(/[0-9]/) == null) decoded += "\0"; else throw new Error("found a digit after '\\0': " + literal); break; } } decoded += src.substring(pos); return decoded; } static function _resolvedPathParts(path : string) : string[] { var tokens = path.split(/[\\\/]+/); if (tokens.length == 1) { return tokens; } for (var i = 0; i < tokens.length;) { if (tokens[i] == ".") { tokens.splice(i, 1); } else if (tokens[i] == ".." && i != 0 && tokens[i - 1] != "..") { tokens.splice(i - 1, 2); i -= 1; } else { i++; } } return tokens; } /** * Canonicalizes the given path. e.g. "a/x/../b" to "a/b" */ static function resolvePath (path : string) : string { return Util._resolvedPathParts(path).join("/"); } /** * Solves the relative path from <code>from</code> to <code>to</code>. * e.g. relativePath("/a/b/c", "/a/d/e") to "../d/e" */ static function relativePath(fromPath : string, toPath: string, isFile : boolean) : string { var f = Util._resolvedPathParts(fromPath); var t = Util._resolvedPathParts(toPath); if (isFile) { f.pop(); // use dirname } // trim the root if (f[0] == "") { f.shift(); } if (t[0] == "") { t.shift(); } var minLen = Math.min(f.length, t.length); var samePartsIndex = minLen; for (var i = 0; i < minLen; ++i) { if (f[i] != t[i]) { samePartsIndex = i; break; } } var pathParts = new string[]; for (var i = samePartsIndex; i < f.length; ++i) { pathParts.push(".."); } return pathParts.concat(t.slice(samePartsIndex)).join("/"); } static function basename(path : string) : string { var parts = Util._resolvedPathParts(path); return parts.pop(); } static function dirname(path : string) : string { var parts = Util._resolvedPathParts(path); parts.pop(); return parts.length != 0 ? parts.join("/") : "."; } static function toOrdinal(n : number) : string { if (10 < n && n < 14) { return n as string + 'th'; } switch (n % 10) { case 1: return n as string + 'st'; case 2: return n as string + 'nd'; case 3: return n as string + 'rd'; default: return n as string + 'th'; } } static function makeErrorMessage (platform : Platform, message : string, filename : Nullable.<string>, lineNumber : number, columnNumber : number, size : number) : string { if (filename == null) { return message + "\n"; } var content = platform.load(filename); var sourceLine = content.split(/\n/)[ lineNumber - 1 ] + "\n"; // fix visual width var TAB_WIDTH = 4; var tabs = sourceLine.slice(0, columnNumber).match(/\t/g); if(tabs != null) { columnNumber += (TAB_WIDTH-1) * tabs.length; } sourceLine = sourceLine.replace(/\t/g, Util.repeat(" ", TAB_WIDTH)); sourceLine += Util.repeat(" ", columnNumber); sourceLine += Util.repeat("^", size); return Util.format("[%1:%2:%3] %4\n%5\n", [filename, lineNumber as string, columnNumber as string, message, sourceLine]); } static function isArrayOf(classDef : ClassDefinition, expectedElementType : Type) : boolean { if (! (classDef instanceof InstantiatedClassDefinition)) { return false; } var instantiatedClassDef = classDef as InstantiatedClassDefinition; if (instantiatedClassDef.getTemplateClassName() != "Array") { return false; } if (! instantiatedClassDef.getTypeArguments()[0].equals(expectedElementType)) { return false; } return true; } } /* * Tow-value data structure class */ class Pair.<T,U> { var first : T; var second : U; function constructor(first : T, second : U) { this.first = first; this.second = second; } } /* * Three-value data structure class */ class Triple.<T,U,V> { var first : T; var second : U; var third : V; function constructor(first : T, second : U, third : V) { this.first = first; this.second = second; this.third = third; } } /** * Map-like container with specified type of keys. */ class TypedMap.<K,V> { var _list = new Pair.<K,V>[]; var _equalsCallback : function(:K,:K):boolean; function constructor() { this(function(x, y) { return x == y; }); } function constructor(equalsCallback : function(:K,:K):boolean) { this._equalsCallback = equalsCallback; } function clone () : TypedMap.<K,V> { var x = new TypedMap.<K,V>(this._equalsCallback); x._list = this._list.concat([]); return x; } function has(key : K) : boolean { return ! this.forEach(function (entryKey, entryValue) { return ! this._equalsCallback(key, entryKey); }); } function set(key : K, val : V) : void { for (var i = 0; i < this._list.length; ++i) { if (this._equalsCallback(this._list[i].first, key)) { this._list[i].second = val; return; } } this._list.push(new Pair.<K,V>(key, val)); } function get(key : K) : Nullable.<V> { for (var i = 0; i < this._list.length; ++i) { if (this._equalsCallback(this._list[i].first, key)) { return this._list[i].second; } } return null; } function delete(key : K) : void { for (var i = 0; i < this._list.length; ++i) { if (this._equalsCallback(this._list[i].first, key)) { this._list.splice(i, 1); return; } } } function clear() : void { this._list.splice(0, this._list.length); } function forEach (cb : function(:K,:V):boolean) : boolean { for (var i = 0; i < this._list.length; ++i) { var e = this._list[i]; if (! cb(e.first, e.second)) { return false; } } return true; } function reversedForEach (cb : function(:K,:V):boolean) : boolean { for (var i = this._list.length-1; i >= 0; --i) { var e = this._list[i]; if (! cb(e.first, e.second)) { return false; } } return true; } } // vim: set noexpandtab: