PROC ... ENDPROC

Syntax:

proc <proc_name> [(<any param1>, <any param2>, ...)]
<statements>
endproc
call <proc_name> [(<any param1>, <any param2>, ...)]

The PROC command introduces a new procedure that spans until the ENDPROC command. Procedures are named subroutines that have a unique variable and label scope. Procedures may have one or more parameters that are passed to by the CALL command. The CALL command is the only way to execute a procedure (you can't GOTO into a procedure, for example). You can use RETURN to early exit a procedure.

Example:

rem ** procedure example **
rem ** these variables are global **
a = 1
b = 2

proc printmin(x, y)
  rem ** x, y and a are local variables **
  a = 3
  if x < y then print x else print y
endproc

call printmin(a, b)
call printmin(-1, -5)
print a

The above program will output (note that the value of a remained 1 in the global scope):

1
-5
1

Parameter types

Parameters, like other variables have types. If the passed parameter's type mismatches the expected type, implicit type conversion will occur and a warning will be emitted.

For example:

proc output_params(byteparam!, intparam)
  print byteparam!
  print intparam
endproc

myByte! = 42
myInt = 300

call output_params(myInt, myByte!)

Note that wrong types are passed in the above example. The compiler will emit a warning about truncating the parameter myInt to byte. The other parameter, myByte! will be silently promoted to integer. Thus the above example will output:

44
42

Accessing global variables

To access global variables from within a procedure, prefix the variable name with the \' modifier. Example:

let a=1

proc someproc
  let a=2
  print \a
endproc
rem ** will display: 1	
call someproc

Simulated output params

Although procedures can't return a value or be passed an output parameter, you can simulate this feature by passing an address to a procedure. Here is an example:

proc sumofints(a, b, result_addr)
  sum = a+b
  doke result_addr, sum
endproc

result = 0
call sumofints(2019, 25, @result)
print result

Locals are static

Local variables of a procedure are static which means they are not dynamically allocated on each procedure call. This also means they keep their values through subsequent executions of the same procedure. Take the following example.

proc staticexample(firstrun)
  dim a
  if firstrun = 1 then let a = 1 else inc a
  print a
endproc

call staticexample(1)
call staticexample(0)

The above program will output:

1
2

Procedures without parameters

To declare and call parameterless procedures, just omit the parentheses:

proc simpleproc
  print "hello from simpleproc"
endproc

call simpleproc

Recursion

XC=BASIC supports direct recursion which means that a procedure can call itself. Indirect recursion is not supported.

When the compiler detects direct recursion, it generates code to save local variables and procedure parameters on the stack and restore them when the procedure returns.

Prevent stack overflow

Be careful with recursion. The C64's stack is very small, it's very easy to provoke a stack overflow. To estimate how big stack space you'll need, you can use this formula:

(size of params + size of local vars + 2 byte address ) * possible number of iterations

Always leave some stack space for normal operations as well (8-16 bytes in general, but this can be more depending on the context).

Tip: a good practice to prevent stack overflow is to move as many local variables to the global scope as possible.