DEFSUB' subroutine entry point


General Form:

[PUBLIC|PRIVATE|FILE PRIVATE] {DEFSUB | CALLBACK | PROTOTYPE } 'label([[BYREF] dim_element [, [BYREF] dim_element] ... [, VARARGS numeric_identifier]]) [[ attribute ] ...]
...
[FINALLY]
...
[END SUB]

Where:
defsub
diml, dim2= dimensions (numeric expressions with a value > 0 and < 65536)
length= numeric expression with a value > 0 and < 65536. If no length is specified then the default is 16
attribute= [ ASclause [ USEclause ] | RETURNSclause] [TRANSclause] [THROWclause]
ASclause= AS type, where type is a predefined type or enumerated type
THROWclause= THROW [ throw1 [ , throw2 ] ...] | ROLLBACK where throw1, throw2 ... are constants
USEclause= MUST USE
TRANSclause= OUTSIDE TRANS | INSIDE_TRANS
RETURNSclause= NEVER RETURNS

The DEFSUB' statement is similar to the obsolete DEFFN' statement in that it defines a subroutine entry point, the difference being that DEFSUB' copies passed arguments into local variables. Additional local variables may be declared with the LOCAL DIM statement. All local variables are lost when the RETURN statement is executed.

The arguments are defined as local variables with the same dimensions and contents as those being passed to the routine. These can either be numeric, string or field variables and arrays. Alternatively explicit dimensions may be specified in the same way as with a regular DIM statement. If the dimensions of the arguments are explicitly defined within the DEFSUB' statement, then the dimensions must match those of the GOSUB' statement or function otherwise an S24 error (Illegal expression or missing variable) will be reported. As the DIM expressions are evaluated at execution time they can use arguments passed to the routine, for example:

DEFSUB'Get_Rec(Reclen, Another$Reclen)

would DIMension the variable Another$ to the size specified by Reclen. Strings passed into DEFSUB' have their receiving parameter variables DIMed to the same size as the number of bytes being passed. In the following example the local variable msg$ will be created with a size of 5 bytes, the size of the string being passed into the subroutine.

'Greeting("hello")
...
DEFSUB'Greeting(msg$)
...

Subroutine scope

As of KCML 6.0, the optional END SUB statement can be used to define subroutine scope. If present then KCML will consider all LOCAL DIM statements between the DEFSUB and the END SUB to be defining local variables for that routine. When displaying code in the Workbench the body of the subroutine between the two statments will be indented. If executed an END SUB will behave like a RETURN.

DEFSUB'Sum(a, b, BYREF c)
    c = a+b
END SUB

Programmers are strongly encouraged to use END SUB in every subroutine. They are required for $COMPLIANCE level 1, the basic level of compliance.

Passing by reference

The BYREF qualifier may be specified against a numeric, string or label argument, and has to be matched by a BYREF qualifier in the GOSUB' statement. This changes the behaviour so that on exit from the subroutine, the value is copied back into the calling argument. The body of the routine does not then need any special constructs when referencing the variable.
For example:

aValue = 10
aString$ = "Hello"
'aRoutine(BYREF aValue, BYREF aString$)

DEFSUB 'aRoutine(BYREF Total, BYREF Name$)
Total += 100
Name$ = & " World!"
RETURN

DEFSUB 'fred(BYREF 'a)
'a(1)
RETURN

would set the variable aValue to 110 and the variable aString$ to "Hello World!".

BYREF can also be used to pass pointers to numbers into $DECLAREd functions. This tells KCML to pass the address of the number rather than the value as would be the normal convention for numbers. Strings are always passed as pointers so BYREF is not needed for them. The keyword RETURN can also be used as an alternative to BYREF but its use for this purpose is now deprecated and BYREF is to be preferred.

$DECLARE 'GetComputerName(RETURN STR(), TO RETURN INT())
len = LEN(STR(name$))
'GetComputerName(name$, BYREF len)

To pass a function as an argument you can do something like

'fred(BYREF 'b)

DEFSUB 'fred(BYREF 'a)
'a(1)
END SUB

DEFSUB 'b(n)
...
END SUB

Using SYM to pass arguments

It is generaly preferred to use BYREF rather than SYM to reference parameters. For more information on SYM parameters, see Specifying SYM types.

Optional arguments

The specified parameters can include optional arguments. This allows extra arguments to be added to a DEFSUB without affecting any GOSUB that calls it. If arguments are omitted from the call then the specified default value is used instead.
For example:

DEFSUB 'aRoutine(First, Second, Third = 123, Fourth$ = "Hello!")

may be called with:

'aRoutine(1, 2, 3, "Nice Day")
'aRoutine(1, 2, 3)
'aRoutine(1, 2)

Optional arguments may only be specified after normal arguments (if any). It is not possible to leave out intermediate arguments. Only simple numeric and string expressions are supported; arrays of all types and fields do not support optional arguments. In the current implementation the defaulting expression will always be executed even if the corresponding parameter is supplied. This will not be true in future implementations and must not be relied upon. Furthermore, to work with future implementations, the default values ought to be simple expressions that can be evaluated at resolve time. Complex defaulting expressions, such as function results, may be permitted by the KCML 6.x parser but their effect may not be predictable.

The syntax for optional string arguments was extended in KCML 6.0 to allow an explict length to be specifed and to allow BYREF on optional arguments.
For example:

DEFSUB 'a(arg$16="Default")

Variable arguments

The VARARGS qualifier may be specified against the final parameter if no optional arguments have been specified. This indicates that in the parameter's place, the subroutine may be called with zero or more arguments, which will be collected into the final parameter as a KCML_ListClass<KCML_Argument> belonging to the function pool. Such argument collections may be unrolled to $PRINTF, #WHERE, or other subroutines by prefixing with a * in the call.

 00010 $COMPLIANCE 3
     : 
     : DEFSUB 'format_message$(fmt$, code, VARARGS v)
     :     REDIM fmt$ = $PRINTF("%d %s", code, fmt$)
     :     RETURN $PRINTF(fmt$, *v)
     : END SUB
     : 
     : DEFSUB 'fn(VARARGS args)
     :     LOCAL DIM arg AS KCML_Argument
     :     PRINT "hello world"
     :     FOR arg IN args
     :         SELECT CASE arg->type
     :             CASE _KCML_ARGUMENT_NUMERIC
     :             PRINT $PRINTF("arg: type 0x%d, num %d", arg->type, arg->num)
     :             CASE _KCML_ARGUMENT_STRING
     :             PRINT $PRINTF("arg: type 0x%d, buf %s", arg->type, arg->buf$)
     :             CASE ELSE
     :             PANIC
     :             END SELECT
     :         END FOR
     : END SUB
     : 
     : 'fn("hello", 3, "world", 7)
     : PRINT 'format_message$("%s %s", 200, "hello", "world")

PUBLIC, PRIVATE and FILE PRIVATE

As of KCML 6.10, an optional PUBLIC or PRIVATE keyword can prefix the DEFSUB to indicate whether the function should be visible outside of any library it might be defined within. If no prefix is used then PUBLIC is implied. In KCML 7.06 FILE PRIVATE was introduced.

The FILE PRIVATE keyword implements the same scope rules as PRIVATE for interpreted code or KCML6 style code compiled with kc6. For KCML7 style compiled code it limits the scope to the defining source file.

Using the PRIVATE keyword ensures that there won't be a name clash with another library and that changes to the function will not affect programs or other libraries. It is good practice to use this keyword to inform other programmers that they can make these assumptions if they need to modify the function.

FINALLY

The optional finally clause can be used to identify code that should be executed after RETURN is executed in order to tidy up. The code in a FINALLY block will also be executed if a subroutine is abandoned prematurely as can happen in the recovery from a THROW ERR, a THROW ROLLBACK, a restart of an outer WHILE TRANS following detection of a deadlock or a RETURN CLEAR. Note that no value is returned in this case.

There can be only one FINALLY clause in a subroutine and it may not contain any RETURN statements. It should not attempt to alter any value already returned by the RETURN statement. It has no effect if executed directly. It requires a $COMPLIANCE level of at least 1 to be effective. At a $COMPLIANCE of 0, any FINALLY will be ignored and RETURN will transfer control directly back to the caller.

Note that a FINALLY clause invoked by a deadlock being detected causing a transaction to be rolled back and restarted will execute in the context of the new transaction and thus ought not to perform database functions.

Nested subroutines

As of KCML 6.10, subroutines can be nested in order that they can properly share local variables declared in the outer routine. Nested subroutines must use END SUB and are only supported if the $COMPLIANCE level is 1 or more. The inner routines of a nested group are presumed to be PRIVATE to the group and they cannot be called directly from outside the group or from outside the program or library.

As of KCML 7.06, nested subroutines in $COMPLIANCE 2 library code use the parent functions namespace. By using the parent functions namespace the function name only needs to be unique withing that parent not the whole program.

Prototyping and callback functions

If you have a function which is passed another function as a BYREF or SYM argument, you can tell the KCML7 compiler exactly what type of function is allowed by including a prototype definition for the callback function thus:

PUBLIC PROTOTYPE 'fred(x, y$_bert)
DEFSUB 'fn(pfn AS SYM('fred))
   ....
END SUB

Then any attempt to call 'fn() with the SYM of a function that does not take exactly the same arguments as the 'fred() prototype will be caught by the compiler. A prototype has no body and whiile the name must agree with the name used in the argument, the actual name does not have to agree with any implementation.

The actual implementation of the callback function can be declared to the compiler by replacing DEFSUB with CALLBACK as in

PUBLIC CALLBACK 'fred_callback(x, y$_bert)
	// don't need y$ this time
	RETURN 'dosomething(x)
END SUB

This behaves exactly like a DEFSUB except that the compiler will not warn about unused parameters in implementations that do not reference all the defined parameters.

PROTOTYPE declarations are ignored by the KCML6.60 interpreter and CALLBACK is considered a synonym for DEFSUB.

AS clause

This allows the return value of a function to be typed so that the kc compiler can check that the caller uses the result in a compatibe way. If the function does not return a value then it can be typed AS Void and the compiler will check that no result is ever expected. Otherwise the types that follow the AS are the same as those that can be used in DIM and LOCAL DIM. It has no runtime effect.

THROW attribute

This can be used for two purposes. In the first if the THROW keyword is followed by a comma separated list of constants it tells the kc compiler that calling it might result in a THROW being executed and that it must therefore be enclosed by a TRY block either in the calling function or in something that calls it. As TRY blocks can be nested the colmpiler will further check that at least one surrounding TRY block has a CATCH for all of the error constants listed. The compiler can therefore guarantee that all THROWen errors can be caught and an uncaught THROW won't result in the program terminating with a PANIC.

The second use is with THROW ROLLBACK and, in a similar way, it tells the compiler to check that the subroutine has been called from a function that wraps it in a WHILE TRANS block.

MUST USE attribute

Adding a MUST USE attribute on a subroutine tells the kc compiler that the return value of the function must be returned and tested or stored and it will issue a warning if it is not i.e. the function is used as if it returned a void result.

DEFSUB 'Write(handle, BYREF row$) AS KDB_ERROR_ENUM MUST USE
	LOCAL DIM s AS KDB_ERROR_ENUM
	CALL KI_WRITE handle, SYM(row$) TO s
	RETURN s
END SUB
...
'Write(h, BYREF r$)		// compiler error
...
eStatus = 'Write(h, BYREF r$)	// OK

This allows the subroutine author to have some confidence that any error status is at least available and hopefully will be acted on by the caller. This attribute has no effect at runtime.

OUTSIDE TRANS | INSIDE TRANS clause

Applying the TRANS clause to DEFSUB tells the kc compiler that the subroutine must not be / must be called from within a transaction. This clause has no effect at runtime.

NEVER RETURNS clause

Applying a NEVER RETURNS attribute to DEFSUB tells the kc compiler that the subroutine will never return control to the next statement after the call. This might be because it is guaranteed to execute a THROW, PANIC or $END. It allows the compiler to warn if there is any code that depends on it returniing which consequently will never be executed. This attribute has no effect at runtime. It is mutually exclusive with the AS and MUST USE attributes.

Compatibility

END SUB, $COMPLIANCE checking, and optional arguments were introduced in KCML 6.00. PUBLIC, PRIVATE and the nesting of DEFSUBs was introduced in 6.10. THROW, AS, MUST USE, NEVER RETURNS, FINALLY, PROTOTYPE and CALLBACK were introduced in KCML 6.60.

See also:

LOCAL DIM, RETURN, GOSUB', DEFTEST, $DECLARE Exception Handling AS types