How to go from method to method without making the call stack grow.
Ravenkallen
Posts: 1,057
Hey, as still being new to the propeller, i have had a question that has really bothered me. Can a program go to one method and then to another, and then, while in that method go back and forth from another method without increasing the stack. Is there some way to constantly cut the stack? If not then would one handle going back in forth between methods? I know in BASIC, one can just use the goto command to zip around the program without worrying about a "Call stack". ANY info would be greatly appreciated
Comments
About the only thing that's difficult to do without GOTOs is an exit from several levels deep, usually for some kind of error recovery. The ABORT statement and "catch" operator ("\") are intended for that case and work quite well. Read the Propeller Manual for details.
I'm really not talking about putting a whole system in just one method. I'm talking about separating the various functional pieces into separate methods, then combining them via method calls into sequences of functional pieces, maybe several levels deep.
How about a description of what you're trying to do?
Have you looked at programs in the Object Exchange? It's kind of complex, but you could look at BoeBotBasic for examples of this kind of structuring. Most of the program exists in only a few levels. For expression handling, there's recursion being used, but the program checks to make sure that there's some stack space left, so the recursion is explicitly limited. Most of the methods are relatively short except for one having to do with expressions and one having to do with statements. In both cases, there are large CASE statements to handle the many possible cases, but the cases are pretty independent and not very complicated.
If so, you can theoretically include a parent object as a sub-object of a child object, but the behavior is a bit weird. Whenever you use an object in more than one place, Spin creates a new set of VARs for each instance, so they essentially act independently; the sub-child object will have different data and not know anything that the parent object knows. BUT Spin only creates one set of an object's DAT data, which is shared between all instances of the object. It's possible to do this deliberately -- you can use DAT interchangeably with VAR for a lot of purposes -- and since all instances of an object share DAT, they will all have the same state and calls to methods of any instance would all have the same effect. This is really useful for things like debug output objects, since you can put them under all your other real objects and they all send data to the same place.
I suspect your problem is falling out of a menu selection back to the menu, from any point (including all kinds of buried errors) within the selected function. That's what abort is for. When you abort, it doesn't return to the PUB or PRI that called it; it returns all the way up the chain until it reaches a call that was issued with the catch flag \. (In fact, abort works across objects, so you can abort all the way out of one object into a call from its parent object.) Here's how you might use it:
Now in your subfunctions you use return or fall out of the method to return normally, but you use abort from ANYWHERE to wash your hands and go back to pickmenu:
What happens here is if somefunction fails and issues its abort, it doesn't return to its call in suba; it returns to the call OF \suba because that has the abort catch \. Combined with the repeat stuff this lets you do pretty much everything you need to for pretty complex call chains.
This usually leads to large tangled and unmanageable code.
As Mike says a better way to do this is to have a data structure to represent the menus. Could be just arrays of menu item strings and associated action codes etc. Then you have some "state" variable to indicate the current position in the menu system, perhaps just an index into those arrays With that in place one can use the same smaller pice of code to navigate around all parts of the menu system. This has the bonus that when you want to add more menu items you add them to the data structure and you don't need to change the menu navigation code.
Basically move your problem out of code into data structure(s). Add a little more "abstraction".
This can be a good technique with menus or many other programming tasks.
I might disagree with localroger about the use of ABORT to do this. I suspect that ABORT should only be used for error situations rather than implementing the main logical pathways through the code.
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
I would disagree that abort should only be used for errors; I'd say it should be used for aborts. Sometimes a main logical pathway IS an abort, and that's the tool Spin gives us to bail out of deep code when there is a good reason. There are many non-error related reasons to want to do this, and without abort I can think of several situations I've been in already where Spin's extra-pure no-goto-ness would have made life impossible.
@localroger.... You have not heard of the Propeller Education Kit Labs Fundamentals book? It is like the official beginners guide.
Each menu starts with a menu description string followed by several choices ended by a zero byte.
The first choice byte is either a relative pointer to a submenu to be used if this choice is selected or
a value $80 or greater that's returned by the menu routines as the final choice code. The next byte
is the character to be typed by the user followed by a string with the menu choice description.
You'd have a simple interpreter that starts with the index of the first entry in the table, displays the
menu description and choices allowed, then waits for the user to enter the code for the choice or
uses the arrow keys or a front panel up and down button to select the choice to be used. If the
choice selects a submenu, you could recursively call the display routine with the index of the
submenu or you could have a small internal stack that remembers where in the menu hierarchy
you might be and the display routine would be in a REPEAT statement. You'd only need a small
byte array for this since the menu table index would be less than 256 bytes the way I've described it.
All sorts of variations on this scheme are possible depending on the specific circumstances involved.
Sadly I have no such examples.
Reminds me of about the first program I ever wrote when myself and a friend were teaching ourselves ALGOL (would you believe?). We were wanting to draw an electronic circuit diagram on a pen plotter. Well every component was drawn stroke by stroke by a different piece of code. There was a transistor routine, a capacitor routine, a resistor routine, etc etc etc. Then a big main function to call all of those and draw all the connections. Well it worked. It was huge, it was unmaintainable. Then it dawned on us "Lets have a function that draws symbols, any symbol, and let the shape of the each be an array of points so that we can easily add more or change what we have" and then "lets do the same for positioning the symbols and drawing the connections"....
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
For me, the past is not over yet.
if you jump down into menu-items maybe 20 levels deep by calling methods with one or two parameters the RAM used for the stack is not that much.
When ever you RETURN from a method the used stackspace will be reduced. This means if you don't allow to jump from sub-sub-sub-sub-menu A to jump DIRECTLY
to sub-sub-sub-sub-menu B like in a fishermans net. If you just allow to jump down Menu-tree A and before you can jump down menu-tree B you have to go back
to the main-menu that way
Path-A
Main-Asub
Main-Asub-sub
Main-Asub-sub-sub
Main-Asub-sub-sub-sub
Main-Asub-sub-sub-sub-sub
going back ONLY upwards possible
Main-Asub-sub-sub-sub-sub
Main-Asub-sub-sub-sub
Main-Asub-sub-sub
Main-Asub-sub
Main-Asub
Main
and then start jumping along the B-path
Path-B
Main-BSub
Main-BSub-Sub
Main-BSub-Sub-Sub
Main-BSub-Sub-Sub-Sub
Main-BSub-Sub-Sub-Sub-Sub
Main-BSub-Sub-Sub-Sub-Sub-Sub
the amount of stack uses for the method-calls will stay pretty small
So I asked myself where is the problem at all?
best regards
Stefan
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
May the road rise to meet you; may the sun shine on your back.
May you create something useful, even if it's just a hack.
Pub main
repeat
·if somevar == 1
· somevar := 0
· main
·if somevar == 2
· waitcnt(clkfreq + cnt)
<More code below>
The first if statement would go to the beginning of pub Main. Would that increase stack space? I don't think it would seeing as you are in the same method.
Pub main calls itself. This is called recursion. The base or exit point is whenever somevar <> 1. If somevar always == 1, you will quickly run out of stack space.
There are very few reasons to use recursion such as in advanced data structures or some math toys. Even there, methods can be written to be iterative (non-recursive).
It is very unlikely that you really need to use recursion. Take Mike and others' advice given here.
Good luck.
--Steve
▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
May the road rise to meet you; may the sun shine on your back.
May you create something useful, even if it's just a hack.
It's for making the solution of some problems easier, utilizing the stack space as memory for storing intermediary results. The easiest example for a recursion is the factorial (whilst it's not very usefull to do that in a recursion).
5! = 1*2*3*4*5
Each recursion needs an end-condition, otherwise you will exceed any available memory - whether it's a propeller having at max. some kB stack or a PC having MBs of stack.
In the propeller case for each call of a function there is at least a return value, a return address, the parameters and any local variable of that function stored on the stack.
So, back to the factorial ... as a recursion you'd say:
pub fac( val )
' this is the end condition
if val=0
return 1
' this is the recursion
else
return val * fac( val-1 )
The initial call of fac with parameter 5 will then result in:
5 * fac( 4 * fac( 3 * fac( 2 * fac( 1 * fac( 0 ) ) ) ) )
The important thing in this recursion is, that the values 5, 4, 3, 2, 1, 0 are put to the stack on it's way to reaching the end-condition. On the way back the intermediary results are calculated and passed back and with the last return (the one of fac( 5 )) we'll then have the end result.
As I said this example is easy and also is easy to be coded in an iteration. But in case more functions are involved in the recursion it get's more and more complex to make it an iteration, as you then have to think about how to organize the intermediary results.
here an example how your example can be done without recursion
inside this code the method main calls the method MyMethod1 again and again.
This is NOT occupying more and more RAM because the calls are done SEQUENTIALLY
Main calls MyMethod1 and a small amount of RAM is used for the callstack
After all lines of code of MyMethod1 are executed the program returns to the level of main
in that moment the stackspaces used for the callstack gets free again
then the method MyMethod1 gets called again AFTER the stackspace is free
occupying again some bytes
so the amount of stackspaces needed alternates between
example
4 bytes
0 bytes
4 bytes
0 bytes
4 bytes
0 bytes
4 bytes
0 bytes
4 bytes
0 bytes
if you do it recursive it looks like this
4 bytes
8bytes
12bytes
16bytes
.......
ALL RAM eaten up by recursive calls
best regards
Stefan
Post Edited (StefanL38) : 5/28/2010 3:42:13 PM GMT
the basic principle is to have a master repeat-loop that calls other methods
To jump back to a higher menu-level you don't call the method again.
You END the execution of the code of the actual method and then the jump takes place automatically
These other methods can call methods again down ONE tree-path (like shown in a former posting)
but come back through exiting the method
I guess you are somehow locked in a kind of thinking with basic goto 100, goto 10
If you would like to attach your code to a psoting we could take a closer look into your menu construction.
best regards
Stefan