reworked access to parent callable implementations...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-05-24 23:59:01 +03:00
parent 1b5d27afc1
commit a5bab42336
4 changed files with 252 additions and 57 deletions

View File

@ -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 : -->

View File

@ -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__ ?

View File

@ -1,6 +1,6 @@
{
"name": "ig-object",
"version": "5.0.0",
"version": "5.0.1",
"description": "",
"main": "object.js",
"scripts": {

144
test.js
View File

@ -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()