Browse Source

Merge pull request #489 from kpullen/js-function-constructors

Js function constructors
Nicolas Petton 11 years ago
parent
commit
8c0a95c1f3
6 changed files with 154 additions and 28 deletions
  1. 30 8
      js/Kernel-Methods.deploy.js
  2. 38 11
      js/Kernel-Methods.js
  3. 22 0
      js/Kernel-Tests.deploy.js
  4. 27 0
      js/Kernel-Tests.js
  5. 21 9
      st/Kernel-Methods.st
  6. 16 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 blankObject = function() {};
+blankObject.prototype = self.prototype;
+var newObject = new blankObject;
+var result = self.apply(newObject, aCollection);
+return typeof result === "object" ? result : newObject;;
+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 blankObject = function() {};
+blankObject.prototype = self.prototype;
+var newObject = new blankObject;
+var result = self.apply(newObject, aCollection);
+return typeof result === "object" ? result : newObject;;
+return self}, function($ctx1) {$ctx1.fill(self,"newWithValues:",{aCollection:aCollection},smalltalk.BlockClosure)})},
+args: ["aCollection"],
+source: "newWithValues: aCollection\x0a\x22Answer an object that's been created in JS via `new` and had `self` applied to it.\x0aThis algorithm 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 `self`'s prototype. Remember, we're 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 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 `self` to the object I just instantiated.\x22\x0a<\x0avar blankObject = function() {};\x0ablankObject.prototype = self.prototype;\x0avar newObject = new blankObject;\x0avar result = self.apply(newObject, aCollection);\x0areturn typeof result === \x22object\x22 ? result : newObject;\x0a>",
 messageSends: [],
 referencedClasses: []
 }),

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

@@ -139,6 +139,28 @@ 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');
+
+"newWithValues: cannot help if the argument list is wrong, and should warn that a mistake was made."
+function constructionShouldFail() {var anotherResult = theWrappedConstructor._newWithValues_('This is so wrong');}
+self._should_raise_(_st(constructionShouldFail), smalltalk.Error);;
+return self}, function($ctx1) {$ctx1.fill(self,"testNewWithValues",{},smalltalk.BlockClosureTest)})},
+messageSends: []}),
+smalltalk.BlockClosureTest);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "testNumArgs",

+ 27 - 0
js/Kernel-Tests.js

@@ -174,6 +174,33 @@ 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');
+
+"newWithValues: cannot help if the argument list is wrong, and should warn that a mistake was made."
+function constructionShouldFail() {var anotherResult = theWrappedConstructor._newWithValues_('This is so wrong');}
+self._should_raise_(_st(constructionShouldFail), smalltalk.Error);;
+return self}, function($ctx1) {$ctx1.fill(self,"testNewWithValues",{},smalltalk.BlockClosureTest)})},
+args: [],
+source: "testNewWithValues\x0a<\x0afunction theTestPrototype() {this.name = \x22theTestPrototype\x22;}\x0afunction theTestConstructor(arg1, arg2, arg3) {}\x0atheTestConstructor.prototype = new theTestPrototype;\x0a\x0avar theWrappedConstructor = _st(theTestConstructor);\x0avar theResult = theWrappedConstructor._newWithValues_([1, 2, 3]);\x0aself._assert_equals_(Object.getPrototypeOf(theResult).name, 'theTestPrototype');\x0a\x0a\x22newWithValues: cannot help if the argument list is wrong, and should warn that a mistake was made.\x22\x0afunction constructionShouldFail() {var anotherResult = theWrappedConstructor._newWithValues_('This is so wrong');}\x0aself._should_raise_(_st(constructionShouldFail), smalltalk.Error);\x0a>",
+messageSends: [],
+referencedClasses: []
+}),
+smalltalk.BlockClosureTest);
+
 smalltalk.addMethod(
 smalltalk.method({
 selector: "testNumArgs",

+ 21 - 9
st/Kernel-Methods.st

@@ -113,21 +113,33 @@ 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
+"Answer an object that's been created in JS via `new` and had `self` applied to it.
+This algorithm 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 `self`'s prototype. Remember, we're 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 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 `self` to the object I just instantiated."
+<
+var blankObject = function() {};
+blankObject.prototype = self.prototype;
+var newObject = new blankObject;
+var result = self.apply(newObject, aCollection);
+return typeof result === "object" ? result : newObject;
+>
 !
 
 timeToRun

+ 16 - 0
st/Kernel-Tests.st

@@ -49,6 +49,22 @@ 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');
+
+"newWithValues: cannot help if the argument list is wrong, and should warn that a mistake was made."
+function constructionShouldFail() {var anotherResult = theWrappedConstructor._newWithValues_('This is so wrong');}
+self._should_raise_(_st(constructionShouldFail), smalltalk.Error);
+>
+!
+
 testNumArgs
 	self assert: [] numArgs equals: 0.
 	self assert: [:a :b | ] numArgs equals: 2