mirror of
https://github.com/flynx/object.js.git
synced 2025-10-29 02:20:08 +00:00
reworked access to parent callable implementations...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
1b5d27afc1
commit
a5bab42336
66
README.md
66
README.md
@ -213,9 +213,16 @@ var Base = object.Constructor('Base', {
|
||||
})
|
||||
|
||||
var Item = object.Constructor('Item', Base, {
|
||||
method: function(){
|
||||
// ...
|
||||
|
||||
// call the "super" method...
|
||||
return object.parentCall(Item.prototype, 'method', this, ...arguments)
|
||||
},
|
||||
|
||||
__init__: function(){
|
||||
// call the "super" method...
|
||||
object.parentCall(this.prototype.__init__, this)
|
||||
object.parentCall(this.__init__, this, ...arguments)
|
||||
|
||||
this.item_attr = 'instance attribute value'
|
||||
},
|
||||
@ -245,7 +252,8 @@ var Action = object.Constructor('Action',
|
||||
|
||||
var Action2 = object.Constructor('Action2', {
|
||||
__call__: function(context, ...args){
|
||||
return this
|
||||
// call the callable parent...
|
||||
return object.parentCall(Action2.prototype, '__call__', this, ...arguments)
|
||||
},
|
||||
})
|
||||
|
||||
@ -270,6 +278,17 @@ represent the two contexts relevant to the callable instance:
|
||||
If the prototype is explicitly defined as a function then it is the
|
||||
user's responsibility to call `.__call__(..)` method.
|
||||
|
||||
When calling the parent passing `'__call__'` will get the parent in both
|
||||
the function and `.__call__(..)` implementations, but extra care must be
|
||||
taken in passing the reference prototype to `.parentCall(..)`, the instance
|
||||
is implemented as a proxy function that will pass the arguments to the
|
||||
implementation (i.e. `this.constructor.prototype(..)`) so this proxy
|
||||
function as well as the `.constructor.prototype(..)` are valid implementations
|
||||
and both will be retrieved by `sources(this, '__call__')`,
|
||||
`values(this, '__call__')` and by extension `parent(this, '__call__')`
|
||||
and friends, so this is another reason not to use `this` in the general
|
||||
case.
|
||||
|
||||
|
||||
**Notes:**
|
||||
- the two approaches (_function_ vs. `.__call__(..)`) will produce
|
||||
@ -497,6 +516,18 @@ one of the following:
|
||||
`callback(..)` and continue.
|
||||
|
||||
|
||||
Special case: get callable implementations
|
||||
```
|
||||
sources(<object>, '__call__')
|
||||
sources(<object>, '__call__', <callback>)
|
||||
-> <list>
|
||||
```
|
||||
|
||||
This will get the callable implementations regardless of the actual
|
||||
implementation details, i.e. both function prototype or `.__call__(..)`
|
||||
methods will be matched.
|
||||
|
||||
|
||||
### `values(..)`
|
||||
|
||||
Get values for attribute in prototype chain
|
||||
@ -528,7 +559,7 @@ callback(<descriptor>, <source>)
|
||||
-> <value>
|
||||
```
|
||||
|
||||
See [`sources(..)`](#sources) for docs on `callback(..)`
|
||||
See [`sources(..)`](#sources) for docs on `callback(..)` and special cases.
|
||||
|
||||
### `parent(..)`
|
||||
|
||||
@ -537,18 +568,37 @@ Get parent attribute value or method
|
||||
parent(<prototype>, <name>)
|
||||
-> <parent-value>
|
||||
-> undefined
|
||||
```
|
||||
|
||||
It is recommended to use the relative`<constructor>.prototype` as
|
||||
`<prototype>` and in turn not recommended to use `this` or `this.__proto__`
|
||||
as they will not provide the appropriate reference point in the prototype
|
||||
chain for the current method and may result in infinite recursion.
|
||||
|
||||
For access to parent methods the following special case is better.
|
||||
|
||||
```
|
||||
parent(<method>, <this>)
|
||||
-> <parent-method>
|
||||
-> undefined
|
||||
```
|
||||
|
||||
|
||||
_Edge case: The `parent(<method>, ..)` has one potential pitfall -- in
|
||||
the rare case where a prototype chain contains two or more references
|
||||
to the same method under the same name, `parent(..)` can't distinguish
|
||||
between these references and will always return the second one._
|
||||
|
||||
|
||||
Special case: get the parent callable implementation
|
||||
```
|
||||
parent(<prototype>, '__call__')
|
||||
-> <parent-value>
|
||||
-> undefined
|
||||
```
|
||||
|
||||
See [`sources(..)`](#sources) for more info on the special case.
|
||||
|
||||
|
||||
### `parentProperty(..)`
|
||||
|
||||
@ -573,6 +623,15 @@ parentCall(<method>, <this>)
|
||||
-> undefined
|
||||
```
|
||||
|
||||
Special case: call the parent callable implementation
|
||||
```
|
||||
parentCall(<prototype>, '__call__', <this>)
|
||||
-> <result>
|
||||
-> undefined
|
||||
```
|
||||
|
||||
See [`parent(..)`](#parent) and [`sources(..)`](#sources) for more details.
|
||||
|
||||
|
||||
### `mixin(..)`
|
||||
|
||||
@ -814,4 +873,5 @@ Still, this is worth some thought.
|
||||
Copyright (c) 2019, Alex A. Naanou,
|
||||
All rights reserved.
|
||||
|
||||
|
||||
<!-- vim:set ts=4 sw=4 spell : -->
|
||||
|
||||
97
object.js
97
object.js
@ -181,13 +181,18 @@ module.STOP =
|
||||
// for any overloading in the instance, though this approach is
|
||||
// not very reusable....
|
||||
// NOTE: this will not trigger any props...
|
||||
//
|
||||
// XXX document the '__call__' cpecial case...
|
||||
// XXX in the call case need to skip the wrapper function... (???)
|
||||
var sources =
|
||||
module.sources =
|
||||
function(obj, name, callback){
|
||||
var o
|
||||
var res = []
|
||||
while(obj != null){
|
||||
if(obj.hasOwnProperty(name)){
|
||||
//if(obj.hasOwnProperty(name)){
|
||||
if(obj.hasOwnProperty(name)
|
||||
|| (name == '__call__' && typeof(obj) == 'function')){
|
||||
// handle callback...
|
||||
o = callback
|
||||
&& callback(obj)
|
||||
@ -223,27 +228,33 @@ function(obj, name, callback){
|
||||
//
|
||||
//
|
||||
// NOTE: for more docs on the callback(..) see sources(..)
|
||||
//
|
||||
// XXX document the '__call__' cpecial case...
|
||||
var values =
|
||||
module.values =
|
||||
function(obj, name, callback, props){
|
||||
props = callback === true ?
|
||||
callback
|
||||
: props
|
||||
var _get = function(obj, name){
|
||||
return props ?
|
||||
Object.getOwnPropertyDescriptor(obj, name)
|
||||
// handle callable instance...
|
||||
: !(name in obj)
|
||||
&& name == '__call__'
|
||||
&& typeof(obj) == 'function' ?
|
||||
obj
|
||||
// normal attr...
|
||||
: obj[name] }
|
||||
// wrap the callback if given...
|
||||
var c = typeof(callback) == 'function'
|
||||
&& function(obj){
|
||||
return callback(
|
||||
props ?
|
||||
Object.getOwnPropertyDescriptor(obj, name)
|
||||
: [ obj[name] ],
|
||||
obj) }
|
||||
return callback(_get(obj, name), obj) }
|
||||
return sources(...(c ?
|
||||
[obj, name, c]
|
||||
: [obj, name]))
|
||||
.map(function(obj){
|
||||
return props ?
|
||||
Object.getOwnPropertyDescriptor(obj, name)
|
||||
: obj[name] }) }
|
||||
return _get(obj, name) }) }
|
||||
|
||||
|
||||
// Find the next parent attribute in the prototype chain.
|
||||
@ -258,6 +269,10 @@ function(obj, name, callback, props){
|
||||
// -> meth
|
||||
// -> undefined
|
||||
//
|
||||
// Get parent object...
|
||||
// parent(this)
|
||||
// -> parent
|
||||
//
|
||||
//
|
||||
// The two forms differ in:
|
||||
// - in parent(method, ..) a method's .name attr is used for name.
|
||||
@ -300,9 +315,20 @@ function(obj, name, callback, props){
|
||||
// and to the method after the match.
|
||||
// NOTE: this is super(..) replacement, usable in any context without
|
||||
// restriction -- super(..) is restricted to class methods only...
|
||||
//
|
||||
// XXX need to be able to get a callable prototype...
|
||||
// parent(proto, '__call__')
|
||||
// This would need to handle two cases transparently:
|
||||
// - parent with .__call__(..) defined...
|
||||
// - parent with callable prototype...
|
||||
// ...should this be handled here or in sources(..)???
|
||||
// XXX document both __call__ cases...
|
||||
var parent =
|
||||
module.parent =
|
||||
function(proto, name){
|
||||
// special case: get parent...
|
||||
if(arguments.length == 1){
|
||||
return proto.__proto__ }
|
||||
// special case: get method...
|
||||
if(typeof(name) != typeof('str')){
|
||||
var that = name
|
||||
@ -317,13 +343,17 @@ function(proto, name){
|
||||
&& module.STOP })
|
||||
.pop() }
|
||||
// get first source...
|
||||
var c = 0
|
||||
var res = sources(proto, name,
|
||||
function(obj){ return module.STOP })
|
||||
function(obj){
|
||||
return c++ == 1
|
||||
&& module.STOP })
|
||||
.pop()
|
||||
return res ?
|
||||
// get next value...
|
||||
res.__proto__[name]
|
||||
: undefined }
|
||||
return !res ?
|
||||
undefined
|
||||
:(!(name in res) && typeof(res) == 'function') ?
|
||||
res
|
||||
: res[name] }
|
||||
|
||||
|
||||
// Find the next parent property descriptor in the prototype chain...
|
||||
@ -368,6 +398,8 @@ function(proto, name){
|
||||
// or:
|
||||
// parent(method, this).call(this, ...)
|
||||
// NOTE: for more docs see parent(..)
|
||||
//
|
||||
// XXX in the call case need to skip the wrapper function... (???)
|
||||
var parentCall =
|
||||
module.parentCall =
|
||||
function(proto, name, that, ...args){
|
||||
@ -575,7 +607,7 @@ function(context, constructor, ...args){
|
||||
var _mirror_doc = function(func, target){
|
||||
Object.defineProperty(func, 'toString', {
|
||||
value: function(...args){
|
||||
var f = typeof(constructor.prototype) == 'function' ?
|
||||
var f = typeof(target.prototype) == 'function' ?
|
||||
target.prototype
|
||||
: target.prototype.__call__
|
||||
return typeof(f) == 'function' ?
|
||||
@ -603,8 +635,13 @@ function(context, constructor, ...args){
|
||||
return (
|
||||
// .prototype is a function...
|
||||
typeof(constructor.prototype) == 'function' ?
|
||||
constructor.prototype
|
||||
.call(obj, this, ...arguments)
|
||||
// NOTE: we are not using .call(..) here as it
|
||||
// may not be accesible through the prototype
|
||||
// chain, this can occur when creating a
|
||||
// callable instance from a non-callable
|
||||
// parent...
|
||||
Reflect.apply(
|
||||
constructor.prototype, obj, [this, ...arguments])
|
||||
// .__call__(..)
|
||||
: constructor.prototype.__call__
|
||||
.call(obj, this, ...arguments)) },
|
||||
@ -779,9 +816,21 @@ function Constructor(name, a, b, c){
|
||||
|
||||
// handle:
|
||||
// Constructor(name, constructor, ..)
|
||||
//
|
||||
// NOTE: this is a bit too functional in style by an if-tree would
|
||||
// be more bulky and less readable...
|
||||
constructor_proto
|
||||
&& proto.__proto__ === Object.prototype
|
||||
&& (proto.__proto__ = constructor_proto.prototype)
|
||||
// XXX need a better test -- need to test if .__proto__ was set
|
||||
// manually and not mess it up...
|
||||
&& (proto.__proto__ === Object.prototype
|
||||
|| proto.__proto__ === Function.prototype)
|
||||
&& (proto.__proto__ = constructor_proto.prototype)
|
||||
// restore func .toString(..) that was replaced to object's .toString(..)
|
||||
// in the previous op but only if it was not set by user...
|
||||
&& (typeof(proto) == 'function'
|
||||
&& proto.toString === Object.prototype.toString)
|
||||
// XXX should we wrap this in normalizeIndent(..) ???
|
||||
&& (proto.toString = Function.prototype.toString)
|
||||
|
||||
// handle: .__extends__
|
||||
if(!constructor_proto){
|
||||
@ -802,15 +851,13 @@ function Constructor(name, a, b, c){
|
||||
constructor_mixin = mixinFlat({}, constructor_mixin)
|
||||
delete constructor_mixin.__extends__ }
|
||||
!!constructor_proto
|
||||
&& (proto.__proto__ = constructor_proto.prototype)
|
||||
}
|
||||
|
||||
&& (proto.__proto__ = constructor_proto.prototype) }
|
||||
|
||||
// the constructor base...
|
||||
var _constructor = function Constructor(){
|
||||
// create raw instance...
|
||||
var obj = _constructor.__rawinstance__ ?
|
||||
_constructor.__rawinstance__(this, ...arguments)
|
||||
_constructor.__rawinstance__(this, ...arguments)
|
||||
: RawInstance(this, _constructor, ...arguments)
|
||||
// initialize...
|
||||
obj.__init__ instanceof Function
|
||||
@ -829,8 +876,8 @@ function Constructor(name, a, b, c){
|
||||
// set .toString(..)...
|
||||
// NOTE: do this only if .toString(..) is not defined by user...
|
||||
// XXX revise this test...
|
||||
;((constructor_mixin || {}).toString === Function.toString
|
||||
|| (constructor_mixin || {}).toString === ({}).toString)
|
||||
;((constructor_mixin || {}).toString === Function.prototype.toString
|
||||
|| (constructor_mixin || {}).toString === Object.prototype.toString)
|
||||
&& Object.defineProperty(_constructor, 'toString', {
|
||||
value: function(){
|
||||
var args = proto.__init__ ?
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ig-object",
|
||||
"version": "5.0.0",
|
||||
"version": "5.0.1",
|
||||
"description": "",
|
||||
"main": "object.js",
|
||||
"scripts": {
|
||||
|
||||
144
test.js
144
test.js
@ -12,6 +12,12 @@ var object = require('./object')
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
module.VERBOSE = false
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// helpers...
|
||||
|
||||
@ -27,59 +33,125 @@ var instances = function(obj){
|
||||
return k[0] == k[0].toLowerCase() && o.constructor }) }
|
||||
|
||||
|
||||
var assert = function(pre, stats){
|
||||
return function(e, msg, ...args){
|
||||
stats
|
||||
&& (stats.assertions += 1)
|
||||
&& !e
|
||||
&& (stats.failures += 1)
|
||||
module.VERBOSE
|
||||
&& console.log(pre +': '+ msg, ...args)
|
||||
console.assert(e, pre +': '+ msg, ...args)
|
||||
return e } }
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
var setups = {
|
||||
basic: function(msg){
|
||||
basic: function(assert){
|
||||
var X, Y, A, B, C
|
||||
return {
|
||||
X: X = object.Constructor('A'),
|
||||
Y: Y = object.C('Y', { }),
|
||||
X: X = assert(object.Constructor('A'), `Constructor`),
|
||||
Y: Y = assert(object.C('Y', { }), ` C`),
|
||||
|
||||
A: A = object.C('A', Y, { }),
|
||||
B: B = object.C('B', A, { }),
|
||||
C: C = object.C('C', B, { }),
|
||||
A: A = assert(object.C('A', Y, { }), `inherit (gen1)`),
|
||||
B: B = assert(object.C('B', A, { }), `inherit (gen2)`),
|
||||
C: C = assert(object.C('C', B, { }), `inherit (gen3)`),
|
||||
} },
|
||||
init: function(msg){
|
||||
init: function(assert){
|
||||
return {
|
||||
|
||||
} },
|
||||
call: function(msg){
|
||||
call: function(assert){
|
||||
var A, B, C, D, F, G
|
||||
return {
|
||||
A: A = assert(object.C('A',
|
||||
function(){
|
||||
// XXX
|
||||
}), 'callable'),
|
||||
B: B = assert(object.C('B', {
|
||||
__call__: function(){
|
||||
// XXX
|
||||
},
|
||||
}), 'callable'),
|
||||
|
||||
C: C = assert(object.C('C', A, {}), 'basic inherit'),
|
||||
D: D = assert(object.C('D', B, {}), 'basic inherit'),
|
||||
|
||||
E: E = assert(object.C('E', A,
|
||||
function(){
|
||||
// XXX how do we get the parent callable???
|
||||
object.parent(this)
|
||||
}), 'call parent'),
|
||||
F: F = assert(object.C('F', B, {
|
||||
__call__: function(){
|
||||
object.parentCall(F.__call__, this)
|
||||
},
|
||||
}), 'call parent\'s .__call__'),
|
||||
|
||||
|
||||
|
||||
} },
|
||||
native: function(assert){
|
||||
return {
|
||||
|
||||
} },
|
||||
native: function(msg){
|
||||
mixin: function(assert){
|
||||
return {
|
||||
|
||||
} },
|
||||
instances: function(msg){
|
||||
instances: function(assert){
|
||||
// XXX generate using tests.instance*
|
||||
// XXX need to be able to use different input setups...
|
||||
return {}
|
||||
},
|
||||
}
|
||||
|
||||
var modifiers = {
|
||||
'as-is': function(msg, setup){
|
||||
// default...
|
||||
'as-is': function(assert, setup){
|
||||
return setup }
|
||||
|
||||
// XXX
|
||||
}
|
||||
|
||||
|
||||
|
||||
var tests = {
|
||||
instance: function(msg, setup, no_new){
|
||||
instance: function(assert, setup, mode){
|
||||
return constructors(setup)
|
||||
.reduce(function(res, [k, O]){
|
||||
var o
|
||||
no_new ?
|
||||
console.assert(o = res[k.toLowerCase()] = O(), `${msg}: new:`, k)
|
||||
: console.assert(o = res[k.toLowerCase()] = new O(), `${msg}: new:`, k)
|
||||
console.assert(o instanceof O, `${msg}: instanceof:`, k)
|
||||
console.assert(o.constructor === O, `${msg}: constructor:`, k)
|
||||
console.assert(o.__proto__ === O.prototype, `${msg}: __proto__:`, k)
|
||||
var o = res[k.toLowerCase()] =
|
||||
mode == 'no_new' ?
|
||||
assert(O(), `new:`, k)
|
||||
: mode == 'raw' ?
|
||||
assert(O.__rawinstance__(), `.__rawinstance__()`, k)
|
||||
: assert(new O(), `new:`, k)
|
||||
assert(o instanceof O, `instanceof:`, k)
|
||||
O.__proto__ instanceof Function
|
||||
&& assert(o instanceof O.__proto__, `instanceof-nested:`, k)
|
||||
assert(o.constructor === O, `.constructor:`, k)
|
||||
assert(o.__proto__ === O.prototype, `.__proto__:`, k)
|
||||
return res }, {}) },
|
||||
instance_no_new: function(msg, setup){
|
||||
return this.instance(msg, setup, true) },
|
||||
instance_no_new: function(assert, setup){
|
||||
return this.instance(assert, setup, 'no_new') },
|
||||
instance_raw: function(assert, setup){
|
||||
return this.instance(assert, setup, 'raw') },
|
||||
|
||||
attributes: function(assert, setup){
|
||||
return {}
|
||||
},
|
||||
|
||||
// XXX
|
||||
methods: function(assert, setup){
|
||||
constructors(setup)
|
||||
.forEach(function([k, O]){
|
||||
Object.keys(O).forEach(function(m){
|
||||
typeof(O[m]) == 'function'
|
||||
&& O[m]() })
|
||||
})
|
||||
return {}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -90,8 +162,18 @@ var cases = {
|
||||
|
||||
|
||||
|
||||
// XXX need to report stats...
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
// XXX need to have two modes:
|
||||
// - clean
|
||||
// - reuse test results again...
|
||||
var runner = function(){
|
||||
var stats = {
|
||||
tests: 0,
|
||||
assertions: 0,
|
||||
failures: 0,
|
||||
}
|
||||
|
||||
// tests...
|
||||
Object.keys(tests)
|
||||
.forEach(function(t){
|
||||
@ -102,15 +184,21 @@ var runner = function(){
|
||||
Object.keys(setups)
|
||||
.forEach(function(s){
|
||||
// run the test...
|
||||
msg =`test:${t}.${s}.${m}`
|
||||
tests[t](msg, modifiers[m](msg, setups[s](msg))) }) }) })
|
||||
stats.tests += 1
|
||||
var _assert = assert(`test:${t}.${s}.${m}`, stats)
|
||||
tests[t](_assert,
|
||||
modifiers[m](_assert,
|
||||
setups[s](_assert))) }) }) })
|
||||
// cases...
|
||||
Object.keys(cases)
|
||||
.forEach(function(c){
|
||||
msg = `case:${c}:`
|
||||
cases[c](msg)
|
||||
})
|
||||
}
|
||||
stats.tests += 1
|
||||
cases[c]( assert(`case:${c}:`, stats) ) })
|
||||
|
||||
// stats...
|
||||
console.log('Tests:', stats.tests,
|
||||
'Assertions:', stats.assertions,
|
||||
'Failures:', stats.failures) }
|
||||
|
||||
|
||||
runner()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user