jsx
Version:
a faster, safer, easier JavaScript
623 lines (555 loc) • 19.2 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 "./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: