! BustAFlow             -       Glulx library that provides up-the-object-tree control flow ("wolverine").
! By Brendan "BrenBarn" Barnwell, alias OKB ([email protected])
!!!
! Development Log
!       Date            What the dilly-o
!       2001/05/28      Finished!  Um, yeah.  Well, I actually started about a week ago, but I initially had
!                       this as part of the Entry library.  Since it's Glulx-only, I decided to separate it from
!                       Entry.  I have done basic testing, and it seems to work with GxScript now.
!       2001/05/29      More testing, a few bug fixes.
!               RELEASED
!!!

!!!!!
! "Documentation"
!       You should be able to get away with Including this library at any point in your source code.
!       99% of this library's work is done with one routine: BustAFlow.  The comments near that routine describe how to use it.  Two other routines, GetParent and AlwaysPass, are provided as defaults for use by BustAFlow.
!       The only reasons this is a Glulx-only library are: A) it uses more than 7 function arguments (the max allowed by the Z-machine); and B) it relies on the legality of passing functions more arguments than they have local variables, which I am not certain of in the Z-machine.  If the Z-machine DOES support extra function arguments, I may do a Z-machine version of this library which passes arguments in another way (such as via an array) to get around reason A.
!       The concept behind BustAFlow is fairly simple, but it makes liberal use of recursion, and hence can sometimes be difficult to puzzle out.  What follows is a basic description of what BustAFlow does and why this is cool.
!       Normally, you invoke an object property with a call like foo.bar().  foo.bar is run, and a value is returned.  That's it.  BustAFlow allows you instead call BustAFlow(bar, foo).  In this case, foo.bar() is run, but it doesn't necessarily end there.  If foo.bar() returns false (or foo doesn't provide bar), (parent(foo)).bar() is run.  If that returns false (or doesn't exist), (parent(parent(foo))).bar() is run, and so on until either: A) something returns a nonzero value; or B) the top of the object tree is reached (the next object has no parent).
!       Why is this cool?  Well, for everyday programming, it's not terribly useful.  Most game objects are not desgined to have this kind of relationship with their ancestors.  (Insert genealogical joke here.)  BustAFlow (like another of my libraries, Entry) is really a meta-tool.  It doesn't help you write games; it helps you write things that help you write games.
!       BustAFlow can be useful when you are creating object trees which are being used to represent something other than actual game objects: some kind of data structure, perhaps.  It allows you to use the object tree in much the same way as you would use a class tree.  Parent objects can define broad behaviors in much the same way as superclasses; child objects can refine those behaviors in much the same way as subclasses.
!       An example of this is my GxScript library.  GxScript uses Inform objects to represent abstract entities (conversation topics), not physical game-world objects.  It uses BustAFlow to enhance the idea of conversation topics "containing" others: a larger conversation topic can define behaviors for an NPC, and more specific topics can override this behavior.
!!!
!       Questions/comments/etc. go to [email protected]
!!!!!

#IfNDef BustAFlow;
#IfDef Target_Glulx;

Message "[Including <BustFlow>]";

! A useful function for creating control-passing object trees (a la GxScript)
! BustAFlow     Property        StartObj        [Test]          [Update]
!               property        object          Routine Routine
! May return anything!
! Returns the return value of the up-the-tree control flow
!       BustAFlow is a Glulx-only function, since it uses more than 7 arguments.
!       BustAFlow is the key to creating object trees which pass control flow from object to object up the tree.  BustAFlow(Property,StartObj) will begin a flow of control up the tree starting at StartObj.  For each object Obj, Obj.Property() will be called.  If any such call returns false (or if Obj does not provide Property), control will be passed to the next object up the tree.  If any such call returns a value other than false, that value will be returned as the return value of BustAFlow.  Your code should call BustAFlow with appropriate arguments to begin the control flow, and can then deal with the return value however you please.
!       An optional arugment Test (a routine) may be provided.  This routine will be passed the current Obj and the Update function.  If it returns true, Obj.Property() will be executed as usual.  If it returns false, Obj will be skipped in the control flow (just as if it did not provide Property).  The default Test routine allows all objects to "pass the test" (i.e., have their Property run).  (Note that Test will NOT be called if Obj does not provide Property.)
!       An optional argument Update (a routine) may be provided.  This routine will be passed the current Obj and the Test function.  It should return the next object up the tree, or false if the control flow should stop here.  (This allows you to pass control along an alternate route instead of straight up the tree.)  The default Update routine simply returns parent(Obj), passing control straight up the object tree.
!       You can pass BustAFlow up to 12 arguments.  Any arguments after the first 4 will be passed as-is to each of the routines that BustAFlow calls (Test, Update, and the specified property routine).  This way you can pass whatever information you want to all these routines, using the extra 8 arguments.  Note, however, that you MUST give all 4 arguments required by BustAFlow before you can give your own arguments (although you can pass 0 for test and update, in which case the default will be used).  So you can't call BustAFlow(myProp,thisNode,SpecialArugment,AnotherSpecialArgument) -- BustAFlow will interpret the last two as test and update, respectively, and this will cause havoc.  Instead you must call BustAFlow(myProp,thisNode,0,0,SpecialArgument,AnotherSpecialArgument).
!       Remember that Test, Update, and Property are passed 2 arguments each anyway: Test is passed the working node and the update function, Update is passed the working node and the test function, and Property is passed the test and update functions.  Any extra arguments you provide will passed in addition to these, so that your first extra argument will be the third argument passed to each of the functions.  For example, if you call BustAFlow(someProp,someNode,someTest,someFunc,foo,bar), it will call someTest(worknode,someFunc,foo,bar), someFunc(worknode,someTest,foo,bar) and worknode.someProp(someTest,someFunc,foo,bar).
[BustAFlow prop worknode test update
x1 x2 x3 x4 x5 x6 x7 x8                 ! These arguments are for the programmer's use
a;                                              ! Extra "scratch" variable
if (test==0) test=AlwaysPass;
if (update==0) update=GetParent;
       while ( worknode && ( (~~(worknode provides prop)) ||
       (~~(test(worknode, update, x1,x2,x3,x4,x5,x6,x7,x8))) ) ) {
worknode=update(worknode, test, x1,x2,x3,x4,x5,x6,x7,x8);
       }
if (worknode==0) rfalse;
a=worknode.prop(test, update, x1,x2,x3,x4,x5,x6,x7,x8);
if (a) return a;
a=update(worknode, test, x1,x2,x3,x4,x5,x6,x7,x8);
if (a) return BustAFlow(prop, a, test, update, x1,x2,x3,x4,x5,x6,x7,x8);
rfalse;
];

! The default Test for BustAFlow
! Always returns true
[AlwaysPass;
rtrue;
];

! The default Update for BustAFlow
! Simply returns the parent of its argument
[GetParent x;
return parent(x);
];
#EndIf;
#EndIf;