FUNCTION and SUB Procedures
The main difference between a FUNCTION
and a SUB is that a FUNCTION returns a value to the FUNCTION call
expression.
FUNCTION...END FUNCTION procedure
Syntax: FUNCTION TheName([Parameters][AS data type])[,AS data type] DIM ReturnValue [Statements] FUNCTION = ReturnValue [Statements] END FUNCTION Parameters:
|
Example:
DIM A! DIM X! DIM Y! X! = 2.2 Y! = 3.1 A! = Myfunc!(X!, Y!) PRINT A! FUNCTION Myfunc!(M!, P!) FUNCTION = (M! * P!) END FUNCTION
Remarks:
Note Well ! Do not modify a literal value or string used as an argument in the FUNCTION calling statement. For example, the attempt to modify "literal" in,
DIM str1$ str1$ = FOO$("literal") PRINT str1$ FUNCTION FOO$(a$) a$ = "k" FUNCTION = a$ END FUNCTION
will cause a crash when the program is run.
OVERLOADED or OPTIONAL FUNCTION procedures are NOT allowed in a user defined type structure.
BCX has support for functions in single line declaration/assignments, for example,
DIM RAW RetVal = foo(x, y, z) AS INTEGER
BCX allows static SUB/FUNCTION to be coded as:
PRIVATE FUNCTION MyFunction() AS INTEGER PRIVATE SUB MySub()
User defined string functions that rely on other string functions usually need to use an intermediate string.
Example:
DIM A$ A$ = Blurb$(65, 66, 67) PRINT A$ FUNCTION Blurb$(A, B, C) DIM Z$ Z$ = CHR$(A) & CHR$(B) & CHR$(C) FUNCTION = Z$ END FUNCTION
FUNCTION declarations emit a TYPE that may be reused.
DIMFUNCTIONFoo$(a$)Any further declarations can be writtenas:DIMMyFunctionPointerASFoo_TYPE
The following is an example which uses the emitted FUNCTION TYPE declaration as a member of a user defined type.
DIMFUNCTIONFoo$(a$)TYPEMyTypeDYNAMICA$[]DYNAMICFN[]ASFoo_TYPE BASINTEGERENDTYPEDIMmtASMyTypeREDIMmt.A$[2]REDIMmt.FN[3]mt.A$[0]="Hello "mt.A$[1]="World"mt.FN[0]=LCASE$mt.FN[1]=MCASE$mt.FN[2]=UCASE$[0](mt.A$[0]), mt.FN$[0](mt.A$[1])[1](mt.A$[0]), mt.FN$[1](mt.A$[1])[2](mt.A$[0]), mt.FN$[2](mt.A$[1])
CALLBACK FUNCTION...END FUNCTION procedure
OVERLOADED or OPTIONAL FUNCTION procedures are NOT allowed in a User Defined TYPE structure.
Syntax 1: CALLBACK FUNCTION CBFuncName() [Statements] END FUNCTION Parameters:
|
Syntax 2: FUNCTION CBFuncName(hWnd AS HWND, _ Msg AS UINT, _ wParam AS WPARAM, _ lParam AS LPARAM) _ AS data type CALLBACK [Statements] FUNCTION = ReturnValue END FUNCTION Parameters:
|
If a return value is not required then the CALLBACK syntax can be expressed as for a SUB using VOID for the CALLBACK data type.
Syntax 3: FUNCTION CBFuncName(hWnd AS HWND, _ Msg AS UINT, _ wParam AS WPARAM, _ lParam AS LPARAM) _ AS VOID CALLBACK [Statements] END FUNCTION Parameters:
|
SUB ... END SUB procedure
OVERLOADED or OPTIONAL SUB procedures are NOT allowed in a User Defined TYPE structure.
Syntax 3: SUB SubName(Parameters) [Statements] END SUB Parameters:
|
Note Well ! Do not modify a literal value or string used as an argument in the SUB calling statement. For example, the attempt to modify "literal" in,
CALL FOO("literal") SUB FOO(a$) a$ = "k" END SUB
will cause a crash when the program is run.
CALL statement
Although subroutines can be invoked with or without the CALL keyword remember that if a SUB does not take arguments and parentheses are omitted, then CALL MUST be used. Following are the only valid methods of calling a SUB with no arguments:
CALL Foo()
or CALL Foo
or Foo()
Let's start with a small Example:
CALL Greeting("Hello from the World of Programming!") Greeting("Hello from the World of Programming!") SUB Greeting(a$) PRINT a$ END SUB
Values can be passed to subroutines. The data type(declarator) of the parameter passing the argument to the subroutine must be indicated. BCX uses this information to construct prototypes for the subroutines and functions so the data type must be explicit for anything except integers. For example, if a string is being passed SUB Passit(A$) would be used not SUB Passit(A). If a DOUBLE is being passed SUB Passit(A#) would be used.
SetConsoleTitle("BCX Demonstration")COLOR15,1frame(15,10,65,20)DIMaINPUTaCOLOR7,0CLSSUBframe(x1, y1, x2, y2)' while interesting, this is meant only as aDIMx' sample SUB. The run-time PANEL statementDIMy' is much faster and more flexibleFORy=y1TOy2FORx=x1TOx2LOCATEy, x,0" ";NEXTNEXTENDSUB
Note Well ! Do not modify a literal value or string used as an argument in the FUNCTION or SUB calling statement. For example, the attempt to modify "literal" in,
CALL FOO("literal") SUB FOO(a$) a$ = "k" END SUB
will cause a crash when the program is run.
Recursive calls can be made from a subroutine. What this means is that the subroutine can be called from within itself. Here's an Example:
Parse("1,2,3,4,5,666,777,88888,99999,101010101010101010") SUB Parse(A$) LOCAL Sep LOCAL B$ Sep = INSTR(A$ , ",") IF Sep > 0 THEN B$ = LEFT$(A$, Sep - 1) PRINT B$ A$ = MID$(A$, Sep + 1, 256) Parse(A$) ' --- Recursive Call --- ELSE PRINT A$ END IF END SUB
Subroutines can be prematurely exited by using the EXIT SUB statement. If you want to use values created in a Subroutine outside of it, then GLOBAL variables must hold the values.
JumpOut() PRINT a$ SUB JumpOut() GLOBAL a$ a$ = "JumpOut" IF a$ = "JumpOut" THEN EXIT SUB END IF PRINT "This line is not executed." END SUB
Arguments and Parameters
Arguments are passed to procedures through parameters.
Passing Arguments to FUNCTION and SUB Procedures
Example 1: This example demonstrates passing a one-dimensional integer array to a function.
DIM A_RAY[10] AS INTEGER Foo(&A_RAY[0], 3) PRINT A_RAY[3] SUB Foo(BYVAL A[], Index AS INTEGER) A [Index] = 12345 END SUB
Example 2: This example demonstrates filling a global two-dimensional array in a function.
GLOBAL DYNAMIC A[10][10] AS INTEGER GLOBAL a AS INTEGER GLOBAL b AS INTEGER CALL FILL(A,10,10) FOR a = 0 TO 9 FOR b = 0 TO 9 ? A[a][b]; NEXT ? NEXT ! getchar(); SUB FILL(B AS INTEGER PTR PTR, Dim1 AS INTEGER, Dim2 AS INTEGER)'The data type descriptor for the B parameter,'in the line above, requires one "PTR"'for EACH DIMENSION of the passed array.RAW a1 RAW b1 Dim1-- Dim2-- FOR a1 = 0 TO Dim1 FOR b1 = 0 TO Dim2 B[a1][b1] = a1 + b1 NEXT NEXT END SUB
Example 3: treats the multi-dimensioned array as a one-dimensioned array inside the sub, and does a little simple maths to calculate the position of elements.
DIM i, j DIM mat[3][3] FOR i = 0 TO 2 FOR j = 0 TO 2 mat[i][j] = i * 10 + j NEXT NEXT PRINT "Initialized data to: " FOR i = 0 TO 2 FOR j = 0 TO 2 PRINT mat[i][j] NEXT NEXT TRY2(mat[0], 3, 3) KEYPRESS SUB TRY2(Mymat[], n1 , n2) DIM k, h PRINT "Using simple maths: " FOR k = 0 TO n1-1 FOR h = 0 TO n2-1 PRINT Mymat[k * n2 + h] NEXT NEXT END SUB
Example 4: stores the array in a TYPE variable, and passes the address of the TYPE to the
function.
DIMi, jTYPEMYARRAY a[3][3]ENDTYPEDIMmatASMYARRAYFORi=0TO2FORj=0TO2mat.a[i][j]=i*10+jNEXTNEXT"Initialized data to: "FORi=0TO2FORj=0TO2[i][j]NEXTNEXTTRY1(mat,3,3)getchar()SUBTRY1(MymatASMYARRAY, n1, n2)DIMk, h"Using a TYPE to hold the array: "FORk=0TOn1-1FORh=0TOn2-1[k][h]NEXTNEXTENDSUB
Example 5: Here is an easy way to pass mutidimensional
static arrays by enclosing the array in a TYPE.
TYPEfltarray2D CA![100,2]ENDTYPEGLOBALmyfloatASfltarray2DCALLfoo(&myfloat)[1,1]SUBfoo(MyfloatASfltarray2DPTR)Myfloat->CA![1,1]=5.33ENDSUB
Example 6: Using a DYNAMIC
array.
GLOBALDYNAMICCA![100,2]CALLfoo(CA)[1,1]SUBfoo(MyfloatASFLOATPTRPTR)'The data type descriptor for the Myfloat parameter,'in the line above, requires one "PTR"'for EACH DIMENSION of the passed array.Myfloat[1,1]=5.33ENDSUB
Example 7: The following simple example shows how to pass
DYNAMIC multi-dimensional numeric arrays to
a procedure by reference
OPTION BASE1GLOBALDYNAMICA[2,2,2]ASFLOATLOCALI, J, K A[1,1,1]=1A[1,1,2]=2A[1,2,1]=3A[1,2,2]=4A[2,1,1]=5A[2,1,2]=6A[2,2,1]=7A[2,2,2]=8FORI=1TO2FORJ=1TO2FORK=1TO2"[", I,",", J,",", K,"] = ", XXX!(A, I, J, K)NEXTKNEXTJNEXTIKEYPRESSENDPROGRAMFUNCTIONXXX!(BASFLOATPTRPTRPTR, i, j, k)'The data type descriptor for the B parameter,'in the line above, requires one "PTR"'for EACH DIMENSION of the passed array.FUNCTION=3.14*B[i, j, k]ENDFUNCTION
Example 8: The following example shows how to pass TYPE and UNION structures to to a procedure
TYPE STA DIM A$ DIM B AS INTEGER END TYPE TYPE STB DIM A AS INTEGER DIM B AS INTEGER END TYPE ' union of all the structures UNION STUNION Atype AS STA Btype AS STB END UNION 'the actual structure that is going to be used TYPE STALL DIM ID AS INTEGER 'tells what structure in the union is going to be used DIM UN AS STUNION END TYPE DIM st AS STALL st.ID = 1 st.UN.Atype.A$ = "How is this?" CALL ExeSTX(&st) st.ID = 2 st.UN.Btype.A = 2 CALL ExeSTX(&st) getchar(); SUB ExeSTX(s AS STALL PTR) SELECT CASE s->ID CASE 1 ? s->UN.Atype.A$ CASE 2 ? s->UN.Btype.A END SELECT END SUB
BCX is able to correctly translate a parameter formed from a call to a FUNCTION or SUB as in this example.
SUB WalkDir(szPath$, szSpec$, bRecurse, _ WalkResult(szTPath$, szFPath$, bType) AS SUB) END SUB
OPTIONAL Parameters in Functions and Subroutines
An OPTIONAL parameter used in a
FUNCTION or SUB
declaration is defined with a default value. This value is used if
the OPTIONAL parameter is omitted in the
calling function or subroutine procedure. If a value is specified
in the OPTIONAL parameter in the calling
procedure, then that value is used instead of the default.
OPTIONAL parameters
are not permitted in DLL functions.
Many BCX functions and subroutines have optional parameters
already built in. For example, all the BCX GUI functions have
optional window style and extended window style parameters which
provide a default that is replaced when an OPTIONAL value is specified.
Syntax 1: FUNCTION MyFunc% OPTIONAL(Normalparam$, Optionalparam$ = "BCX")Syntax 2: FUNCTION MyFunc% OPTIONAL(Normalparam$, Optionalparam AS data type = Value) |
In the function or subroutine call, OPTIONAL parameters can be omitted from the right side
of the parameter list.
The 'value' assigned to OPTIONAL
parameters must be a scalar value, that is a string literal or
number, not a function or some other complex expression.
In the function definition, when using an empty string as an
OPTIONAL parameter argument, a NULL POINTER
is passed to the function or subroutine. The NULL POINTER is
passed as the text string (null) not as an
empty string.
Study the example below to see how this "C" limitation can be handled using BCX.
Alert() Alert("Hello BCX Lovers!") Alert("Announcing OPTIONAL ARGUMENTS", "Very Cool!", 64) SUB Alert OPTIONAL(_Message$ = "" , _Title$ = "", Style = 0) LOCAL Message$, Title$ Message$ = _Message$ & "" Title$ = _Title$ & "" IF TRIM$(Message$) = "(null)" THEN Message$ = "Alert ... Alert ...Alert!" END IF IF TRIM$(Title$) = "(null)" THEN Title$ = "Generic Default Title" END IF IF Style = 0 THEN Style = MB_OK MSGBOX Message$, Title$, Style END SUB
$FSSTATIC directive
The $FSSTATIC directive causes the BCX
translator to place the storage class specifier 'static' before
function and subroutine definitions in the output C code.
Like many of the other BCX meta-statements, the $FSSTATIC command is like a toggle. The first instance
of $FSSTATIC turns on the function and the
second instance turns it off.
$FSSTATIC SUB GoForward(iBrowser AS IWebBrowser2 PTR) END SUB $FSSTATIC
translates to
static void GoForward(IWebBrowser2 *iBrowser) { }
and without $FSSTATIC, BCX translates
SUB GoForward(iBrowser AS IWebBrowser2 PTR) END SUB
to
void GoForward(IWebBrowser2 *iBrowser)
{
}