Browse Source

Make BlockClosure work with some constructors

Some JS functions expect to be used as constructors; these functions
usually have their prototype property set to something other than
Object, and they expect that you'll call them like this:

    var myVar = new constructorFunction(someArguments...);

I don't know of a way in Smalltalk to call these kinds of functions, so
I must step down to JS to do it. And for a while there's been a few
methods on BlockClosure to do it for you, but they only accept at most 3
arguments.

So, what if you want to call a constructor function with 4 or more
arguments? Before you'd probably just add a new message to BlockClosure
that accepted more arguments. But now you can use

    BlockClosure#newWithValues:

This message accepts an array (well, really it should accept any
collection that can be turned into an Array) and will return an object
after applying the contructor function to it. Here's how you'd use it:

    | something |
    something := constructorFunction newWithValues: { 1. 2. 3. 4 }

Here's how it works:

1) I create a new function object.
2) I set it's prototype to be the prototype of the constructor function
   you're calling.
3) I create a new instance of the object created in step 1.
4) I `apply` the constructor function to the object, and pass in the
   arguments you sent to newWithValues:

The key to this is step 3, where the interpreter assigns whatever is in
the blank function object's prototype property to the newly created
object's internal [[prototype]] property. The internal property is not
accessible to us, and the interpreter only sets it from a call to "new",
so I as far as I can tell I have to do this.

I got inspiration for this from StackOverflow[1], after spending a lot
of time trying to figure out how the new keyword works.

[1]: http://stackoverflow.com/a/6069331
Kenneth Pullen 11 years ago
parent
commit
b0ea440d3e
6 changed files with 152 additions and 28 deletions
  1. 30 8
      js/Kernel-Methods.deploy.js
  2. 38 11
      js/Kernel-Methods.js
  3. 21 0
      js/Kernel-Tests.deploy.js
  4. 26 0
      js/Kernel-Tests.js
  5. 22 9
      st/Kernel-Methods.st
  6. 15 0
      st/Kernel-Tests.st

+ 30 - 8
js/Kernel-Methods.deploy.js

@@ -90,9 +90,11 @@ selector: "newValue:",
 fn: function (anObject){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-return new self(anObject);
-return self}, function($ctx1) {$ctx1.fill(self,"newValue:",{anObject:anObject},smalltalk.BlockClosure)})},
-messageSends: []}),
+var $1;
+$1=_st(self)._newWithValues_([anObject]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"newValue:",{anObject:anObject},smalltalk.BlockClosure)})},
+messageSends: ["newWithValues:"]}),
 smalltalk.BlockClosure);
 
 smalltalk.addMethod(
@@ -101,9 +103,11 @@ selector: "newValue:value:",
 fn: function (anObject,anObject2){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-return new self(anObject, anObject2);
-return self}, function($ctx1) {$ctx1.fill(self,"newValue:value:",{anObject:anObject,anObject2:anObject2},smalltalk.BlockClosure)})},
-messageSends: []}),
+var $1;
+$1=_st(self)._newWithValues_([anObject,anObject2]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"newValue:value:",{anObject:anObject,anObject2:anObject2},smalltalk.BlockClosure)})},
+messageSends: ["newWithValues:"]}),
 smalltalk.BlockClosure);
 
 smalltalk.addMethod(
@@ -112,8 +116,26 @@ selector: "newValue:value:value:",
 fn: function (anObject,anObject2,anObject3){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-return new self(anObject, anObject2,anObject3);
-return self}, function($ctx1) {$ctx1.fill(self,"newValue:value:value:",{anObject:anObject,anObject2:anObject2,anObject3:anObject3},smalltalk.BlockClosure)})},
+var $1;
+$1=_st(self)._newWithValues_([anObject,anObject2,anObject3]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"newValue:value:value:",{anObject:anObject,anObject2:anObject2,anObject3:anObject3},smalltalk.BlockClosure)})},
+messageSends: ["newWithValues:"]}),
+smalltalk.BlockClosure);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "newWithValues:",
+fn: function (aCollection){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+
+var duck = function() {};
+duck.prototype = self.prototype;
+var duckInstance = new duck;
+var result = self.apply(duckInstance, aCollection);
+return typeof result === "object" ? result : duckInstance;;
+return self}, function($ctx1) {$ctx1.fill(self,"newWithValues:",{aCollection:aCollection},smalltalk.BlockClosure)})},
 messageSends: []}),
 smalltalk.BlockClosure);
 

+ 38 - 11
js/Kernel-Methods.js

@@ -127,11 +127,13 @@ category: 'evaluating',
 fn: function (anObject){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-return new self(anObject);
-return self}, function($ctx1) {$ctx1.fill(self,"newValue:",{anObject:anObject},smalltalk.BlockClosure)})},
+var $1;
+$1=_st(self)._newWithValues_([anObject]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"newValue:",{anObject:anObject},smalltalk.BlockClosure)})},
 args: ["anObject"],
-source: "newValue: anObject\x0a\x09\x22Use the receiver as a JS constructor.\x0a\x09*Do not* use this method to instanciate Smalltalk objects!\x22\x0a\x09<return new self(anObject)>",
-messageSends: [],
+source: "newValue: anObject\x0a^ self newWithValues: { anObject }",
+messageSends: ["newWithValues:"],
 referencedClasses: []
 }),
 smalltalk.BlockClosure);
@@ -143,11 +145,13 @@ category: 'evaluating',
 fn: function (anObject,anObject2){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-return new self(anObject, anObject2);
-return self}, function($ctx1) {$ctx1.fill(self,"newValue:value:",{anObject:anObject,anObject2:anObject2},smalltalk.BlockClosure)})},
+var $1;
+$1=_st(self)._newWithValues_([anObject,anObject2]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"newValue:value:",{anObject:anObject,anObject2:anObject2},smalltalk.BlockClosure)})},
 args: ["anObject", "anObject2"],
-source: "newValue: anObject value: anObject2\x0a\x09\x22Use the receiver as a JS constructor.\x0a\x09*Do not* use this method to instanciate Smalltalk objects!\x22\x0a\x09<return new self(anObject, anObject2)>",
-messageSends: [],
+source: "newValue: anObject value: anObject2\x0a^ self newWithValues: { anObject. anObject2 }.",
+messageSends: ["newWithValues:"],
 referencedClasses: []
 }),
 smalltalk.BlockClosure);
@@ -159,10 +163,33 @@ category: 'evaluating',
 fn: function (anObject,anObject2,anObject3){
 var self=this;
 return smalltalk.withContext(function($ctx1) { 
-return new self(anObject, anObject2,anObject3);
-return self}, function($ctx1) {$ctx1.fill(self,"newValue:value:value:",{anObject:anObject,anObject2:anObject2,anObject3:anObject3},smalltalk.BlockClosure)})},
+var $1;
+$1=_st(self)._newWithValues_([anObject,anObject2,anObject3]);
+return $1;
+}, function($ctx1) {$ctx1.fill(self,"newValue:value:value:",{anObject:anObject,anObject2:anObject2,anObject3:anObject3},smalltalk.BlockClosure)})},
 args: ["anObject", "anObject2", "anObject3"],
-source: "newValue: anObject value: anObject2 value: anObject3\x0a\x09\x22Use the receiver as a JS constructor.\x0a\x09*Do not* use this method to instanciate Smalltalk objects!\x22\x0a\x09<return new self(anObject, anObject2,anObject3)>",
+source: "newValue: anObject value: anObject2 value: anObject3\x0a^ self newWithValues: { anObject. anObject2. anObject3 }.",
+messageSends: ["newWithValues:"],
+referencedClasses: []
+}),
+smalltalk.BlockClosure);
+
+smalltalk.addMethod(
+smalltalk.method({
+selector: "newWithValues:",
+category: 'evaluating',
+fn: function (aCollection){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+
+var duck = function() {};
+duck.prototype = self.prototype;
+var duckInstance = new duck;
+var result = self.apply(duckInstance, aCollection);
+return typeof result === "object" ? result : duckInstance;;
+return self}, function($ctx1) {$ctx1.fill(self,"newWithValues:",{aCollection:aCollection},smalltalk.BlockClosure)})},
+args: ["aCollection"],
+source: "newWithValues: aCollection\x0a\x22I'll apply a variable number of arguments to a function, and return the object created.\x0a\x0aNote: This is apparently just what Coffeescript does; I know because I was 'inspired' by http://stackoverflow.com/a/6069331.\x0a\x0aHere's a general breakdown of what's going on:\x0a1) Create a new, blank function object.\x0a2) Set it's prototype to 'my' prototype. Remember, we are in a BlockClosure, and presumably this BlockClosure is wrapping a JS function, and also presumably this function is used as a constructor.\x0a3) Instantiate a new version of the function object we just created. This forces the interpreter to set the internal [[prototype]] property to what was set on the function before. This has to be done, as we have no access to the [[prototype]] property externally.\x0a4) Apply 'myself' to the object I just instantiated.\x22\x0a<\x0avar duck = function() {};\x0aduck.prototype = self.prototype;\x0avar duckInstance = new duck;\x0avar result = self.apply(duckInstance, aCollection);\x0areturn typeof result === \x22object\x22 ? result : duckInstance;\x0a>",
 messageSends: [],
 referencedClasses: []
 }),

+ 21 - 0
js/Kernel-Tests.deploy.js

@@ -139,6 +139,27 @@ return self}, function($ctx1) {$ctx1.fill(self,"testExceptionSemantics",{},small
 messageSends: ["timeout:", "valueWithTimeout:", "async:", "on:do:", "finished", "assert:", "signal", "deny:"]}),
 smalltalk.BlockClosureTest);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "testNewWithValues",
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+
+function theTestPrototype() {
+	this.name = "theTestPrototype";
+}
+function theTestConstructor(arg1, arg2, arg3) {}
+theTestConstructor.prototype = new theTestPrototype;
+
+var theWrappedConstructor = _st(theTestConstructor);
+var theResult = theWrappedConstructor._newWithValues_([1, 2, 3]);
+
+self._assert_equals_(Object.getPrototypeOf(theResult).name, 'theTestPrototype');;
+return self}, function($ctx1) {$ctx1.fill(self,"testNewWithValues",{},smalltalk.BlockClosureTest)})},
+messageSends: []}),
+smalltalk.BlockClosureTest);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "testNumArgs",

+ 26 - 0
js/Kernel-Tests.js

@@ -174,6 +174,32 @@ referencedClasses: ["Error"]
 }),
 smalltalk.BlockClosureTest);
 
+smalltalk.addMethod(
+smalltalk.method({
+selector: "testNewWithValues",
+category: 'tests',
+fn: function (){
+var self=this;
+return smalltalk.withContext(function($ctx1) { 
+
+function theTestPrototype() {
+	this.name = "theTestPrototype";
+}
+function theTestConstructor(arg1, arg2, arg3) {}
+theTestConstructor.prototype = new theTestPrototype;
+
+var theWrappedConstructor = _st(theTestConstructor);
+var theResult = theWrappedConstructor._newWithValues_([1, 2, 3]);
+
+self._assert_equals_(Object.getPrototypeOf(theResult).name, 'theTestPrototype');;
+return self}, function($ctx1) {$ctx1.fill(self,"testNewWithValues",{},smalltalk.BlockClosureTest)})},
+args: [],
+source: "testNewWithValues\x0a<\x0afunction theTestPrototype() {\x0a\x09this.name = \x22theTestPrototype\x22;\x0a}\x0afunction theTestConstructor(arg1, arg2, arg3) {}\x0atheTestConstructor.prototype = new theTestPrototype;\x0a\x0avar theWrappedConstructor = _st(theTestConstructor);\x0avar theResult = theWrappedConstructor._newWithValues_([1, 2, 3]);\x0a\x0aself._assert_equals_(Object.getPrototypeOf(theResult).name, 'theTestPrototype');\x0a>",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.BlockClosureTest);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "testNumArgs",

+ 22 - 9
st/Kernel-Methods.st

@@ -113,21 +113,34 @@ new
 !
 
 newValue: anObject
-	"Use the receiver as a JS constructor.
-	*Do not* use this method to instanciate Smalltalk objects!!"
-	<return new self(anObject)>
+^ self newWithValues: { anObject }
 !
 
 newValue: anObject value: anObject2
-	"Use the receiver as a JS constructor.
-	*Do not* use this method to instanciate Smalltalk objects!!"
-	<return new self(anObject, anObject2)>
+^ self newWithValues: { anObject. anObject2 }.
 !
 
 newValue: anObject value: anObject2 value: anObject3
-	"Use the receiver as a JS constructor.
-	*Do not* use this method to instanciate Smalltalk objects!!"
-	<return new self(anObject, anObject2,anObject3)>
+^ self newWithValues: { anObject. anObject2. anObject3 }.
+!
+
+newWithValues: aCollection
+"I'll apply a variable number of arguments to a function, and return the object created.
+
+Note: This is apparently just what Coffeescript does; I know because I was 'inspired' by http://stackoverflow.com/a/6069331.
+
+Here's a general breakdown of what's going on:
+1) Create a new, blank function object.
+2) Set it's prototype to 'my' prototype. Remember, we are in a BlockClosure, and presumably this BlockClosure is wrapping a JS function, and also presumably this function is used as a constructor.
+3) Instantiate a new version of the function object we just created. This forces the interpreter to set the internal [[prototype]] property to what was set on the function before. This has to be done, as we have no access to the [[prototype]] property externally.
+4) Apply 'myself' to the object I just instantiated."
+<
+var duck = function() {};
+duck.prototype = self.prototype;
+var duckInstance = new duck;
+var result = self.apply(duckInstance, aCollection);
+return typeof result === "object" ? result : duckInstance;
+>
 !
 
 timeToRun

+ 15 - 0
st/Kernel-Tests.st

@@ -49,6 +49,21 @@ testExceptionSemantics
 	]) valueWithTimeout: 0
 !
 
+testNewWithValues
+<
+function theTestPrototype() {
+	this.name = "theTestPrototype";
+}
+function theTestConstructor(arg1, arg2, arg3) {}
+theTestConstructor.prototype = new theTestPrototype;
+
+var theWrappedConstructor = _st(theTestConstructor);
+var theResult = theWrappedConstructor._newWithValues_([1, 2, 3]);
+
+self._assert_equals_(Object.getPrototypeOf(theResult).name, 'theTestPrototype');
+>
+!
+
 testNumArgs
 	self assert: [] numArgs equals: 0.
 	self assert: [:a :b | ] numArgs equals: 2