diff --git a/benchmarks/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java b/benchmarks/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java new file mode 100644 index 0000000000..f1e81d25e4 --- /dev/null +++ b/benchmarks/org/mozilla/javascript/benchmarks/BuiltinBenchmark.java @@ -0,0 +1,383 @@ +package org.mozilla.javascript.benchmarks; + +import java.lang.reflect.InvocationTargetException; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.IdFunctionObject; +import org.mozilla.javascript.IdScriptableObject; +import org.mozilla.javascript.LambdaConstructor; +import org.mozilla.javascript.Script; +import org.mozilla.javascript.ScriptRuntime; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; +import org.mozilla.javascript.annotations.*; +import org.openjdk.jmh.annotations.*; + +public class BuiltinBenchmark { + + @State(Scope.Thread) + public static class AbstractClassState { + + public void init() + throws IllegalAccessException, InvocationTargetException, InstantiationException { + cx = Context.enter(); + cx.setOptimizationLevel(9); + cx.setLanguageVersion(Context.VERSION_ES6); + + scope = cx.initStandardObjects(); + ScriptableObject.defineClass(scope, AnnotatedClass.class); + IdClass.init(scope); + DumbLambdaClass.init(scope); + } + + void compileScript(String testClassName) { + testScript = + cx.compileString( + "o = new " + + testClassName + + "();\n" + + "o.setValue(99);\n" + + "for (var i = 0; i < 1000; i++) {\n" + + "if (o.getValue() !== 99) { throw 'Not working!'; }\n" + + "}\n" + + "o.getValue();", + "test.js", + 1, + null); + } + + @TearDown(Level.Trial) + public void close() { + Context.exit(); + } + + Context cx; + Scriptable scope; + Script testScript; + } + + @State(Scope.Thread) + public static class AnnotatedClassState extends AbstractClassState { + + @Setup(Level.Trial) + public void init() + throws IllegalAccessException, InvocationTargetException, InstantiationException { + super.init(); + compileScript("AnnotatedClass"); + } + } + + @Benchmark + public Object annotatedClassMethods(AnnotatedClassState state) { + return state.testScript.exec(state.cx, state.scope); + } + + public static class AnnotatedClass extends ScriptableObject { + + @Override + public String getClassName() { + return "AnnotatedClass"; + } + + @JSFunction + public void one() {} + + @JSFunction + public void two() {} + + @JSFunction + public void three() {} + + @JSFunction + public void four() {} + + @JSFunction + public void five() {} + + @JSFunction + public void six() {} + + @JSFunction + public void seven() {} + + @JSFunction + public void eight() {} + + @JSFunction + public void nine() {} + + @JSFunction + public void setValue(int value) { + this.value = value; + } + + @JSFunction + public int getValue() { + return value; + } + + private int value; + } + + @State(Scope.Thread) + public static class IdClassState extends AbstractClassState { + + @Setup(Level.Trial) + public void init() + throws IllegalAccessException, InvocationTargetException, InstantiationException { + super.init(); + compileScript("IdClass"); + } + } + + @Benchmark + public Object idClassMethods(IdClassState state) { + return state.testScript.exec(state.cx, state.scope); + } + + public static class IdClass extends IdScriptableObject { + + private static final String TAG = "IdClass"; + + public static void init(Scriptable scope) { + IdClass idc = new IdClass(); + idc.exportAsJSClass(MAX_ID, scope, false); + } + + @Override + public String getClassName() { + return "IdClass"; + } + + @Override + protected void initPrototypeId(int id) { + String s, fnName = null; + int arity; + switch (id) { + case Id_one: + arity = 0; + s = "one"; + break; + case Id_two: + arity = 0; + s = "two"; + break; + case Id_three: + arity = 0; + s = "three"; + break; + case Id_four: + arity = 0; + s = "four"; + break; + case Id_five: + arity = 0; + s = "five"; + break; + case Id_six: + arity = 0; + s = "six"; + break; + case Id_seven: + arity = 0; + s = "seven"; + break; + case Id_eight: + arity = 0; + s = "eight"; + break; + case Id_nine: + arity = 0; + s = "nine"; + break; + case Id_setValue: + arity = 1; + s = "setValue"; + break; + case Id_getValue: + arity = 0; + s = "getValue"; + break; + case Id_constructor: + arity = 0; + s = "constructor"; + break; + default: + throw new IllegalArgumentException(String.valueOf(id)); + } + + initPrototypeMethod(TAG, id, s, fnName, arity); + } + + @Override + public Object execIdCall( + IdFunctionObject f, + Context cx, + Scriptable scope, + Scriptable thisObj, + Object[] args) { + if (!f.hasTag(TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + IdClass self; + switch (id) { + case Id_constructor: + return new IdClass(); + case Id_setValue: + self = (IdClass) thisObj; + if (args.length < 1) { + throw ScriptRuntime.throwError(cx, scope, "not enough args"); + } + self.value = ScriptRuntime.toInt32(args[0]); + break; + case Id_getValue: + self = (IdClass) thisObj; + return self.value; + default: + throw new IllegalArgumentException( + "Array.prototype has no method: " + f.getFunctionName()); + } + return Undefined.instance; + } + + // #string_id_map# + + @Override + protected int findPrototypeId(String s) { + int id; + // #generated# Last update: 2021-04-13 16:17:26 PDT + switch (s) { + case "one": + id = Id_one; + break; + case "two": + id = Id_two; + break; + case "three": + id = Id_three; + break; + case "four": + id = Id_four; + break; + case "five": + id = Id_five; + break; + case "six": + id = Id_six; + break; + case "seven": + id = Id_seven; + break; + case "eight": + id = Id_eight; + break; + case "nine": + id = Id_nine; + break; + case "getValue": + id = Id_getValue; + break; + case "setValue": + id = Id_setValue; + break; + case "constructor": + id = Id_constructor; + break; + default: + id = 0; + break; + } + // #/generated# + return id; + } + + private static final int Id_one = 1, + Id_two = 2, + Id_three = 3, + Id_four = 4, + Id_five = 5, + Id_six = 6, + Id_seven = 7, + Id_eight = 8, + Id_nine = 9, + Id_getValue = 10, + Id_setValue = 11, + Id_constructor = 12, + MAX_ID = Id_constructor; + + // #/string_id_map# + + private int value; + } + + @State(Scope.Thread) + public static class DumbLambdaState extends AbstractClassState { + + @Setup(Level.Trial) + public void init() + throws IllegalAccessException, InvocationTargetException, InstantiationException { + super.init(); + compileScript("DumbLambdaClass"); + } + } + + @Benchmark + public Object dumbLambdaClassMethods(DumbLambdaState state) { + return state.testScript.exec(state.cx, state.scope); + } + + private static class DumbLambdaClass extends ScriptableObject { + + private static Object noop( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + return Undefined.instance; + } + + private static Object setValue( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + if (args.length < 1) { + throw ScriptRuntime.throwError(cx, scope, "Not enough args"); + } + DumbLambdaClass self = + LambdaConstructor.convertThisObject(thisObj, DumbLambdaClass.class); + self.value = ScriptRuntime.toInt32(args[0]); + return Undefined.instance; + } + + private static Object getValue( + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { + DumbLambdaClass self = + LambdaConstructor.convertThisObject(thisObj, DumbLambdaClass.class); + return self.value; + } + + public static void init(Scriptable scope) { + LambdaConstructor cons = + new LambdaConstructor( + scope, + "DumbLambdaClass", + 0, + (Context cx, Scriptable s, Object[] args) -> new DumbLambdaClass()); + cons.definePrototypeMethod(scope, "one", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "two", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "three", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "four", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "five", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "six", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "seven", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "eight", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "nine", 0, DumbLambdaClass::noop); + cons.definePrototypeMethod(scope, "setValue", 1, DumbLambdaClass::setValue); + cons.definePrototypeMethod(scope, "getValue", 1, DumbLambdaClass::getValue); + ScriptableObject.putProperty(scope, "DumbLambdaClass", cons); + } + + @Override + public String getClassName() { + return "DumbLambdaClass"; + } + + private int value; + } +} diff --git a/build.gradle b/build.gradle index c2c787401e..d2a3dc52ea 100644 --- a/build.gradle +++ b/build.gradle @@ -108,10 +108,10 @@ task testBenchmark() {} testBenchmark.dependsOn sunSpiderBenchmark testBenchmark.dependsOn v8Benchmark -task microBenchmarks(type: JavaExec, description: 'JMH benchmarks') { +task builtinBenchmarks(type: JavaExec, description: 'JMH benchmarks') { classpath = sourceSets.jmh.runtimeClasspath main = 'org.openjdk.jmh.Main' - args '-f', '1', '-bm', 'avgt', '-tu', 'ns', 'ObjectCall' + args '-f', '1', '-bm', 'avgt', '-tu', 'ns', 'Builtin' } task jmhHelp(type: JavaExec, description: 'JMH benchmarks') { diff --git a/src/org/mozilla/javascript/BaseFunction.java b/src/org/mozilla/javascript/BaseFunction.java index e29a97234d..ed95eedd29 100644 --- a/src/org/mozilla/javascript/BaseFunction.java +++ b/src/org/mozilla/javascript/BaseFunction.java @@ -7,31 +7,29 @@ package org.mozilla.javascript; /** - * The base class for Function objects. That is one of two purposes. It is also - * the prototype for every "function" defined except those that are used - * as GeneratorFunctions via the ES6 "function *" syntax. + * The base class for Function objects. That is one of two purposes. It is also the prototype for + * every "function" defined except those that are used as GeneratorFunctions via the ES6 "function + * *" syntax. + * + *
See ECMA 15.3.
*
- * See ECMA 15.3.
* @author Norris Boyd
*/
-public class BaseFunction extends IdScriptableObject implements Function
-{
+public class BaseFunction extends IdScriptableObject implements Function {
private static final long serialVersionUID = 5311394446546053859L;
private static final Object FUNCTION_TAG = "Function";
private static final String FUNCTION_CLASS = "Function";
static final String GENERATOR_FUNCTION_CLASS = "__GeneratorFunction";
- static void init(Scriptable scope, boolean sealed)
- {
+ static void init(Scriptable scope, boolean sealed) {
BaseFunction obj = new BaseFunction();
// Function.prototype attributes: see ECMA 15.3.3.1
obj.prototypePropertyAttributes = DONTENUM | READONLY | PERMANENT;
obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
}
- static Object initAsGeneratorFunction(Scriptable scope, boolean sealed)
- {
+ static Object initAsGeneratorFunction(Scriptable scope, boolean sealed) {
BaseFunction obj = new BaseFunction(true);
// Function.prototype attributes: see ECMA 15.3.3.1
obj.prototypePropertyAttributes = READONLY | PERMANENT;
@@ -63,143 +61,142 @@ protected boolean isGeneratorFunction() {
/**
* Gets the value returned by calling the typeof operator on this object.
+ *
* @see org.mozilla.javascript.ScriptableObject#getTypeOf()
- * @return "function" or "undefined" if {@link #avoidObjectDetection()} returns true
+ * @return "function" or "undefined" if {@link #avoidObjectDetection()} returns true
+ *
*/
@Override
- public String getTypeOf()
- {
+ public String getTypeOf() {
return avoidObjectDetection() ? "undefined" : "function";
}
/**
* Implements the instanceof operator for JavaScript Function objects.
- *
- * This method is invoked by the runtime in order to satisfy a use of the JavaScript Note that the array of arguments is not guaranteed to have length greater than 0.
*
* @param cx the current Context for this thread
- * @param scope the scope to execute the function relative to. This is
- * set to the value returned by getParentScope() except
- * when the function is called from a closure.
+ * @param scope the scope to execute the function relative to. This is set to the value returned
+ * by getParentScope() except when the function is called from a closure.
* @param thisObj the JavaScript This method is invoked by the runtime in order to satisfy a use of the JavaScript In micro benchmarks (as of 2021) using this class to implement a built-in class is about 15%
+ * more efficient than using IdScriptableObject, and about 25% faster than using reflection via the
+ * ScriptableObject.defineClass() family of methods. Furthermore, it results in code that more
+ * directly maps to JavaScript idioms than either methods, it is much easier to implement than
+ * IdScriptableObject, and the lambda pattern makes it easier to maintain state in various ways that
+ * don't always map directly to the existing concepts.
+ */
+public class LambdaConstructor extends LambdaFunction {
+
+ private static final long serialVersionUID = 2691205302914111400L;
+
+ /** If this flag is set, the constructor may be invoked as an ordinary function */
+ public static final int CONSTRUCTOR_FUNCTION = 1;
+ /** If this flag is set, the constructor may be invoked using "new" */
+ public static final int CONSTRUCTOR_NEW = 1 << 1;
+ /** By default, the constructor may be invoked either way */
+ public static final int CONSTRUCTOR_DEFAULT = CONSTRUCTOR_FUNCTION | CONSTRUCTOR_NEW;
+
+ // Lambdas should not be serialized.
+ private final transient Constructable targetConstructor;
+ private final int flags;
+
+ /**
+ * Create a new function that may be used as a constructor. The new object will have the
+ * Function prototype and no parent. The caller is responsible for binding this object to the
+ * appropriate scope.
+ *
+ * @param scope scope of the calling context
+ * @param name name of the function
+ * @param length the arity of the function
+ * @param target an object that implements the function in Java. Since Constructable is a
+ * single-function interface this will typically be implemented as a lambda.
+ */
+ public LambdaConstructor(Scriptable scope, String name, int length, Constructable target) {
+ super(scope, name, length, null);
+ this.targetConstructor = target;
+ this.flags = CONSTRUCTOR_DEFAULT;
+ }
+
+ /**
+ * Create a new function and control whether it may be invoked using new, as a function, or
+ * both.
+ */
+ public LambdaConstructor(
+ Scriptable scope, String name, int length, int flags, Constructable target) {
+ super(scope, name, length, null);
+ this.targetConstructor = target;
+ this.flags = flags;
+ }
+
+ @Override
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
+ if ((flags & CONSTRUCTOR_FUNCTION) == 0) {
+ throw ScriptRuntime.typeErrorById("msg.constructor.no.function", getFunctionName());
+ }
+ return targetConstructor.construct(cx, scope, args);
+ }
+
+ @Override
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
+ if ((flags & CONSTRUCTOR_NEW) == 0) {
+ throw ScriptRuntime.typeErrorById("msg.no.new", getFunctionName());
+ }
+ Scriptable obj = targetConstructor.construct(cx, scope, args);
+ obj.setPrototype(getClassPrototype());
+ obj.setParentScope(scope);
+ return obj;
+ }
+
+ /**
+ * Define a function property on the prototype of the constructor using a LambdaFunction under
+ * the covers.
+ */
+ public void definePrototypeMethod(Scriptable scope, String name, int length, Callable target) {
+ LambdaFunction f = new LambdaFunction(scope, name, length, target);
+ ScriptableObject proto = getPrototypeScriptable();
+ proto.defineProperty(name, f, 0);
+ }
+
+ /** Define a property that may be of any type on the prototype of this constructor. */
+ public void definePrototypeProperty(String name, Object value, int attributes) {
+ ScriptableObject proto = getPrototypeScriptable();
+ proto.defineProperty(name, value, 0);
+ }
+
+ public void definePrototypeProperty(Symbol key, Object value, int attributes) {
+ ScriptableObject proto = getPrototypeScriptable();
+ proto.defineProperty(key, value, 0);
+ }
+
+ /**
+ * Define a function property directly on the constructor that is implemented under the covers
+ * by a LambdaFunction.
+ */
+ public void defineConstructorMethod(
+ Scriptable scope, String name, int length, Callable target) {
+ LambdaFunction f = new LambdaFunction(scope, name, length, target);
+ defineProperty(name, f, DONTENUM);
+ }
+
+ /**
+ * A convenience method to convert JavaScript's "this" object into a target class and throw a
+ * TypeError if it does not match. This is useful for implementing lambda functions, as "this"
+ * in JavaScript doesn't necessarily map to an instance of the class.
+ */
+ @SuppressWarnings("unchecked")
+ public static
+ *
+ *
* foo = new Foo();
*
- * @param instance The value that appeared on the LHS of the instanceof
- * operator
- * @return true if the "prototype" property of "this" appears in
- * value's prototype chain
- *
+ * @param instance The value that appeared on the LHS of the instanceof operator
+ * @return true if the "prototype" property of "this" appears in value's prototype chain
*/
@Override
- public boolean hasInstance(Scriptable instance)
- {
+ public boolean hasInstance(Scriptable instance) {
Object protoProp = ScriptableObject.getProperty(this, "prototype");
if (protoProp instanceof Scriptable) {
- return ScriptRuntime.jsDelegatesTo(instance, (Scriptable)protoProp);
+ return ScriptRuntime.jsDelegatesTo(instance, (Scriptable) protoProp);
}
- throw ScriptRuntime.typeErrorById("msg.instanceof.bad.prototype",
- getFunctionName());
+ throw ScriptRuntime.typeErrorById("msg.instanceof.bad.prototype", getFunctionName());
}
-// #string_id_map#
-
- private static final int
- Id_length = 1,
- Id_arity = 2,
- Id_name = 3,
- Id_prototype = 4,
- Id_arguments = 5,
+ // #string_id_map#
- MAX_INSTANCE_ID = 5;
+ private static final int Id_length = 1,
+ Id_arity = 2,
+ Id_name = 3,
+ Id_prototype = 4,
+ Id_arguments = 5,
+ MAX_INSTANCE_ID = 5;
@Override
- protected int getMaxInstanceId()
- {
+ protected int getMaxInstanceId() {
return MAX_INSTANCE_ID;
}
@Override
- protected int findInstanceIdInfo(String s)
- {
+ protected int findInstanceIdInfo(String s) {
int id;
-// #generated# Last update: 2021-03-21 09:52:05 MEZ
+ // #generated# Last update: 2021-03-21 09:52:05 MEZ
switch (s) {
- case "length":
- id = Id_length;
- break;
- case "arity":
- id = Id_arity;
- break;
- case "name":
- id = Id_name;
- break;
- case "prototype":
- id = Id_prototype;
- break;
- case "arguments":
- id = Id_arguments;
- break;
- default:
- id = 0;
- break;
+ case "length":
+ id = Id_length;
+ break;
+ case "arity":
+ id = Id_arity;
+ break;
+ case "name":
+ id = Id_name;
+ break;
+ case "prototype":
+ id = Id_prototype;
+ break;
+ case "arguments":
+ id = Id_arguments;
+ break;
+ default:
+ id = 0;
+ break;
}
-// #/generated#
-// #/string_id_map#
+ // #/generated#
+ // #/string_id_map#
if (id == 0) return super.findInstanceIdInfo(s);
int attr;
switch (id) {
- case Id_length:
- case Id_arity:
- case Id_name:
- attr = DONTENUM | READONLY | PERMANENT;
- break;
- case Id_prototype:
- // some functions such as built-ins don't have a prototype property
- if (!hasPrototypeProperty()) {
- return 0;
- }
- attr = prototypePropertyAttributes;
- break;
- case Id_arguments:
- attr = argumentsAttributes;
- break;
- default: throw new IllegalStateException();
+ case Id_length:
+ case Id_arity:
+ case Id_name:
+ attr = DONTENUM | READONLY | PERMANENT;
+ break;
+ case Id_prototype:
+ // some functions such as built-ins don't have a prototype property
+ if (!hasPrototypeProperty()) {
+ return 0;
+ }
+ attr = prototypePropertyAttributes;
+ break;
+ case Id_arguments:
+ attr = argumentsAttributes;
+ break;
+ default:
+ throw new IllegalStateException();
}
return instanceIdInfo(attr, id);
}
@Override
- protected String getInstanceIdName(int id)
- {
+ protected String getInstanceIdName(int id) {
switch (id) {
- case Id_length: return "length";
- case Id_arity: return "arity";
- case Id_name: return "name";
- case Id_prototype: return "prototype";
- case Id_arguments: return "arguments";
+ case Id_length:
+ return "length";
+ case Id_arity:
+ return "arity";
+ case Id_name:
+ return "name";
+ case Id_prototype:
+ return "prototype";
+ case Id_arguments:
+ return "arguments";
}
return super.getInstanceIdName(id);
}
@Override
- protected Object getInstanceIdValue(int id)
- {
+ protected Object getInstanceIdValue(int id) {
switch (id) {
- case Id_length: return ScriptRuntime.wrapInt(getLength());
- case Id_arity: return ScriptRuntime.wrapInt(getArity());
- case Id_name: return getFunctionName();
- case Id_prototype: return getPrototypeProperty();
- case Id_arguments: return getArguments();
+ case Id_length:
+ return ScriptRuntime.wrapInt(getLength());
+ case Id_arity:
+ return ScriptRuntime.wrapInt(getArity());
+ case Id_name:
+ return getFunctionName();
+ case Id_prototype:
+ return getPrototypeProperty();
+ case Id_arguments:
+ return getArguments();
}
return super.getInstanceIdValue(id);
}
@Override
- protected void setInstanceIdValue(int id, Object value)
- {
+ protected void setInstanceIdValue(int id, Object value) {
switch (id) {
case Id_prototype:
if ((prototypePropertyAttributes & READONLY) == 0) {
- prototypeProperty = (value != null)
- ? value : UniqueTag.NULL_VALUE;
+ prototypeProperty = (value != null) ? value : UniqueTag.NULL_VALUE;
}
return;
case Id_arguments:
@@ -222,8 +219,7 @@ protected void setInstanceIdValue(int id, Object value)
}
@Override
- protected void setInstanceIdAttributes(int id, int attr)
- {
+ protected void setInstanceIdAttributes(int id, int attr) {
switch (id) {
case Id_prototype:
prototypePropertyAttributes = attr;
@@ -236,8 +232,7 @@ protected void setInstanceIdAttributes(int id, int attr)
}
@Override
- protected void fillConstructorProperties(IdFunctionObject ctor)
- {
+ protected void fillConstructorProperties(IdFunctionObject ctor) {
// Fix up bootstrapping problem: getPrototype of the IdFunctionObject
// can not return Function.prototype because Function object is not
// yet defined.
@@ -246,18 +241,36 @@ protected void fillConstructorProperties(IdFunctionObject ctor)
}
@Override
- protected void initPrototypeId(int id)
- {
+ protected void initPrototypeId(int id) {
String s;
int arity;
switch (id) {
- case Id_constructor: arity=1; s="constructor"; break;
- case Id_toString: arity=0; s="toString"; break;
- case Id_toSource: arity=1; s="toSource"; break;
- case Id_apply: arity=2; s="apply"; break;
- case Id_call: arity=1; s="call"; break;
- case Id_bind: arity=1; s="bind"; break;
- default: throw new IllegalArgumentException(String.valueOf(id));
+ case Id_constructor:
+ arity = 1;
+ s = "constructor";
+ break;
+ case Id_toString:
+ arity = 0;
+ s = "toString";
+ break;
+ case Id_toSource:
+ arity = 1;
+ s = "toSource";
+ break;
+ case Id_apply:
+ arity = 2;
+ s = "apply";
+ break;
+ case Id_call:
+ arity = 1;
+ s = "call";
+ break;
+ case Id_bind:
+ arity = 1;
+ s = "bind";
+ break;
+ default:
+ throw new IllegalArgumentException(String.valueOf(id));
}
initPrototypeMethod(FUNCTION_TAG, id, s, arity);
}
@@ -267,8 +280,8 @@ static boolean isApply(IdFunctionObject f) {
}
static boolean isApplyOrCall(IdFunctionObject f) {
- if(f.hasTag(FUNCTION_TAG)) {
- switch(f.methodId()) {
+ if (f.hasTag(FUNCTION_TAG)) {
+ switch (f.methodId()) {
case Id_apply:
case Id_call:
return true;
@@ -278,79 +291,74 @@ static boolean isApplyOrCall(IdFunctionObject f) {
}
@Override
- public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
- Scriptable thisObj, Object[] args)
- {
+ public Object execIdCall(
+ IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!f.hasTag(FUNCTION_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int id = f.methodId();
switch (id) {
- case Id_constructor:
- return jsConstructor(cx, scope, args);
-
- case Id_toString: {
- BaseFunction realf = realFunction(thisObj, f);
- int indent = ScriptRuntime.toInt32(args, 0);
- return realf.decompile(indent, 0);
- }
-
- case Id_toSource: {
- BaseFunction realf = realFunction(thisObj, f);
- 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;
+ case Id_constructor:
+ return jsConstructor(cx, scope, args);
+
+ case Id_toString:
+ {
+ BaseFunction realf = realFunction(thisObj, f);
+ int indent = ScriptRuntime.toInt32(args, 0);
+ return realf.decompile(indent, 0);
}
- }
- return realf.decompile(indent, flags);
- }
- case Id_apply:
- case Id_call:
- return ScriptRuntime.applyOrCall(id == Id_apply,
- cx, scope, thisObj, args);
+ case Id_toSource:
+ {
+ BaseFunction realf = realFunction(thisObj, f);
+ 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 realf.decompile(indent, flags);
+ }
- case Id_bind:
- if ( !(thisObj instanceof Callable) ) {
- throw ScriptRuntime.notFunctionError(thisObj);
- }
- Callable targetFunction = (Callable) thisObj;
- int argc = args.length;
- final Scriptable boundThis;
- final Object[] boundArgs;
- if (argc > 0) {
- boundThis = ScriptRuntime.toObjectOrNull(cx, args[0], scope);
- boundArgs = new Object[argc-1];
- System.arraycopy(args, 1, boundArgs, 0, argc-1);
- } else {
- boundThis = null;
- boundArgs = ScriptRuntime.emptyArgs;
- }
- return new BoundFunction(cx, scope, targetFunction, boundThis, boundArgs);
+ case Id_apply:
+ case Id_call:
+ return ScriptRuntime.applyOrCall(id == Id_apply, cx, scope, thisObj, args);
+
+ case Id_bind:
+ if (!(thisObj instanceof Callable)) {
+ throw ScriptRuntime.notFunctionError(thisObj);
+ }
+ Callable targetFunction = (Callable) thisObj;
+ int argc = args.length;
+ final Scriptable boundThis;
+ final Object[] boundArgs;
+ if (argc > 0) {
+ boundThis = ScriptRuntime.toObjectOrNull(cx, args[0], scope);
+ boundArgs = new Object[argc - 1];
+ System.arraycopy(args, 1, boundArgs, 0, argc - 1);
+ } else {
+ boundThis = null;
+ boundArgs = ScriptRuntime.emptyArgs;
+ }
+ return new BoundFunction(cx, scope, targetFunction, boundThis, boundArgs);
}
throw new IllegalArgumentException(String.valueOf(id));
}
- private static BaseFunction realFunction(Scriptable thisObj, IdFunctionObject f)
- {
+ private static BaseFunction realFunction(Scriptable thisObj, IdFunctionObject f) {
Object x = thisObj.getDefaultValue(ScriptRuntime.FunctionClass);
if (x instanceof Delegator) {
- x = ((Delegator)x).getDelegee();
+ x = ((Delegator) x).getDelegee();
}
return ensureType(x, BaseFunction.class, f);
}
- /**
- * Make value as DontEnum, DontDelete, ReadOnly
- * prototype property of this Function object
- */
- public void setImmunePrototypeProperty(Object value)
- {
+ /** Make value as DontEnum, DontDelete, ReadOnly prototype property of this Function object */
+ public void setImmunePrototypeProperty(Object value) {
if ((prototypePropertyAttributes & READONLY) != 0) {
throw new IllegalStateException();
}
@@ -358,8 +366,7 @@ public void setImmunePrototypeProperty(Object value)
prototypePropertyAttributes = DONTENUM | PERMANENT | READONLY;
}
- protected Scriptable getClassPrototype()
- {
+ protected Scriptable getClassPrototype() {
Object protoVal = getPrototypeProperty();
if (protoVal instanceof Scriptable) {
return (Scriptable) protoVal;
@@ -367,24 +374,19 @@ protected Scriptable getClassPrototype()
return ScriptableObject.getObjectPrototype(this);
}
- /**
- * Should be overridden.
- */
+ /** Should be overridden. */
@Override
- public Object call(Context cx, Scriptable scope, Scriptable thisObj,
- Object[] args)
- {
+ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
return Undefined.instance;
}
@Override
- public Scriptable construct(Context cx, Scriptable scope, Object[] args)
- {
+ public Scriptable construct(Context cx, Scriptable scope, Object[] args) {
Scriptable result = createObject(cx, scope);
if (result != null) {
Object val = call(cx, scope, result, args);
if (val instanceof Scriptable) {
- result = (Scriptable)val;
+ result = (Scriptable) val;
}
} else {
Object val = call(cx, scope, null, args);
@@ -392,10 +394,12 @@ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
// It is program error not to return Scriptable from
// the call method if createObject returns null.
throw new IllegalStateException(
- "Bad implementaion of call as constructor, name="
- +getFunctionName()+" in "+getClass().getName());
+ "Bad implementaion of call as constructor, name="
+ + getFunctionName()
+ + " in "
+ + getClass().getName());
}
- result = (Scriptable)val;
+ result = (Scriptable) val;
if (result.getPrototype() == null) {
Scriptable proto = getClassPrototype();
if (result != proto) {
@@ -413,17 +417,13 @@ public Scriptable construct(Context cx, Scriptable scope, Object[] args)
}
/**
- * Creates new script object.
- * The default implementation of {@link #construct} uses the method to
- * to get the value for
* foo instanceof Foo; // true
* thisObj
argument when invoking
- * {@link #call}.
- * The methos is allowed to return null
to indicate that
- * {@link #call} will create a new object itself. In this case
- * {@link #construct} will set scope and prototype on the result
+ * Creates new script object. The default implementation of {@link #construct} uses the method
+ * to to get the value for thisObj
argument when invoking {@link #call}. The methos
+ * is allowed to return null
to indicate that {@link #call} will create a new
+ * object itself. In this case {@link #construct} will set scope and prototype on the result
* {@link #call} unless they are already set.
*/
- public Scriptable createObject(Context cx, Scriptable scope)
- {
+ public Scriptable createObject(Context cx, Scriptable scope) {
Scriptable newInstance = new NativeObject();
newInstance.setPrototype(getClassPrototype());
newInstance.setParentScope(getParentScope());
@@ -431,15 +431,12 @@ public Scriptable createObject(Context cx, Scriptable scope)
}
/**
- * Decompile the source information associated with this js
- * function/script back into a string.
+ * Decompile the source information associated with this js function/script back into a string.
*
* @param indent How much to indent the decompiled result.
- *
* @param flags Flags specifying format of decompilation output.
*/
- String decompile(int indent, int flags)
- {
+ String decompile(int indent, int flags) {
StringBuilder sb = new StringBuilder();
boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG));
if (!justbody) {
@@ -456,9 +453,13 @@ String decompile(int indent, int flags)
return sb.toString();
}
- public int getArity() { return 0; }
+ public int getArity() {
+ return 0;
+ }
- public int getLength() { return 0; }
+ public int getLength() {
+ return 0;
+ }
public String getFunctionName() {
return "";
@@ -484,7 +485,11 @@ protected Object getPrototypeProperty() {
return result;
}
- private synchronized Object setupDefaultPrototype() {
+ protected void setPrototypeProperty(Object prototype) {
+ this.prototypeProperty = prototype;
+ }
+
+ protected synchronized Object setupDefaultPrototype() {
if (prototypeProperty != null) {
return prototypeProperty;
}
@@ -503,29 +508,25 @@ private synchronized Object setupDefaultPrototype() {
return obj;
}
- private Object getArguments()
- {
- //
+ * new
operator. This method is expected to create a new object and return it.
+ *
+ * @param cx the current Context for this thread
+ * @param scope an enclosing scope of the caller except when the function is called from a
+ * closure.
+ * @param args the array of arguments
+ * @return the allocated object
+ */
+ Scriptable construct(Context cx, Scriptable scope, Object[] args);
+}
diff --git a/src/org/mozilla/javascript/Function.java b/src/org/mozilla/javascript/Function.java
index 89c6d4b473..4d70231883 100644
--- a/src/org/mozilla/javascript/Function.java
+++ b/src/org/mozilla/javascript/Function.java
@@ -9,45 +9,40 @@
package org.mozilla.javascript;
/**
- * This is interface that all functions in JavaScript must implement.
- * The interface provides for calling functions and constructors.
+ * This is interface that all functions in JavaScript must implement. The interface provides for
+ * calling functions and constructors.
*
* @see org.mozilla.javascript.Scriptable
* @author Norris Boyd
*/
-
-public interface Function extends Scriptable, Callable
-{
+public interface Function extends Scriptable, Callable, Constructable {
/**
* Call the function.
*
- * Note that the array of arguments is not guaranteed to have
- * length greater than 0.
+ * this
object
* @param args the array of arguments
* @return the result of the call
*/
@Override
- public Object call(Context cx, Scriptable scope, Scriptable thisObj,
- Object[] args);
+ Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args);
/**
* Call the function as a constructor.
*
- * This method is invoked by the runtime in order to satisfy a use
- * of the JavaScript new
operator. This method is
- * expected to create a new object and return it.
+ *
+ * new
operator. This method is expected to create a new object and return it.
*
* @param cx the current Context for this thread
- * @param scope an enclosing scope of the caller except
- * when the function is called from a closure.
+ * @param scope an enclosing scope of the caller except when the function is called from a
+ * closure.
* @param args the array of arguments
* @return the allocated object
*/
- public Scriptable construct(Context cx, Scriptable scope, Object[] args);
+ @Override
+ Scriptable construct(Context cx, Scriptable scope, Object[] args);
}
diff --git a/src/org/mozilla/javascript/LambdaConstructor.java b/src/org/mozilla/javascript/LambdaConstructor.java
new file mode 100644
index 0000000000..914cb1094b
--- /dev/null
+++ b/src/org/mozilla/javascript/LambdaConstructor.java
@@ -0,0 +1,134 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.javascript;
+
+/**
+ * This class implements a JavaScript function that may be used as a constructor by delegating to an
+ * interface that can be easily implemented as a lambda. The LambdaFunction class may be used to add
+ * functions to the prototype that are also implemented as lambdas.
+ *
+ *