Lexikos wrote:trismarck wrote:Using Functors would change the target of the invocation, which (preserving the target) is needed to detect the existence of the method in the inheritance chain the target is part of.
Huh? The target of invocation is
this, which is passed as an additional parameter to the functor in place of the method name. If you meant whichever class defined the method, you know that at load time because you know where you wrote the method.
Yes, I didn't actually think when I wrote this. What I've meant was this: lets suppose we're invoking
x.fun3() and there is a key
"fun3" at the end of the inheritance tree of
x, in the third base. In the first or second base, there is a functor instance defined as the
__Call meta-function. When that functor instance is invoked, the
original invocation can't detect the existence of the method in the rest of inheritance tree of 'x' anymore, because that invocation has just ended (by 'has just ended' I actually mean that the invocation won't traverse
automatically through the rest of the inheritance tree of
x) (actually, I'd have to check if the original invocation really 'ends' at that point - probably not - the original invocation call is I guess still on the callstack during the whole time the new invocation was spawned). What I've meant was sort of described in
this thread and just mentioned it again here, w/o providing the link (should have done that really). But lets leave that aside.
Lexikos wrote:Likewise, for functors you just need to include some information in the functor to identify which class it belongs to.
Didn't realize that it's possible to pass 'parameters' to the functor handler by just putting the parameters inside of the functor. Thanks.
Ok, so to implement checking of too few/too many params with functors: lets suppose we have the following classes:
Code: Select all
class dx {
fun3(a) {
msgbox fun3
}
}
class cx extends dx {
__Call(aName, aP*) {
msgbox __Call
}
fun1(a) {
msgbox fun1
}
fun2(a) {
}
}
We include the following:
Code: Select all
class Properties__Call {
__Call(aTarget, aName, aP*) {
this.__CallstackDepthFix := this.__CallstackDepthFix = "" ? 0 : this.__CallstackDepthFix
aCurrentClass := this.__CurrentClass
if(not isObject(aCurrentClass) )
throw, Exception("Functor instance lacks current class specification.", -1)
; call the user-made __Call, if it's there
if(ObjHasKey(aCurrentClass, "__Call_") )
aCurrentClass.__Call_.Call(aTarget, aName, aP*) ; for now, skip the return value
; if the class object has the requested key
if(ObjHasKey(aCurrentClass, aName) ) {
; call the custom behaviour for a method, if it's there
if(ObjHasKey(this, aName) )
this[aName].Call(this, aTarget, aCurrentClass, aP*) ; discard return value
; call the default behaviour for a method
Properties__Call.DefaultBehaviourForExistentFunction.Call(this, aTarget, aCurrentClass, aName, aP*) ; discard return value
; call the method of the class
return aCurrentClass[aName].Call(aTarget, aP*)
}
; if the class object lacks the requested key
; change the base of the target
target_base := aTarget.base ; relies on correct implementation of __Get of bases of aTarget
aTarget.base := aCurrentClass.base ; relies on correct implementation of __Set in bases of aCurrentClass
; 'continue' the invocation the functor instance has interrupted, from the point of interuption
ret := aTarget[aName](aP*)
; restore the base of the target
aTarget.base := target_base
; return the return value of the method
return ret
}
DefaultBehaviourForExistentFunction(aTarget, aCurrentClass, aName, aP*) {
Properties__Call.CheckIfValueCallable.Call(this, aTarget, aCurrentClass, aName, aP*)
Properties__Call.CheckParamCount.Call(this, aTarget, aCurrentClass, aName, aP*)
}
CheckParamCount(aTarget, aCurrentClass, aName, aP*) {
; suppose value callable
o_fun := aCurrentClass[aName]
max_index := Round(aP.MaxIndex() )
if(max_index < o_fun.MinParams - 1)
throw, Exception("Too few parameters passed to function: " aName, -3 - this.__CallstackDepthFix)
if(max_index > o_fun.MaxParams - 1)
{
if(not o_fun.IsVariadic)
throw, Exception("Too many parameters passed to function: " aName, -3 - this.__CallstackDepthFix)
}
}
CheckIfValueCallable(aTarget, aCurrentClass, aName, aP*) {
o_fun := aCurrentClass[aName]
if(isObject(o_fun) )
if(o_fun.IsBuiltIn = 0 or o_fun.IsBuiltIn = 1)
return ; ok
throw, Exception("Uncallable value: " o_fun, -3 - this.__CallstackDepthFix)
}
}
Lets suppose we want to have parameter checking in class
dx. We modify the class like this:
Code: Select all
class dx {
class __Call extends Properties__Call {
static __CurrentClass := dx
static __CallstackDepthFix := 1
fun3(aTarget, aCurrentClass, aP*) {
msgbox behaviour_fun3
}
}
fun3(a) {
msgbox fun3
return 5555
}
}
class cx extends dx {
__Call(aName, aP*) {
msgbox __Call_
}
fun1(a) {
msgbox fun1
return 4444
}
fun2(a) {
}
}
And now the script should throw an exception for fun3() for too few / too many / uncallable value for fun3(). The functor could have as well be attached to
cx. Complete example:
Code: Select all
class dx {
class __Call extends Properties__Call {
static __CurrentClass := dx
static __CallstackDepthFix := 1
fun3(aTarget, aCurrentClass, aP*) {
msgbox behaviour_fun3
}
}
fun3(a) {
msgbox fun3
return 5555
}
}
class cx extends dx {
static __Call := {base: Properties__Call, __CurrentClass: cx, __CallstackDepthFix: 0} ; one-liner
; class __Call extends Properties__Call {
; static __CurrentClass := cx
; static __CallstackDepthFix := 0
; fun1(aTarget, aCurrentClass, aP*) {
; msgbox behaviour_fun1
; }
; }
; supposing __Call was defined in a class, if we want to call
; that __Call even if functor specified for the meta-function,
; append '_' at end of __Call. The functor will call __Call_.
__Call_(aName, aP*) {
msgbox __Call_
}
fun1(a) {
msgbox fun1
return 4444
}
fun2(a) {
}
}
x := new cx()
; x.fun1() ; too few
; x.fun1("aaa") ; ok
; x.fun1("aaa", "bbb") ; too many
; x.base.fun1 := 3
; x.fun1("aaa") ; uncallable value
;msgbox % x.fun1("aaa") ; 4444 - return value correct
;msgbox % x.fun3("aaa") ; 5555 - continuation of original invocation works
;msgbox % x.fun1("aaa") ; behaviour for fun1() called
x.fun3("aaa", "bbb")
The above is untested. Perhaps it could be used for debugging purposes.
__CallstackDepthFix is specified manually because I didn't know how to fix it. The callstack depth changes whenever the functor handler 'continues' the previous invocation and because the base of target is temporarily changed for that resumed invocation, the functor handler can't scan the original inheritance tree of the target to determine the proper callstack depth fix (or at least it looks like it).
//edit: the example requires >=1.1.20 (because of
this) or at least >=1.1.19 because of
.Call.