Skip to content

Commit 1d3a084

Browse files
committed
Bug fix in getRoleMember() function
1 parent 3ad09cf commit 1d3a084

File tree

6 files changed

+391
-0
lines changed

6 files changed

+391
-0
lines changed

dci/dci-amd.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
define(["require", "exports"], function(require, exports) {
2+
var isNodeJs = (typeof window == 'undefined' && typeof global != 'undefined');
3+
var globalNamespace = isNodeJs ? global : window;
4+
5+
//TODO Support DestinationAccount['de' + 'posit'](amount)
6+
//
7+
//DestinationAccount['de' + 'posit'](amount) could be rewritten to:
8+
//__context.__$DestinationAccount['de' + 'posit'].call(__context.DestinationAccount, amount)
9+
//
10+
//would be better to give a nice error message if role method not found:
11+
//DCI.getRoleMethod(__context, 'DestinationAccount', 'de' + 'posit').call(__context.DestinationAccount, amount)
12+
function getRoleMethod(context, roleName, methodName) {
13+
var roleMethod = context['__$' + roleName][methodName];
14+
if (!roleMethod) {
15+
throw new Error('Method "' + methodName + '" not found on role "' + roleName + '"');
16+
}
17+
return roleMethod;
18+
}
19+
exports.getRoleMethod = getRoleMethod;
20+
21+
//This function is for handling calls beginning with `this`; it calls a method on the current role player,
22+
//which could be either a role method or a method on the data object.
23+
//
24+
// TODO test data object methods that aren't overridden by the role
25+
//
26+
function callMethodOnSelf(context, player, roleName, methodName, args) {
27+
if (player != context[roleName]) {
28+
if (!player[methodName]) {
29+
var msg = 'Method "' + methodName + '" not found';
30+
if (player['constructor'].name) {
31+
msg += ' on object of type "' + player['constructor'].name + '".';
32+
}
33+
throw new Error(msg);
34+
}
35+
return player[methodName].apply(player, args);
36+
//Old version: commenting since it's inconsistent...this makes `this.someRoleMethod()` work
37+
//even inside a closure like [1,2,3].forEach(...),
38+
//but using `this` in other ways, like `this` as an argument, would not necessarily work.
39+
//Better to just use `self`.
40+
/*
41+
42+
//support functions within role methods for which `this` is unbound - but make sure we
43+
//don't wrongly assume `this` refers to the current role
44+
if (player != undefined && player != globalNamespace) {
45+
//not a role method; call normally
46+
if (!player[methodName]) {
47+
var msg = 'Method "' + methodName + '" not found';
48+
if (player['constructor'].name) {
49+
msg += ' on object of type "' + player['constructor'].name + '".';
50+
}
51+
throw new Error(msg);
52+
}
53+
return player[methodName].apply(player, args);
54+
}
55+
//if we're here, then `this` probably *should* refer to the current role and the only
56+
//reason it's undefined is most likely a nested function
57+
//(e.g. a callback function passed to forEach() to loop over an array)
58+
else {
59+
player = context[roleName];
60+
}
61+
*/
62+
}
63+
64+
var roleMethod = context['__$' + roleName][methodName];
65+
66+
if (typeof roleMethod == 'function') {
67+
return (args ? roleMethod.apply(player, args) : roleMethod.call(player));
68+
} else {
69+
if (!player[methodName]) {
70+
var msg = 'Method "' + methodName + '" not found';
71+
if (player['constructor'].name)
72+
msg += ' on object of type "' + player['constructor'].name + '" nor on any of the roles it is currently playing.';
73+
throw new Error(msg);
74+
}
75+
return player[methodName].apply(player, args);
76+
}
77+
}
78+
exports.callMethodOnSelf = callMethodOnSelf;
79+
});

dci/dci.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
var isNodeJs = (typeof window == 'undefined' && typeof global != 'undefined');
2+
var globalNamespace = isNodeJs ? global : window;
3+
4+
if (!isNodeJs) {
5+
if (!Function.prototype.bind) {
6+
Function.prototype.bind = function (oThis) {
7+
if (typeof this !== "function") {
8+
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
9+
}
10+
11+
var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {
12+
}, fBound = function () {
13+
return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
14+
};
15+
16+
fNOP.prototype = this.prototype;
17+
fBound.prototype = new fNOP();
18+
19+
return fBound;
20+
};
21+
}
22+
}
23+
24+
//TODO Support DestinationAccount['de' + 'posit'](amount)
25+
//
26+
//DestinationAccount['de' + 'posit'](amount) could be rewritten to:
27+
//__context.__$DestinationAccount['de' + 'posit'].call(__context.DestinationAccount, amount)
28+
//
29+
//would be better to give a nice error message if role method not found:
30+
//DCI.getRoleMember(__context, __context.DestinationAccount, 'DestinationAccount', 'de' + 'posit').call(__context.DestinationAccount, amount)
31+
//Gets a member on a role player - can be either a role method or a method or property of the role player object
32+
function getRoleMember(context, player, roleName, memberName) {
33+
var roleMethod = context['__$' + roleName][memberName];
34+
if (roleMethod) {
35+
//bind the role player as `this` on the specified role method
36+
return roleMethod.bind(player);
37+
} else {
38+
if (!player[memberName]) {
39+
throw new Error('Method or property "' + memberName + '" not found on role "' + roleName + '" nor on its current role player.');
40+
}
41+
return player[memberName];
42+
}
43+
}
44+
exports.getRoleMember = getRoleMember;
45+
46+
//This function is for handling calls beginning with `this`; it calls a method on the current role player,
47+
//which could be either a role method or a method on the data object.
48+
//
49+
// TODO test data object methods that aren't overridden by the role
50+
//
51+
function callMethodOnSelf(context, player, roleName, methodName, args) {
52+
if (player != context[roleName]) {
53+
if (!player[methodName]) {
54+
var msg = 'Method "' + methodName + '" not found';
55+
if (player['constructor'].name) {
56+
msg += ' on object of type "' + player['constructor'].name + '".';
57+
}
58+
throw new Error(msg);
59+
}
60+
return player[methodName].apply(player, args);
61+
//Old version: commenting since it's inconsistent...this makes `this.someRoleMethod()` work
62+
//even inside a closure like [1,2,3].forEach(...),
63+
//but using `this` in other ways, like `this` as an argument, would not necessarily work.
64+
//Better to just use `self`.
65+
/*
66+
67+
//support functions within role methods for which `this` is unbound - but make sure we
68+
//don't wrongly assume `this` refers to the current role
69+
if (player != undefined && player != globalNamespace) {
70+
//not a role method; call normally
71+
if (!player[methodName]) {
72+
var msg = 'Method "' + methodName + '" not found';
73+
if (player['constructor'].name) {
74+
msg += ' on object of type "' + player['constructor'].name + '".';
75+
}
76+
throw new Error(msg);
77+
}
78+
return player[methodName].apply(player, args);
79+
}
80+
//if we're here, then `this` probably *should* refer to the current role and the only
81+
//reason it's undefined is most likely a nested function
82+
//(e.g. a callback function passed to forEach() to loop over an array)
83+
else {
84+
player = context[roleName];
85+
}
86+
*/
87+
}
88+
89+
var roleMethod = context['__$' + roleName][methodName];
90+
91+
if (typeof roleMethod == 'function') {
92+
return (args ? roleMethod.apply(player, args) : roleMethod.call(player));
93+
} else {
94+
if (!player[methodName]) {
95+
var msg = 'Method "' + methodName + '" not found';
96+
if (player['constructor'].name)
97+
msg += ' on object of type "' + player['constructor'].name + '" nor on any of the roles it is currently playing.';
98+
throw new Error(msg);
99+
}
100+
return player[methodName].apply(player, args);
101+
}
102+
}
103+
exports.callMethodOnSelf = callMethodOnSelf;
104+

dci/dci.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
//DCI
2+
3+
/*
4+
5+
SourceAccount {
6+
foo() {
7+
this['bar']();
8+
}
9+
bar() {}
10+
}
11+
12+
var handler = {
13+
get: function(target, propName) {
14+
//TEMP
15+
return 'test';
16+
17+
//This is all ONLY to check for the case where this['someMethod'] is called
18+
//from within a role method.
19+
//Role method calls that begin with the role name, e.g. DestinationAccount['deposit'](),
20+
//are taken care of at compile time.
21+
22+
//the function that attempted to access a property on this object
23+
var caller = arguments.callee.caller;
24+
25+
//if caller is a role method, then we want to check if propName is also a role method
26+
if (caller.__isRoleMethod) {
27+
var potentialRoleMethodName = propName;
28+
var context = caller.__context,
29+
roleName = caller.__roleName,
30+
rolePlayer = target;
31+
32+
var role = context['__$' + roleName];
33+
if (role[potentialRoleMethodName]) {
34+
//bind() wraps the function with an outer function which ensures
35+
//that the `this` keyword works within the called role method
36+
return role[potentialRoleMethodName].bind(rolePlayer);
37+
}
38+
}
39+
}
40+
};
41+
42+
Object.prototype = new Proxy({}, handler);
43+
Object.prototype.foo;
44+
45+
//var o = {};
46+
//o.foo;
47+
48+
*/
49+
50+
51+
declare var global: Object;
52+
53+
var isNodeJs = (typeof window == 'undefined' && typeof global != 'undefined');
54+
var globalNamespace = isNodeJs ? global: window;
55+
56+
//Ensure that Function.prototype.bind() is available (used by getRoleMember() function below)
57+
if (!isNodeJs) {
58+
//shim for Function.prototype.bind() for older browsers
59+
if (!Function.prototype.bind) {
60+
Function.prototype.bind = function (oThis) {
61+
if (typeof this !== "function") {
62+
// closest thing possible to the ECMAScript 5 internal IsCallable function
63+
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
64+
}
65+
66+
var aArgs = Array.prototype.slice.call(arguments, 1),
67+
fToBind = this,
68+
fNOP = function () {},
69+
fBound = function () {
70+
return fToBind.apply(this instanceof fNOP && oThis
71+
? this
72+
: oThis,
73+
aArgs.concat(Array.prototype.slice.call(arguments)));
74+
};
75+
76+
fNOP.prototype = this.prototype;
77+
fBound.prototype = new fNOP();
78+
79+
return fBound;
80+
};
81+
}
82+
}
83+
84+
//TODO Support DestinationAccount['de' + 'posit'](amount)
85+
//
86+
//DestinationAccount['de' + 'posit'](amount) could be rewritten to:
87+
//__context.__$DestinationAccount['de' + 'posit'].call(__context.DestinationAccount, amount)
88+
//
89+
//would be better to give a nice error message if role method not found:
90+
//DCI.getRoleMember(__context, __context.DestinationAccount, 'DestinationAccount', 'de' + 'posit').call(__context.DestinationAccount, amount)
91+
92+
//Gets a member on a role player - can be either a role method or a method or property of the role player object
93+
export function getRoleMember(context: Object, player: Object, roleName: string, memberName: string) {
94+
var roleMethod = context['__$' + roleName][memberName];
95+
if (roleMethod) {
96+
//bind the role player as `this` on the specified role method
97+
return roleMethod.bind(player);
98+
}
99+
else {
100+
//check for property on role player
101+
if (!player[memberName]) {
102+
throw new Error('Method or property "' + memberName + '" not found on role "' + roleName + '" nor on its current role player.');
103+
}
104+
return player[memberName];
105+
}
106+
}
107+
108+
109+
110+
//This function is for handling calls beginning with `this`; it calls a method on the current role player,
111+
//which could be either a role method or a method on the data object.
112+
//
113+
// TODO test data object methods that aren't overridden by the role
114+
//
115+
export function callMethodOnSelf(context: Object, player: Object, roleName: string, methodName: string, args: Array): any {
116+
//if `this` is not equal to the current role player
117+
if (player != context[roleName]) {
118+
//If we're here, then it's a data object method, not a role method
119+
//(Either that or the programmer used `this` inside a closure when they should have used `self`.
120+
//In other words this code would also be reached if `this` is equal to `undefined`, `global`, or `window`.)
121+
if (!player[methodName]) {
122+
var msg = 'Method "' + methodName + '" not found';
123+
if (player['constructor'].name) {
124+
msg += ' on object of type "' + player['constructor'].name + '".';
125+
}
126+
throw new Error(msg);
127+
}
128+
return player[methodName].apply(player, args);
129+
130+
//Old version: commenting since it's inconsistent...this makes `this.someRoleMethod()` work
131+
//even inside a closure like [1,2,3].forEach(...),
132+
//but using `this` in other ways, like `this` as an argument, would not necessarily work.
133+
//Better to just use `self`.
134+
/*
135+
136+
//support functions within role methods for which `this` is unbound - but make sure we
137+
//don't wrongly assume `this` refers to the current role
138+
if (player != undefined && player != globalNamespace) {
139+
//not a role method; call normally
140+
if (!player[methodName]) {
141+
var msg = 'Method "' + methodName + '" not found';
142+
if (player['constructor'].name) {
143+
msg += ' on object of type "' + player['constructor'].name + '".';
144+
}
145+
throw new Error(msg);
146+
}
147+
return player[methodName].apply(player, args);
148+
}
149+
//if we're here, then `this` probably *should* refer to the current role and the only
150+
//reason it's undefined is most likely a nested function
151+
//(e.g. a callback function passed to forEach() to loop over an array)
152+
else {
153+
player = context[roleName];
154+
}
155+
*/
156+
}
157+
158+
var roleMethod = context['__$'+roleName][methodName];
159+
//if the method exists on the role, we always call it (role methods override data object methods)
160+
if (typeof roleMethod == 'function') {
161+
return (args ? roleMethod.apply(player, args): roleMethod.call(player));
162+
}
163+
//otherwise, call the data object method
164+
//(if the method is not found on the data object, this will throw Javascript's usual
165+
//'[function name] is not defined' error)
166+
else {
167+
if (!player[methodName]) {
168+
var msg = 'Method "' + methodName + '" not found';
169+
if (player['constructor'].name)
170+
msg += ' on object of type "' + player['constructor'].name + '" nor on any of the roles it is currently playing.';
171+
throw new Error(msg);
172+
}
173+
return player[methodName].apply(player, args);
174+
}
175+
}

dci/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "dci",
3+
"version": "0.0.1",
4+
"repository": {
5+
"type": "git",
6+
"url": "git://github.com/mbrowne/typescript-dci.git"
7+
},
8+
"main": "./dci.js",
9+
"bugs": {
10+
"url": "https://github.com/mbrowne/typescript-dci/issues"
11+
},
12+
"readme": "ERROR: No README data found!",
13+
"_id": "dci@0.0.1",
14+
"dist": {
15+
"shasum": "8bb836dd0f83e93250a9aab7070f19e63bb611ad"
16+
},
17+
"_from": "/Users/mbrowne/_temp/dci"
18+
}

dci/samples

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../samples/dci

dci/tmp.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
var handler = {
2+
get: function(target, propName) {
3+
return 47;
4+
}
5+
};
6+
7+
//This works in Firefox
8+
var p = new Proxy({}, handler);
9+
p.foo; //outputs 47
10+
11+
//This doesn't.
12+
//Will it work in V8 / node.js when Proxy support is added?
13+
Object.prototype = new Proxy({}, handler);
14+
Object.prototype.foo;

0 commit comments

Comments
 (0)