FUNCTION ... END FUNCTION procedure
FUNCTION ... END FUNCTION encapsulates a series of statements to form a procedure which returns a value to the FUNCTION call expression.
Syntax 1: FUNCTION TheName([Parameters][AS data type])[,AS data type] DIM ReturnValue [Statements] FUNCTION = ReturnValue END FUNCTIONSyntax 2: FUNCTION TheName([Parameters][AS data type])[,AS data type] DIM ReturnValue [Statements] RETURN ReturnValue 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
Result:
6.82
Remarks:
☞ An argument in the FUNCTION calling statement cannot be modified. For example, the attempt to modify "This can not be changed!" will fail in,
DIM literal$ literal$ = FunOne$("This can not be changed!") PRINT literal$ FUNCTION FunOne$(idiom$) idiom$ = "You should keep your eye out for this!" FUNCTION = idiom$ END FUNCTION
Result:
The result is undefined behaviour which is dependent on the usage context.
☞ 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()
FUNCTION declarations emit a TYPE that may be reused. For example, when a FUNCTION has been declared as
DIM FUNCTION Foo$(a$)any further declarations can be written as:
DIM MyFunctionPointer AS Foo_TYPE
The following are examples which use the emitted FUNCTION as a Foo_TYPE declaration as a member of a user defined TYPE.
Example 1:
DIM FUNCTION Foo$(a as LPCTSTR) TYPE MyType DYNAMIC A$[] DYNAMIC FN[] AS Foo_TYPE B AS INTEGER END TYPE DIM mt AS MyType REDIM mt.A$[2] REDIM mt.FN[3] mt.A$[0] = "Hello " mt.A$[1] = "World" mt.FN[0] = LCASE mt.FN[1] = MCASE mt.FN[2] = UCASE PRINT mt.FN$[0](mt.A$[0]), mt.FN$[0](mt.A$[1]) PRINT mt.FN$[1](mt.A$[0]), mt.FN$[1](mt.A$[1]) PRINT mt.FN$[2](mt.A$[0]), mt.FN$[2](mt.A$[1])
Result:
hello world Hello World HELLO WORLD
Example 2:
GLOBAL Func(DUMMY AS INTEGER) AS FUNCTION INTEGER SetFunc(x0) ? Func(2) SetFunc(x1) ? Func(2) SUB SetFunc (f AS Func_TYPE) Func = f END SUB '----------------------------------------------- FUNCTION x0(x AS INTEGER) AS INTEGER FUNCTION = x * x END FUNCTION FUNCTION x1(x AS INTEGER) AS INTEGER FUNCTION = x * x * 2 END FUNCTION
Result:
4 8
Example 3:
GLOBAL PF_1_ARG(DUMMY AS INTEGER) AS FUNCTION INTEGER SET Funcs3[] AS PF_1_ARG_TYPE n0, n1, n2, n3, n4 END SET FUNCTION n0(n AS INTEGER) FUNCTION = n * 0 END FUNCTION FUNCTION n1(n AS INTEGER) FUNCTION = n * 1 END FUNCTION FUNCTION n2(n AS INTEGER) FUNCTION = n * 2 END FUNCTION FUNCTION n3(n AS INTEGER) FUNCTION = n * 3 END FUNCTION FUNCTION n4(n AS INTEGER) FUNCTION = n * 4 END FUNCTION '------------------------------ DIM i FOR i = 0 TO 4 ? Funcs3[i](i) NEXT
Result:
0 1 4 9 16
Subclassing Windows Controls
If you want a control to do something that is not already built in to the control, subclassing allows you to add that new functionality at runtime.
Subclassing involves tapping into the windows message stream for a custom control. Text labels, editboxes, listboxes, etc all have their own windows procedure. You usually don't need to concern yourself with these.
CALLBACK FUNCTION ... END FUNCTION procedure
Purpose:
CALLBACK indicates that a procedure is to be subclassed.
Syntax 1: CALLBACK FUNCTION CBFuncName() [Statements] END FUNCTION Parameters:
|
Example 1: Below is a tiny example of subclassing a control, in this case a BCX_BUTTON. By subclassing controls in this way, you can organize your GUI apps such that your BEGIN EVENTS ... END EVENTS loop is not overloaded with code. Instead, you direct each control to do your bidding, in this case, the BCX_BUTTON control detects the left mouse click and displays a MSGBOX when clicked.
GUI "Subclassing Example" SUB FormLoad DIM AS CONTROL Form1 = BCX_FORM ("Subclassing Example") DIM AS CONTROL Button1 = BCX_BUTTON("Click Me!", Form1, WM_USER+10, 100, 40, 40, 20) '------------------------------------------------------------------------------------- GLOBAL Button_Before_Subclass AS WNDPROC Button_Before_Subclass = SubclassWindow (Button1, Button1_Events) '------------------------------------------------------------------------------------- CENTER Form1 SHOW Form1 END SUB BEGIN EVENTS SELECT CASE CBMSG CASE WM_CLOSE LOCAL id id = MessageBox(hWnd, "Are you sure?", "Quit Program!", MB_YESNO | MB_ICONQUESTION) IF id = IDYES THEN DestroyWindow (hWnd) END SELECT END EVENTS CALLBACK FUNCTION Button1_Events SELECT CASE CBMSG CASE WM_LBUTTONUP MSGBOX "Hello From Button1_Events" EXIT FUNCTION END SELECT FUNCTION = CallWindowProc (Button_Before_Subclass, CBHWND, CBMSG, CBWPARAM, CBLPARAM) END FUNCTION
Example 2: This example of Syntax 1 shows how to set up two forms subclassing the second with a CALLBACK. The trick involves:
GUI "Two_Forms_Demo" GLOBAL Form1 AS HWND GLOBAL Form2 AS HWND GLOBAL Form1_Button1 AS HWND GLOBAL Form2_Button1 AS HWND GLOBAL lpForm2_Proc AS WNDPROC MACRO IDC_FORM1_BUTTON1 = 201 MACRO IDC_FORM2_BUTTON1 = 202 SUB FORMLOAD Form1 = BCX_FORM("Form 1", 100, 100, 160, 100) Form1_Button1 = BCX_BUTTON("Show", Form1, IDC_FORM1_BUTTON1, 64, 40, 40, 14) Form2 = BCX_FORM("Form 2", 270, 110, 200, 140) Form2_Button1 = BCX_BUTTON("Hide",Form2, IDC_FORM2_BUTTON1, 80, 70, 40, 14) lpForm2_Proc = SubclassWindow(Form2, Form2_Proc) SHOW(Form1) END SUB BEGIN EVENTS SELECT CASE CBMSG '********************** CASE WM_COMMAND '********************** IF CBCTL = IDC_FORM1_BUTTON1 THEN SHOW(Form2) END IF EXIT FUNCTION '********************** CASE WM_CLOSE '********************** DIM RAW id id = MSGBOX("Are you sure?","Close Window!",MB_YESNO OR MB_ICONQUESTION) IF id THEN PostQuitMessage(0) EXIT FUNCTION END SELECT END EVENTS CALLBACK FUNCTION Form2_Proc() SELECT CASE CBMSG '********************** CASE WM_COMMAND '********************** IF CBCTL = IDC_FORM2_BUTTON1 THEN HIDE(CBHWND) EXIT FUNCTION '********************** CASE WM_CLOSE '********************** HIDE(CBHWND) 'Don't CLOSE it, HIDE it EXIT FUNCTION '********************** CASE WM_DESTROY 'Don't DESTROY it, HIDE it '********************** HIDE(CBHWND) END SELECT END FUNCTION
Example 3: This example shows how to subclass a multi-line BCX_EDIT control. The CALLBACK FUNCTION restricts typing to only numbers into the control and also allows the TAB key to tab out of it.
GUI "Subclassed_Edit_Control" SUB FORMLOAD GLOBAL Form1 AS HWND GLOBAL hEdit AS HWND GLOBAL hButt AS HWND GLOBAL hDate AS HWND CONST ID_Edit = 1000 CONST ID_Butt = 2000 CONST ID_Date = 3000 CONST STYLE = WS_BORDER | WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | WS_TABSTOP Form1 = BCX_FORM ("SubClassed Edit Ctrl Demo", 0, 0, 300, 160) hEdit = BCX_EDIT ("", Form1, ID_Edit, 5, 1, 280, 100, STYLE) hDate = BCX_DATEPICK("", Form1, ID_Date, 5, 110, 135, 35) hButt = BCX_BUTTON ("Bye Bye", Form1, ID_Butt, 240, 110, 35, 35) ' Subclass the edit control SetProp(hEdit, "oldproc", (HANDLE)GetWindowLongPtr(hEdit, GWLP_WNDPROC)) SetWindowLongPtr(hEdit, GWLP_WNDPROC, (LONG_PTR) EditProc ) SetFocus(hEdit) CENTER Form1 ' Center our Form on the screen SHOW Form1 ' Display our creation! END SUB BEGIN EVENTS SELECT CASE CBMSG '************** CASE WM_CREATE '************** MSGBOX "This Edit Control has been subclassed to accept only " & _ CRLF$ & "numbers and can be exited with the TAB key." EXIT FUNCTION '************** CASE WM_COMMAND IF CBCTL = ID_Butt THEN END '************** END SELECT END EVENTS CALLBACK FUNCTION EditProc DIM AS WNDPROC lpfnOldProc DIM AS LRESULT lResult lpfnOldProc = (WNDPROC)GetProp(CBHWND, "oldproc") SELECT CASE CBMSG CASE WM_GETDLGCODE ' Handle TAB key within multiline edit control DIM AS UINT uintCode uintCode = CallWindowProc(lpfnOldProc, CBHWND, CBMSG, CBWPARAM, CBLPARAM) FUNCTION = uintCode OR DLGC_WANTTAB CASE WM_KEYDOWN ' Handle TAB key to move focus IF CBWPARAM = VK_TAB THEN ' Call the original procedure lResult = CallWindowProc(lpfnOldProc, CBHWND, CBMSG, CBWPARAM, CBLPARAM) ' Move focus to the next control SetFocus(GetNextDlgTabItem(GetParent(CBHWND), CBHWND, FALSE)) FUNCTION = 0 END IF CASE WM_CHAR ' Only allow number input IF (CBWPARAM < ASC("0") OR CBWPARAM > ASC ("9")) AND CBWPARAM <> VK_TAB THEN ' Allow TAB (ASCII 9) EXIT FUNCTION END IF lResult = CallWindowProc(lpfnOldProc, CBHWND, CBMSG, CBWPARAM, CBLPARAM) FUNCTION = lResult CASE WM_DESTROY ' Restore original window procedure and cleanup SetWindowLongPtr(CBHWND, GWLP_WNDPROC, (LONG_PTR)GetProp(CBHWND, "oldproc")) RemoveProp(CBHWND, "oldproc") FUNCTION = CallWindowProc(lpfnOldProc, CBHWND, CBMSG, CBWPARAM, CBLPARAM) END SELECT FUNCTION = CallWindowProc(lpfnOldProc, CBHWND, CBMSG, CBWPARAM, CBLPARAM) END FUNCTION
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:
|
☞ The DefDlgProc function or the DefWindowProc function must not be called by a dialog box callback procedure; doing so results in recursive execution.
Example 3:
In the sample below, each label control is subclassed and reflects the WM_CTLCOLORSTATIC message to the subclassed windows procedure. This demo of CALLBACK function Syntax 2 creates a form with a black background and two BCX_LABEL controls that have their foreground and background colors changed by the CALLBACK functions.
GUI "test" GLOBAL Form1 AS CONTROL GLOBAL Label_1 AS CONTROL GLOBAL Label_2 AS CONTROL SUB FORMLOAD Form1 = BCX_FORM("test",67,42,160,130) Label_1 = BCX_LABEL("Label 1",Form1,100,5,10) Label_2 = BCX_LABEL("Label 2",Form1,101,5,50) BCX_SET_FORM_COLOR(Form1,RGB(0,0,0)) SubClassLabel1() SubClassLabel2() CENTER(Form1) SHOW(Form1) END SUB BEGIN EVENTS IF CBMSG = WM_CTLCOLORSTATIC THEN FUNCTION = SendMessage((HWND)lParam, Msg, wParam, lParam) END IF END EVENTS SUB SubClassLabel1 GLOBAL Original_Label_1_WndProc AS WNDPROC Original_Label_1_WndProc = SetWindowLongPtr(Label_1,GWLP_WNDPROC,Label_1_WndProc) END SUB SUB SubClassLabel2 GLOBAL Original_Label_2_WndProc AS WNDPROC Original_Label_2_WndProc = SetWindowLongPtr(Label_2,GWLP_WNDPROC,Label_2_WndProc) END SUB FUNCTION Label_1_WndProc(hWnd AS HWND, _ Msg AS UINT, _ wParam AS WPARAM, _ lParam AS LPARAM) _ AS LRESULT CALLBACK IF CBMSG = WM_CTLCOLORSTATIC THEN FUNCTION = SetColor(RGB(0,225,0),RGB(0,0,0),wParam,lParam) END IF FUNCTION = CallWindowProc(Original_Label_1_WndProc,hWnd,Msg,wParam,lParam) END FUNCTION FUNCTION Label_2_WndProc(hWnd AS HWND, _ Msg AS UINT, _ wParam AS WPARAM, _ lParam AS LPARAM) _ AS LRESULT CALLBACK IF CBMSG = WM_CTLCOLORSTATIC THEN FUNCTION = SetColor(RGB(255,0,0),RGB(0,0,0),wParam,lParam) END IF FUNCTION = CallWindowProc(Original_Label_2_WndProc,hWnd,Msg,wParam,lParam) END FUNCTION FUNCTION SetColor(TxtColr, BkColr, wParam, lParam) AS LRESULT GLOBAL ReUsableBrush AS HBRUSH DeleteObject(ReUsableBrush) ReUsableBrush = CreateSolidBrush(BkColr) SetTextColor((HDC)wParam, TxtColr) SetBkColor((HDC)wParam, BkColr) FUNCTION =(LRESULT) ReUsableBrush END FUNCTION
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
Purpose:
SUB ... END SUB encapsulates a series of statements to form a procedure.
Syntax: SUB SubName(Parameters) [Statements] END SUB Parameters:
|
Remarks:
By default, string variables are passed by reference, while numeric variables are passed by value. To pass numeric values by reference instead of by value, see the BYREF qualifier.
☞ An argument in the SUB calling statement cannot be modified. For example, the attempt to modify "This can not be changed!" will fail in,
CALL FunOne("This can not be changed!") SUB FunOne(idiom$) idiom$ = "You should keep your eye out for this!" PRINT idiom$ END SUB
Result:
The result is undefined behaviour which is dependent on the usage context.
CALL statement
Purpose:
CALL must precede a SUB calling procedure if the SUB does not take arguments and parentheses are omitted.
☞ This the only valid method of calling a SUB with no arguments and no parentheses
CALL TheNameOfTheSUB
Example:
CALL TheNameOfTheSUB SUB TheNameOfTheSUB() PRINT "HMCS CC-1" END SUB
Values can be passed to subroutines. The data type 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") COLOR 15, 1 frame(15, 10, 65, 20) DIM a INPUT a COLOR 7, 0 CLS SUB frame(x1, y1, x2, y2) ' while interesting, this is meant only as a DIM x ' sample SUB. The run-time PANEL statement DIM y ' is much faster and more flexible FOR y = y1 TO y2 FOR x = x1 TO x2 LOCATE y, x, 0 PRINT " "; NEXT NEXT 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
Result:
JumpOutArguments and Parameters
Passing Arguments to FUNCTION and SUB procedures
Arguments are passed to procedures through parameters.
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
Result:
12345
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 ! PAUSE 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
Result:
0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 10 2 3 4 5 6 7 8 9 10 11 3 4 5 6 7 8 9 10 11 12 4 5 6 7 8 9 10 11 12 13 5 6 7 8 9 10 11 12 13 14 6 7 8 9 10 11 12 13 14 15 7 8 9 10 11 12 13 14 15 16 8 9 10 11 12 13 14 15 16 17 9 10 11 12 13 14 15 16 17 18
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
Result:
Initialized data to: 0 1 2 10 11 12 20 21 22 Using simple maths: 0 1 2 10 11 12 20 21 22
Example 4: stores the array in a TYPE variable, and passes the address of the TYPE to the function.
DIM i, j TYPE MYARRAY a[3][3] END TYPE DIM mat AS MYARRAY FOR i = 0 TO 2 FOR j = 0 TO 2 mat.a[i][j] = i*10+j NEXT NEXT PRINT "Initialized data to: " FOR i = 0 TO 2 FOR j = 0 TO 2 PRINT mat.a[i][j] NEXT NEXT TRY1(mat, 3, 3) PAUSE SUB TRY1(Mymat AS MYARRAY, n1, n2) DIM k, h PRINT "Using a TYPE to hold the array: " FOR k = 0 TO n1 - 1 FOR h = 0 TO n2 - 1 PRINT Mymat.a[k][h] NEXT NEXT END SUB
Result:
Initialized data to: 0 1 2 10 11 12 20 21 22 Using a TYPE to hold the array: 0 1 2 10 11 12 20 21 22
Example 5: Here is an easy way to pass mutidimensional static arrays by enclosing the array in a TYPE.
TYPE fltarray2D CA![100, 2] END TYPE GLOBAL myfloat AS fltarray2D CALL foo(&myfloat) PRINT myfloat.CA![1, 1] SUB foo(Myfloat AS fltarray2D PTR) Myfloat->CA![1, 1] = 5.33 END SUB
Result:
5.33
Example 6: Using a DYNAMIC array.
GLOBAL DYNAMIC CA![100, 2] CALL foo(CA) PRINT CA[1, 1] SUB foo(Myfloat AS FLOAT PTR PTR) '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.33 END SUB
Result:
5.33
Example 7: The following simple example shows how to pass DYNAMIC multi-dimensional numeric arrays to a procedure by reference
OPTION BASE 1 GLOBAL DYNAMIC A[2, 2, 2] AS FLOAT LOCAL I, J, K A[1, 1, 1] = 1 A[1, 1, 2] = 2 A[1, 2, 1] = 3 A[1, 2, 2] = 4 A[2, 1, 1] = 5 A[2, 1, 2] = 6 A[2, 2, 1] = 7 A[2, 2, 2] = 8 FOR I = 1 TO 2 FOR J = 1 TO 2 FOR K = 1 TO 2 PRINT "[", I, ",", J, ",", K, "] = ", XXX!(A, I, J, K) NEXT K NEXT J NEXT I KEYPRESS END PROGRAM FUNCTION XXX!(B AS FLOAT PTR PTR PTR, 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] END FUNCTION
Result:
[ 1, 1, 1] = 3.14 [ 1, 1, 2] = 6.28 [ 1, 2, 1] = 9.42 [ 1, 2, 2] = 12.56 [ 2, 1, 1] = 15.7 [ 2, 1, 2] = 18.84 [ 2, 2, 1] = 21.98 [ 2, 2, 2] = 25.12
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) PAUSE 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
Result:
How is this? 2
Recursive FUNCTION and SUB procedures
Can BCX handle recursive procedures? Yes, a recursive call can be made from FUNCTION and SUB procedures. What this means is that the procedure can be called from within itself.
Here is an easy to follow example of a recursive FUNCTION
DIM Result AS UINT64 Result = Factorial(16) PRINT "Factorial of 16 is "; USING$("#,", Result) PAUSE FUNCTION Factorial(N AS UINT64) AS UINT64 IF N <= 1 THEN FUNCTION = 1 ELSE FUNCTION = N * Factorial(N - 1) END IF END FUNCTION
Result:
Factorial of 16 is 20,922,789,888,000 Press any key to continue . . .
The following example will find the smallest value in an array of DOUBLE floating point numbers.
RANDOMIZE TIMER DIM BUF[20] AS DOUBLE FOR INT i = 0 TO UBOUND(BUF) BUF[i] = RND * 99.13579 ' Fill our array with random values PRINT BUF#[i] ' Show them to us NEXT PRINT PRINT "Smallest Value:", FindSmallest# (BUF, UBOUND(BUF)) ' Find smallest value in our unsorted array PAUSE FUNCTION FindSmallest(column[] AS DOUBLE, cellnumber AS INT) AS DOUBLE '*********************************************************************************************************** ' Original function by (kmkaplan) ' https://stackoverflow.com/questions/13596455/finding-the-minimum-element-of-an-array-using-tail-recursion '*********************************************************************************************************** IF cellnumber = 1 THEN FUNCTION = column[0] ELSE DECR cellnumber FUNCTION = FindSmallest(column + (column[0] > column[cellnumber]), cellnumber) END IF END FUNCTION
Here's one popular example of a recursive SUB.
' Example subroutine call, usage 1 ' SearchForFiles("C:\", "win.ini") ' Example subroutine call, usage 2 CALL SearchForFiles("C:\Windows", "Notepad.exe") SUB SearchForFiles(startPath$, FileToFind$) DIM RAW fPath$, wildpath$, fName$, fPathName$ DIM RAW hfind AS HANDLE DIM RAW WFD AS WIN32_FIND_DATA DIM RAW found AS BOOL DIM RAW filename$ ' Prepare filename$ for comparison with found file names filename$ = LCASE$(FileToFind$) ' Make a local copy of the search path fPath$ = startPath$ ' Make sure search path ends with a backslash IF RIGHT$(fPath$, 1) <> "\" THEN fPath$ = fPath$ + "\" ' We want to find all files and directories so we'll ' add a wildcard asterisk to the search path wildpath$ = fPath$ + "*" ' Ask Windows API for first file or directory ' in our search path hfind = FindFirstFile(wildpath$, &WFD) ' Did we get one? IF hfind != INVALID_HANDLE_VALUE THEN found = TRUE ELSE found = FALSE END IF DO WHILE found ' Get the name of the file or directory ' from the WIN32_FIND_DATA structure fName$ = WFD.cFileName ' Add the file or directory name to the search path fPathName$ = fPath$ + fName$ IF fName$ != "." THEN ' ignore "current directory" IF fName$ != ".." THEN ' ignore "parent directory" ' Did we find a file or a directory? IF (WFD.dwFileAttributes BAND FILE_ATTRIBUTE_DIRECTORY) THEN ' Found a directory so look for our target ' file in that directory SearchForFiles(fPathName$, filename$) ' Found a file so does it match our target? ELSEIF LCASE$(fName$) = filename$ THEN ' Code here could call another routine, ' add the found file to a listbox, ' or whatever. We'll just display it's ' full path and name in a message box PRINT "Found ", fPathName$ ELSE ' File searching can be a lengthy process ' so give other processes a chance to do ' whatever they need to do DOEVENTS END IF END IF END IF ' Ask Windows API for the next file or ' directory in our search path found = FindNextFile(hfind, &WFD) LOOP ' Tell Windows API we've finished searching FindClose(hfind) END SUB
Result:
Found C:\Windows\notepad.exe Found C:\Windows\System32\notepad.exe Found C:\Windows\SysWOW64\notepad.exe Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.117_none_4d353cf1ceb5d6d2\f\notepad.exe Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.117_none_4d353cf1ceb5d6d2\notepad.exe Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.117_none_4d353cf1ceb5d6d2\r\notepad.exe Found C:\Windows\WinSxS\amd64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.1_none_250b9aff0f5d41ee\notepad.exe Found C:\Windows\WinSxS\wow64_microsoft-windows-notepad_31bf3856ad364e35_10.0.19041.1_none_2f60455143be03e9\notepad.exe
Here's another SUB recursion example:
DIM C$ C$ = "1,2,3,4,5,666,777,88888,99999,101010101010101010" CALL Parse(C$) 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
Result:
1 2 3 4 5 666 777 88888 99999 101010101010101010
FUNCTION as Parameter
BCX is able to correctly translate a parameter formed from a call to a FUNCTION as in this example.
$TYPEDEF CHAR * (__cdecl *STF_TYPE)(CHAR *) SET pfStringFunctions[] AS STF_TYPE do1, do2, do3, NULL END SET '---------word functions----------------------------------------- FUNCTION do1$(whatstring$) FUNCTION = REVERSE$(whatstring$) END FUNCTION FUNCTION do2$(whatstring$) FUNCTION = UCASE$(whatstring$) END FUNCTION FUNCTION do3$(whatstring$) FUNCTION = ENC$(whatstring$) END FUNCTION '------------sentence function-------------------------------------- 'Now I have a function that will loop through a string and do something 'to each word: FUNCTION dotoeachword$(whatsentence$, dowhat AS STF_TYPE) DIM tmp$ 'for each word in sentence (etc.) tmp$ = dowhat$(whatsentence$) 'next FUNCTION = tmp$ END FUNCTION '----------main function---------------------------------------- DIM sentence$,sentence1$,sentence2$,sentence3$ sentence$ = "George went to town." sentence1$ = dotoeachword$(sentence$, do1) sentence2$ = dotoeachword$(sentence$, do2) sentence3$ = dotoeachword$(sentence$, do3) PRINT sentence1$ PRINT sentence2$ PRINT sentence3$
Result:
.nwot ot tnew egroeG GEORGE WENT TO TOWN. "George went to town."
OPTIONAL parameters
☞ The OPTIONAL keyword is optional since BCX 7.7.2.
An OPTIONAL parameter used in a FUNCTION or SUB declaration can be defined with a default argument. This argument is used if the OPTIONAL parameter argument is omitted in the function or subroutine call. If an argument is specified for the OPTIONAL parameter in the calling procedure, then that value is used instead of the default.
OPTIONAL parameters must be placed to the right of any normal parameters in the list.
Syntax 1: FUNCTION MyFunc% OPTIONAL(Normalparam AS STRING, Optionalparam AS STRING = "BCX")Syntax 2: FUNCTION MyFunc%(Normalparam AS STRING, Optionalparam AS STRING = "BCX")Syntax 3: FUNCTION MyFunc% OPTIONAL(Normalparam AS STRING, Optionalparam AS data type = Value)Syntax 4: FUNCTION MyFunc%(Normalparam AS STRING, Optionalparam AS data type = Value) |
In the FUNCTION or SUB call, OPTIONAL parameters can be omitted from the right side of the parameter list.
Example:
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$) = "" THEN Message$ = " Alert ... Alert ...Alert! " END IF IF TRIM$(Title$) = "" THEN Title$ = "Generic Default Title" END IF IF Style = 0 THEN Style = MB_OK MSGBOX Message$, Title$, Style END SUB
Remarks:
☞ OPTIONAL parameters are not permitted in exported DLL functions.
☞ OPTIONAL parameters are not allowed in FUNCTION or SUB procedures within a user defined TYPE structure.
☞ OVERLOADED procedures cannot use OPTIONAL parameters.
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.
Many BCX functions and subroutines have optional parameter default arguments. For example, all the BCX GUI functions have optional window style and extended window style parameters with defined default arguments that can be replaced with user specified arguments.
BYREF keyword
Purpose: BYREF indicates that the following parameter is passing the argument by reference in the SUB or FUNCTION declaration.
Syntax 1: SUB | FUNCTION MySubOrFunc(BYREF var1 AS INTEGER, BYREF var2 AS DOUBLE, ...)Syntax 2: SUB | FUNCTION MySubOrFunc(BYREF var1 AS INTEGER, BYREF var2 AS DOUBLE, ...) Parameters: |
Anytime you want to be able to modify the contents of an argument, you must pass the argument BYREF. Otherwise, a copy of the value is normally passed to the SUB or FUNCTION.
Strings are automatically passed by reference, so BYREF is not needed when passing string arguments. do not use BYREF within SUB or FUNCTION parameter lists when referencing arrays.
☞ In the FUNCTION or SUB calling statement, the variable being passed by reference to the procedure must be prepended by an ampersand.
Example 1:
In this BYREF example, an ampersand is prepended to the ChangeUp parameter in the CALL Test2(&ChangeUp) statement. to qualify the passed argument.
CALL Test1 : PAUSE SUB Test1 LOCAL ChangeUp ChangeUp = 3 PRINT "ChangeUp", ChangeUp CALL Test2(&ChangeUp) ' ChangeUp variable will be modified by SUB test2! PRINT "ChangeUp", ChangeUp END SUB SUB Test2(BYREF Slider AS INTEGER) Slider = Slider + 2 PRINT "Slider", Slider END SUB
Result:
ChangeUp 3 Slider 5 ChangeUp 5
Example 2:
Here is another example that uses BYREF to access a file scoped RECT structure from within a subroutine. In SUB rectProc2, an alternate syntax is used to accomplish the same access as BYREF by qualifying rct AS RECT PTR.
DIM rct AS RECT rct.left = 1 rct.top = 2 rct.right = 100 rct.bottom = 100 rectProc1(&rct) ' The argument being passed by reference rectProc2(&rct) ' must be preceded by an ampersand. ? rct.left ? rct.top ? rct.right ? rct.bottom ? PAUSE SUB rectProc1(BYREF iAm AS RECT) ? (iAm).left ? (iAm).top ? (iAm).right ? (iAm).bottom ? iAm.left = 2 iAm.top = 4 iAm.right = 200 iAm.bottom = 200 ? rct.left ? rct.top ? rct.right ? rct.bottom ? END SUB SUB rectProc2(uIs AS RECT PTR) ? uIs->left ? uIs->top ? uIs->right ? uIs->bottom ? END SUB
Result:
1 2 100 100 2 4 200 200 2 4 200 200 2 4 200 200
Remarks:
☞ Note carefully the use of the parentheses in SUB rectProc1. The BCX translator requires that only parentheses, in this case, (rct), are syntaxed instead of the typical (*rct) used, when dereferencing a pointer to a struct. The BCX translator adds the asterisk in the 'C' code.
$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.
$FSSTATIC SUB GoForward(iBrowser AS IWebBrowser2 PTR) END SUB
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)
{
}
FUNCNAME$ keyword
The FUNCNAME$ keyword is a case-insensitive keyword that equates to __func__ which is a C99/C++11 macro that returns the name of the FUNCTION or SUB within which it is used. FUNCNAME$ is intended as one more debugging tool -- useful while you develop your applications. Below is a simple example.
$BCXVERSION "7.8.0" CALL Test_Funcname SUB Test_Funcname PRINT "Entering ", FUNCNAME$ PRINT "Leaving ", FUNCNAME$ END SUB
Result:
Entering Test_Funcname Leaving Test_Funcname