Implementing uneval/toSource functionality of SpiderMonkey. Foe details, see http://bugzilla.mozilla.org/show_bug.cgi?id=225465.

This commit is contained in:
igor%mir2.org 2003-11-13 17:15:31 +00:00
parent 56ad1b1cba
commit 2e75448c13
14 changed files with 238 additions and 80 deletions

View File

@ -91,6 +91,21 @@ public class BaseFunction extends IdScriptable implements Function {
functionName);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
{
int indent = 0;
int flags = Decompiler.TO_SOURCE_FLAG;
if (args.length != 0) {
indent = ScriptRuntime.toInt32(args[0]);
if (indent >= 0) {
flags = 0;
} else {
indent = 0;
}
}
return decompile(cx, indent, flags);
}
protected int getIdAttributes(int id)
{
switch (id) {

View File

@ -78,6 +78,11 @@ public class Decompiler
*/
public static final int ONLY_BODY_FLAG = 1 << 0;
/**
* Flag to indicate that the decompilation generates toSource result.
*/
public static final int TO_SOURCE_FLAG = 1 << 1;
/**
* Decompilation property to specify initial ident value.
*/
@ -304,6 +309,7 @@ public class Decompiler
StringBuffer result = new StringBuffer();
boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG));
// Spew tokens in source, for debugging.
// as TYPE number char
@ -332,11 +338,6 @@ public class Decompiler
System.err.println();
}
// add an initial newline to exactly match js.
result.append('\n');
for (int j = 0; j < indent; j++)
result.append(' ');
int braceNesting = 0;
boolean afterFirstEOL = false;
int i = 0;
@ -348,6 +349,17 @@ public class Decompiler
topFunctionType = source.charAt(i + 1);
}
if (!toSource) {
// add an initial newline to exactly match js.
result.append('\n');
for (int j = 0; j < indent; j++)
result.append(' ');
} else {
if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
result.append('(');
}
}
while (i < length) {
switch(source.charAt(i)) {
case Token.NAME:
@ -380,7 +392,7 @@ public class Decompiler
break;
case Token.FUNCTION:
++i; // skip function type, it is used only when decompiling
++i; // skip function type
result.append("function ");
break;
@ -441,6 +453,7 @@ public class Decompiler
break;
case Token.EOL: {
if (toSource) break;
boolean newLine = true;
if (!afterFirstEOL) {
afterFirstEOL = true;
@ -785,9 +798,15 @@ public class Decompiler
++i;
}
// add that trailing newline if it's an outermost function.
if (!justFunctionBody)
result.append('\n');
if (!toSource) {
// add that trailing newline if it's an outermost function.
if (!justFunctionBody)
result.append('\n');
} else {
if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) {
result.append(')');
}
}
return result.toString();
}

View File

@ -158,12 +158,12 @@ public class NativeArray extends IdScriptable {
return jsConstructor(cx, scope, args, f, thisObj == null);
case Id_toString:
return toStringHelper(cx, thisObj,
return toStringHelper(cx, scope, thisObj,
cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE),
false);
case Id_toLocaleString:
return toStringHelper(cx, thisObj, false, true);
return toStringHelper(cx, scope, thisObj, false, true);
case Id_join:
return js_join(cx, thisObj, args);
@ -317,6 +317,13 @@ public class NativeArray extends IdScriptable {
return super.getDefaultValue(hint);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
throws JavaScriptException
{
return toStringHelper(cx, scope, this, true, false);
}
/**
* See ECMA 15.4.1,2
*/
@ -449,7 +456,8 @@ public class NativeArray extends IdScriptable {
}
}
private static String toStringHelper(Context cx, Scriptable thisObj,
private static String toStringHelper(Context cx, Scriptable scope,
Scriptable thisObj,
boolean toSource, boolean toLocale)
throws JavaScriptException
{
@ -498,7 +506,10 @@ public class NativeArray extends IdScriptable {
}
haslast = true;
if (elem instanceof String) {
if (toSource) {
result.append(ScriptRuntime.uneval(cx, scope, elem));
} else if (elem instanceof String) {
String s = (String)elem;
if (toSource) {
result.append('\"');
@ -507,6 +518,7 @@ public class NativeArray extends IdScriptable {
} else {
result.append(s);
}
} else {
if (toLocale && elem != Undefined.instance &&
elem != null)

View File

@ -66,6 +66,14 @@ final class NativeBoolean extends IdScriptable {
return super.getDefaultValue(typeHint);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
{
if (booleanValue)
return "(new Boolean(true))";
else
return "(new Boolean(false))";
}
public int methodArity(int methodId) {
if (prototypeFlag) {
if (methodId == Id_constructor) return 1;

View File

@ -91,6 +91,11 @@ final class NativeDate extends IdScriptable {
super.fillConstructorProperties(cx, ctor, sealed);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
{
return "(new Date("+ScriptRuntime.toString(date)+"))";
}
public int methodArity(int methodId) {
if (prototypeFlag) {
switch (methodId) {

View File

@ -55,6 +55,17 @@ final class NativeError extends IdScriptable
obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
throws JavaScriptException
{
Object value = messageValue;
if (value == NOT_FOUND)
return "(new Error())";
String str = ScriptRuntime.toString(value);
str = ScriptRuntime.escapeString(str);
return "(new Error(\""+str+"\"))";
}
protected int getIdAttributes(int id)
{
if (id == Id_message || id == Id_name) { return EMPTY; }

View File

@ -70,6 +70,7 @@ public class NativeGlobal implements Serializable, IdFunctionMaster
case Id_parseFloat: name = "parseFloat"; break;
case Id_parseInt: name = "parseInt"; break;
case Id_unescape: name = "unescape"; break;
case Id_uneval: name = "uneval"; break;
default:
Kit.codeBug(); name = null;
}
@ -173,6 +174,12 @@ public class NativeGlobal implements Serializable, IdFunctionMaster
case Id_unescape:
return js_unescape(cx, args);
case Id_uneval: {
Object value = (args.length != 0)
? args[0] : Undefined.instance;
return ScriptRuntime.uneval(cx, scope, value);
}
case Id_new_CommonError:
return new_CommonError(function, cx, scope, args);
}
@ -194,6 +201,7 @@ public class NativeGlobal implements Serializable, IdFunctionMaster
case Id_parseFloat: return 1;
case Id_parseInt: return 2;
case Id_unescape: return 1;
case Id_uneval: return 1;
case Id_new_CommonError: return 1;
}
@ -748,10 +756,11 @@ public class NativeGlobal implements Serializable, IdFunctionMaster
Id_parseFloat = 9,
Id_parseInt = 10,
Id_unescape = 11,
Id_uneval = 12,
LAST_SCOPE_FUNCTION_ID = 11,
LAST_SCOPE_FUNCTION_ID = 12,
Id_new_CommonError = 12;
Id_new_CommonError = 13;
private boolean scopeSlaveFlag;

View File

@ -56,6 +56,11 @@ final class NativeMath extends IdScriptable
public String getClassName() { return "Math"; }
protected String toSource(Context cx, Scriptable scope, Object[] args)
{
return "Math";
}
protected int getIdAttributes(int id)
{
if (id > LAST_METHOD_ID) {

View File

@ -86,6 +86,11 @@ final class NativeNumber extends IdScriptable {
super.fillConstructorProperties(cx, ctor, sealed);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
{
return "(new Number("+ScriptRuntime.toString(doubleValue)+"))";
}
public int methodArity(int methodId) {
if (prototypeFlag) {
switch (methodId) {

View File

@ -62,6 +62,7 @@ public class NativeObject extends IdScriptable
protected int mapNameToId(String s) { return 0; }
protected String getIdName(int id) { return null; }
}
final class NativeObjectPrototype extends NativeObject
@ -81,6 +82,7 @@ final class NativeObjectPrototype extends NativeObject
case Id_hasOwnProperty: return 1;
case Id_propertyIsEnumerable: return 1;
case Id_isPrototypeOf: return 1;
case Id_toSource: return 0;
}
return super.methodArity(methodId);
}
@ -104,9 +106,20 @@ final class NativeObjectPrototype extends NativeObject
return ScriptRuntime.toObject(cx, scope, args[0]);
}
case Id_toString:
case Id_toLocaleString: /* For now just alias toString */
return js_toString(cx, thisObj);
case Id_toLocaleString: // For now just alias toString
case Id_toString: {
if (cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE)) {
String s = toSource(cx, scope, thisObj, args);
int L = s.length();
if (L != 0 && s.charAt(0) == '(' && s.charAt(L - 1) == ')')
{
// Strip () that surrounds toSource
s = s.substring(1, L - 1);
}
return s;
}
return toString(thisObj);
}
case Id_valueOf:
return thisObj;
@ -147,75 +160,27 @@ final class NativeObjectPrototype extends NativeObject
}
return Boolean.FALSE;
}
case Id_toSource:
return toSource(cx, scope, thisObj, args);
}
return super.execMethod(methodId, f, cx, scope, thisObj, args);
}
static String toString(Scriptable thisObj)
{
Context cx = Context.getCurrentContext();
if (cx != null) {
return js_toString(cx, thisObj);
} else {
return "[object " + thisObj.getClassName() + "]";
}
}
private static String js_toString(Context cx, Scriptable thisObj)
{
if (cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE)) {
return toSource(cx, thisObj);
}
return "[object " + thisObj.getClassName() + "]";
}
private static String toSource(Context cx, Scriptable thisObj)
private static String toSource(Context cx, Scriptable scope,
Scriptable thisObj, Object[] args)
throws JavaScriptException
{
StringBuffer result = new StringBuffer(256);
result.append('{');
boolean toplevel, iterating;
if (cx.iterating == null) {
toplevel = true;
iterating = false;
cx.iterating = new ObjToIntMap(31);
} else {
toplevel = false;
iterating = cx.iterating.has(thisObj);
if (thisObj instanceof ScriptableObject) {
ScriptableObject so = (ScriptableObject)thisObj;
return so.toSource(cx, scope, args);
}
// Make sure cx.iterating is set to null when done
// so we don't leak memory
try {
if (!iterating) {
cx.iterating.put(thisObj, 0); // stop recursion.
Object[] ids = thisObj.getIds();
for(int i=0; i < ids.length; i++) {
if (i > 0)
result.append(", ");
Object id = ids[i];
result.append(id);
result.append(':');
Object p = (id instanceof String)
? thisObj.get((String) id, thisObj)
: thisObj.get(((Integer) id).intValue(), thisObj);
if (p instanceof String) {
result.append('\"');
result.append(ScriptRuntime.escapeString((String)p));
result.append('\"');
} else {
result.append(ScriptRuntime.toString(p));
}
}
}
} finally {
if (toplevel) {
cx.iterating = null;
}
}
result.append('}');
return result.toString();
return ScriptRuntime.toString(thisObj);
}
protected String getIdName(int id)
@ -228,6 +193,7 @@ final class NativeObjectPrototype extends NativeObject
case Id_hasOwnProperty: return "hasOwnProperty";
case Id_propertyIsEnumerable: return "propertyIsEnumerable";
case Id_isPrototypeOf: return "isPrototypeOf";
case Id_toSource: return "toSource";
}
return null;
}
@ -237,11 +203,14 @@ final class NativeObjectPrototype extends NativeObject
protected int mapNameToId(String s)
{
int id;
// #generated# Last update: 2001-04-24 12:37:03 GMT+02:00
// #generated# Last update: 2003-11-11 01:51:40 CET
L0: { id = 0; String X = null; int c;
L: switch (s.length()) {
case 7: X="valueOf";id=Id_valueOf; break L;
case 8: X="toString";id=Id_toString; break L;
case 8: c=s.charAt(3);
if (c=='o') { X="toSource";id=Id_toSource; }
else if (c=='t') { X="toString";id=Id_toString; }
break L;
case 11: X="constructor";id=Id_constructor; break L;
case 13: X="isPrototypeOf";id=Id_isPrototypeOf; break L;
case 14: c=s.charAt(0);
@ -264,7 +233,8 @@ final class NativeObjectPrototype extends NativeObject
Id_hasOwnProperty = 5,
Id_propertyIsEnumerable = 6,
Id_isPrototypeOf = 7,
MAX_PROTOTYPE_ID = 7;
Id_toSource = 8,
MAX_PROTOTYPE_ID = 8;
// #/string_id_map#
}

View File

@ -72,6 +72,11 @@ final class NativeString extends IdScriptable {
super.fillConstructorProperties(cx, ctor, sealed);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
{
return "(new String(\""+ScriptRuntime.escapeString(string)+"\"))";
}
protected int getIdAttributes(int id)
{
if (id == Id_length) {

View File

@ -548,6 +548,49 @@ public class ScriptRuntime {
}
static String uneval(Context cx, Scriptable scope, Object value)
throws JavaScriptException
{
if (value == null) {
return "null";
}
if (value instanceof String) {
String escaped = escapeString((String)value);
StringBuffer sb = new StringBuffer(escaped.length() + 2);
sb.append('\"');
sb.append(escaped);
sb.append('\"');
return sb.toString();
}
if (value instanceof Number) {
double d = ((Number)value).doubleValue();
if (d == 0 && 1 / d < 0) {
return "-0";
}
return toString(d);
}
if (value instanceof Boolean) {
return toString(value);
}
if (value == Undefined.instance) {
return "undefined";
}
if (value instanceof Scriptable) {
Scriptable obj = (Scriptable)value;
Object v = ScriptableObject.getProperty(obj, "toSource");
if (v instanceof Function) {
Function f = (Function)v;
return toString(f.call(cx, scope, obj, emptyArgs));
}
if (value instanceof ScriptableObject) {
ScriptableObject so = (ScriptableObject)obj;
return so.toSource(cx, scope, emptyArgs);
}
return toString(value);
}
throw errorWithClassName("msg.invalid.type", value);
}
public static Scriptable toObject(Scriptable scope, Object val)
{
if (val instanceof Scriptable && val != Undefined.instance) {

View File

@ -677,6 +677,50 @@ public abstract class ScriptableObject implements Scriptable, Serializable,
return ScriptRuntime.jsDelegatesTo(instance, this);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
throws JavaScriptException
{
StringBuffer result = new StringBuffer(256);
result.append("({");
boolean toplevel, iterating;
if (cx.iterating == null) {
toplevel = true;
iterating = false;
cx.iterating = new ObjToIntMap(31);
} else {
toplevel = false;
iterating = cx.iterating.has(this);
}
// Make sure cx.iterating is set to null when done
// so we don't leak memory
try {
if (!iterating) {
cx.iterating.intern(this); // stop recursion.
Object[] ids = this.getIds();
for(int i=0; i < ids.length; i++) {
if (i > 0)
result.append(", ");
Object id = ids[i];
result.append(id);
result.append(':');
Object p = (id instanceof String)
? this.get((String) id, this)
: this.get(((Integer) id).intValue(), this);
result.append(ScriptRuntime.uneval(cx, scope, p));
}
}
} finally {
if (toplevel) {
cx.iterating = null;
}
}
result.append("})");
return result.toString();
}
/**
* Defines JavaScript objects from a Java class that implements Scriptable.
*

View File

@ -2625,8 +2625,15 @@ System.out.println("Testing at " + x.cp + ", op = " + op);
throw ScriptRuntime.constructError("SyntaxError", msg);
}
protected String toSource(Context cx, Scriptable scope, Object[] args)
throws JavaScriptException
{
return toString();
}
protected int getIdAttributes(int id)
{
{
switch (id) {
case Id_lastIndex:
return ScriptableObject.PERMANENT | ScriptableObject.DONTENUM;