Summary:
See also: Variables, Data Types, Flow Control
The FUNCTION statement defines a named program block containing a set of statements to be executed when the function is invoked.
[PUBLIC|PRIVATE] FUNCTION function-name ( [ argument [,...]
] )
[ define-statement | constant-statement ]
{ fgl-statement | sql-statement | return-statement
} [...]
END FUNCTION
where return-statement is:
RETURN expression [,...]
A function can be invoked with the the CALL statement.
By default, functions are public; They can be called by any other module of the program. If a function is only used by the current module, you may want to hide that function to other modules, to make sure that it will not be called by mistake. To keep a function local to the module, add the PRIVATE keyword before the function header. Private functions are only hidden to external modules, all function of the current module can still call local private functions.
The data type of each formal argument of the function must be specified by a DEFINE statement that immediately follows the argument list. The actual argument in a call to the function need not be of the declared data type of the formal argument. If data type conversion is not possible, a runtime error occurs.
Function arguments are passed by value (i.e. value is copied on the stack) for basic data types and records, while dynamic arrays and objects are passed by reference (i.e. a handle to the original data is copied on the stack and thus allows modification of the original data inside the function). See also BDL Stack section.
Local variables are not visible in other program blocks. The identifiers of local variables must be unique among the variables that are declared in the same FUNCTION definition. Any global or module variable that has the same identifier as a local variable, however, is not visible within the scope of the local variable.
A function that returns one or more values to the calling routine must include the return-statement. Values specified in RETURN must correspond in number and position, and must be of the same or of compatible data types, to the variables in the RETURNING clause of the CALL statement. If the function returns a single value, it can be invoked as an operand within a expression. Otherwise, you must invoke it with the CALL statement with a RETURNING clause. An error results if the list of returned values in the RETURN statement conflicts in number or in data type with the RETURNING clause of the CALL statement that invokes the function. See also BDL Stack section.
Any GOTO or WHENEVER ERROR GOTO statement in a function must reference a statement label within the same FUNCTION block.
A function can invoke itself recursively with a CALL statement.
If no argument is specified, an empty argument list must still be supplied, enclosed between the parentheses.
If the name is also the name of a built-in function, an error occurs at link time, even if the program does not reference the built-in function.
When passing arguments to a function or when returning values from a function, you are using the Genero BDL stack. When you call a function, parameters are pushed on the stack; before the function code executes, parameters are popped from the stack in the local variables defined in the function. When a function returns values, data is pushed on the stack and popped into variables specified in the RETURNING clause of the caller.
Elements are pushed on the stack in a given order, then popped from the stack in the reverse order. This is transparent to the BDL programmer. However, if you want to implement a C Extension, you must keep this in mind.
According to the data type, parameters are passed by value or by reference.
The following data types are passed by value to a function:
When passing by value, the runtime system pushes a copy of the parameter data on the stack. As a result, inside the function, the local variable value receiving the parameter value can be changed without affecting the data used by the caller.
You can pass a RECORD structure as a function parameter with the dot star (.*) notation. In this case, the record is expanded and each member of the structure is pushed on the stack. The receiving local variables in the function can then be defined individually or with the same record structure as the caller. The next example illustrates this:
01
MAIN02
DEFINE rec RECORD03
a INT, b VARCHAR(50)04
END RECORD05
CALL func_r(rec.*)06
CALL func_ab(rec.*)07
END MAIN08
-- function defining a record like that in the caller09
FUNCTION func_r(r)10
DEFINE r RECORD11
a INT, b VARCHAR(50)12
END RECORD13
...14
END FUNCTION15
-- function defining two individual variables16
FUNCTION func_ab(a, b)17
DEFINE a INT, b VARCHAR(50)18
...19
END FUNCTION
It is possible to pass a complete static ARRAY as a function parameter, but this is not recommended. in this case, the complete array is copied on the stack and every element is passed by value. The receiving local variables in the function must be defined with the same static array definition as the caller:
01
MAIN02
DEFINE arr ARRAY[5] OF INT03
CALL func(arr)04
END MAIN05
-- function defining same static array as the caller06
FUNCTION func(x)07
DEFINE x ARRAY[5] OF INT08
...09
END FUNCTION
Therefore, it is better to use dynamic arrays instead.
The following data types are passed by reference to a function:
Passing a dynamic ARRAY as a function parameter or as returning element is legal and efficient. When passed as parameter, the runtime system pushes a reference of the dynamic array on the stack, and the receiving local variables in the function can then manipulate the original data. You can also return a dynamic array from a function: The runtime system pushes also a reference of the dynamic array. The next example illustrates this:
01
MAIN02
DEFINE arr DYNAMIC ARRAY OF INT03
DISPLAY arr.getLength()04
LET arr = init(10)03
DISPLAY arr.getLength()04
CALL modify(arr)05
DISPLAY arr[50]06
DISPLAY arr[51]07
DISPLAY arr.getLength()08
END MAIN09
--10
FUNCTION init(c)11
DEFINE c INT12
DEFINE x DYNAMIC ARRAY OF INT13
FOR i=1 TO c14
LET x[i] = i15
END FOR16
RETURN x17
END FUNCTION18
--19
FUNCTION modify(x)20
DEFINE x DYNAMIC ARRAY OF INT21
LET x[50] = 22222
LET x[51] = 33323
END FUNCTION
Output of the program:
0
10
222
333
51
Like other OOP languages, Genero passes objects of classes or java classes by reference. It would not make much sense to pass an object by value, actually. The runtime pushes the reference of the object on the stack (i.e. the object handler is passed by value), and the reference is then popped to the receiving object variable in the function. The function can then be used to manipulate the original object.
01
MAIN02
DEFINE ch base.Channel03
LET ch= base.Channel.create()04
CALL open(ch)05
CALL ch.close()06
END MAIN07
-- function using the channel object reference08
FUNCTION open(x)09
DEFINE x base.Channel10
CALL x.openFile("filename","r")11
END FUNCTION
BYTE or TEXT data types are actually large data object handlers internally implemented as "LOB locators". When you pass a BYTE or TEXT to a function, the LOB locator is pushed on the stack and popped to the receiving BYTE or TEXT variable in the function. The actual LOB data is not copied, only the LOB handler is passed by value. However, the data of the LOB locator is copied - information like LOCATE filename is copied. If you modify the locator inside the function, the locator in the caller will become invalid.
Note that you should not re-locate a BYTE or TEXT locator in a function. Since the LOB locator information is copied by value, modifying the LOB handler inside a function makes the LOB handler of the caller invalid.
When you return values from a function with the RETURN clause, the values are pushed on the stack and popped to the variables specified in the RETURNING clause.
Simple data types that are passed by value are returned by value following the same principle. Complex data types are not returned by value, only the reference is pushed on the stack. You can, for example, create an object or a dynamic array in a function and return it to the caller for usage.
When a value or a reference is popped from the stack, implicit data conversion takes place. This means, for example, that you can pass a string value to a function that defines the receiving variable as a numeric data type; no compilation error will occur, but you can get a runtime error if the string cannot be converted to a numeric. The same principle applies to values returned from functions, since the stack is also used in this case.
For more details about possible data conversions, see the Data Type Conversion Table.
01
FUNCTION findCustomerNumber(name)02
DEFINE name VARCHAR(50)03
DEFINE num INTEGER04
CONSTANT sqltxt = "SELECT cust_num FROM customer WHERE cust_name = ?"05
PREPARE stmt FROM sqltxt06
EXECUTE stmt INTO num USING name07
IF SQLCA.SQLCODE = 100 THEN08
LET num =-109
END IF10
RETURN num11
END FUNCTION
01
PRIVATE FUNCTION checkIdentifier(name)02
DEFINE name VARCHAR(50)03
IF name IS NULL THEN RETURN FALSE END IF04
IF name MATCHES "[0123456789]*" THEN RETURN FALSE END IF05
RETURN TRUE06
END FUNCTION