Παρασκευή 6 Σεπτεμβρίου 2024

M2000 Check the call stack depth for modules, functions (and lambda functions), subs and simple functions

M2000 is an interpreter, so calling modules and functions needs more stack space (actually there M2000 Interpreter is a big function which call other functions including the big function).

We can say about "call stack depth", when each 1 more depth is one call in M2000 level. In M2000 way to deploy a call, the original return or process stack of processor used for specific internal calls and the parameters known from M2000 Interpreter at hidden level. The parameters at the high level are passed to stack of values which isn't in return/process stack.  So the number of parameters at call at M2000 Interpreter level are not interfere to number of calls. The stack of values, uses objects from a pool of objects, which can expand from a start number of 5000 objects. A call to a module pass the so called current stack of values. A call to function when this happen in an expression uses a new stack of value, with all parameters, with the first from the left on the first object (position 1) of stack of values, so the 3rd  parameter written to 3rd object on stack of values. When we pop a value to a variable (using Read statement) M2000 place the object to pool, with one cxception: If we copy objects on top of others, for same value the interpreter just copy the pointer to object, so when we pop a value, and there are more than one pointer attach to object, the object not passed to pool. The internal objects use a counter of pointers to it, and when the counter turn to zero the object deleted. For stack of values, the objects not used more placed to pool, so never goes under 1. Using the Clear statement at immediate mode, clear all variables and set the pool to 5000 objects. 

The program has five modules.

Output:

Recursion depth calling modules using CALL:7037
Recursion depth calling function using CALL:7037
Recursion depth calling function in expression:3314
Calling functions depth (A() calling B(), B() calling A()):2297
Calling modules depth (A calling B, B calling A):8329

Program. Instructions: Type Edit A, copy this program, then press Esc to exit from editor, type A and press Enter key to run it:

global Rep$
document Rep$ ' upgrade string to document object
module global A {
global z
module rec (x){
z<=x
call rec, x+1
}
try {
call rec 1
}
Rep$ <= "Recursion depth calling modules using CALL:" + z + {
}
}
module global B {
global z
function rec(x){
z<=x
call rec(x+1)
}
try {
call rec(1)
}
Rep$ <= "Recursion depth calling function using CALL:" + z + {
}
}
module global C {
global z
function rec(x){
z<=x:=rec(x+1)
}
try {
m=rec(1)
}
Rep$ <= "Recursion depth calling function in expression:" + z + {
}
}
module global D1 {
global z
function global a(x) {
z<=x
m=b(x+1)
}
function global b(x) {
z<=x
m=a(x+1)
}
try {
m=a(1)
}
Rep$ <= "Calling functions depth (A() calling B(), B() calling A()):" + z + {
}
}
module global D2 {
global z
module global a (x, y, k) {
z<=x
b x+1, 2, 3
}
module global b (x, y, k) {
z<=x
a x+1, 2, 3
}
try {
a 1,2, 3
}
Rep$ <= "Calling modules depth (A calling B, B calling A):" + z + {
}
}
a : b : c : d1 :d2
Report Rep$
Clipboard Rep$


Recursion for Subs


There is another call type, for SUBS. There is a Recursion.Limit statement to set the limit for Subs. If we call beyond the limit we get a fatal error (not stopped from Try statement), we return to immediate mode (if the program run from that mode, else the interpreter terminate execution). This limit can be used when we didn't use blocs of statements (using curly brackets) or statements which add another level like the  block  { }, like While End While. The if then/else.if then/end if muliline statement not used block, same the one line if then or if else (but not if then else), but the if then  else then  in one line, or multiline using { }, they use blocks.
By default recursion.limit set to 10000

In the example below we set linit it to 100000 calls.

Calling subs depth (a() calling b(), b(0) calling a()):100000
global r.limit=100000
set recursion.limit r.limit


global z=0
module testme {
a(1, 2, 3)

Sub a(x, y, k)
z<=x
if z<r.limit then b(x+1, 2, 3)
End Sub
Sub b(x, y, k)
z<=x
if z<r.limit then a(x+1, 2, 3)
End Sub
}
testme
Rep$ = "Calling subs depth (a() calling b(), b(0) calling a()):" + z + {
}
report Rep$
clipboard Rep$


Another variant (we can't go to 33000 calls)

The Gosub clause is optional for calling subs


Out of limit in b()
Calling subs depth (a() calling b(), b(0) calling a()):32700

global r.limit=32700, Rep$
Document Rep$
set recursion.limit r.limit


global z=0
module testme {
a(1, 2, 3)

Sub a(x, y, k)
z<=x
if z<r.limit then
gosub b(x+1, 2, 3)
else
Rep$ <=  "Out of limit in a()"+{
}
end if
End Sub
Sub b(x, y, k)
z<=x
if z<r.limit then
gosub a(x+1, 2, 3)
else
Rep$ <= "Out of limit in b()"+{
}
end if
End Sub
}
testme
Rep$ <= "Calling subs depth (a() calling b(), b(0) calling a()):" + z + {
}
report Rep$
clipboard Rep$


For simple functions:

We can't go to r.limit, because the expression calling depth stopped to 2328 calls. This we can handle with Try { } block. Check that calling simple functions we use @ at the calling. Simple functions can't be called using Call statement. Also the stack of value are not new for each call. Interpreter pass the same stack of value and put the parameters to pop first, starting at top with the left one on the call parameter list.

Calling subs depth (a() calling b(), b(0) calling a()):2328

global r.limit=32700, Rep$
Document Rep$
set recursion.limit r.limit


global z=0
module testme {
m=@a(1, 2, 3)

Function a(x, y, k)
z<=x
if z<r.limit then
local m=@b(x+1, 2, 3)
else
Rep$ <=  "Out of limit in a()"+{
}
end if
=100
End Function
Function b(x, y, k)
z<=x
if z<r.limit then
local m=@a(x+1, 2, 3)
else
Rep$ <= "Out of limit in b()"+{
}
end if
=200
End Function
}
try {
testme
}
Rep$ <= "Calling subs depth (a() calling b(), b(0) calling a()):" + z + {
}
report Rep$
clipboard Rep$




Lambda functions are like functions.
Add this module to first program and add the call to D3 after the call of D2
We get the same value for functions:

Calling lambda functions depth (A() calling B(), B() calling A()):2297


module global D3 {
global z
global a=lambda (x) ->{
z<=x
m=b(x+1)
}
global b=lambda (x) -> {
z<=x
m=a(x+1)
}
try {
m=a(1)
}
Rep$ <= "Calling lambda functions depth (A() calling B(), B() calling A()):" + z + {
}
}


Y combinator style call:

We can call a no names function (is a lambda function) using Y combinator.

fibonacci 10th = 55

clipboard "fibonacci 10th = "+(lambda (g, x)->{=g(g, x)}(lambda (g, x)->if(x<=1->x,g(g, x-1)+g(g, x-2)), 10))
wait 10 ' give some time for system
print clipboard$


factorial of 27 = 10888869450418352160768000000

clipboard "factorial of 27 = "+(lambda (g, x)->{=g(g, x)}(lambda (g, x)->if(x=0->1, x*g(g, x-1)), 27@))
print clipboard$

Epilogue

We see all the type of calls, except some extra non ordinary, which have to do with "call back" way of call. Parameters passed by default as copies (there are rules for objects, some of them are passed as is but when we pop them we get a copy, like arrays with ( ), bit not for arrays as pointers to array objects). We can pass by reference but we have to declare we need a reference at the called function/module/sub/simple function/lambda

This is an example for call back call, and for lazy evaluation. The Link statement normally can't link twice an identifier, except for functions (don't do that with functions as members of objects of type Group, at newer version interpreter will not allowed it).
 
m=0
counter=0
document resp$
// normaly m, counter and resp$ not seen from a normal function
// nut this used as a code of this module - see later
function dummy(value_for_counter) {
m+=20
counter+=value_for_counter
resp$=format$("{0::-10} +", value_for_counter)+{
}
}


module callback_example (&func()) {
for m=1 to 10
call func(random(1, 2))
next
}
// lazy$ make a reference to dummy() for later evaluation, like it is at this level
// so m exist. The code of dummy() called like it is at this level
// but if we make some new variables or modules or functions...
// these deleted at the return of the call
callback_example lazy$(&dummy())
resp$=format$("{0::-10} =", counter)+{
m=200 is }+(m=200)+{
done
}
// Report resp$ place the text with proportional rendering which we didn't want here
// Print #-2  render like we place text in a file, but we get it in screen.
// There is no page hold function like the one for the Report statement
Print #-2, resp$
link weak lazy$(m + 500) to k()
Print k()=700
link weak lazy$(m + 600) to k()
Print k()=800
module see_this (&f()) {
Print f()=800
}
// This is a laxy evaluation
see_this lazy$(m+600)